Index: modules/taxonomy/taxonomy.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.admin.inc,v retrieving revision 1.104 diff -u -p -r1.104 taxonomy.admin.inc --- modules/taxonomy/taxonomy.admin.inc 6 May 2010 05:59:31 -0000 1.104 +++ modules/taxonomy/taxonomy.admin.inc 11 May 2010 16:17:56 -0000 @@ -109,6 +109,7 @@ function taxonomy_form_vocabulary($form, 'description' => '', 'hierarchy' => 0, 'weight' => 0, + 'sort_order' => 'alpha', ); $form['#vocabulary'] = (object) $edit; // Check whether we need a deletion confirmation form. @@ -158,7 +159,15 @@ function taxonomy_form_vocabulary($form, '#type' => 'value', '#value' => '0', ); - + $sort_orders = array('alpha' => t('Alphabetical'), 'created' => t('Created')); + // Set the default sort order to either alphabetical or created. + $form['sort_order'] = array( + '#type' => 'select', + '#title' => t('Default term sort order'), + '#options' => $sort_orders, + '#default_value' => $edit['sort_order'], + '#description' => t('Changing this does not sort existing taxonomy terms in this vocabulary.') + ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); if (isset($edit['vid'])) { @@ -621,13 +630,18 @@ function taxonomy_form_term($form, &$for $vocabulary = taxonomy_vocabulary_load($edit->vid); $edit = (array) $edit; } + + // Get the correct weight for a newly created term. + if (!isset($edit['weight'])) { + $edit['weight'] = taxonomy_term_get_new_weight($vocabulary); + } + $edit += array( 'name' => '', 'description' => '', 'format' => filter_default_format(), 'vocabulary_machine_name' => $vocabulary->machine_name, 'tid' => NULL, - 'weight' => 0, ); // Take into account multi-step rebuilding. Index: modules/taxonomy/taxonomy.install =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.install,v retrieving revision 1.42 diff -u -p -r1.42 taxonomy.install --- modules/taxonomy/taxonomy.install 6 May 2010 06:08:28 -0000 1.42 +++ modules/taxonomy/taxonomy.install 11 May 2010 16:17:56 -0000 @@ -154,6 +154,13 @@ function taxonomy_schema() { 'default' => 0, 'description' => 'The weight of this vocabulary in relation to other vocabularies.', ), + 'sort_order' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The vocabulary\'s default sort order. (alpha = alphabetical, created = created)', + ), ), 'primary key' => array('vid'), 'indexes' => array( @@ -504,3 +511,25 @@ function taxonomy_update_7008() { ), )); } + +/** + * Add {vocabulary}.sort_order column and update it to the default value. + */ +function taxonomy_update_7009() { + $field = array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The vocabulary\'s default sort order. (alpha = alphabetical, created = created)', + ); + + db_add_field('taxonomy_vocabulary', 'sort_order', $field); + + foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) { + db_update('taxonomy_vocabulary') + ->fields(array('sort_order' => 'alpha')) + ->condition('vid', $vid) + ->execute(); + } +} Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.589 diff -u -p -r1.589 taxonomy.module --- modules/taxonomy/taxonomy.module 6 May 2010 05:59:31 -0000 1.589 +++ modules/taxonomy/taxonomy.module 11 May 2010 16:17:57 -0000 @@ -1293,6 +1293,7 @@ function taxonomy_field_formatter_prepar // Rekey the items array. $items[$id] = array_values($items[$id]); } + usort($items[$id], '_taxonomy_term_prepare_view_compare'); } } } @@ -1318,6 +1319,9 @@ function taxonomy_field_widget_form(&$fo $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']); } + // Sort the tags according to weight then name. + usort($tags, '_taxonomy_term_compare'); + $element += array( '#type' => 'textfield', '#default_value' => taxonomy_implode_tags($tags), @@ -1358,6 +1362,7 @@ function taxonomy_autocomplete_validate( 'vid' => $vids[0], 'name' => $typed_term, 'vocabulary_machine_name' => $vocabulary->machine_name, + 'weight' => taxonomy_term_get_new_weight($vocabulary), ); taxonomy_term_save($term); } @@ -1457,6 +1462,57 @@ function taxonomy_rdf_mapping() { } /** + * Returns the weight for a new taxonomy term depending upon the vocabulary's sort order. + * + * @param $vocabulary + * A vocabulary object. + * @return + * The weight for the new term. + */ +function taxonomy_term_get_new_weight($vocabulary) { + $weight = 0; + if ($vocabulary->sort_order == 'created') { + $max_weight = db_query("SELECT MAX(weight) FROM {taxonomy_term_data} WHERE vid = :vid", array(':vid' => $vocabulary->vid))->fetchField(); + $weight = isset($max_weight) ? $max_weight + 1 : 0; + } + return $weight; +} + +/** + * Helper function to sort an array of terms for taxonomy_field_formatter_prepare_view(). + * + * @param $a + * An array containing a taxonomy term object. + * @param $b + * An array containing a taxonomy term object. + * @return + * An integer less than, equal to, or greater than zero if the first argument is considered to be respectively + * less than, equal to, or greater than the second. + */ +function _taxonomy_term_prepare_view_compare($a, $b) { + return _taxonomy_term_compare($a['taxonomy_term'], $b['taxonomy_term']); +} + +/** + * Helper function to sort an array of taxonomy term objects. + * + * @param $a + * A taxonomy term object. + * @param $b + * A taxonomy term object. + * @return + * An integer less than, equal to, or greater than zero if the first argument is considered to be respectively + * less than, equal to, or greater than the second. + */ +function _taxonomy_term_compare($a, $b) { + if (isset($a->weight) && isset($b->weight) && !($a->weight == $b->weight)) { + return ($a->weight < $b->weight) ? -1 : 1; + } + // The terms were the same weight, so sort alphabetically. + return strcmp($a->name, $b->name); +} + +/** * @defgroup taxonomy indexing Taxonomy functions maintaining {taxonomy_index}. * * Taxonomy uses default field storage to store canonical relationships Index: modules/taxonomy/taxonomy.test =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v retrieving revision 1.78 diff -u -p -r1.78 taxonomy.test --- modules/taxonomy/taxonomy.test 30 Apr 2010 12:52:10 -0000 1.78 +++ modules/taxonomy/taxonomy.test 11 May 2010 16:17:57 -0000 @@ -38,6 +38,22 @@ class TaxonomyWebTestCase extends Drupal taxonomy_term_save($term); return $term; } + + /** + * Create a term through administration form. + */ + function createTermThroughInterface($vocabulary, $name = NULL) { + $edit = array( + 'name' => is_null($name) ? $this->randomName() : $name, + 'description[value]' => $this->randomName(100), + ); + // Explicitly set the parents field to 'root', to ensure that + // taxonomy_form_term_submit() handles the invalid term ID correctly. + $edit['parent[]'] = array(0); + + // Create the term. + $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add', $edit, t('Save')); + } } /** @@ -1007,3 +1023,173 @@ class TaxonomyTokenReplaceTestCase exten } } } + +/** + * Tests for taxonomy term ordering functions. + */ +class TaxonomyTermOrderTestCase extends TaxonomyWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Taxonomy term ordering.', + 'description' => 'Test multiple taxonomy terms ordering. How they are created and appear on a node.', + 'group' => 'Taxonomy', + ); + } + + function setUp() { + parent::setUp('taxonomy'); + $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access')); + $this->drupalLogin($this->admin_user); + $this->vocabulary = $this->createVocabulary(); + + $field = array( + 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name, + 'type' => 'taxonomy_term_reference', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vid' => $this->vocabulary->vid, + 'parent' => 0, + ), + ), + ), + ); + field_create_field($field); + + $this->instance = array( + 'field_name' => 'taxonomy_' . $this->vocabulary->machine_name, + 'bundle' => 'article', + 'entity_type' => 'node', + 'widget' => array( + 'type' => 'options_select', + ), + 'display' => array( + 'full' => array( + 'type' => 'taxonomy_term_reference_link', + ), + ), + ); + field_create_instance($this->instance); + } + + /** + * Save 3 terms using the user interface and test ordering. + */ + function testTermAddOrderAlpha() { + // Create a list of names in the reverse of alphabetic order + $names = array( + 'z' . $this->randomName(), + 'y' . $this->randomName(), + 'x' . $this->randomName(), + ); + + $this->createTermThroughInterface($this->vocabulary, $names[0]); + $this->createTermThroughInterface($this->vocabulary, $names[1]); + $this->createTermThroughInterface($this->vocabulary, $names[2]); + + // Fetch the created terms in the default order, i.e. the order in + // which they were created. + drupal_static_reset('taxonomy_get_tree'); + drupal_static_reset('taxonomy_get_treeparent'); + drupal_static_reset('taxonomy_get_treeterms'); + list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid); + $this->assertEqual(array($term1->name, $term2->name, $term3->name), array_reverse($names), t('Terms created through admin interface are listed in alphabetical order.')); + } + + /** + * Save 3 terms using the user interface and test ordering. + */ + function testTermAddOrderAlphaNode() { + // Create a list of names in the reverse of alphabetic order + $names = array( + 'z' . $this->randomName(), + 'y' . $this->randomName(), + 'x' . $this->randomName(), + ); + //Create a node a add the terms to it in a free tagging field in non alphabetic order + $instance = $this->instance; + $instance['widget'] = array('type' => 'taxonomy_autocomplete'); + $instance['bundle'] = 'page'; + field_create_instance($instance); + $edit = array(); + $langcode = LANGUAGE_NONE; + $edit["title"] = $this->randomName(); + $edit["body[$langcode][0][value]"] = $this->randomName(); + // Insert the terms in a comma separated list. Vocabulary 1 is a + // free-tagging field created by the default profile. + $edit[$instance['field_name'] . "[$langcode]"] = implode(', ', $names); + $this->drupalPost('node/add/page', $edit, t('Save')); + $this->assertRaw(t('@type %title has been created.', array('@type' => t('Basic page'), '%title' => $edit["title"])), t('The node was created successfully')); + $this->assertPattern('|' . implode('.*', array_reverse($names)) . '|', t('The terms were saved and appear in alphabetical order on the node page')); + + // Edit node to check term order. + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertPattern('|' . implode('.*', array_reverse($names)) . '|', t('The terms appear in alphabetical order on the node edit page')); + } + + /** + * Save 3 terms using the user interface and test ordering. + */ + function testTermAddOrderCreated() { + //Change default order to created + $this->vocabulary->sort_order = 'created'; + taxonomy_vocabulary_save($this->vocabulary); + + // Create a list of names in the reverse of alphabetic order + $names = array( + 'z' . $this->randomName(), + 'y' . $this->randomName(), + 'x' . $this->randomName(), + ); + + $this->createTermThroughInterface($this->vocabulary, $names[0]); + $this->createTermThroughInterface($this->vocabulary, $names[1]); + $this->createTermThroughInterface($this->vocabulary, $names[2]); + + // Fetch the created terms in the default order, i.e. the order in + // which they were created. + drupal_static_reset('taxonomy_get_tree'); + drupal_static_reset('taxonomy_get_treeparent'); + drupal_static_reset('taxonomy_get_treeterms'); + list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid); + $this->assertEqual(array($term1->name, $term2->name, $term3->name), $names, t('Terms created through admin interface are listed in the order in which they were created.')); + } + /** + * Save 3 terms using the user interface and test ordering. + */ + function testTermAddOrderCreatedNode() { + //Change default order to created + $this->vocabulary->sort_order = 'created'; + taxonomy_vocabulary_save($this->vocabulary); + + // Create a list of names in the reverse of alphabetic order + $names = array( + 'z' . $this->randomName(), + 'y' . $this->randomName(), + 'x' . $this->randomName(), + ); + //Create a node a add the terms to it in a free tagging field in alphabetic order + $instance = $this->instance; + $instance['widget'] = array('type' => 'taxonomy_autocomplete'); + $instance['bundle'] = 'page'; + field_create_instance($instance); + $edit = array(); + $langcode = LANGUAGE_NONE; + $edit["title"] = $this->randomName(); + $edit["body[$langcode][0][value]"] = $this->randomName(); + // Insert the terms in a comma separated list. Vocabulary 1 is a + // free-tagging field created by the default profile. + $edit[$instance['field_name'] . "[$langcode]"] = implode(', ', $names); + $this->drupalPost('node/add/page', $edit, t('Save')); + $this->assertRaw(t('@type %title has been created.', array('@type' => t('Basic page'), '%title' => $edit["title"])), t('The node was created successfully')); + $this->assertPattern('|' . implode('.*', $names) . '|', t('The terms were saved and appear in created order on the node page')); + + // Edit node to check term order. + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertPattern('|' . implode('.*', $names) . '|', t('The terms appear in created order on the node edit page')); + } +}