5 * Install, update and uninstall functions for the taxonomy module.
9 * Implements hook_uninstall().
11 function taxonomy_uninstall() {
13 variable_del('taxonomy_override_selector');
14 variable_del('taxonomy_terms_per_page_admin');
15 // Remove taxonomy_term bundles.
16 $vocabularies = db_query("SELECT machine_name FROM {taxonomy_vocabulary}")->fetchCol();
17 foreach ($vocabularies as $vocabulary) {
18 field_attach_delete_bundle('taxonomy_term', $vocabulary);
23 * Implements hook_schema().
25 function taxonomy_schema() {
26 $schema['taxonomy_term_data'] = array(
27 'description' => 'Stores term information.',
33 'description' => 'Primary Key: Unique term ID.',
40 'description' => 'The {taxonomy_vocabulary}.vid of the vocabulary to which the term is assigned.',
47 'description' => 'The term name.',
48 'translatable' => TRUE,
50 'description' => array(
54 'description' => 'A description of the term.',
55 'translatable' => TRUE,
61 'description' => 'The {filter_format}.format of the description.',
67 'description' => 'The weight of this term in relation to other terms.',
70 'primary key' => array('tid'),
71 'foreign keys' => array(
72 'vocabulary' => array(
73 'table' => 'taxonomy_vocabulary',
74 'columns' => array('vid' => 'vid'),
78 'taxonomy_tree' => array('vid', 'weight', 'name'),
79 'vid_name' => array('vid', 'name'),
80 'name' => array('name'),
84 $schema['taxonomy_term_hierarchy'] = array(
85 'description' => 'Stores the hierarchical relationship between terms.',
92 'description' => 'Primary Key: The {taxonomy_term_data}.tid of the term.',
99 'description' => "Primary Key: The {taxonomy_term_data}.tid of the term's parent. 0 indicates no parent.",
103 'parent' => array('parent'),
105 'foreign keys' => array(
106 'taxonomy_term_data' => array(
107 'table' => 'taxonomy_term_data',
108 'columns' => array('tid' => 'tid'),
111 'primary key' => array('tid', 'parent'),
114 $schema['taxonomy_vocabulary'] = array(
115 'description' => 'Stores vocabulary information.',
121 'description' => 'Primary Key: Unique vocabulary ID.',
128 'description' => 'Name of the vocabulary.',
129 'translatable' => TRUE,
131 'machine_name' => array(
136 'description' => 'The vocabulary machine name.',
138 'description' => array(
142 'description' => 'Description of the vocabulary.',
143 'translatable' => TRUE,
145 'hierarchy' => array(
151 'description' => 'The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)',
158 'description' => 'The module which created the vocabulary.',
164 'description' => 'The weight of this vocabulary in relation to other vocabularies.',
167 'primary key' => array('vid'),
169 'list' => array('weight', 'name'),
171 'unique keys' => array(
172 'machine_name' => array('machine_name'),
176 $schema['taxonomy_index'] = array(
177 'description' => 'Maintains denormalized information about node/term relationships.',
180 'description' => 'The {node}.nid this record tracks.',
187 'description' => 'The term ID.',
194 'description' => 'Boolean indicating whether the node is sticky.',
201 'description' => 'The Unix timestamp when the node was created.',
208 'term_node' => array('tid', 'sticky', 'created'),
209 'nid' => array('nid'),
211 'foreign keys' => array(
212 'tracked_node' => array(
214 'columns' => array('nid' => 'nid'),
217 'table' => 'taxonomy_term_data',
218 'columns' => array('tid' => 'tid'),
227 * Implements hook_field_schema().
229 function taxonomy_field_schema($field) {
239 'tid' => array('tid'),
241 'foreign keys' => array(
243 'table' => 'taxonomy_term_data',
244 'columns' => array('tid' => 'tid'),
251 * Implements hook_update_dependencies().
253 function taxonomy_update_dependencies() {
254 // taxonomy_update_7004() migrates taxonomy term data to fields and therefore
255 // must run after all Field modules have been enabled, which happens in
256 // system_update_7027().
257 $dependencies['taxonomy'][7004] = array(
261 return $dependencies;
265 * Utility function: get the list of vocabularies directly from the database.
267 * This function is valid for a database schema version 7002.
269 * @ingroup update_api
271 function _update_7002_taxonomy_get_vocabularies() {
272 return db_query('SELECT v.* FROM {taxonomy_vocabulary} v ORDER BY v.weight, v.name')->fetchAllAssoc('vid', PDO::FETCH_OBJ);
276 * Rename taxonomy tables.
278 function taxonomy_update_7001() {
279 db_rename_table('term_data', 'taxonomy_term_data');
280 db_rename_table('term_hierarchy', 'taxonomy_term_hierarchy');
281 db_rename_table('term_node', 'taxonomy_term_node');
282 db_rename_table('term_relation', 'taxonomy_term_relation');
283 db_rename_table('term_synonym', 'taxonomy_term_synonym');
284 db_rename_table('vocabulary', 'taxonomy_vocabulary');
285 db_rename_table('vocabulary_node_types', 'taxonomy_vocabulary_node_type');
289 * Add {vocabulary}.machine_name column.
291 function taxonomy_update_7002() {
297 'description' => 'The vocabulary machine name.',
300 db_add_field('taxonomy_vocabulary', 'machine_name', $field);
302 // Do a direct query here, rather than calling taxonomy_get_vocabularies(),
303 // in case Taxonomy module is disabled.
304 $vids = db_query('SELECT vid FROM {taxonomy_vocabulary}')->fetchCol();
305 foreach ($vids as $vid) {
306 $machine_name = 'vocabulary_' . $vid;
307 db_update('taxonomy_vocabulary')
308 ->fields(array('machine_name' => $machine_name))
309 ->condition('vid', $vid)
313 // The machine_name unique key can only be added after we ensure the
314 // machine_name column contains unique values.
315 db_add_unique_key('taxonomy_vocabulary', 'machine_name', array('machine_name'));
319 * Remove the related terms setting from vocabularies.
321 * This setting has not been used since Drupal 6. The {taxonomy_relations} table
322 * itself is retained to allow for data to be upgraded.
324 function taxonomy_update_7003() {
325 db_drop_field('taxonomy_vocabulary', 'relations');
329 * Move taxonomy vocabulary associations for nodes to fields and field instances.
331 function taxonomy_update_7004() {
332 $taxonomy_index = array(
333 'description' => 'Maintains denormalized information about node/term relationships.',
336 'description' => 'The {node}.nid this record tracks.',
343 'description' => 'The term ID.',
350 'description' => 'Boolean indicating whether the node is sticky.',
357 'description' => 'The Unix timestamp when the node was created.',
365 'term_node' => array('tid', 'sticky', 'created'),
366 'nid' => array('nid'),
368 'foreign keys' => array(
369 'tracked_node' => array(
371 'columns' => array('nid' => 'nid'),
374 'table' => 'taxonomy_term_data',
375 'columns' => array('tid' => 'tid'),
379 db_create_table('taxonomy_index', $taxonomy_index);
381 // Use an inline version of Drupal 6 taxonomy_get_vocabularies() here since
382 // we can no longer rely on $vocabulary->nodes from the API function.
383 $result = db_query('SELECT v.*, n.type FROM {taxonomy_vocabulary} v LEFT JOIN {taxonomy_vocabulary_node_type} n ON v.vid = n.vid ORDER BY v.weight, v.name');
384 $vocabularies = array();
385 foreach ($result as $record) {
386 // If no node types are associated with a vocabulary, the LEFT JOIN will
387 // return a NULL value for type.
388 if (isset($record->type)) {
389 $node_types[$record->vid][$record->type] = $record->type;
390 unset($record->type);
391 $record->nodes = $node_types[$record->vid];
393 elseif (!isset($record->nodes)) {
394 $record->nodes = array();
396 $vocabularies[$record->vid] = $record;
399 foreach ($vocabularies as $vocabulary) {
400 $field_name = 'taxonomy_' . $vocabulary->machine_name;
402 'field_name' => $field_name,
403 'module' => 'taxonomy',
404 'type' => 'taxonomy_term_reference',
405 'cardinality' => $vocabulary->multiple || $vocabulary->tags ? FIELD_CARDINALITY_UNLIMITED : 1,
407 'required' => $vocabulary->required ? TRUE : FALSE,
408 'allowed_values' => array(
410 'vocabulary' => $vocabulary->machine_name,
416 _update_7000_field_create_field($field);
418 foreach ($vocabulary->nodes as $bundle) {
420 'label' => $vocabulary->name,
421 'field_name' => $field_name,
423 'entity_type' => 'node',
424 'settings' => array(),
425 'description' => $vocabulary->help,
426 'required' => $vocabulary->required,
430 'type' => 'taxonomy_term_reference_link',
434 'type' => 'taxonomy_term_reference_link',
439 if ($vocabulary->tags) {
440 $instance['widget'] = array(
441 'type' => 'taxonomy_autocomplete',
442 'module' => 'taxonomy',
445 'autocomplete_path' => 'taxonomy/autocomplete',
450 $instance['widget'] = array(
452 'module' => 'options',
453 'settings' => array(),
456 _update_7000_field_create_instance($field, $instance);
460 // Some contrib projects stored term node associations without regard for the
461 // selections in the taxonomy_vocabulary_node_types table, or have more terms
462 // for a single node than the vocabulary allowed. We construct the
463 // taxonomyextra field to store all the extra stuff.
465 // Allowed values for this extra vocabs field is every vocabulary.
466 $allowed_values = array();
467 foreach (_update_7002_taxonomy_get_vocabularies() as $vocabulary) {
468 $allowed_values[] = array(
469 'vocabulary' => $vocabulary->machine_name,
474 $field_name = 'taxonomyextra';
476 'field_name' => $field_name,
477 'module' => 'taxonomy',
478 'type' => 'taxonomy_term_reference',
479 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
482 'allowed_values' => $allowed_values,
485 _update_7000_field_create_field($field);
487 foreach (_update_7000_node_get_types() as $bundle) {
489 'label' => 'Taxonomy upgrade extras',
490 'field_name' => $field_name,
491 'entity_type' => 'node',
492 'bundle' => $bundle->type,
493 'settings' => array(),
494 'description' => 'Debris left over after upgrade from Drupal 6',
497 'type' => 'taxonomy_autocomplete',
498 'module' => 'taxonomy',
499 'settings' => array(),
503 'type' => 'taxonomy_term_reference_link',
507 'type' => 'taxonomy_term_reference_link',
512 _update_7000_field_create_instance($field, $instance);
515 $fields = array('help', 'multiple', 'required', 'tags');
516 foreach ($fields as $field) {
517 db_drop_field('taxonomy_vocabulary', $field);
522 * Migrate {taxonomy_term_node} table to field storage.
524 * @todo: This function can possibly be made much faster by wrapping a
525 * transaction around all the inserts.
527 function taxonomy_update_7005(&$sandbox) {
528 // $sandbox contents:
529 // - total: The total number of term_node relationships to migrate.
530 // - count: The number of term_node relationships that have been
532 // - last: The db_query_range() offset to use when querying
533 // term_node; this field is incremented in quantities of $batch
534 // (1000) but at the end of each call to this function, last and
535 // count are the same.
536 // - vocabularies: An associative array mapping vocabulary id and node
537 // type to field name. If a voc id/node type pair does not appear
538 // in this array but a term_node relationship exists mapping a
539 // term in voc id to node of that type, the relationship is
540 // assigned to the taxonomymyextra field which allows terms of all
542 // - cursor[values], cursor[deltas]: The contents of $values and
543 // $deltas at the end of the previous call to this function. These
544 // need to be preserved across calls because a single batch of
545 // 1000 rows from term_node may end in the middle of the terms for
546 // a single node revision.
548 // $values is the array of values about to be/most recently inserted
549 // into the SQL data table for the taxonomy_term_reference
550 // field. Before $values is constructed for each record, the
551 // $values from the previous insert is checked to see if the two
552 // records are for the same node revision id; this enables knowing
553 // when to reset the delta counters which are incremented across all
554 // terms for a single field on a single revision, but reset for each
555 // new field and revision.
557 // $deltas is an associative array mapping field name to the number
558 // of term references stored so far for the current revision, which
559 // provides the delta value for each term reference data insert. The
560 // deltas are reset for each new revision.
563 'type' => 'taxonomy_term_reference',
566 $field_info = _update_7000_field_read_fields($conditions, 'field_name');
568 // This is a multi-pass update. On the first call we need to initialize some
570 if (!isset($sandbox['total'])) {
571 $sandbox['last'] = 0;
572 $sandbox['count'] = 0;
574 // Run the same joins as the query that is used later to retrieve the
575 // term_node data, this ensures that bad records in that table - for
576 // tids which aren't in taxonomy_term_data or nids which aren't in {node}
577 // are not included in the count.
578 $sandbox['total'] = db_query('SELECT COUNT(*) FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid INNER JOIN {node} n ON tn.nid = n.nid LEFT JOIN {node} n2 ON tn.vid = n2.vid')->fetchField();
580 // Use an inline version of Drupal 6 taxonomy_get_vocabularies() here since
581 // we can no longer rely on $vocabulary->nodes from the API function.
582 $result = db_query('SELECT v.vid, v.machine_name, n.type FROM {taxonomy_vocabulary} v INNER JOIN {taxonomy_vocabulary_node_type} n ON v.vid = n.vid');
583 $vocabularies = array();
584 foreach ($result as $record) {
586 // If no node types are associated with a vocabulary, the LEFT JOIN will
587 // return a NULL value for type.
588 if (isset($record->type)) {
589 $vocabularies[$record->vid][$record->type] = 'taxonomy_'. $record->machine_name;
593 if (!empty($vocabularies)) {
594 $sandbox['vocabularies'] = $vocabularies;
597 db_create_table('taxonomy_update_7005', array(
598 'description' => 'Stores temporary data for taxonomy_update_7005.',
601 'description' => 'Preserve order.',
646 'is_current' => array(
652 'primary key' => array('n'),
655 // Query selects all revisions at once and processes them in revision and
656 // term weight order.
657 $query = db_select('taxonomy_term_data', 'td');
658 // We are migrating term-node relationships. If there are none for a
659 // term, we do not need the term_data row.
660 $query->join('taxonomy_term_node', 'tn', 'td.tid = tn.tid');
661 // If a term-node relationship exists for a nid that does not exist, we
662 // cannot migrate it as we have no node to relate it to; thus we do not
663 // need that row from term_node.
664 $query->join('node', 'n', 'tn.nid = n.nid');
665 // If the current term-node relationship is for the current revision of
666 // the node, this left join will match and is_current will be non-NULL
667 // (we also get the current sticky and created in this case). This
668 // tells us whether to insert into the current data tables in addition
669 // to the revision data tables.
670 $query->leftJoin('node', 'n2', 'tn.vid = n2.vid');
671 $query->addField('td', 'vid', 'vocab_id');
672 $query->addField('td', 'tid');
673 $query->addField('tn', 'nid');
674 $query->addField('tn', 'vid');
675 $query->addField('n', 'type');
676 $query->addField('n2', 'created');
677 $query->addField('n2', 'sticky');
678 $query->addField('n2', 'status');
679 $query->addField('n2', 'nid', 'is_current');
680 // This query must return a consistent ordering across multiple calls.
681 // We need them ordered by node vid (since we use that to decide when
682 // to reset the delta counters) and by term weight so they appear
683 // within each node in weight order. However, tn.vid,td.weight is not
684 // guaranteed to be unique, so we add tn.tid as an additional sort key
685 // because tn.tid,tn.vid is the primary key of the D6 term_node table
686 // and so is guaranteed unique. Unfortunately it also happens to be in
687 // the wrong order which is less efficient, but c'est la vie.
688 $query->orderBy('tn.vid');
689 $query->orderBy('td.weight');
690 $query->orderBy('tn.tid');
692 // Work around a bug in the PostgreSQL driver that would result in fatal
693 // errors when this subquery is used in the insert query below. See
694 // https://drupal.org/node/2057693.
695 $fields = &$query->getFields();
696 unset($fields['td.weight']);
697 unset($fields['tn.tid']);
699 db_insert('taxonomy_update_7005')
704 // We do each pass in batches of 1000.
707 $result = db_query_range('SELECT vocab_id, tid, nid, vid, type, created, sticky, status, is_current FROM {taxonomy_update_7005} ORDER BY n', $sandbox['last'], $batch);
708 if (isset($sandbox['cursor'])) {
709 $values = $sandbox['cursor']['values'];
710 $deltas = $sandbox['cursor']['deltas'];
715 foreach ($result as $record) {
716 $sandbox['count'] += 1;
718 // Use the valid field for this vocabulary and node type or use the
719 // overflow vocabulary if there is no valid field.
720 $field_name = isset($sandbox['vocabularies'][$record->vocab_id][$record->type]) ? $sandbox['vocabularies'][$record->vocab_id][$record->type] : 'taxonomyextra';
721 $field = $field_info[$field_name];
723 // Start deltas from 0, and increment by one for each term attached to a
725 if (!isset($deltas[$field_name])) {
726 $deltas[$field_name] = 0;
729 if (isset($values)) {
731 // If the last inserted revision_id is the same as the current record,
732 // use the previous deltas to calculate the next delta.
733 if ($record->vid == $values[2]) {
735 // For limited cardinality fields, the delta must not be allowed to
736 // exceed the cardinality during the update. So ensure that the
737 // delta about to be inserted is within this limit.
738 // @see field_default_validate().
739 if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ($deltas[$field_name] + 1) > $field['cardinality']) {
741 // For excess values of a single-term vocabulary, switch over to
742 // the overflow field.
743 $field_name = 'taxonomyextra';
744 $field = $field_info[$field_name];
745 if (!isset($deltas[$field_name])) {
746 $deltas[$field_name] = 0;
752 // When the record is a new revision, empty the deltas array.
753 $deltas = array($field_name => 0);
757 // Table and column found in the field's storage details. During upgrades,
759 $table_name = "field_data_{$field_name}";
760 $revision_name = "field_revision_{$field_name}";
761 $value_column = $field_name . '_tid';
763 // Column names and values in field storage are the same for current and
765 $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'language', 'delta', $value_column);
766 $values = array('node', $record->nid, $record->vid, $record->type, LANGUAGE_NONE, $deltas[$field_name]++, $record->tid);
768 // Insert rows into the revision table.
769 db_insert($revision_name)->fields($columns)->values($values)->execute();
771 // is_current column is a node ID if this revision is also current.
772 if ($record->is_current) {
773 db_insert($table_name)->fields($columns)->values($values)->execute();
774 // Only insert a record in the taxonomy index if the node is published.
775 if ($record->status) {
776 // Update the {taxonomy_index} table.
777 db_insert('taxonomy_index')
778 ->fields(array('nid', 'tid', 'sticky', 'created',))
779 ->values(array($record->nid, $record->tid, $record->sticky, $record->created))
785 // Store the set of inserted values and the current revision's deltas in the
787 $sandbox['cursor'] = array(
791 $sandbox['last'] += $batch;
794 if ($sandbox['count'] < $sandbox['total']) {
795 $sandbox['#finished'] = FALSE;
798 db_drop_table('taxonomy_vocabulary_node_type');
799 db_drop_table('taxonomy_term_node');
801 // If there are no vocabs, we're done.
802 db_drop_table('taxonomy_update_7005');
803 $sandbox['#finished'] = TRUE;
805 // Determine necessity of taxonomyextras field.
806 $field = $field_info['taxonomyextra'];
807 $revision_name = 'field_revision_' . $field['field_name'];
808 $node_types = db_select($revision_name)->distinct()->fields($revision_name, array('bundle'))
809 ->execute()->fetchCol();
811 if (empty($node_types)) {
812 // Delete the overflow field if there are no rows in the revision table.
813 _update_7000_field_delete_field('taxonomyextra');
816 // Remove instances which are not actually used.
817 $bundles = db_query('SELECT bundle FROM {field_config_instance} WHERE field_name = :field_name', array(':field_name' => 'taxonomyextra'))->fetchCol();
818 $bundles = array_diff($bundles, $node_types);
819 foreach ($bundles as $bundle) {
820 _update_7000_field_delete_instance('taxonomyextra', 'node', $bundle);
827 * Add {taxonomy_term_data}.format column.
829 function taxonomy_update_7006() {
830 db_add_field('taxonomy_term_data', 'format', array(
834 'description' => 'The {filter_format}.format of the description.',
839 * Add index on {taxonomy_term_data}.name column to speed up taxonomy_get_term_by_name().
841 function taxonomy_update_7007() {
842 db_add_index('taxonomy_term_data', 'name', array('name'));
846 * Change the weight columns to normal int.
848 function taxonomy_update_7008() {
849 db_drop_index('taxonomy_term_data', 'taxonomy_tree');
850 db_change_field('taxonomy_term_data', 'weight', 'weight', array(
854 'description' => 'The weight of this term in relation to other terms.',
857 'taxonomy_tree' => array('vid', 'weight', 'name'),
861 db_drop_index('taxonomy_vocabulary', 'list');
862 db_change_field('taxonomy_vocabulary', 'weight', 'weight', array(
866 'description' => 'The weight of this vocabulary in relation to other vocabularies.',
869 'list' => array('weight', 'name'),
875 * Change {taxonomy_term_data}.format into varchar.
877 function taxonomy_update_7009() {
878 db_change_field('taxonomy_term_data', 'format', 'format', array(
882 'description' => 'The {filter_format}.format of the description.',
887 * Change {taxonomy_index}.created to support signed int.
889 function taxonomy_update_7010() {
890 db_change_field('taxonomy_index', 'created', 'created', array(
891 'description' => 'The Unix timestamp when the node was created.',
900 * @addtogroup updates-7.x-extra
905 * Drop unpublished nodes from the index.
907 function taxonomy_update_7011(&$sandbox) {
908 // Initialize information needed by the batch update system.
909 if (!isset($sandbox['progress'])) {
910 $sandbox['progress'] = 0;
911 $sandbox['max'] = db_query('SELECT COUNT(DISTINCT n.nid) FROM {node} n INNER JOIN {taxonomy_index} t ON n.nid = t.nid WHERE n.status = :status', array(':status' => NODE_NOT_PUBLISHED))->fetchField();
912 // If there's no data, don't bother with the extra work.
913 if (empty($sandbox['max'])) {
918 // Process records in groups of 5000.
920 $nids = db_query_range('SELECT DISTINCT n.nid FROM {node} n INNER JOIN {taxonomy_index} t ON n.nid = t.nid WHERE n.status = :status', 0, $limit, array(':status' => NODE_NOT_PUBLISHED))->fetchCol();
922 db_delete('taxonomy_index')
923 ->condition('nid', $nids)
927 // Update our progress information for the batch update.
928 $sandbox['progress'] += $limit;
930 // Indicate our current progress to the batch update system, if the update is
932 if ($sandbox['progress'] < $sandbox['max']) {
933 $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
938 * @} End of "addtogroup updates-7.x-extra".