Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save makara/1164547 to your computer and use it in GitHub Desktop.
Save makara/1164547 to your computer and use it in GitHub Desktop.
Drupal 7
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 379de71..761cc08 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -1715,6 +1715,15 @@ function taxonomy_rdf_mapping() {
function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
if ($item['tid'] == 'autocreate') {
+ // Avoid duplicating tags within the same vocabulary.
+ $tid = db_query_range("SELECT tid FROM {taxonomy_term_data} WHERE name = :name AND vid = :vid", 0, 1, array(
+ ':name' => trim($item['name']),
+ ':vid' => $item['vid'],
+ ))->fetchField();
+ if (!empty($tid)) {
+ $items[$delta]['tid'] = $tid;
+ continue;
+ }
$term = (object) $item;
unset($term->tid);
taxonomy_term_save($term);
@@ -1724,48 +1733,75 @@ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langc
}
/**
- * Implements hook_field_insert().
+ * Implements hook_node_insert().
*/
-function taxonomy_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
- // We maintain a denormalized table of term/node relationships, containing
- // only data for current, published nodes.
- if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node' && $entity->status) {
- $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created', ));
- foreach ($items as $item) {
- $query->values(array(
- 'nid' => $entity->nid,
- 'tid' => $item['tid'],
- 'sticky' => $entity->sticky,
- 'created' => $entity->created,
- ));
- }
- $query->execute();
- }
+function taxonomy_node_insert($node) {
+ // Add taxonomy index entries for the node.
+ taxonomy_build_node_index($node);
}
/**
- * Implements hook_field_update().
+ * Builds and inserts taxonomy index entries for a given node.
+ *
+ * The index lists all terms that are related to a given node entity, and is
+ * therefore maintained at the entity level.
+ *
+ * @param $node
+ * The node object.
*/
-function taxonomy_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
- if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node') {
- $first_call = &drupal_static(__FUNCTION__, array());
-
- // We don't maintain data for old revisions, so clear all previous values
- // from the table. Since this hook runs once per field, per object, make
- // sure we only wipe values once.
- if (!isset($first_call[$entity->nid])) {
- $first_call[$entity->nid] = FALSE;
- db_delete('taxonomy_index')->condition('nid', $entity->nid)->execute();
+function taxonomy_build_node_index($node) {
+ // We maintain a denormalized table of term/node relationships, containing
+ // only data for current, published nodes.
+ $status = NULL;
+ if (variable_get('taxonomy_maintain_index_table', TRUE)) {
+ // If a node property is not set in the node object when node_save() is
+ // called, the old value from $node->original is used.
+ if (!empty($node->original)) {
+ $status = (int)(!empty($node->status) || (!isset($node->status) && !empty($node->original->status)));
+ $sticky = (int)(!empty($node->sticky) || (!isset($node->sticky) && !empty($node->original->sticky)));
+ }
+ else {
+ $status = (int)(!empty($node->status));
+ $sticky = (int)(!empty($node->sticky));
+ }
+ }
+ // We only maintain the taxonomy index for published nodes.
+ if ($status || variable_get('taxonomy_index_unpublished', FALSE)) {
+ // Collect a unique list of all the term IDs from all node fields.
+ $tid_all = array();
+ foreach (field_info_instances('node', $node->type) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') {
+ // If a field value is not set in the node object when node_save() is
+ // called, the old value from $node->original is used.
+ if (isset($node->{$field_name})) {
+ $items = $node->{$field_name};
+ }
+ elseif (isset($node->original->{$field_name})) {
+ $items = $node->original->{$field_name};
+ }
+ else {
+ continue;
+ }
+ foreach (field_available_languages('node', $field) as $langcode) {
+ if (!empty($items[$langcode])) {
+ foreach ($items[$langcode] as $item) {
+ $tid_all[$item['tid']] = $item['tid'];
+ }
+ }
+ }
+ }
}
- // Only save data to the table if the node is published.
- if ($entity->status) {
+ // Insert index entries for all the node's terms.
+ if (!empty($tid_all)) {
$query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
- foreach ($items as $item) {
+ foreach ($tid_all as $tid) {
$query->values(array(
- 'nid' => $entity->nid,
- 'tid' => $item['tid'],
- 'sticky' => $entity->sticky,
- 'created' => $entity->created,
+ 'nid' => $node->nid,
+ 'tid' => $tid,
+ 'sticky' => $sticky,
+ 'created' => $node->created,
));
}
$query->execute();
@@ -1774,11 +1810,30 @@ function taxonomy_field_update($entity_type, $entity, $field, $instance, $langco
}
/**
+ * Implements hook_node_update().
+ */
+function taxonomy_node_update($node) {
+ // Always rebuild the node's taxonomy index entries on node save.
+ taxonomy_delete_node_index($node);
+ taxonomy_build_node_index($node);
+}
+
+/**
* Implements hook_node_delete().
*/
function taxonomy_node_delete($node) {
+ // Clean up the {taxonomy_index} table when nodes are deleted.
+ taxonomy_delete_node_index($node);
+}
+
+/**
+ * Deletes taxonomy index entries for a given node.
+ *
+ * @param $node
+ * The node object.
+ */
+function taxonomy_delete_node_index($node) {
if (variable_get('taxonomy_maintain_index_table', TRUE)) {
- // Clean up the {taxonomy_index} table when nodes are deleted.
db_delete('taxonomy_index')->condition('nid', $node->nid)->execute();
}
}
diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test
index 9a89b9c..1b43b7e 100644
--- a/modules/taxonomy/taxonomy.test
+++ b/modules/taxonomy/taxonomy.test
@@ -829,6 +829,326 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
}
/**
+ * Tests the hook implementations that maintain the taxonomy index.
+ */
+class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term index',
+ 'description' => 'Tests the hook implementations that maintain the taxonomy index.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+
+ // Create a vocabulary and add two term reference fields to article nodes.
+ $this->vocabulary = $this->createVocabulary();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Tests that the taxonomy index is maintained properly.
+ */
+ function testTaxonomyIndex() {
+ // Create terms in the vocabulary.
+ $term_1 = $this->createTerm($this->vocabulary);
+ $term_2 = $this->createTerm($this->vocabulary);
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_1->tid;
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_1->tid;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is indexed, and only once.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed once.'));
+
+ // Update the article to change one term.
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change another term.
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed once.'));
+
+ // Redo the above tests without interface.
+ $update_node = array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'uid' => $node->uid,
+ 'type' => $node->type,
+ 'title' => $this->randomName(),
+ );
+
+ // Update the article with no term changed.
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that the index was not changed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed once.'));
+
+ // Update the article to change one term.
+ $update_node[$this->field_name_1][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change another term.
+ $update_node[$this->field_name_2][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed once.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 2 is not indexed.'));
+ }
+}
+
+/**
+ * Tests term autocreation with multiple autocomplete widgets.
+ */
+class TaxonomyTermAutocreateTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term autocreation',
+ 'description' => 'Tests term autocreation with multiple autocomplete widgets.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+
+ // Create a vocabulary and add two autocomplete fields on article nodes.
+ $this->vocabulary = $this->createVocabulary();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Tests adding the same term to multiple autocomplete fields.
+ */
+ function testTaxonomyTagging() {
+ $term1_name = $this->randomName();
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode]"] = $term1_name;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is saved and can be retrieved.
+ $terms = taxonomy_get_term_by_name($term1_name);
+ $this->assertEqual(1, count($terms), t('The term was saved.'));
+
+ // Add a new term to two different fields.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $term2_name = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode]"] = $term2_name;
+ $edit["{$this->field_name_2}[$langcode]"] = $term2_name;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that the term is displayed multiple times.
+ $this->assertText($term2_name, t('The term was saved and appears on the node page.'));
+ $this->assertNoUniqueText($term2_name, t('The term displays multiple times.'));
+
+ // Check that the term is saved only once.
+ $terms = taxonomy_get_term_by_name($term2_name);
+ $this->assertEqual(1, count($terms), t('The term was saved only once.'));
+ }
+}
+
+/**
* Test the taxonomy_term_load_multiple() function.
*/
class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {
diff --git modules/taxonomy/taxonomy.module modules/taxonomy/taxonomy.module
index dc2847d..16df2f1 100644
--- modules/taxonomy/taxonomy.module
+++ modules/taxonomy/taxonomy.module
@@ -1683,48 +1683,58 @@ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langc
}
/**
- * Implements hook_field_insert().
+ * Implements hook_node_insert().
*/
-function taxonomy_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+function taxonomy_node_insert($node) {
// We maintain a denormalized table of term/node relationships, containing
// only data for current, published nodes.
- if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node' && $entity->status) {
- $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created', ));
- foreach ($items as $item) {
- $query->values(array(
- 'nid' => $entity->nid,
- 'tid' => $item['tid'],
- 'sticky' => $entity->sticky,
- 'created' => $entity->created,
- ));
+ $status = NULL;
+ if (variable_get('taxonomy_maintain_index_table', TRUE)) {
+ // Status and sticky can be in $node->original.
+ if (!empty($node->original)) {
+ $status = (int)(!empty($node->status) || (!isset($node->status) && !empty($node->original->status)));
+ $sticky = (int)(!empty($node->sticky) || (!isset($node->sticky) && !empty($node->original->sticky)));
+ }
+ else {
+ $status = (int)(!empty($node->status));
+ $sticky = (int)(!empty($node->sticky));
}
- $query->execute();
}
-}
-
-/**
- * Implements hook_field_update().
- */
-function taxonomy_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
- if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node') {
- $first_call = &drupal_static(__FUNCTION__, array());
-
- // We don't maintain data for old revisions, so clear all previous values
- // from the table. Since this hook runs once per field, per object, make
- // sure we only wipe values once.
- if (!isset($first_call[$entity->nid])) {
- $first_call[$entity->nid] = FALSE;
- db_delete('taxonomy_index')->condition('nid', $entity->nid)->execute();
+ if ($status || variable_get('taxonomy_index_unpublished', FALSE)) {
+ // Collect all the terms.
+ $tid_all = array();
+ foreach (field_info_instances('node', $node->type) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') {
+ // Field items can be in $node->original.
+ if (isset($node->{$field_name})) {
+ $items = $node->{$field_name};
+ }
+ elseif (isset($node->original->{$field_name})) {
+ $items = $node->original->{$field_name};
+ }
+ else {
+ continue;
+ }
+ foreach (field_available_languages('node', $field) as $langcode) {
+ if (!empty($items[$langcode])) {
+ foreach ($items[$langcode] as $item) {
+ $tid_all[$item['tid']] = $item['tid'];
+ }
+ }
+ }
+ }
}
- // Only save data to the table if the node is published.
- if ($entity->status) {
+ // Insert index for all the terms.
+ if (!empty($tid_all)) {
$query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
- foreach ($items as $item) {
+ foreach ($tid_all as $tid) {
$query->values(array(
- 'nid' => $entity->nid,
- 'tid' => $item['tid'],
- 'sticky' => $entity->sticky,
- 'created' => $entity->created,
+ 'nid' => $node->nid,
+ 'tid' => $tid,
+ 'sticky' => $sticky,
+ 'created' => $node->created,
));
}
$query->execute();
@@ -1733,6 +1743,14 @@ function taxonomy_field_update($entity_type, $entity, $field, $instance, $langco
}
/**
+ * Implements hook_node_update().
+ */
+function taxonomy_node_update($node) {
+ taxonomy_node_delete($node);
+ taxonomy_node_insert($node);
+}
+
+/**
* Implements hook_node_delete().
*/
function taxonomy_node_delete($node) {
diff --git modules/taxonomy/taxonomy.test modules/taxonomy/taxonomy.test
index aa7cc2e..92d1350 100644
--- modules/taxonomy/taxonomy.test
+++ modules/taxonomy/taxonomy.test
@@ -829,6 +829,204 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
}
/**
+ * Tests for taxonomy term index.
+ */
+class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term index',
+ 'description' => 'Test the index of tid to nid (taxonomy_index).',
+ '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();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Test index.
+ */
+ function testTaxonomyIndex() {
+ // Create terms in the vocabulary.
+ $term_1 = $this->createTerm($this->vocabulary);
+ $term_2 = $this->createTerm($this->vocabulary);
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_1->tid;
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_1->tid;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is indexed, and only once.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+
+ // Update the article to change one term.
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change another term.
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Redo the above tests without interface.
+ $update_node = array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'uid' => $node->uid,
+ 'type' => $node->type,
+ 'title' => $this->randomName(),
+ );
+
+ // Update the article with no term changed.
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that the index was not changed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change one term.
+ $update_node[$this->field_name_1][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change another term.
+ $update_node[$this->field_name_2][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 2 is indexed.'));
+ }
+}
+
+/**
* Test the taxonomy_term_load_multiple() function.
*/
class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {
diff --git modules/taxonomy/taxonomy.module modules/taxonomy/taxonomy.module
index eb81870..cd05514 100644
--- modules/taxonomy/taxonomy.module
+++ modules/taxonomy/taxonomy.module
@@ -1698,6 +1698,15 @@ function taxonomy_rdf_mapping() {
function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
if ($item['tid'] == 'autocreate') {
+ // Avoid duplicating tags in a same vocabulary.
+ $tid = db_query_range("SELECT tid FROM {taxonomy_term_data} WHERE name = :name AND vid = :vid", 0, 1, array(
+ ':name' => trim($item['name']),
+ ':vid' => $item['vid'],
+ ))->fetchField();
+ if (!empty($tid)) {
+ $items[$delta]['tid'] = $tid;
+ continue;
+ }
$term = (object) $item;
unset($term->tid);
taxonomy_term_save($term);
@@ -1707,48 +1716,58 @@ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langc
}
/**
- * Implements hook_field_insert().
+ * Implements hook_node_insert().
*/
-function taxonomy_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+function taxonomy_node_insert($node) {
// We maintain a denormalized table of term/node relationships, containing
// only data for current, published nodes.
- if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node' && $entity->status) {
- $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created', ));
- foreach ($items as $item) {
- $query->values(array(
- 'nid' => $entity->nid,
- 'tid' => $item['tid'],
- 'sticky' => $entity->sticky,
- 'created' => $entity->created,
- ));
+ $status = NULL;
+ if (variable_get('taxonomy_maintain_index_table', TRUE)) {
+ // Status and sticky can be in $node->original.
+ if (!empty($node->original)) {
+ $status = (int)(!empty($node->status) || (!isset($node->status) && !empty($node->original->status)));
+ $sticky = (int)(!empty($node->sticky) || (!isset($node->sticky) && !empty($node->original->sticky)));
+ }
+ else {
+ $status = (int)(!empty($node->status));
+ $sticky = (int)(!empty($node->sticky));
}
- $query->execute();
}
-}
-
-/**
- * Implements hook_field_update().
- */
-function taxonomy_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
- if (variable_get('taxonomy_maintain_index_table', TRUE) && $field['storage']['type'] == 'field_sql_storage' && $entity_type == 'node') {
- $first_call = &drupal_static(__FUNCTION__, array());
-
- // We don't maintain data for old revisions, so clear all previous values
- // from the table. Since this hook runs once per field, per object, make
- // sure we only wipe values once.
- if (!isset($first_call[$entity->nid])) {
- $first_call[$entity->nid] = FALSE;
- db_delete('taxonomy_index')->condition('nid', $entity->nid)->execute();
+ if ($status || variable_get('taxonomy_index_unpublished', FALSE)) {
+ // Collect all the terms.
+ $tid_all = array();
+ foreach (field_info_instances('node', $node->type) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') {
+ // Field items can be in $node->original.
+ if (isset($node->{$field_name})) {
+ $items = $node->{$field_name};
+ }
+ elseif (isset($node->original->{$field_name})) {
+ $items = $node->original->{$field_name};
+ }
+ else {
+ continue;
+ }
+ foreach (field_available_languages('node', $field) as $langcode) {
+ if (!empty($items[$langcode])) {
+ foreach ($items[$langcode] as $item) {
+ $tid_all[$item['tid']] = $item['tid'];
+ }
+ }
+ }
+ }
}
- // Only save data to the table if the node is published.
- if ($entity->status) {
+ // Insert index for all the terms.
+ if (!empty($tid_all)) {
$query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created'));
- foreach ($items as $item) {
+ foreach ($tid_all as $tid) {
$query->values(array(
- 'nid' => $entity->nid,
- 'tid' => $item['tid'],
- 'sticky' => $entity->sticky,
- 'created' => $entity->created,
+ 'nid' => $node->nid,
+ 'tid' => $tid,
+ 'sticky' => $sticky,
+ 'created' => $node->created,
));
}
$query->execute();
@@ -1757,6 +1776,14 @@ function taxonomy_field_update($entity_type, $entity, $field, $instance, $langco
}
/**
+ * Implements hook_node_update().
+ */
+function taxonomy_node_update($node) {
+ taxonomy_node_delete($node);
+ taxonomy_node_insert($node);
+}
+
+/**
* Implements hook_node_delete().
*/
function taxonomy_node_delete($node) {
diff --git modules/taxonomy/taxonomy.test modules/taxonomy/taxonomy.test
index 9a89b9c..eadd656 100644
--- modules/taxonomy/taxonomy.test
+++ modules/taxonomy/taxonomy.test
@@ -829,6 +829,319 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
}
/**
+ * Tests for taxonomy term index.
+ */
+class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term index',
+ 'description' => 'Test the index of tid to nid (taxonomy_index).',
+ '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();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Test index.
+ */
+ function testTaxonomyIndex() {
+ // Create terms in the vocabulary.
+ $term_1 = $this->createTerm($this->vocabulary);
+ $term_2 = $this->createTerm($this->vocabulary);
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_1->tid;
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_1->tid;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is indexed, and only once.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed once.'));
+
+ // Update the article to change one term.
+ $edit["{$this->field_name_1}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change another term.
+ $edit["{$this->field_name_2}[$langcode][]"] = $term_2->tid;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed once.'));
+
+ // Redo the above tests without interface.
+ $update_node = array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'uid' => $node->uid,
+ 'type' => $node->type,
+ 'title' => $this->randomName(),
+ );
+
+ // Update the article with no term changed.
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that the index was not changed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 1 is not indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed once.'));
+
+ // Update the article to change one term.
+ $update_node[$this->field_name_1][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that both terms are indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 2 is indexed.'));
+
+ // Update the article to change another term.
+ $update_node[$this->field_name_2][$langcode] = array(array('tid' => $term_1->tid));
+ $updated_node = (object) $update_node;
+ node_save($updated_node);
+
+ // Check that only one term is indexed.
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_1->tid,
+ ))->fetchField();
+ $this->assertEqual(1, $index_count, t('Term 1 is indexed once.'));
+ $index_count = db_query('SELECT COUNT(*) FROM {taxonomy_index} WHERE nid = :nid AND tid = :tid', array(
+ ':nid' => $node->nid,
+ ':tid' => $term_2->tid,
+ ))->fetchField();
+ $this->assertEqual(0, $index_count, t('Term 2 is not indexed.'));
+ }
+}
+
+/**
+ * Tests for multiple free-tagging fields referencing to a same vocabulary.
+ */
+class TaxonomyTermTaggingTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term tagging',
+ 'description' => 'Tests for multiple free-tagging fields referencing to a same vocabulary.',
+ '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();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Test tagging.
+ */
+ function testTaxonomyTagging() {
+ $term_name = $this->randomName();
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode]"] = $term_name;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is saved and can be retrieved.
+ $terms = taxonomy_get_term_by_name($term_name);
+ $this->assertEqual(1, count($terms), t('The term was saved.'));
+
+ // Update the article with a different tag.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $term_name = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode]"] = $term_name;
+ $edit["{$this->field_name_2}[$langcode]"] = $term_name;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that the term is displayed multiple times.
+ $this->assertText($term_name, t('The term was saved and appears on the node page.'));
+ $this->assertNoUniqueText($term_name, t('The term displays multiple times.'));
+
+ // Check that the term is saved only once.
+ $terms = taxonomy_get_term_by_name($term_name);
+ $this->assertEqual(1, count($terms), t('The term was saved only once.'));
+ }
+}
+
+/**
* Test the taxonomy_term_load_multiple() function.
*/
class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {
diff --git modules/field/field.attach.inc modules/field/field.attach.inc
index 2419201..b3a7050 100644
--- modules/field/field.attach.inc
+++ modules/field/field.attach.inc
@@ -423,7 +423,7 @@ function _field_invoke_get_instances($entity_type, $bundle, $options) {
elseif (isset($options['field_name'])) {
// Single-field mode by field name: field_info_instance() does the
// filtering.
- $instances = array(field_info_instance($entity_type, $options['field_name'], $bundle));
+ $instances = array_filter(array(field_info_instance($entity_type, $options['field_name'], $bundle)));
}
else {
$instances = field_info_instances($entity_type, $bundle);
diff --git includes/database/mysql/schema.inc includes/database/mysql/schema.inc
index 4e88fa1..d0f4259 100644
--- includes/database/mysql/schema.inc
+++ includes/database/mysql/schema.inc
@@ -131,8 +131,13 @@ class DatabaseSchema_mysql extends DatabaseSchema {
protected function createFieldSql($name, $spec) {
$sql = "`" . $name . "` " . $spec['mysql_type'];
- if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT')) && isset($spec['length'])) {
- $sql .= '(' . $spec['length'] . ')';
+ if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) {
+ if (isset($spec['length'])) {
+ $sql .= '(' . $spec['length'] . ')';
+ }
+ if (!empty($spec['binary'])) {
+ $sql .= ' BINARY';
+ }
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
diff --git includes/database/schema.inc includes/database/schema.inc
index e2a1c4c..b84c129 100644
--- includes/database/schema.inc
+++ includes/database/schema.inc
@@ -76,6 +76,10 @@ require_once dirname(__FILE__) . '/query.inc';
* the precision (total number of significant digits) and scale
* (decimal digits right of the decimal point). Both values are
* mandatory. Ignored for other field types.
+ * - 'binary': A boolean indicating that MySQL should force 'char',
+ * 'varchar' or 'text' fields to use case-sensitive binary collation.
+ * This has no effect on other database types for which case sensitivity
+ * is already the default behavior.
* All parameters apart from 'type' are optional except that type
* 'numeric' columns must specify 'precision' and 'scale'.
* - 'primary key': An array of one or more key column specifiers (see below)
diff --git modules/simpletest/tests/database_test.install modules/simpletest/tests/database_test.install
index 4dce2b1..867d813 100644
--- modules/simpletest/tests/database_test.install
+++ modules/simpletest/tests/database_test.install
@@ -28,6 +28,7 @@ function database_test_schema() {
'length' => 255,
'not null' => TRUE,
'default' => '',
+ 'binary' => TRUE,
),
'age' => array(
'description' => "The person's age",
diff --git modules/simpletest/tests/database_test.test modules/simpletest/tests/database_test.test
index 76ca103..2903ed8 100644
--- modules/simpletest/tests/database_test.test
+++ modules/simpletest/tests/database_test.test
@@ -3111,6 +3111,39 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase {
}
/**
+ * Test case sensitivity handling.
+ */
+class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Case sensitivity',
+ 'description' => 'Test handling case sensitive collation.',
+ 'group' => 'Database',
+ );
+ }
+
+ /**
+ * Test BINARY collation in MySQL.
+ */
+ function testCaseSensitiveInsert() {
+ $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
+
+ $john = db_insert('test')
+ ->fields(array(
+ 'name' => 'john', // <- A record already exists with name 'John'.
+ 'age' => 2,
+ 'job' => 'Baby',
+ ))
+ ->execute();
+
+ $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
+ $this->assertIdentical($num_records_before + 1, (int) $num_records_after, t('Record inserts correctly.'));
+ $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
+ $this->assertIdentical($saved_age, '2', t('Can retrieve after inserting.'));
+ }
+}
+
+/**
* Test invalid data handling.
*/
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 8cf6487..61d6ff1 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -1722,6 +1722,15 @@ function taxonomy_rdf_mapping() {
function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
foreach ($items as $delta => $item) {
if ($item['tid'] == 'autocreate') {
+ // Avoid duplicating tags within the same vocabulary.
+ $tid = db_query_range("SELECT tid FROM {taxonomy_term_data} WHERE name = :name AND vid = :vid", 0, 1, array(
+ ':name' => trim($item['name']),
+ ':vid' => $item['vid'],
+ ))->fetchField();
+ if (!empty($tid)) {
+ $items[$delta]['tid'] = $tid;
+ continue;
+ }
$term = (object) $item;
unset($term->tid);
taxonomy_term_save($term);
@@ -1764,7 +1773,7 @@ function taxonomy_build_node_index($node) {
}
}
// We only maintain the taxonomy index for published nodes.
- if ($status) {
+ if ($status || variable_get('taxonomy_index_unpublished', FALSE)) {
// Collect a unique list of all the term IDs from all node fields.
$tid_all = array();
foreach (field_info_instances('node', $node->type) as $instance) {
diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test
index 25743bf..59f2e39 100644
--- a/modules/taxonomy/taxonomy.test
+++ b/modules/taxonomy/taxonomy.test
@@ -1108,6 +1108,124 @@ class TaxonomyTermIndexTestCase extends TaxonomyWebTestCase {
}
/**
+ * Tests term autocreation with multiple autocomplete widgets.
+ */
+class TaxonomyTermAutocreateTestCase extends TaxonomyWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Taxonomy term autocreation',
+ 'description' => 'Tests term autocreation with multiple autocomplete widgets.',
+ 'group' => 'Taxonomy',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy');
+ // Create an administrative user.
+ $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+ $this->drupalLogin($this->admin_user);
+
+ // Create a vocabulary and add two autocomplete fields on article nodes.
+ $this->vocabulary = $this->createVocabulary();
+
+ $this->field_name_1 = drupal_strtolower($this->randomName());
+ $this->field_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_1);
+ $this->instance_1 = array(
+ 'field_name' => $this->field_name_1,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_1);
+
+ $this->field_name_2 = drupal_strtolower($this->randomName());
+ $this->field_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'type' => 'taxonomy_term_reference',
+ 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $this->vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($this->field_2);
+ $this->instance_2 = array(
+ 'field_name' => $this->field_name_2,
+ 'bundle' => 'article',
+ 'entity_type' => 'node',
+ 'widget' => array(
+ 'type' => 'taxonomy_autocomplete',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ ),
+ ),
+ );
+ field_create_instance($this->instance_2);
+ }
+
+ /**
+ * Tests adding the same term to multiple autocomplete fields.
+ */
+ function testTaxonomyTagging() {
+ $term1_name = $this->randomName();
+
+ // Post an article.
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode]"] = $term1_name;
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+
+ // Check that the term is saved and can be retrieved.
+ $terms = taxonomy_get_term_by_name($term1_name);
+ $this->assertEqual(1, count($terms), t('The term was saved.'));
+
+ // Add a new term to two different fields.
+ $node = $this->drupalGetNodeByTitle($edit["title"]);
+ $term2_name = $this->randomName();
+ $edit["{$this->field_name_1}[$langcode]"] = $term2_name;
+ $edit["{$this->field_name_2}[$langcode]"] = $term2_name;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+
+ // Check that the term is displayed multiple times.
+ $this->assertText($term2_name, t('The term was saved and appears on the node page.'));
+ $this->assertNoUniqueText($term2_name, t('The term displays multiple times.'));
+
+ // Check that the term is saved only once.
+ $terms = taxonomy_get_term_by_name($term2_name);
+ $this->assertEqual(1, count($terms), t('The term was saved only once.'));
+ }
+}
+
+/**
* Test the taxonomy_term_load_multiple() function.
*/
class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {
diff --git modules/user/user.module modules/user/user.module
index 044ad46..624e92c 100644
--- modules/user/user.module
+++ modules/user/user.module
@@ -3003,34 +3003,41 @@ function user_role_change_permissions($rid, array $permissions = array()) {
}
/**
- * Grant permissions to a user role.
+ * Grants permissions to a user role.
*
* @param $rid
* The ID of a user role to alter.
* @param $permissions
- * A list of permission names to grant.
+ * An array of permission names to grant. Non-existing permissions are
+ * ignored.
*
* @see user_role_change_permissions()
* @see user_role_revoke_permissions()
*/
function user_role_grant_permissions($rid, array $permissions = array()) {
$modules = user_permission_get_modules();
- // Grant new permissions for the role.
- foreach ($permissions as $name) {
- db_merge('role_permission')
- ->key(array(
- 'rid' => $rid,
- 'permission' => $name,
- ))
- ->fields(array(
- 'module' => $modules[$name],
- ))
- ->execute();
- }
- // Clear the user access cache.
- drupal_static_reset('user_access');
- drupal_static_reset('user_role_permissions');
+ // Ignore non-existing permissions.
+ $permissions = array_intersect($permissions, array_keys($modules));
+
+ if ($permissions) {
+ // Grant new permissions for the role.
+ foreach ($permissions as $name) {
+ db_merge('role_permission')
+ ->key(array(
+ 'rid' => $rid,
+ 'permission' => $name,
+ ))
+ ->fields(array(
+ 'module' => $modules[$name],
+ ))
+ ->execute();
+ }
+
+ // Clear the user access cache.
+ drupal_static_reset('user_access');
+ drupal_static_reset('user_role_permissions');
+ }
}
/**
diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc
index 4e88fa1..d0f4259 100644
--- a/includes/database/mysql/schema.inc
+++ b/includes/database/mysql/schema.inc
@@ -131,8 +131,13 @@ class DatabaseSchema_mysql extends DatabaseSchema {
protected function createFieldSql($name, $spec) {
$sql = "`" . $name . "` " . $spec['mysql_type'];
- if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT')) && isset($spec['length'])) {
- $sql .= '(' . $spec['length'] . ')';
+ if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) {
+ if (isset($spec['length'])) {
+ $sql .= '(' . $spec['length'] . ')';
+ }
+ if (!empty($spec['binary'])) {
+ $sql .= ' BINARY';
+ }
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
diff --git a/includes/database/schema.inc b/includes/database/schema.inc
index e2a1c4c..b84c129 100644
--- a/includes/database/schema.inc
+++ b/includes/database/schema.inc
@@ -76,6 +76,10 @@ require_once dirname(__FILE__) . '/query.inc';
* the precision (total number of significant digits) and scale
* (decimal digits right of the decimal point). Both values are
* mandatory. Ignored for other field types.
+ * - 'binary': A boolean indicating that MySQL should force 'char',
+ * 'varchar' or 'text' fields to use case-sensitive binary collation.
+ * This has no effect on other database types for which case sensitivity
+ * is already the default behavior.
* All parameters apart from 'type' are optional except that type
* 'numeric' columns must specify 'precision' and 'scale'.
* - 'primary key': An array of one or more key column specifiers (see below)
diff --git a/modules/simpletest/tests/database_test.install b/modules/simpletest/tests/database_test.install
index 4dce2b1..867d813 100644
--- a/modules/simpletest/tests/database_test.install
+++ b/modules/simpletest/tests/database_test.install
@@ -28,6 +28,7 @@ function database_test_schema() {
'length' => 255,
'not null' => TRUE,
'default' => '',
+ 'binary' => TRUE,
),
'age' => array(
'description' => "The person's age",
diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test
index 7b15cf3..399e2d2 100644
--- a/modules/simpletest/tests/database_test.test
+++ b/modules/simpletest/tests/database_test.test
@@ -3121,6 +3121,39 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase {
}
/**
+ * Test case sensitivity handling.
+ */
+class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Case sensitivity',
+ 'description' => 'Test handling case sensitive collation.',
+ 'group' => 'Database',
+ );
+ }
+
+ /**
+ * Test BINARY collation in MySQL.
+ */
+ function testCaseSensitiveInsert() {
+ $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
+
+ $john = db_insert('test')
+ ->fields(array(
+ 'name' => 'john', // <- A record already exists with name 'John'.
+ 'age' => 2,
+ 'job' => 'Baby',
+ ))
+ ->execute();
+
+ $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
+ $this->assertIdentical($num_records_before + 1, (int) $num_records_after, t('Record inserts correctly.'));
+ $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
+ $this->assertIdentical($saved_age, '2', t('Can retrieve after inserting.'));
+ }
+}
+
+/**
* Test invalid data handling.
*/
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test
index 4c56bfc..3df31ba 100644
--- a/modules/simpletest/tests/file.test
+++ b/modules/simpletest/tests/file.test
@@ -2026,6 +2026,19 @@ class FileSaveTest extends FileHookTestCase {
$loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
$this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File');
$this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly."));
+
+ // Try to insert a second file with the same name apart from case insensitivity
+ // to ensure the 'uri' index allows for filenames with different cases.
+ $file = (object) array(
+ 'uid' => 1,
+ 'filename' => 'DRUPLICON.txt',
+ 'uri' => 'public://DRUPLICON.txt',
+ 'filemime' => 'text/plain',
+ 'timestamp' => 1,
+ 'status' => FILE_STATUS_PERMANENT,
+ );
+ file_put_contents($file->uri, 'hello world');
+ file_save($file);
}
}
diff --git a/modules/system/system.install b/modules/system/system.install
index aed7cc4..bbc471f 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -817,6 +817,7 @@ function system_schema() {
'length' => 255,
'not null' => TRUE,
'default' => '',
+ 'binary' => TRUE,
),
'filemime' => array(
'description' => "The file's MIME type.",
@@ -2180,6 +2181,7 @@ function system_update_7034() {
'length' => 255,
'not null' => TRUE,
'default' => '',
+ 'binary' => TRUE,
),
'filemime' => array(
'description' => "The file's MIME type.",
@@ -2979,6 +2981,23 @@ function system_update_7072() {
}
/**
+ * Apply binary collation to the file_managed.uri column.
+ */
+function system_update_7073() {
+ $spec = array(
+ 'description' => 'The URI to access the file (either local or remote).',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'binary' => TRUE,
+ );
+ db_drop_unique_key('file_managed', 'uri');
+ db_change_field('file_managed', 'uri', 'uri', $spec,
+ array('unique keys' => array('uri' => array('uri'))));
+}
+
+/**
* @} End of "defgroup updates-6.x-to-7.x"
* The next series of updates should start at 8000.
*/
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index d63a59b..8737336 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -289,12 +289,12 @@ abstract class DrupalCacheArray implements ArrayAccess {
/**
* A cid to pass to cache_set() and cache_get().
*/
- private $cid;
+ protected $cid;
/**
* A bin to pass to cache_set() and cache_get().
*/
- private $bin;
+ protected $bin;
/**
* An array of keys to add to the cache at the end of the request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment