<?php
// $Id: taxonomy.install,v 1.41 2010/04/20 07:47:55 webchick Exp $

/**
 * @file
 * Install, update and uninstall functions for the taxonomy module.
 */

/**
 * Implements hook_uninstall().
 */
function taxonomy_uninstall() {
  // Remove variables.
  variable_del('taxonomy_override_selector');
  variable_del('taxonomy_terms_per_page_admin');
}

/**
 * Implements hook_schema().
 */
function taxonomy_schema() {
  $schema['taxonomy_term_data'] = array(
    'description' => 'Stores term information.',
    'fields' => array(
      'tid' => array(
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'description' => 'Primary Key: Unique term ID.',
      ),
      'vid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => 'The {taxonomy_vocabulary}.vid of the vocabulary to which the term is assigned.',
      ),
      'name' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
        'description' => 'The term name.',
        'translatable' => TRUE,
      ),
      'description' => array(
        'type' => 'text',
        'not null' => FALSE,
        'size' => 'big',
        'description' => 'A description of the term.',
        'translatable' => TRUE,
      ),
      'format' => array(
        'type' => 'int',
        'size' => 'small',
        'not null' => TRUE,
        'default' => 0,
        'description' => 'The {filter_format}.format of the description.',
      ),
      'weight' => array(
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
        'description' => 'The weight of this term in relation to other terms.',
      ),
    ),
    'primary key' => array('tid'),
    'foreign keys' => array(
      'vid' => array('taxonomy_vocabulary' => 'vid'),
    ),
    'indexes' => array(
      'taxonomy_tree' => array('vid', 'weight', 'name'),
      'vid_name' => array('vid', 'name'),
      'name' => array('name'),
    ),
  );

  $schema['taxonomy_term_hierarchy'] = array(
    'description' => 'Stores the hierarchical relationship between terms.',
    'fields' => array(
      'tid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => 'Primary Key: The {taxonomy_term_data}.tid of the term.',
      ),
      'parent' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => "Primary Key: The {taxonomy_term_data}.tid of the term's parent. 0 indicates no parent.",
      ),
    ),
    'indexes' => array(
      'parent' => array('parent'),
    ),
    'foreign keys' => array(
      'tid' => array('taxonomy_term_data' => 'tid'),
    ),
    'primary key' => array('tid', 'parent'),
  );

  $schema['taxonomy_vocabulary'] = array(
    'description' => 'Stores vocabulary information.',
    'fields' => array(
      'vid' => array(
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'description' => 'Primary Key: Unique vocabulary ID.',
      ),
      'name' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
        'description' => 'Name of the vocabulary.',
        'translatable' => TRUE,
      ),
      'machine_name' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
        'description' => 'The vocabulary machine name.',
      ),
      'description' => array(
        'type' => 'text',
        'not null' => FALSE,
        'size' => 'big',
        'description' => 'Description of the vocabulary.',
        'translatable' => TRUE,
      ),
      'hierarchy' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'size' => 'tiny',
        'description' => 'The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)',
      ),
      'module' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
        'description' => 'The module which created the vocabulary.',
      ),
      'weight' => array(
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
        'description' => 'The weight of this vocabulary in relation to other vocabularies.',
      ),
    ),
    'primary key' => array('vid'),
    'indexes' => array(
      'list' => array('weight', 'name'),
    ),
    'unique keys' => array(
      'machine_name' => array('machine_name'),
    ),
  );

  $schema['taxonomy_index'] = array(
    'description' => 'Maintains denormalized information about node/term relationships.',
    'fields' => array(
      'nid' => array(
        'description' => 'The {node}.nid this record tracks.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'tid' => array(
         'description' => 'The term ID.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
      ),
      'sticky' => array(
        'description' => 'Boolean indicating whether the node is sticky.',
        'type' => 'int',
        'not null' => FALSE,
        'default' => 0,
        'size' => 'tiny',
      ),
      'created' => array(
        'description' => 'The Unix timestamp when the node was created.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default'=> 0,
      ),
    ),
    'indexes' => array(
      'term_node' => array('tid', 'sticky', 'created'),
      'nid' => array('nid'),
    ),
    'foreign keys' => array(
      'node' => 'nid',
      'taxonomy_term_data' => 'tid',
    ),
  );

  return $schema;
}

/**
 * Implements hook_update_dependencies().
 */
function taxonomy_update_dependencies() {
  // Taxonomy update 7002 creates comment Field API bundles and therefore must
  // run after the Field module has been enabled, but before upgrading field
  // data.
  $dependencies['taxonomy'][7002] = array(
    'system' => 7049,
  );
  $dependencies['user'][7006] = array(
    'taxonomy' => 7002,
  );
  $dependencies['system'][7050] = array(
    'taxonomy' => 7002,
  );
  // It also must run before nodes are upgraded to use the Field API.
  $dependencies['node'][7006] = array(
    'taxonomy' => 7002,
  );

  return $dependencies;
}

/**
 * Rename taxonomy tables.
 */
function taxonomy_update_7001() {
  db_rename_table('term_data', 'taxonomy_term_data');
  db_rename_table('term_hierarchy', 'taxonomy_term_hierarchy');
  db_rename_table('term_node', 'taxonomy_term_node');
  db_rename_table('term_relation', 'taxonomy_term_relation');
  db_rename_table('term_synonym', 'taxonomy_term_synonym');
  db_rename_table('vocabulary', 'taxonomy_vocabulary');
  db_rename_table('vocabulary_node_types', 'taxonomy_vocabulary_node_type');
}

/**
 * Add {vocabulary}.machine_name column.
 */
function taxonomy_update_7002() {
  $field = array(
    'type' => 'varchar',
    'length' => 255,
    'not null' => TRUE,
    'default' => '',
    'description' => 'The vocabulary machine name.',
  );

  db_add_field('taxonomy_vocabulary', 'machine_name', $field);

  foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
    $machine_name = 'vocabulary_' . $vid;
    db_update('taxonomy_vocabulary')
      ->fields(array('machine_name' => $machine_name))
      ->condition('vid', $vid)
      ->execute();
    field_attach_create_bundle('taxonomy_term', $machine_name);
  }

  // The machine_name unique key can only be added after we ensure the
  // machine_name column contains unique values.
  db_add_unique_key('taxonomy_vocabulary', 'machine_name', array('machine_name'));
}

/**
 * Remove the related terms setting from vocabularies.
 *
 * This setting has not been used since Drupal 6. The {taxonomy_relations} table
 * itself is retained to allow for data to be upgraded.
 */
function taxonomy_update_7003() {
  db_drop_field('taxonomy_vocabulary', 'relations');
}

/**
 * Move taxonomy vocabulary associations for nodes to fields and field instances.
 */
function taxonomy_update_7004() {
  $taxonomy_index = array(
    'description' => 'Maintains denormalized information about node/term relationships.',
    'fields' => array(
      'nid' => array(
        'description' => 'The {node}.nid this record tracks.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ),
      'tid' => array(
         'description' => 'The term ID.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
      ),
      'sticky' => array(
        'description' => 'Boolean indicating whether the node is sticky.',
        'type' => 'int',
        'not null' => FALSE,
        'default' => 0,
        'size' => 'tiny',
      ),
      'created' => array(
        'description' => 'The Unix timestamp when the node was created.',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default'=> 0,
      ),
    ),
    'indexes' => array(
      'term_node' => array('tid', 'sticky', 'created'),
      'nid' => array('nid'),
    ),
    'foreign keys' => array(
      'node' => 'nid',
      'taxonomy_term_data' => 'tid',
    ),
  );
  db_create_table('taxonomy_index', $taxonomy_index);

  // Use an inline version of Drupal 6 taxonomy_get_vocabularies() here since
  // we can no longer rely on $vocabulary->nodes from the API function.
  $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');
  $vocabularies = array();
  foreach ($result as $record) {
    // If no node types are associated with a vocabulary, the LEFT JOIN will
    // return a NULL value for type.
    if (isset($record->type)) {
      $node_types[$record->vid][$record->type] = $record->type;
      unset($record->type);
      $record->nodes = $node_types[$record->vid];
    }
    elseif (!isset($record->nodes)) {
      $record->nodes = array();
    }
    $vocabularies[$record->vid] = $record;
  }

  foreach ($vocabularies as $vocabulary) {
    $field_name = 'taxonomy_' . $vocabulary->machine_name;
    $field = array(
      'field_name' => $field_name,
      'type' => 'taxonomy_term_reference',
      'cardinality' => $vocabulary->multiple || $vocabulary->tags ? FIELD_CARDINALITY_UNLIMITED : 1,
      'settings' => array(
        'required' => $vocabulary->required ? TRUE : FALSE,
        'allowed_values' => array(
          array(
            'vid' => $vocabulary->vid,
            'parent' => 0,
          ),
        ),
      ),
    );
    field_create_field($field);

    foreach ($vocabulary->nodes as $bundle) {
      $instance = array(
        'label' => $vocabulary->name,
        'field_name' => $field_name,
        'bundle' => $bundle,
        'entity_type' => 'node',
        'description' => $vocabulary->help,
        'widget' => array(
          'type' => $vocabulary->tags ? 'taxonomy_autocomplete' : 'select',
        ),
      );
      field_create_instance($instance);
    }
  }
  db_drop_table('taxonomy_vocabulary_node_type');
  $fields = array('help', 'multiple', 'required', 'tags');
  foreach ($fields as $field) {
    db_drop_field('taxonomy_vocabulary', $field);
  }
}

/**
 * Migrate {taxonomy_term_node} table to field storage.
 */
function taxonomy_update_7005(&$sandbox) {
  // Since we are upgrading from Drupal 6, we know that only
  // field_sql_storage.module will be enabled.
  $field = field_info_field($field['field_name']);
  $data_table = _field_sql_storage_tablename($field);
  $revision_table = _field_sql_storage_revision_tablename($field);
  $etid = _field_sql_storage_etid('node');
  $value_column = $field['field_name'] . '_value';
  $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', $value_column);

  // This is a multi-pass update. On the first call we need to initialize some
  // variables.
  if (!isset($sandbox['total'])) {
    $sandbox['last'] = 0;
    $sandbox['count'] = 0;

    $query = db_select('taxonomy_term_node', 't');
    $sandbox['total'] = $query->countQuery()->execute()->fetchField();
    $found = (bool) $sandbox['total'];
  }
  else {
    // We do each pass in batches of 1000, this should result in a
    // maximum of 2000 insert queries each operation.
    $batch = 1000 + $sandbox['last'];

    // Query and save data for the current revision.
    $result = db_query_range('SELECT td.tid, tn.nid, td.weight, tn.vid, n2.type, n2.created, n2.sticky FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid INNER JOIN {node} n2 ON tn.nid = n2.nid INNER JOIN {node} n ON tn.vid = n.vid AND td.vid = :vocabulary_id ORDER BY td.weight ASC', array(':vocabulary_id' => $vocabulary->vid), $sandbox['last'], $batch);
    $deltas = array();
    foreach ($result as $record) {
      $found = TRUE;
      $sandbox['count'] += 1;
      // Start deltas from 0, and increment by one for each
      // term attached to a node.
      $deltas[$record->nid] = isset($deltas[$record->nid]) ? ++$deltas[$record->nid] : 0;
      $values = array($etid, $record->nid, $record->vid, $record->type, $deltas[$record->nid], $record->tid);
      db_insert($data_table)->fields($columns)->values($values)->execute();

      // Update the {taxonomy_index} table.
      db_insert('taxonomy_index')
        ->fields(array('nid', 'tid', 'sticky', 'created',))
        ->values(array($record->nid, $record->tid, $record->sticky, $record->created))
        ->execute();
    }

    // Query and save data for all revisions.
    $result = db_query('SELECT td.tid, tn.nid, td.weight, tn.vid, n.type FROM {taxonomy_term_data} td INNER JOIN {taxonomy_term_node} tn ON td.tid = tn.tid AND td.vid = :vocabulary_id INNER JOIN {node} n ON tn.nid = n.nid ORDER BY td.weight ASC', array(':vocabulary_id' => $vocabulary->vid), $sandbox['last'][$batch]);
    $deltas = array();
    foreach ($result as $record) {
      $found = TRUE;
      $sandbox['count'] += 1;
      // Start deltas at 0, and increment by one for each term attached to a revision.
      $deltas[$record->vid] = isset($deltas[$record->vid]) ? ++$deltas[$record->vid] : 0;
      $values = array($etid, $record->nid, $record->vid, $record->type, $deltas[$record->vid], $record->tid);
      db_insert($revision_table)->fields($columns)->values($values)->execute();
    }
    $sandbox['last'] = $batch;
  }
  if (!$found) {
   db_drop_table('taxonomy_term_node');
  }
}

/**
 * Add {taxonomy_term_data}.format column.
 */
function taxonomy_update_7006() {
  db_add_field('taxonomy_term_data', 'format', array(
    'type' => 'int',
    'size' => 'small',
    'not null' => TRUE,
    'default' => 0,
    'description' => 'The {filter_format}.format of the description.',
  ));
}

/**
 * Add index on {taxonomy_term_data}.name column to speed up taxonomy_get_term_by_name().
 */
function taxonomy_update_7007() {
  db_add_index('taxonomy_term_data', 'name', array('name'));
}

/**
 * Change the weight columns to normal int.
 */
function taxonomy_update_7008() {
  db_drop_index('taxonomy_term_data', 'taxonomy_tree');
  db_change_field('taxonomy_term_data', 'weight', 'weight', array(
    'type' => 'int',
    'not null' => TRUE,
    'default' => 0,
    'description' => 'The weight of this term in relation to other terms.',
  ), array(
    'indexes' => array(
       'taxonomy_tree' => array('vid', 'weight', 'name'),
    ),
  ));

  db_drop_index('taxonomy_vocabulary', 'list');
  db_change_field('taxonomy_vocabulary', 'weight', 'weight', array(
    'type' => 'int',
    'not null' => TRUE,
    'default' => 0,
    'description' => 'The weight of this vocabulary in relation to other vocabularies.',
  ), array(
    'indexes' => array(
      'list' => array('weight', 'name'),
    ),
  ));
}
