123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- <?php
- /**
- * Implementation of hook_permission().
- *
- * Adds a permission to merge terms.
- */
- function term_merge_permission() {
- return array(
- 'merge terms' => array(
- 'title' => t('Merge terms'),
- 'description' => t('Gives the user the ability to merge terms.'),
- )
- );
- }
- /**
- * Implements hook_menu():
- */
- function term_merge_menu() {
- $items = array(
- 'admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/merge' => array(
- 'title' => 'Merge terms',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('term_merge_term_merge_form', 3),
- 'access arguments' => array('merge terms'),
- 'type' => MENU_LOCAL_TASK,
- )
- );
- return $items;
- }
- /**
- * Implements hook_help().
- */
- function term_merge_help($path, $arg) {
- switch ($path) {
- // Main module help for the block module
- case 'admin/help#term_merge':
- return '<p>Allows you to merge multiple terms into one and and at the same time update all fields referencing to the old ones.</p>';
- /*case 'admin/structure/taxonomy/%/merge':
- return '<p>This form simply lets you merge multiple terms into one and at the same time update all fields referencing to the old ones.</p>';*/
- }
- }
- /**
- * Implements hook_form_FORM_ID_alter().
- *
- * Inserts the Merge button into the actions of the form.
- */
- function term_merge_form_taxonomy_overview_terms_alter(&$form, &$form_state, $form_id) {
- if ($form['#total_entries'] > 0) {
- $form['actions'][] = array(
- 'merge' => array(
- '#type' => 'submit',
- '#value' => t('Merge'),
- '#submit' => array('term_merge_form_taxonomy_overview_terms_merge_submit'),
- '#weight' => 1,
- )
- );
- $form['actions']['submit']['#weight'] = 0;
- $form['actions']['reset_alphabetical']['#weight'] = 2;
- }
- }
- /**
- * Submit function for the merge button in the alteration of form form_taxonomy_overview_terms (see above).
- * Redirects the user to the term merge form/page.
- */
- function term_merge_form_taxonomy_overview_terms_merge_submit($form, &$form_state) {
- unset($form_state['storage']);
- $form_state['redirect'] = 'admin/structure/taxonomy/' . $form['#vocabulary']->machine_name . '/merge';
- }
- /**
- * Implements hook_form().
- *
- * Menu callback; displays components to let user select one or more terms for merging and
- * let them be merged into another, specified term.
- */
- function term_merge_term_merge_form($form, $form_state, $vocabulary) {
- if (!isset($form_state['storage']['term_list'])) { // Create th merge form.
- $form = array(
- 'term_list' => array(
- '#type' => 'select',
- '#title' => t('Merge terms'),
- '#options' => _term_delete_form_replacement_term_options($vocabulary->vid, 0, FALSE),
- '#multiple' => TRUE,
- '#required' => TRUE,
- '#attributes' => array('size' => 15),
- '#prefix' => '<div class="merge-container clearfix"><div class="terms-selector">',
- ),
- 'replacement_term' => array(
- '#type' => 'select',
- '#title' => t('Into'),
- '#description' => t('Cannot be any of the terms selected in the list to the left.'),
- '#options' => _term_delete_form_replacement_term_options($vocabulary->vid, 0, TRUE),
- '#prefix' => '</div><div class="merge-selectors">',
- ),
- 'replacement_term_new' => array(
- '#type' => 'textfield',
- '#title' => t('Name of new term'),
- '#description' => t('A new term will be created with the specified name.'),
- '#suffix' => '</div>',
- ),
- 'keep_merged' => array(
- '#type' => 'checkbox',
- '#title' => t('Only merge occurrences'),
- '#description' => t('Check this if you only want to merge occurrences of the merged terms leaving them as they are in the vocabulary.'),
- '#prefix' => '</div>',
- ),
- '#vocabulary' => $vocabulary,
- 'merge_redirect_path' => array(
- '#type' => 'value',
- '#value' => 'admin/structure/taxonomy/' . $vocabulary->machine_name,
- ),
- );
- // Adds the .js and .css used by form.
- $form['#after_build'] = array('_term_merge_form_attach');
- }
- else { // Let the user confirm the deletion.
- $form = array(
- 'confirm_markup' => array(
- '#markup' => _term_merge_get_confirm_merge_markup($form_state),
- '#prefix' => '<div>',
- '#suffix' => '</div>',
- )
- );
- }
- $form['actions'] = array (
- 'submit' => array(
- '#type' => 'submit',
- '#value' => t('Merge'),
- ),
- 'cancel' => array(
- '#type' => 'link',
- '#title' => t('Cancel'),
- '#href' => 'admin/structure/taxonomy/' . $vocabulary->machine_name,
- )
- );
- return $form;
- }
- /**
- * Validation handler for term_merge_term_merge_form().
- */
- function term_merge_term_merge_form_validate($form, &$form_state) {
- $form_values = $form_state['values'];
- if (!isset($form_state['storage']['term_list'])) {
- // Convert the array of terms to merge into a normally keyed array.
- $form_values['term_list'] = array_values($form_values['term_list']);
- if ($form_values['replacement_term'] == '0' &&
- empty($form_values['replacement_term_new'])) {
- form_set_error('replacement_term_new', t('You must enter a name for the new term.'));
- }
- elseif ($pos = array_search($form_values['replacement_term'], $form_values['term_list'])) {
- array_splice($form_values['term_list'], $pos, 1);
- }
- }
- }
- /**
- * Submit handler for term_merge_term_merge_form().
- *
- * Collects data about what terms to merge into what term and sends it to term_merge().
- */
- function term_merge_term_merge_form_submit($form, &$form_state) {
- if (!isset($form_state['storage']['term_list'])) { // Store values and rebuild form for merge confirmation.
- $form_values = $form_state['values'];
- $form_state['storage']['term_list'] = $form_values['term_list'];
- $form_state['storage']['replacement_term'] = $form_values['replacement_term'];
- $form_state['storage']['replacement_term_new'] = $form_values['replacement_term_new'];
- $form_state['storage']['merge_redirect_path'] = $form_values['merge_redirect_path'];
- $form_state['storage']['keep_merged'] = $form_values['keep_merged'];
- $form_state['storage']['working_vocabulary'] = $form['#vocabulary'];
- $form_state['rebuild'] = TRUE;
- }
- else { // Merge confirmed, do it!
- $replacement_term = $form_state['storage']['replacement_term'];
- $merged_terms = $form_state['storage']['term_list'];
- $replacement_term_new = $form_state['storage']['replacement_term_new'];
- // Create a new tid id the value of $replacement_term is equal to 0.
- if ($replacement_term == '0') {
- $new_term = _term_merge_term_create($replacement_term_new, $form_state['storage']['working_vocabulary'], $merged_terms);
- $dest_tid = $new_term->tid;
- }
- else { // Else, the value of $replacement_term contains the tid of the new term.
- $dest_tid = $replacement_term;
- }
- // Redirect user after merge.
- $form_state['redirect'] = $form_state['storage']['merge_redirect_path'];
- // Now merge.
- term_merge($merged_terms, $dest_tid, $form_state['storage']['keep_merged']);
- }
- }
- /**
- * Implements hook_form_FORM_ID_alter().
- *
- * Add fields to allow term merging on the confirm deletion state of the form_taxonomy_form_term form.
- */
- function term_merge_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) {
- // Only modify the form if it is in the "Confirm Delete" states.
- if (isset($form_state['confirm_delete']) && $form_state['confirm_delete'] === TRUE && user_access('term merge')) {
- $form['description']['#markup'] .= '<br /><br />' . t('You can replace any occurrences of the deleted term with another term by specifying the replacement below. This is useful when correcting typos, or to consolidate multiple, synonymous terms.') . '<br /><br />';
- $form['replacement_term_replace'] = array(
- '#type' => 'checkbox',
- '#title' => t('Replace deleted term with:'),
- '#prefix' => '<div>',
- );
- $form['replacement_term'] = array(
- '#type' => 'select',
- '#options' => _term_delete_form_replacement_term_options($form['#vocabulary']->vid, $form['#term']->tid, TRUE),
- '#suffix' => '</div>',
- );
- $form['replacement_term_new'] = array(
- '#type' => 'textfield',
- '#title' => t('Name of new term'),
- );
- $form['#after_build'] = array('_term_merge_form_attach');
- $form['#submit'] = array('term_merge_form_taxonomy_form_term_submit');
- $form['#validate'] = array('term_merge_form_taxonomy_form_term_validate');
- }
- }
- /**
- * Validation function for override of form_taxonomy_form_term (see above).
- *
- * If the user want's to create a new term, check to see if he has entered a name for it.
- */
- function term_merge_form_taxonomy_form_term_validate($form, &$form_state) {
- $form_values = $form_state['values'];
- if ($form_values['replacement_term'] == '0' &&
- empty($form_state['values']['replacement_term_new'])) {
- form_set_error('replacement_term_new', t('You must enter a name for the new term.'));
- }
- }
- /**
- * Submit function for alteration of form_taxonomy_form_term (see above).
- *
- * If the user choose to create a new term for merging, create and save a term with the entered name.
- * Call the merge function to merge the replacement term into the deleted term.
- */
- function term_merge_form_taxonomy_form_term_submit($form, &$form_state) {
- if ($form_state['values']['replacement_term_replace']) {
- $replacement_term = $form_state['values']['replacement_term'];
- /// Create a new tid id the value of $replacement_term is equal to 0.
- if ($replacement_term == '0') {
- $new_term = _term_merge_term_create(check_plain($form_state['values']['replacement_term_new']), $form['#vocabulary'], $form['#term']->tid);
- $dest_tid = $new_term->tid;
- }
- else { // Else, the value of $replacement_term contains the tid of the new term.
- $dest_tid = $replacement_term;
- }
- // Merge.
- term_merge($form['#term']->tid, $dest_tid, TRUE);
- }
- else {
- // Continue as the confirm deletion form would normally.
- taxonomy_term_confirm_delete_submit($form, $form_state);
- }
- }
- /**
- * Merge source terms into one destination term.
- * @param $source
- * A single term tid or an array of term tids to be merged.
- * @param $dest
- * The tid of the term to merge sources into.
- */
- function term_merge($source, $dest, $keep_merged) {
- // Create an array of source if it isn't already one.
- if (!is_array($source)) {
- $source = array($source);
- }
- $source = array_values($source);
- // Create a skeleton for the merging batch.
- $update_batch = array(
- 'title' => t('Merging terms'),
- 'operations' => array(
- array('_term_merge_batch_store_merged_terms', array(
- $keep_merged ? array() : $source,
- count($source),
- ))
- ),
- 'finished' => '_term_merge_finished',
- );
- // For every field existing on the site.
- foreach (field_info_fields() as $key => $field) {
- if ($field['type'] == 'taxonomy_term_reference') {
- $table_name = 'field_data_' . $key;
- $tid_field = $key . '_tid';
- $update_data = array();
- foreach ($source as $source_tid) {
- // Get data from fields previously containing a reference to the merged term.
- $fields_result = db_select($table_name, 'df')
- ->fields('df', array('entity_type', 'entity_id', 'delta'))
- ->condition($tid_field, $source_tid)
- ->execute();
- // Collect update data form the batch process and group them by their entity.
- foreach ($fields_result as $field_row) {
- $batch_key = $field_row->entity_type . '_' . $field_row->entity_id;
- $update_data[$batch_key][] = array(
- 'field_name' => $key,
- 'source_tid' => $source_tid,
- 'dest_tid' => $dest,
- );
- }
- }
- // Create a batch processing step for every entity that needs an update.
- foreach ($update_data as $key => $batch_data) {
- $delimiter_pos = strripos($key, '_');
- $type = drupal_substr($key, 0, $delimiter_pos);
- $id = drupal_substr($key, $delimiter_pos + 1);
- $batch_data = array(
- 'entity_type' => $type,
- 'entity_id' => $id,
- 'data' => $batch_data
- );
- $update_batch['operations'][] = array(
- '_term_merge_insert_field_values',
- array($batch_data)
- );
- }
- }
- }
- // Initialize the batch process.
- batch_set($update_batch);
- }
- /**
- * Creates a new term with the specified name. The new term will have the same
- * parent as the merged terms as long as long as they all a common parent.
- */
- function _term_merge_term_create($name, $vocabulary, $merged_terms) {
- $new_term = new stdClass();
- if (!is_array($merged_terms)) {
- $merged_terms = array(0 => $merged_terms);
- }
- // Get parent of merged terms.
- $merged_parent_query = db_select('taxonomy_term_hierarchy', 'tth')
- ->fields('tth', array('parent'));
- $conditions = db_or();
- foreach ($merged_terms as $merged_tid) {
- $conditions->condition('tid', $merged_tid);
- }
- $merged_parent_result = $merged_parent_query->condition($conditions)
- ->execute()->fetchCol();
- if (count($parent = array_unique($merged_parent_result)) == 1) {
- $parent_tid = $parent[0];
- }
- else {
- $parent_tid = 0;
- }
- $term = new stdClass();
- $term->name = $name;
- $term->vid = $vocabulary->vid;
- $term->parent = $parent_tid;
- $term->vocabulary_machine_name = $vocabulary->machine_name;
- $term->description = '';
- taxonomy_term_save((object) $term);
- return $term;
- }
- /**
- * Stores the merged terms in the context of the batch operation,
- * I need them in the finish function of the batch operation.
- */
- function _term_merge_batch_store_merged_terms($merged_terms_delete, $num_merged_terms, &$context) {
- $context['results']['merged_terms_delete'] = $merged_terms_delete;
- $context['results']['num_merged_terms'] = $num_merged_terms;
- }
- /**
- * Batch operation for adding field values into entities.
- *
- * Loads the entity specified in $update_data and add the term reference
- * to all fields specified in $update_data['data'].
- */
- function _term_merge_insert_field_values($update_data, &$context) {
- // Extract entity information.
- $entity_type = $update_data['entity_type'];
- $entity_id = $update_data['entity_id'];
- // Load the entity.
- $entity = entity_load($entity_type, array($entity_id));
- $entity = $entity[$entity_id];
- $context['message'] = 'Merging terms in fields of ' . $entity_type . ' ' . $entity->title . '.';
- $target_merged = FALSE;
- foreach ($update_data['data'] as $i => &$update_data_arr) {
- $update_data_arr = array_reverse($update_data_arr);
-
- $field_lang = field_language('node', $entity, $update_data_arr['field_name']);
- $field_values = &$entity->{$update_data_arr['field_name']}[$field_lang];
- foreach ($field_values as $i => $value) {
- if ($value['tid'] == $update_data_arr['source_tid']) {
- if (!$target_merged) {
- $field_values[$i]['tid'] = $update_data_arr['dest_tid']->tid;
- $target_merged = TRUE;
- }
- // Any more occurrences of the source tid will simply be removed.
- // We don't want duplicates.
- else {
- array_splice($field_values, $i, 1);
- }
- // If target tid is one of the source tids, remove it from the array
- // of terms that will be removed at the end of the process.
- if($update_data_arr['source_tid']== $update_data_arr['dest_tid']->tid) {
- foreach ($context['results']['merged_terms_delete'] as $i => $merged_term) {
- if ($merged_term == $update_data_arr['source_tid']) {
- array_splice($context['results']['merged_terms_delete'], $i, 1);
- }
- }
- }
- $context['results'][] = 'Merged ' . $update_data_arr['source_tid'] . ' with '
- . $update_data_arr['dest_tid']->tid . ' in ' . $update_data_arr['field_name']
- . ' of ' . $entity_type . ' ' . $entity_id;
- break;
- }
- }
- }
- if (empty($context['sandbox'])) {
- $context['sandbox']['progress'] = 0;
- }
- $context['sandbox']['progress']++;
- $context['finished'] = $context['sandbox']['progress']/$context['results']['num_merged_terms'];
- // Save the new field values if something has changed.
- if ($target_merged) {
- field_attach_update($entity_type, $entity);
- }
- }
- /**
- * The merge batch operation was finished.
- */
- function _term_merge_finished($success, $results, $operations) {
- if ($success) {
- $message = format_plural($results['num_merged_terms'], 'One term merged.', '@count terms merged.');
- $message .= '<br />';
- $message .= format_plural(count($results) - 2, 'One term occurrence merged.', '@count term occurrences merged.');
- if (count($results['merged_terms_delete']) > 0) {
- $message .= '<br />';
- $message .= format_plural(count($results['merged_terms_delete']), 'One term deleted', '@count terms deleted.');
- }
- }
- else {
- $message = t('Finished with an error.');
- }
- drupal_set_message($message);
- // Delete merged terms on success. We don't want to do this earlier if something went wrong during the batch process.
- if ($success) {
- foreach ($results['merged_terms_delete'] as $merged_term) {
- taxonomy_term_delete($merged_term);
- }
- }
- }
- /**
- * Returns the markup text for the confirmation message of term merging.
- */
- function _term_merge_get_confirm_merge_markup($form_state) {
- $form_values = $form_state['values'];
- $terms = taxonomy_term_load_multiple($form_values['term_list']);
- $dest_term_name = ($dest_term = taxonomy_term_load($form_values['replacement_term'])) ?
- $dest_term->name : $form_values['replacement_term_new'];
- $merged_names = '';
- foreach (array_values($terms) as $i => $term) {
- $merged_names .= $term->name;
- if ($i < count($terms) - 2) {
- $merged_names .= ', ';
- }
- else if ($i == count($terms) - 2) {
- $merged_names .= ' & ';
- }
- }
- $output = 'Are you sure you want to merge <b>%term_names</b> with <b>%destination_term</b>'
- . (($form_state['storage']['replacement_term_new']) ? ' (new)' : '')
- . ' ? ';
- if ($form_values['keep_merged']) {
- $output .= 'Due to your choice, the merged terms will not be deleted from their vocabulary.<br /> ';
- }
- else {
- $output .= '<br /><u>All merged terms will be deleted</u>!<br /> ';
- }
- $output = t($output, array('%term_names' => $merged_names, '%destination_term' => $dest_term_name));
- return $output;
- }
- /**
- * Returns values for the replacement_term select box in the form (see form builder at top).
- * Don't return the value for the term that will be merged.
- */
- function _term_delete_form_replacement_term_options($vid, $merged_term_tid, $add_new_option) {
- $tree = taxonomy_get_tree($vid);
- foreach ($tree as $i => $term) {
- if ($merged_term_tid != $term->tid) {
- $options[$term->tid] = $term->name;
- }
- }
- if ($add_new_option) {
- $options['0'] = t('New term, specified below');
- }
- return $options;
- }
- /**
- * Add necessary JavaScript and css to the form.
- */
- function _term_merge_form_attach($form_element) {
- if ($form_element['#id'] == 'term-merge-term-merge-form') {
- drupal_add_js(drupal_get_path('module', 'term_merge') . '/term_merge.js');
- }
- elseif ($form_element['#id'] == 'taxonomy-form-term') {
- drupal_add_js(drupal_get_path('module', 'term_merge') . '/term_merge_deletion.js');
- }
- drupal_add_css(drupal_get_path('module', 'term_merge') . '/term_merge.css');
- return $form_element;
- }
|