drupalCreateUser(array('administer taxonomy', 'administer menu', 'bypass node access')); $this->drupalLogin($admin_user); // Create a vocabulary and a hierarchy of taxonomy terms for it. $this->vocabulary = $this->createVocabulary(); $this->hierarchy = $this->createTermsHierarchy($this->vocabulary); } /** * Asserts that the number of menu links is equal to the number of taxonomy * terms for a given menu name. * * @param $terms * The terms, which are created in this class. */ protected function assertEqualNumberTermsMenuLinks($terms_count, $vocabulary, $menu_name) { $vid = $vocabulary->vid; // Building a query getting the number of menu links for this vocabulary. $query = db_select('menu_links', 'ml') ->fields('ml') ->condition('ml.module', 'taxonomy_menu') ->condition('ml.menu_name', $menu_name); $query->join('taxonomy_menu', 'tm', 'ml.mlid = tm.mlid'); $query->condition('tm.vid', $vid, '='); $query_count = $query->execute()->rowCount(); $message = $query_count . ' menu links were found for the ' . $terms_count . ' taxonomy terms of vocabulary ' . $vocabulary->name . '.'; $this->assertEqual($terms_count, $query_count, $message); } /** * Creates a hierarchy of taxonomy terms for the vocabulary defined in the * current class. The hierarchy is as follow: * * terms[1] | depth: 0 * -- terms[2] | depth: 1 * -- terms[3] | depth: 1 * ---- terms[4] | depth: 2 * -- terms[5] | depth: 1 * terms[6] | depth: 0 * -- terms[7] | depth: 1 * * @TODO Add multiple parents when taxonomy_menu can deal with it. */ protected function createTermsHierarchy($vocabulary) { $terms = array(); // Create taxonomy terms. for ($i = 1; $i < 8; $i++) { $terms[$i] = $this->createTerm($vocabulary); } // Set the hierarchy. $terms[2]->parent = array($terms[1]->tid); taxonomy_term_save($terms[2]); $terms[3]->parent = array($terms[1]->tid); taxonomy_term_save($terms[3]); $terms[4]->parent = array($terms[3]->tid); taxonomy_term_save($terms[4]); $terms[5]->parent = array($terms[1]->tid); taxonomy_term_save($terms[5]); $terms[7]->parent = array($terms[6]->tid); taxonomy_term_save($terms[7]); return $terms; } /** * Fetch the menu item from the database and compare it to the specified * array. * * @param $mlid * Menu item id. * @param $item * Array containing properties to verify. */ function assertMenuLink($mlid, array $expected_item) { // Retrieve menu link. $item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array(':mlid' => $mlid))->fetchAssoc(); $options = unserialize($item['options']); if (!empty($options['query'])) { $item['link_path'] .= '?' . drupal_http_build_query($options['query']); } if (!empty($options['fragment'])) { $item['link_path'] .= '#' . $options['fragment']; } foreach ($expected_item as $key => $value) { $this->assertEqual($item[$key], $value, t('Parameter %key had expected value.', array('%key' => $key))); } } } /** * Tests the taxonomy vocabulary interface. */ class TaxonomyMenuFunctionalTest extends TaxonomyMenuWebTestCase { public static function getInfo() { return array( 'name' => 'Vocabulary interface', 'description' => 'Test the taxonomy menu vocabulary interface.', 'group' => 'Taxonomy menu', ); } function setUp() { parent::setUp('taxonomy_menu'); } /** * Save, edit and delete a taxonomy vocabulary using the user interface. */ function testTaxonomyMenuVocabularyInterface() { $menu_name = 'user-menu'; $vocab = $this->vocabulary->machine_name; // Visit the main taxonomy administration page. $this->drupalGet('admin/structure/taxonomy/' . $vocab . '/edit'); // Options for the taxonomy vocabulary edit form. $edit = array(); // Try to submit a vocabulary when menu location is a root menu item. $edit['taxonomy_menu[vocab_parent]'] = $menu_name . ':0'; $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('The Taxonomy menu has been created.')); // Try to re-submit a vocabulary when an option has changed. $edit['taxonomy_menu[options][hide_empty_terms]'] = TRUE; $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $this->assertRaw(t('The Taxonomy menu has been updated.')); // Try to re-submit a vocabulary without changing any option. $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $this->assertRaw(t('The Taxonomy menu was not updated. Nothing to update.')); // Try to submit a vocabulary removing the menu location. $edit['taxonomy_menu[vocab_parent]'] = '0'; $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $this->assertRaw(t('The Taxonomy menu has been removed.')); } /** * Re-order terms on the terms' overview page. */ function testTaxonomyMenuTermsOverviewInterface() { $term7 = $this->hierarchy[7]; // last term of our hierarchy. // Submit the main taxonomy administration page. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[sync]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/edit', $edit, t('Save')); // Visit the terms overview page. $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name); // Take last term, place it on top and save. $edit = array( 'tid:' . $term7->tid . ':0[tid]' => $term7->tid, 'tid:' . $term7->tid . ':0[parent]' => 0, 'tid:' . $term7->tid . ':0[depth]' => 0, 'tid:' . $term7->tid . ':0[weight]' => -5, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('The Taxonomy menu has been updated.')); // Test "Reset to alphabetical". $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name, array(), t('Reset to alphabetical')); $this->drupalPost(NULL, array(), t('Reset to alphabetical')); $this->assertRaw(t('The Taxonomy menu has been updated.')); } /** * Save, edit and delete a term using the user interface. */ function testTaxonomyMenuTermInterface() { $menu_name = 'main-menu'; $vocab_name = $this->vocabulary->machine_name; $edit = array(); $edit['taxonomy_menu[vocab_parent]'] = $menu_name . ':0'; $this->drupalPost('admin/structure/taxonomy/' . $vocab_name . '/edit', $edit, t('Save')); // Create a new term from the interface. $edit = array(); $edit = array( 'name' => $this->randomName(12), 'description[value]' => $this->randomName(100), ); $this->drupalPost('admin/structure/taxonomy/' . $vocab_name . '/add', $edit, t('Save')); $terms = taxonomy_get_term_by_name($edit['name']); $term = reset($terms); $options = array('%term' => $edit['name'], '%menu_name' => $menu_name); $this->assertRaw(t('Added term %term to taxonomy menu %menu_name.', $options)); // Update an existing term from the interface. $edit = array( 'name' => $this->randomName(12), 'description[value]' => $this->randomName(100), ); $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save')); $options = array('%term' => $edit['name'], '%menu_name' => $menu_name); $this->assertRaw(t('Updated term %term in taxonomy menu %menu_name.', $options)); // Delete an existing term from the interface. $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Delete')); $this->drupalPost(NULL, NULL, t('Delete')); // Confirm deletion. $options = array('%term' => $edit['name'], '%menu_name' => $menu_name); $this->assertRaw(t('Deleted term %term from taxonomy menu %menu_name.', $options)); } } /** * Tests for taxonomy vocabulary functions. */ class TaxonomyMenuUnitTest extends TaxonomyMenuWebTestCase { public static function getInfo() { return array( 'name' => 'CRUD functions', 'description' => 'Test CRUD functions', 'group' => 'Taxonomy menu', ); } function setUp() { parent::setUp('taxonomy_menu'); } /** * Tests CRUD functions. */ function testTaxonomyMenuCRUD() { $menu_name = 'main-menu'; $vocabulary = $this->vocabulary; $terms = $this->hierarchy; $hierarchy_term = $this->hierarchy[3]; // Arbitrary term for hierarchy. $vid = $this->vocabulary->vid; // Ensure that the taxonomy vocabulary form is successfully submitted. $edit['taxonomy_menu[vocab_parent]'] = $menu_name . ':0'; $vocab = $this->vocabulary->machine_name; $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $this->assertResponse(200); // Ensure that the same number of menu links are created from the taxonomy // terms of the vocabulary. $this->assertEqualNumberTermsMenuLinks(count($terms), $vocabulary, $menu_name); // Ensure that the menu link is updated when the taxonomy term is updated. $new_name = $this->randomName(); $hierarchy_term->name = $new_name; taxonomy_term_save($hierarchy_term); $this->drupalGet('admin/structure/menu/manage/' . $menu_name); $this->assertLink($new_name); // Ensure that the menu link is deleted when the taxonomy term is deleted. // Hierarchy term [3] has 1 children, if we delete it, 2 taxonomy terms // should be deleted. $orig_mlid = _taxonomy_menu_get_mlid($hierarchy_term->tid, $vid); taxonomy_term_delete($hierarchy_term->tid); $this->assertEqualNumberTermsMenuLinks(count($terms) - 2, $vocabulary, $menu_name); $menu_link = menu_link_load($orig_mlid); $message = 'The menu link ' . $orig_mlid . ' associated to the term ' . $hierarchy_term->tid . ' could not be found.'; $this->assertFalse($menu_link, $message); $mlid = _taxonomy_menu_get_mlid($hierarchy_term->tid, $vid); $message = 'The ( mlid = ' . $orig_mlid . ' / tid = ' . $hierarchy_term->tid . ') association could not be found in {taxonomy_menu} table.'; $this->assertFalse($mlid, $message); // Ensure that all menu links and all associations in {taxonomy_menu} table // are deleted when a vocabulary is deleted. $mlids = _taxonomy_menu_get_menu_items($vid); taxonomy_vocabulary_delete($vid); $this->assertEqualNumberTermsMenuLinks(0, $vocabulary, $menu_name); } /** * Tests the hierarchy of menu links in a menu. */ function testTaxonomyMenuTermsHierarchy() { $vocab_machine_name = $this->vocabulary->machine_name; $vid = $this->vocabulary->vid; $edit = array(); // Settings $edit['taxonomy_menu[vocab_parent]'] = 'main-menu:0'; $this->drupalPost('admin/structure/taxonomy/' . $vocab_machine_name . '/edit', $edit, t('Save')); $this->assertResponse(200); // Given a taxonomy term, which id is tid: // - ptid --> - plid // - tid --> - mlid // Do the following verification for each term in the hierarchy; the ending // plid determined by the methods below should be equal. // - method1: tid --> mlid --> plid // - method2: tid --> ptid --> plid foreach ($this->hierarchy as $term) { // 1. Get plid by getting the associated mlid for the term tid. $mlid = _taxonomy_menu_get_mlid($term->tid, $vid); if ($mlid) { $menu_link = menu_link_load($mlid); $plid_from_mlid = $menu_link['plid']; // 2. Get plid by getting the associated mlid for the parent term ptid. // We don't handle multiple parents, break after first one. $parents = taxonomy_get_parents($term->tid); if (!empty($parents)) { foreach ($parents as $ptid => $parent) { $plid_from_ptid = _taxonomy_menu_get_mlid($ptid, $vid); // Assert that both plid found by the two different methods are equal. $message = 'Parent mlids from taxonomy term ' . $term->tid . ' are a match.'; $this->assertEqual($plid_from_mlid, $plid_from_ptid, $message); break; } } else { // Current term has no parent term. This means that the name of the // vocabulary should be associated to the 'navigation' root. // Menu link of the current term as defined by taxonomy menu table. $this->assertEqual($menu_link['plid'], 0); } } else { $this->fail("mlid for taxonomy term " . $term->tid . " could not be found."); } } } /** * Tests creation of menu links in a custom menu. */ function testTaxonomyMenuCustomMenu() { $vocabulary = $this->vocabulary; $terms = $this->hierarchy; $custom_menu_name = $this->randomName(16); $menu_machine_name = drupal_substr(hash('sha256', $custom_menu_name), 0, MENU_MAX_MENU_NAME_LENGTH_UI); // Submit the menu creation form. $menu_edit = array(); $menu_edit['title'] = $custom_menu_name; $menu_edit['menu_name'] = $menu_machine_name; $this->drupalPost('admin/structure/menu/add', $menu_edit, 'Save'); $this->assertResponse(200); // Submit the vocabulary edit form. $vocab_edit = array(); $vocab_edit['taxonomy_menu[vocab_parent]'] = 'menu-' . $menu_machine_name . ':0'; $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit', $vocab_edit, 'Save'); $this->assertResponse(200); // Check that the menu links were created in the custom menu. $this->assertEqualNumberTermsMenuLinks(count($terms), $vocabulary, 'menu-' . $menu_machine_name); } } /** * Tests Taxonomy menu configuration options. */ class TaxonomyMenuConfigurationTest extends TaxonomyMenuWebTestCase { public static function getInfo() { return array( 'name' => 'Configuration options', 'description' => 'Test configuration options.', 'group' => 'Taxonomy menu', ); } function setUp() { parent::setUp('taxonomy_menu'); } /** * Tests Taxonommy Menu sync option. */ function testTaxonomyMenuSyncOption() { $vocab = $this->vocabulary->machine_name; // Set settings (no sync on main-menu). $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[sync]' => FALSE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $this->assertResponse(200); // Prepare changes. $new_name = $this->randomName(12); $test_term = $this->hierarchy[3]; // Arbitrary term from hierarchy $test_term->name = $new_name; // Save new term's name with sync option off. taxonomy_term_save($test_term); $mlid = _taxonomy_menu_get_mlid($test_term->tid, $this->vocabulary->vid); $menu_link = menu_link_load($mlid); $this->assertNotEqual($new_name, $menu_link['link_title']); // Switch to sync option on and save. $edit['taxonomy_menu[sync]'] = TRUE; $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $this->assertResponse(200); $mlid = _taxonomy_menu_get_mlid($test_term->tid, $this->vocabulary->vid); $menu_link = menu_link_load($mlid); $this->assertEqual($new_name, $menu_link['link_title']); } /** * Tests Taxonommy Menu expand option. */ function testTaxonomyMenuExpandedOption() { $vocab = $this->vocabulary->machine_name; // Set settings on expanded. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][expanded]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); // Build the base query. $base_query = db_select('menu_links', 'ml'); $base_query->join('taxonomy_menu', 'tm', 'ml.mlid = tm.mlid'); $base_query->fields('ml') ->condition('tm.vid', $this->vocabulary->vid) ->condition('ml.module', 'taxonomy_menu'); // Assert that menu links are expanded. $query = $base_query->condition('ml.expanded', TRUE); $row_count = $query->execute()->rowCount(); $this->assertEqual(count($this->hierarchy), $row_count); // Set settings on not expanded. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][expanded]' => FALSE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); // Assert that menu links are not expanded anymore. $query = $base_query->condition('ml.expanded', FALSE); $row_count = $query->execute()->rowCount(); $this->assertEqual(0, $row_count); } /** * Tests Taxonommy Menu "Term description" option. */ function testTaxonomyMenuTermDescriptionOption() { $vocab = $this->vocabulary->machine_name; // Set settings on expanded. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][term_item_description]' => FALSE, 'taxonomy_menu[options][display_title_attr]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); // Assert that menu links does not have the term description. $term = $this->hierarchy[3]; $mlid = _taxonomy_menu_get_mlid($term->tid, $this->vocabulary->vid); $menu_link = menu_link_load($mlid); $menu_link_title = $menu_link['options']['attributes']['title']; $this->assertEqual($menu_link_title, ''); // Assert that menu links does have the term description, when the option is // checked. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][term_item_description]' => TRUE, 'taxonomy_menu[options][display_title_attr]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $menu_link = menu_link_load($mlid); $menu_link_title = $menu_link['options']['attributes']['title']; $this->assertEqual($menu_link_title, $term->description); // Assert that menu links does not have the term description, when the option // for displaying a description is on but the display title option is off. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][term_item_description]' => TRUE, 'taxonomy_menu[options][display_title_attr]' => FALSE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $menu_link = menu_link_load($mlid); $menu_link_title = $menu_link['options']['attributes']['title']; $this->assertEqual($menu_link_title, ''); } /** * Tests Taxonommy Menu "Flatten" option. */ function testTaxonomyMenuFlattenOption() { $vocab = $this->vocabulary->machine_name; // Set settings. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][flat]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); // Assert that all of the menu links have no children with the root being // the menu. $query = db_select('menu_links', 'ml'); $query->join('taxonomy_menu', 'tm', 'ml.mlid = tm.mlid'); $query->fields('ml'); $query ->condition('tm.vid', $this->vocabulary->vid) ->condition('ml.menu_name', 'main-menu') ->condition('ml.module', 'taxonomy_menu') ->condition('ml.has_children', 0) ->condition('ml.plid', 0); $row_count = $query->execute()->rowCount(); $this->assertEqual(count($this->hierarchy), $row_count); // Assert that all of the menu links have no children with the root being // a menu item. $item = array( 'menu_name' => 'main-menu', 'weight' => 0, 'link_title' => 'test', 'link_path' => '', 'module' => 'taxonomy_menu', ); $mlid = menu_link_save($item); menu_cache_clear('main-menu'); $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:' . $mlid, 'taxonomy_menu[options][flat]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $vocab . '/edit', $edit, t('Save')); $query = db_select('menu_links', 'ml'); $query->join('taxonomy_menu', 'tm', 'ml.mlid = tm.mlid'); $query->fields('ml'); $query ->condition('tm.vid', $this->vocabulary->vid) ->condition('ml.menu_name', 'main-menu') ->condition('ml.module', 'taxonomy_menu') ->condition('ml.has_children', 0) ->condition('ml.plid', $mlid); $row_count = $query->execute()->rowCount(); $this->assertEqual(count($this->hierarchy), $row_count); } /** * Tests Taxonommy Menu "Hide Empty terms" option. */ function testTaxonomyMenuHideEmptyTerms() { $voc_machine_name = $this->vocabulary->machine_name; // Create several nodes and attach them to different terms of our hierarchy // in order to match the following scheme. /** terms[1] | depth: 0 | 0 node -> displayed * -- terms[2] | depth: 1 | 0 node -> hidden * -- terms[3] | depth: 1 | 2 nodes -> displayed * ---- terms[4] | depth: 2 | 0 node -> hidden * -- terms[5] | depth: 1 | 1 node -> displayed * terms[6] | depth: 0 | 0 node -> hidden * -- terms[7] | depth: 1 | 0 node -> hidden */ // Add a taxonomy reference to the "Article" content type. $field = array( 'field_name' => 'taxonomy_' . $voc_machine_name, 'type' => 'taxonomy_term_reference', 'cardinality' => FIELD_CARDINALITY_UNLIMITED, 'settings' => array( 'allowed_values' => array( array( 'vocabulary' => $voc_machine_name, 'parent' => 0, ), ), ), ); field_create_field($field); $this->instance = array( 'field_name' => 'taxonomy_' . $voc_machine_name, 'bundle' => 'article', 'entity_type' => 'node', 'widget' => array( 'type' => 'options_select', ), 'display' => array( 'default' => array( 'type' => 'taxonomy_term_reference_link', ), ), ); field_create_instance($this->instance); // Create 3 articles respecting the previous scheme. $terms_index = array(3, 3, 5); foreach ($terms_index as $index) { $edit = array(); $langcode = LANGUAGE_NONE; $edit["title"] = $this->randomName(); $edit["body[$langcode][0][value]"] = $this->randomName(); $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $this->hierarchy[$index]->tid; $this->drupalPost('node/add/article', $edit, t('Save')); } // Set settings and save. $edit = array( 'taxonomy_menu[vocab_parent]' => 'main-menu:0', 'taxonomy_menu[options][hide_empty_terms]' => TRUE, ); $this->drupalPost('admin/structure/taxonomy/' . $voc_machine_name . '/edit', $edit, t('Save')); // Assert that the hidden property of the taxonomy menu's menu links are // set according to the scheme. $visible_terms_index = array(1, 3, 5); $hidden_item = array('hidden' => TRUE); $visible_item = array('hidden' => FALSE); $index = 1; foreach ($this->hierarchy as $term) { $mlid = _taxonomy_menu_get_mlid($term->tid, $this->vocabulary->vid); if ($mlid) { if (in_array($index, $visible_terms_index)) { $this->assertMenuLink($mlid, $visible_item); } else { $this->assertMenuLink($mlid, $hidden_item); } } else { $this->fail('No mlid could be found for the term ' . $term->tid); } $index++; } } }