term_merge.pages.inc 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. <?php
  2. /**
  3. * @file
  4. * Menu page callbacks for Term Merge module.
  5. */
  6. /**
  7. * Base of the term merge action form.
  8. *
  9. * It is extracted into a separate function to better internal code reuse.
  10. *
  11. * @param object $vocabulary
  12. * Fully loaded Taxonomy vocabulary where the merge should occur
  13. * @param array $term_branch_value
  14. * Array of Taxonomy term IDs that are nominated as branch terms.
  15. */
  16. function term_merge_form_base(&$form, &$form_state, $vocabulary, $term_branch_value) {
  17. $form['#vocabulary'] = $vocabulary;
  18. $form['#term_merge_term_branch'] = $term_branch_value;
  19. $tree = taxonomy_get_tree($vocabulary->vid);
  20. $form['term_trunk'] = array(
  21. '#type' => 'fieldset',
  22. '#title' => t('Merge Into'),
  23. '#prefix' => '<div id="term-merge-form-term-trunk">',
  24. '#suffix' => '</div>',
  25. '#tree' => TRUE,
  26. '#element_validate' => array('term_merge_form_base_term_trunk_validate'),
  27. );
  28. // Array of currently available widgets for choosing term trunk.
  29. $term_trunk_widget_options = array(
  30. 'autocomplete' => 'Autocomplete',
  31. );
  32. if (variable_get('taxonomy_override_selector', FALSE) && module_exists('hs_taxonomy')) {
  33. $term_trunk_widget_options['hs_taxonomy'] = t('Hierarchical Select');
  34. $term_trunk_widget = 'hs_taxonomy';
  35. }
  36. else {
  37. $term_trunk_widget_options['select'] = t('Select');
  38. $term_trunk_widget = 'select';
  39. }
  40. // If the vocabulary is too big, by default we want the trunk term widget to
  41. // be autocomplete instead of select or hs_taxonomy.
  42. if (count($tree) > 200) {
  43. $term_trunk_widget = 'autocomplete';
  44. }
  45. // Override the term trunk widget if settings are found in $form_state.
  46. if (isset($form_state['values']['term_trunk']['widget']) && in_array($form_state['values']['term_trunk']['widget'], array_keys($term_trunk_widget_options))) {
  47. $term_trunk_widget = $form_state['values']['term_trunk']['widget'];
  48. }
  49. // TODO: the trunk term widgets should be implemented as cTools plugins.
  50. $form['term_trunk']['widget'] = array(
  51. '#type' => 'radios',
  52. '#title' => t('Widget'),
  53. '#required' => TRUE,
  54. '#options' => $term_trunk_widget_options,
  55. '#default_value' => $term_trunk_widget,
  56. '#description' => t('Choose what widget you prefer for entering the term trunk.'),
  57. '#ajax' => array(
  58. 'callback' => 'term_merge_form_term_trunk',
  59. 'wrapper' => 'term-merge-form-term-trunk',
  60. 'method' => 'replace',
  61. 'effect' => 'fade',
  62. ),
  63. );
  64. // @todo:
  65. // There is a known bug, if user has selected something in one widget, and
  66. // then changes the widget, $form_states['values'] will hold the value for
  67. // term trunk form element in the format that is used in one widget, while
  68. // this value will be passed to another widget. This triggers different
  69. // unpleasant effects like showing tid instead of term's name or vice-versa.
  70. // I think we should just empty $form_state['values'] for the term trunk
  71. // form element when widget changes. Better ideas are welcome!
  72. $function = 'term_merge_form_term_trunk_widget_' . $term_trunk_widget;
  73. $function($form, $form_state, $vocabulary, $term_branch_value);
  74. // Ensuring the Merge Into form element has the same title no matter what
  75. // widget has been used.
  76. $form['term_trunk']['tid']['#title'] = t('Merge into');
  77. // Adding necessary options of merging.
  78. $form += term_merge_merge_options_elements($vocabulary);
  79. }
  80. /**
  81. * Menu callback.
  82. *
  83. * Allow user to specify which terms to be merged into which term and any
  84. * other settings needed for the term merge action.
  85. *
  86. * @param object $vocabulary
  87. * Fully loaded taxonomy vocabulary object
  88. * @param object $term
  89. * Fully loaded taxonomy term object that should be selected as the default
  90. * merge term in the form. If the $vocabulary is omitted, the vocabulary of
  91. * $term is considered
  92. *
  93. * @return array
  94. * Array of the form in Form API format
  95. *
  96. * TODO: accidentally this function happens to implement hook_form() which is
  97. * definitely not my intention. This function must be renamed to a safer name.
  98. */
  99. function term_merge_form($form, $form_state, $vocabulary = NULL, $term = NULL) {
  100. if (is_null($vocabulary)) {
  101. $vocabulary = taxonomy_vocabulary_load($term->vid);
  102. }
  103. if (!isset($form_state['storage']['confirm'])) {
  104. // We are at the set up step.
  105. $tree = taxonomy_get_tree($vocabulary->vid);
  106. $term_branch_value = is_null($term) ? array() : array($term->tid);
  107. if (variable_get('taxonomy_override_selector', FALSE) && module_exists('hs_taxonomy')) {
  108. // We use Hierarchical Select module if it's available and configured to
  109. // be used for taxonomy selects.
  110. $form['term_branch'] = array(
  111. '#type' => 'hierarchical_select',
  112. // @todo: figure out why #required => TRUE doesn't work.
  113. // As a matter of fact, this issue seems to cover our case.
  114. // http://drupal.org/node/1275862.
  115. //'#required' => TRUE,
  116. '#config' => array(
  117. 'module' => 'hs_taxonomy',
  118. 'params' => array(
  119. 'vid' => $vocabulary->vid,
  120. 'exclude_tid' => NULL,
  121. 'root_term' => FALSE,
  122. ),
  123. 'enforce_deepest' => 0,
  124. 'entity_count' => 0,
  125. 'require_entity' => 0,
  126. 'save_lineage' => 0,
  127. 'level_labels' => array(
  128. 'status' => 0,
  129. ),
  130. 'dropbox' => array(
  131. 'status' => 1,
  132. 'limit' => 0,
  133. ),
  134. 'editability' => array(
  135. 'status' => 0,
  136. ),
  137. 'resizable' => TRUE,
  138. 'render_flat_select' => 0,
  139. ),
  140. );
  141. }
  142. else {
  143. // Falling back on a simple <select>.
  144. $options = array();
  145. foreach ($tree as $v) {
  146. $options[$v->tid] = str_repeat('-', $v->depth) . $v->name . ' [tid: ' . $v->tid . ']';
  147. }
  148. $form['term_branch'] = array(
  149. '#type' => 'select',
  150. '#required' => TRUE,
  151. '#multiple' => TRUE,
  152. '#options' => $options,
  153. '#size' => 8,
  154. );
  155. }
  156. $form['term_branch'] = array(
  157. '#title' => t('Terms to Merge'),
  158. '#description' => t('Please, choose the terms you want to merge into another term.'),
  159. '#ajax' => array(
  160. 'callback' => 'term_merge_form_term_trunk',
  161. 'wrapper' => 'term-merge-form-term-trunk',
  162. 'method' => 'replace',
  163. 'effect' => 'fade',
  164. ),
  165. '#default_value' => $term_branch_value,
  166. ) + $form['term_branch'];
  167. // We overwrite term branch value with the one from $form_state, if there is
  168. // something there.
  169. if (isset($form_state['values']['term_branch']) && is_array($form_state['values']['term_branch'])) {
  170. $term_branch_value = $form_state['values']['term_branch'];
  171. }
  172. term_merge_form_base($form, $form_state, $vocabulary, $term_branch_value);
  173. $form['actions'] = array(
  174. '#type' => 'actions',
  175. );
  176. $form['actions']['submit'] = array(
  177. '#type' => 'submit',
  178. '#value' => t('Submit'),
  179. );
  180. }
  181. else {
  182. // We are at the confirmation step.
  183. $count = count($form_state['values']['term_branch']);
  184. $question = format_plural($count, 'Are you sure want to merge 1 term?', 'Are you sure want to merge @count terms?');
  185. $form = confirm_form($form, $question, 'admin/structure/taxonomy/' . $vocabulary->machine_name);
  186. }
  187. return $form;
  188. }
  189. /**
  190. * Submit handler for term_merge_form(). Merge terms one into another.
  191. */
  192. function term_merge_form_submit($form, &$form_state) {
  193. if (!isset($form_state['storage']['confirm'])) {
  194. // Since merging terms is an important operation, we better confirm user
  195. // really wants to do this.
  196. $form_state['storage']['confirm'] = 0;
  197. $form_state['rebuild'] = TRUE;
  198. $form_state['storage']['info'] = $form_state['values'];
  199. $form_state['storage']['merge_settings'] = term_merge_merge_options_submit($form, $form_state, $form);
  200. }
  201. else {
  202. // The user has confirmed merging. We pull up the submitted values.
  203. $form_state['values'] = $form_state['storage']['info'];
  204. term_merge(array_values($form_state['values']['term_branch']), $form_state['values']['term_trunk']['tid'], $form_state['storage']['merge_settings']);
  205. $form_state['redirect'] = array('taxonomy/term/' . $form_state['values']['term_trunk']['tid']);
  206. }
  207. }
  208. /**
  209. * Menu page callback function.
  210. *
  211. * Autocomplete callback function for the trunk term form element in the widget
  212. * of autocomplete. The code of this function was mainly copy-pasted from
  213. * Taxonomy autocomplete widget menu callback function.
  214. *
  215. * @param object $vocabulary
  216. * Fully loaded vocabulary object inside of which the terms are about to be
  217. * merged
  218. */
  219. function term_merge_form_term_trunk_widget_autocomplete_autocomplete($vocabulary) {
  220. // If the request has a '/' in the search text, then the menu system will have
  221. // split it into multiple arguments, recover the intended $tags_typed.
  222. $args = func_get_args();
  223. // Shift off the $vocabulary argument.
  224. array_shift($args);
  225. $tags_typed = implode('/', $args);
  226. // Querying database for suggestions.
  227. $query = db_select('taxonomy_term_data', 't');
  228. $tags_return = $query->addTag('translatable')
  229. ->addTag('term_access')
  230. ->fields('t', array('tid', 'name'))
  231. ->condition('t.vid', $vocabulary->vid)
  232. ->condition('t.name', '%' . db_like($tags_typed) . '%', 'LIKE')
  233. ->range(0, 10)
  234. ->execute()
  235. ->fetchAllKeyed();
  236. $term_matches = array();
  237. foreach ($tags_return as $tid => $name) {
  238. // Add both term name and tid to array key in order to allow multiple terms
  239. // with same name to be displayed.
  240. $key = "$name ($tid)";
  241. // Names containing commas or quotes must be wrapped in quotes.
  242. if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
  243. $key = '"' . str_replace('"', '""', $key) . '"';
  244. }
  245. $term_matches[$key] = check_plain($name . ' [tid: ' . $tid . ']');
  246. }
  247. drupal_json_output($term_matches);
  248. }
  249. /**
  250. * Ajax callback function.
  251. *
  252. * Used in term_merge_term_merge_form() to replace the term_trunk element
  253. * depending on already selected term_branch values.
  254. */
  255. function term_merge_form_term_trunk($form, $form_state) {
  256. return $form['term_trunk'];
  257. }
  258. /**
  259. * Generate 'term_merge_duplicates_form'.
  260. *
  261. * Allow merging terms with the same or similar names.
  262. *
  263. * @param object $vocabulary
  264. * Fully loaded taxonomy vocabulary object inside of which term merging
  265. * occurs, if this argument is omitted, then $parent_term is required and will
  266. * be used to obtain information about Taxonomy vocabulary
  267. * @param object $parent_term
  268. * Fully loaded taxonomy term object using which the function will pull up
  269. * the vocabulary inside of which term merging occurs. Duplicate terms will be
  270. * sought only among children of this term
  271. */
  272. function term_merge_duplicates_form($form, &$form_state, $vocabulary = NULL, $parent_term = NULL) {
  273. $form['#attached']['js'][drupal_get_path('module', 'term_merge') . '/js/duplicate.form.js'] = array();
  274. // Checking if we were not given vocabulary object, we will use term object to
  275. // obtain the former.
  276. if (!is_null($parent_term) && is_null($vocabulary)) {
  277. $vocabulary = taxonomy_vocabulary_load($parent_term->vid);
  278. }
  279. $tree = taxonomy_get_tree($vocabulary->vid, is_null($parent_term) ? 0 : $parent_term->tid);
  280. // Helpful and self explaining text that should help people understand what's
  281. // up.
  282. $form['help'] = array(
  283. '#markup' => '<p>' . t('Here you can merge terms with the same names. It is a useful tool against term-duplicates. If this tool is invoked on a term (not on the entire vocabulary), duplicate terms will be sought only among children of that term. The terms are grouped by names. Term into which the merging will occur is selected manually by user, however you must know that it is impossible to merge a parent term into any of its children.') . '</p>',
  284. );
  285. $form['settings'] = array(
  286. '#type' => 'fieldset',
  287. '#title' => t('Advanced settings'),
  288. '#description' => t('Fine tune the duplicate search tool. You can adjust these settings if your vocabulary is very large. Also, you can control within these settings how the potential duplicates are presented below.'),
  289. '#tree' => TRUE,
  290. '#collapsible' => TRUE,
  291. );
  292. $form['settings']['help'] = array(
  293. '#markup' => '<p>' . format_plural(count($tree), 'Vocabulary %vocabulary has only 1 term. It is very unlikely you will merge anything here.', 'Vocabulary %vocabulary has @count terms. If this tool works slow, you may instruct the duplicate finder tool to terminate its work after it has found a specific number of possible duplicates.', array(
  294. '%vocabulary' => $vocabulary->name,
  295. )) . '</p>',
  296. );
  297. $form['settings']['max_duplicates'] = array(
  298. '#type' => 'textfield',
  299. '#title' => t('Show N duplicates'),
  300. '#description' => t('Input an integer here - this many duplicates will be shown on the form. Once this amount of possible duplicates is found, the search process terminates.'),
  301. '#required' => TRUE,
  302. '#default_value' => isset($form_state['values']['settings']['max_duplicates']) ? $form_state['values']['settings']['max_duplicates'] : 300,
  303. '#element_validate' => array('element_validate_integer_positive'),
  304. );
  305. $options = array();
  306. foreach (term_merge_duplicate_suggestion() as $plugin) {
  307. $options[$plugin['name']] = $plugin['title'];
  308. }
  309. $form['settings']['duplicate_suggestion'] = array(
  310. '#type' => 'checkboxes',
  311. '#title' => t('Mark terms as duplicate if all the checked conditions stand true'),
  312. '#options' => $options,
  313. '#default_value' => isset($form_state['values']['settings']['duplicate_suggestion']) ? $form_state['values']['settings']['duplicate_suggestion'] : array('name'),
  314. );
  315. $options = array();
  316. $bundle = field_extract_bundle('taxonomy_term', $vocabulary);
  317. foreach (field_info_instances('taxonomy_term', $bundle) as $instance) {
  318. $options[$instance['field_name']] = $instance['label'];
  319. }
  320. if (!empty($options)) {
  321. $form['settings']['fields'] = array(
  322. '#type' => 'checkboxes',
  323. '#title' => t('Display fields'),
  324. '#description' => t('Check which fields you wish to display in the results below for each possible duplicate term.'),
  325. '#options' => $options,
  326. '#default_value' => isset($form_state['values']['settings']['fields']) ? array_values(array_filter($form_state['values']['settings']['fields'])) : array(),
  327. );
  328. }
  329. $form['settings']['update'] = array(
  330. '#type' => 'button',
  331. '#value' => t('Re-run duplicate search'),
  332. '#ajax' => array(
  333. 'callback' => 'term_merge_duplicates_form_settings',
  334. 'wrapper' => 'term-merge-duplicate-wrapper',
  335. 'method' => 'replace',
  336. 'effect' => 'fade',
  337. ),
  338. );
  339. // Amount of found duplicates.
  340. $count = 0;
  341. // Array of groups of terms with the same name. Each group is an array of
  342. // duplicates. Trunk term of each group will be chosen by user.
  343. $groups = array();
  344. foreach ($tree as $term) {
  345. if ($count >= $form['settings']['max_duplicates']['#default_value']) {
  346. // We have reached the limit of possible duplicates to be found.
  347. break;
  348. }
  349. $hash = '';
  350. foreach ($form['settings']['duplicate_suggestion']['#default_value'] as $duplicate_suggestion) {
  351. $duplicate_suggestion = term_merge_duplicate_suggestion($duplicate_suggestion);
  352. $function = ctools_plugin_get_function($duplicate_suggestion, 'hash callback');
  353. if ($function) {
  354. $hash .= $function($term);
  355. }
  356. }
  357. if (!isset($groups[$hash])) {
  358. $groups[$hash] = array();
  359. }
  360. else {
  361. // We increment count by one for the just encountered duplicate. Plus, if
  362. // it is the second duplicate in this group, we also increment it by one
  363. // for the 1st duplicate in the group.
  364. $count++;
  365. if (count($groups[$hash]) == 1) {
  366. $count++;
  367. }
  368. }
  369. $groups[$hash][$term->tid] = $term;
  370. }
  371. $form['wrapper'] = array(
  372. '#prefix' => '<div id="term-merge-duplicate-wrapper">',
  373. '#suffix' => '</div>',
  374. );
  375. if ($count > 0) {
  376. $form['wrapper']['global_switch'] = array(
  377. '#type' => 'checkbox',
  378. '#title' => t('Select All Terms'),
  379. '#description' => t('Checking here will select for merging all the encountered duplicate terms.'),
  380. '#attributes' => array(
  381. 'class' => array('term-merge-duplicate-general-switch'),
  382. ),
  383. );
  384. }
  385. $form['wrapper']['group'] = array(
  386. '#tree' => TRUE,
  387. );
  388. $groups = array_filter($groups, 'term_merge_duplicates_form_filter');
  389. $tids = array();
  390. foreach ($groups as $group) {
  391. $tids = array_merge($tids, array_keys($group));
  392. }
  393. // This array will be keyed by term tid and values will be counts of how many
  394. // other entities reference to this term through values of fields attached to
  395. // them.
  396. $terms_count = array_fill_keys($tids, 0);
  397. if (!empty($tids)) {
  398. foreach (term_merge_fields_with_foreign_key('taxonomy_term_data', 'tid') as $referencing_field) {
  399. if ($referencing_field['storage']['type'] == 'field_sql_storage') {
  400. $table = array_keys($referencing_field['storage']['details']['sql'][FIELD_LOAD_CURRENT]);
  401. $table = reset($table);
  402. $column = $referencing_field['storage']['details']['sql'][FIELD_LOAD_CURRENT][$table][$referencing_field['term_merge_field_column']];
  403. $select = db_select($table, 'reference')
  404. ->condition($column, $tids);
  405. $select->addField('reference', $column, 'tid');
  406. $select->addExpression('COUNT(1)', 'count');
  407. $select->groupBy($column);
  408. $select = $select->execute();
  409. foreach ($select as $row) {
  410. $terms_count[$row->tid] += $row->count;
  411. }
  412. }
  413. }
  414. }
  415. if (!empty($form['settings']['fields']['#default_value'])) {
  416. // We need to load full term entities, because we are requested to show
  417. // fields.
  418. $terms = taxonomy_term_load_multiple($tids);
  419. foreach ($groups as $i => $group) {
  420. $groups[$i] = array_intersect_key($terms, $group);
  421. }
  422. }
  423. foreach ($groups as $i => $group) {
  424. // Sorting terms by tid for better usage experience.
  425. ksort($group);
  426. $first_term = reset($group);
  427. $options = array();
  428. foreach ($group as $term) {
  429. $parents = array();
  430. // Adding Root to the hierarchy.
  431. $parents[] = t('Vocabulary Root');
  432. foreach (taxonomy_get_parents_all($term->tid) as $parent) {
  433. // We do not include the current term in the hierarchy.
  434. if ($parent->tid != $term->tid) {
  435. $parents[] = $parent->name;
  436. }
  437. }
  438. $language = isset($term->language) ? $term->language : LANGUAGE_NONE;
  439. if ($language == LANGUAGE_NONE) {
  440. $language = t('Not Specified');
  441. }
  442. $options[$term->tid] = array(
  443. 'id' => $term->tid,
  444. 'title' => l($term->name, 'taxonomy/term/' . $term->tid),
  445. 'language' => $language,
  446. 'description' => check_markup($term->description, $term->format),
  447. 'parents' => implode(' &raquo; ', $parents),
  448. 'count' => format_plural($terms_count[$term->tid], '@count time', '@count times'),
  449. );
  450. if (isset($form['settings']['fields'])) {
  451. foreach ($form['settings']['fields']['#default_value'] as $instance) {
  452. $field = field_info_field($instance);
  453. $items = field_get_items('taxonomy_term', $term, $field['field_name']);
  454. $options[$term->tid][$field['field_name']] = '';
  455. if (is_array($items)) {
  456. $options[$term->tid][$field['field_name']] = array(
  457. '#theme' => 'item_list',
  458. '#items' => array(),
  459. );
  460. foreach ($items as $item) {
  461. switch ($field['type']) {
  462. case 'image':
  463. $display = array();
  464. $image_style = image_style_load('thumbnail');
  465. if ($image_style) {
  466. $cache = _field_info_field_cache();
  467. $display = $cache->prepareInstanceDisplay($display, $field['type']);
  468. $display['settings']['image_style'] = $image_style['name'];
  469. }
  470. $rendered_item = drupal_render(field_view_value('taxonomy_term', $term, $field['field_name'], $item, $display));
  471. break;
  472. default:
  473. $rendered_item = drupal_render(field_view_value('taxonomy_term', $term, $field['field_name'], $item));
  474. break;
  475. }
  476. $options[$term->tid][$field['field_name']]['#items'][] = $rendered_item;
  477. }
  478. if (count($options[$term->tid][$field['field_name']]['#items']) > 1) {
  479. $options[$term->tid][$field['field_name']] = drupal_render($options[$term->tid][$field['field_name']]);
  480. }
  481. else {
  482. $options[$term->tid][$field['field_name']] = $options[$term->tid][$field['field_name']]['#items'][0];
  483. }
  484. }
  485. }
  486. }
  487. }
  488. $form['wrapper']['group'][$i] = array(
  489. '#type' => 'fieldset',
  490. '#title' => check_plain($first_term->name),
  491. '#collapsible' => TRUE,
  492. '#pre_render' => array('term_merge_duplicates_fieldset_preprocess'),
  493. '#element_validate' => array('term_merge_duplicates_fieldset_validate'),
  494. );
  495. $header = array(
  496. 'id' => t('ID'),
  497. 'title' => t('Title'),
  498. 'description' => t('Description'),
  499. 'language' => t('Language'),
  500. 'parents' => t('Parents'),
  501. 'count' => t('Referenced'),
  502. );
  503. if (isset($form['settings']['fields'])) {
  504. $header += array_map('check_plain', array_intersect_key($form['settings']['fields']['#options'], drupal_map_assoc($form['settings']['fields']['#default_value'])));
  505. }
  506. $form['wrapper']['group'][$i]['duplicates'] = array(
  507. '#type' => 'tableselect',
  508. '#title' => 'Duplicates',
  509. '#header' => $header,
  510. '#options' => $options,
  511. );
  512. $options = array();
  513. foreach ($group as $term) {
  514. $options[$term->tid] = $term->name;
  515. }
  516. $form['wrapper']['group'][$i]['trunk_tid'] = array(
  517. '#type' => 'radios',
  518. '#title' => t('Merge Into'),
  519. '#options' => $options,
  520. '#attributes' => array(
  521. 'class' => array('term-merge-duplicate-trunk'),
  522. ),
  523. );
  524. }
  525. if ($count > 0) {
  526. // Adding necessary options of merging.
  527. $form += term_merge_merge_options_elements($vocabulary);
  528. $form['actions'] = array(
  529. '#type' => 'actions',
  530. );
  531. $form['actions']['submit'] = array(
  532. '#type' => 'submit',
  533. '#value' => t('Submit'),
  534. );
  535. }
  536. else {
  537. if (is_null($parent_term)) {
  538. $no_match_text = t('Sorry, seems like we were not able to find any possible duplicate terms in %vocabulary vocabulary.', array(
  539. '%vocabulary' => $vocabulary->name,
  540. ));
  541. }
  542. else {
  543. $no_match_text = t('Sorry, seems like we were not able to find any possible duplicate terms among children of %term term. You may want to search for duplicates through the entire <a href="!url">vocabulary</a>.', array(
  544. '%term' => $parent_term->name,
  545. '!url' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/merge/duplicates'),
  546. ));
  547. }
  548. $form['nothing_found'] = array(
  549. '#markup' => '<p><b>' . $no_match_text . '</b></p>',
  550. );
  551. }
  552. return $form;
  553. }
  554. /**
  555. * Submit handler for 'term_merge_duplicates_form'.
  556. *
  557. * Actually merge duplicate terms.
  558. */
  559. function term_merge_duplicates_form_submit($form, &$form_state) {
  560. $batch = array(
  561. 'title' => t('Merging terms'),
  562. 'operations' => array(),
  563. 'finished' => 'term_merge_batch_finished',
  564. 'file' => drupal_get_path('module', 'term_merge') . '/term_merge.batch.inc',
  565. );
  566. // Processing general options for merging.
  567. $merge_settings = term_merge_merge_options_submit($form, $form_state, $form);
  568. if (isset($form_state['values']['group'])) {
  569. foreach ($form_state['values']['group'] as $values) {
  570. // Filtering out only the selected duplicate terms.
  571. $term_branches = array_filter($values['duplicates']);
  572. // We also do not want to have trunk term to be among the branch terms.
  573. unset($term_branches[$values['trunk_tid']]);
  574. if (!empty($term_branches)) {
  575. // If something has been selected in this group we schedule its merging.
  576. $batch['operations'][] = array('_term_merge_batch_process', array(
  577. $term_branches,
  578. $values['trunk_tid'],
  579. $merge_settings,
  580. ));
  581. }
  582. }
  583. }
  584. if (empty($batch['operations'])) {
  585. drupal_set_message(t('No merging has been made, because you have not selected any duplicate term to merge.'));
  586. }
  587. else {
  588. batch_set($batch);
  589. }
  590. }
  591. /**
  592. * Form element preprocess function.
  593. *
  594. * Insert extra column for choosing term trunk into tableselect of terms to be
  595. * merged.
  596. */
  597. function term_merge_duplicates_fieldset_preprocess($element) {
  598. $options = &$element['duplicates']['#options'];
  599. foreach ($options as $tid => $row) {
  600. $element['trunk_tid'][$tid]['#title_display'] = 'invisible';
  601. $options[$tid] = array(
  602. 'trunk' => drupal_render($element['trunk_tid'][$tid]),
  603. ) + $options[$tid];
  604. }
  605. $element['trunk_tid']['#title_display'] = 'invisible';
  606. $element['duplicates']['#header'] = array(
  607. 'trunk' => $element['trunk_tid']['#title'],
  608. ) + $element['duplicates']['#header'];
  609. return $element;
  610. }
  611. /**
  612. * FAPI element validation callback.
  613. *
  614. * Validate fieldset of a 'term_merge_duplicates_form' form, if any duplicate
  615. * has been selected for merging, it makes sure the trunk term has been
  616. * selected. We can't allow merging without knowing the explicit trunk term.
  617. */
  618. function term_merge_duplicates_fieldset_validate($element, &$form_state, $form) {
  619. if (!empty($element['duplicates']['#value']) && !is_numeric($element['trunk_tid']['#value'])) {
  620. form_error($element, t('Please, choose %trunk_tid_label for the group %group_label', array(
  621. '%trunk_tid_label' => $element['trunk_tid']['#title'],
  622. '%group_label' => $element['#title'],
  623. )));
  624. }
  625. }
  626. /**
  627. * Ajax callback function.
  628. *
  629. * Used in term_merge_duplicates_form() to replace the duplicates tables with
  630. * new data per current settings.
  631. */
  632. function term_merge_duplicates_form_settings($form, &$form_state) {
  633. return $form['wrapper'];
  634. }
  635. /**
  636. * Supportive array_filter() callback function.
  637. *
  638. * Eliminate all array elements, whose length is less than 1.
  639. */
  640. function term_merge_duplicates_form_filter($array_element) {
  641. return count($array_element) > 1;
  642. }