term_merge.pages.inc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. <?php
  2. /**
  3. * @file
  4. * Menu page callbacks for Term Merge module.
  5. */
  6. /**
  7. * Menu callback.
  8. *
  9. * Allow user to specify which terms to be merged into which term and any
  10. * other settings needed for the term merge action.
  11. *
  12. * @param object $vocabulary
  13. * Fully loaded taxonomy vocabulary object
  14. * @param object $term
  15. * Fully loaded taxonomy term object that should be selected as the default
  16. * merge term in the form. If the $vocabulary is omitted, the vocabulary of
  17. * $term is considered
  18. *
  19. * @return array
  20. * Array of the form in Form API format
  21. */
  22. function term_merge_form($form, $form_state, $vocabulary = NULL, $term = NULL) {
  23. if (is_null($vocabulary)) {
  24. $vocabulary = taxonomy_vocabulary_load($term->vid);
  25. }
  26. // It's always handy to have the vocabulary by hand.
  27. $form['#vocabulary'] = $vocabulary;
  28. if (!isset($form_state['storage']['confirm'])) {
  29. // We are at the set up step.
  30. $tree = taxonomy_get_tree($vocabulary->vid);
  31. $term_branch_value = is_null($term) ? NULL : array($term->tid);
  32. if (variable_get('taxonomy_override_selector', FALSE) && module_exists('hs_taxonomy')) {
  33. // We use Hierarchical Select module if it's available and configured to
  34. // be used for taxonomy selects.
  35. $form['term_branch'] = array(
  36. '#type' => 'hierarchical_select',
  37. // @todo: figure out why #required => TRUE doesn't work.
  38. // As a matter of fact, this issue seems to cover our case.
  39. // http://drupal.org/node/1275862.
  40. //'#required' => TRUE,
  41. '#config' => array(
  42. 'module' => 'hs_taxonomy',
  43. 'params' => array(
  44. 'vid' => $vocabulary->vid,
  45. 'exclude_tid' => NULL,
  46. 'root_term' => FALSE,
  47. ),
  48. 'enforce_deepest' => 0,
  49. 'entity_count' => 0,
  50. 'require_entity' => 0,
  51. 'save_lineage' => 0,
  52. 'level_labels' => array(
  53. 'status' => 0,
  54. ),
  55. 'dropbox' => array(
  56. 'status' => 1,
  57. 'limit' => 0,
  58. ),
  59. 'editability' => array(
  60. 'status' => 0,
  61. ),
  62. 'resizable' => TRUE,
  63. 'render_flat_select' => 0,
  64. ),
  65. );
  66. }
  67. else {
  68. // Falling back on a simple <select>.
  69. $options = array();
  70. foreach ($tree as $v) {
  71. $options[$v->tid] = str_repeat('-', $v->depth) . $v->name . ' [tid: ' . $v->tid . ']';
  72. }
  73. $form['term_branch'] = array(
  74. '#type' => 'select',
  75. '#required' => TRUE,
  76. '#multiple' => TRUE,
  77. '#options' => $options,
  78. '#size' => 8,
  79. );
  80. }
  81. $form['term_branch'] = array(
  82. '#title' => t('Terms to Merge'),
  83. '#description' => t('Please, choose the terms you want to merge into another term.'),
  84. '#ajax' => array(
  85. 'callback' => 'term_merge_form_term_trunk',
  86. 'wrapper' => 'term-merge-form-term-trunk',
  87. 'method' => 'replace',
  88. 'effect' => 'fade',
  89. ),
  90. '#default_value' => $term_branch_value,
  91. ) + $form['term_branch'];
  92. if (is_null($form['term_branch']['#default_value'])) {
  93. unset($form['term_branch']['#default_value']);
  94. }
  95. $form['term_trunk'] = array(
  96. '#type' => 'fieldset',
  97. '#title' => t('Merge Into'),
  98. '#prefix' => '<div id="term-merge-form-term-trunk">',
  99. '#suffix' => '</div>',
  100. '#tree' => TRUE,
  101. );
  102. // Array of currently available widgets for choosing term trunk.
  103. $term_trunk_widget_options = array(
  104. 'autocomplete' => 'Autocomplete',
  105. );
  106. if (variable_get('taxonomy_override_selector', FALSE) && module_exists('hs_taxonomy')) {
  107. $term_trunk_widget_options['hs_taxonomy'] = t('Hierarchical Select');
  108. $term_trunk_widget = 'hs_taxonomy';
  109. }
  110. else {
  111. $term_trunk_widget_options['select'] = t('Select');
  112. $term_trunk_widget = 'select';
  113. }
  114. // If the vocabulary is too big, by default we want the trunk term widget to
  115. // be autocomplete instead of select or hs_taxonomy.
  116. if (count($tree) > 200) {
  117. $term_trunk_widget = 'autocomplete';
  118. }
  119. // Override the term trunk widget if settings are found in $form_state.
  120. if (isset($form_state['values']['term_trunk']['widget']) && in_array($form_state['values']['term_trunk']['widget'], array_keys($term_trunk_widget_options))) {
  121. $term_trunk_widget = $form_state['values']['term_trunk']['widget'];
  122. }
  123. $form['term_trunk']['widget'] = array(
  124. '#type' => 'radios',
  125. '#title' => t('Widget'),
  126. '#required' => TRUE,
  127. '#options' => $term_trunk_widget_options,
  128. '#default_value' => $term_trunk_widget,
  129. '#description' => t('Choose what widget you prefer for entering the term trunk.'),
  130. '#ajax' => array(
  131. 'callback' => 'term_merge_form_term_trunk',
  132. 'wrapper' => 'term-merge-form-term-trunk',
  133. 'method' => 'replace',
  134. 'effect' => 'fade',
  135. ),
  136. );
  137. // @todo:
  138. // There is a known bug, if user has selected something in one widget, and
  139. // then changes the widget, $form_states['values'] will hold the value for
  140. // term trunk form element in the format that is used in one widget, while
  141. // this value will be passed to another widget. This triggers different
  142. // unpleasant effects like showing tid instead of term's name or vice-versa.
  143. // I think we should just empty $form_state['values'] for the term trunk
  144. // form element when widget changes. Better ideas are welcome!
  145. $function = 'term_merge_form_term_trunk_widget_' . $term_trunk_widget;
  146. $function($form, $form_state, $vocabulary);
  147. // Ensuring the Merge Into form element has the same title no matter what
  148. // widget has been used.
  149. $form['term_trunk']['tid']['#title'] = t('Merge into');
  150. // Adding necessary options of merging.
  151. $form += term_merge_merge_options_elements($vocabulary);
  152. $form['actions'] = array(
  153. '#type' => 'actions',
  154. );
  155. $form['actions']['submit'] = array(
  156. '#type' => 'submit',
  157. '#value' => t('Submit'),
  158. );
  159. }
  160. else {
  161. // We are at the confirmation step.
  162. $count = count($form_state['values']['term_branch']);
  163. $question = format_plural($count, 'Are you sure want to merge 1 term?', 'Are you sure want to merge @count terms?');
  164. $form = confirm_form($form, $question, 'admin/structure/taxonomy/' . $vocabulary->machine_name);
  165. }
  166. return $form;
  167. }
  168. /**
  169. * Supportive function.
  170. *
  171. * Validate the term_merge_form(). Make sure term trunk is not among the
  172. * selected term branches or their children.
  173. */
  174. function term_merge_form_validate($form, &$form_state) {
  175. if (!isset($form_state['storage']['confirm'])) {
  176. // We only validate the 1st step of the form.
  177. $prohibited_trunks = array();
  178. foreach ($form_state['values']['term_branch'] as $term_branch) {
  179. $children = taxonomy_get_tree($form['#vocabulary']->vid, $term_branch);
  180. $prohibited_trunks[] = $term_branch;
  181. foreach ($children as $child) {
  182. $prohibited_trunks[] = $child->tid;
  183. }
  184. }
  185. if (in_array($form_state['values']['term_trunk']['tid'], $prohibited_trunks)) {
  186. form_error($form['term_trunk']['tid'], t('Trunk term cannot be one of the selected branch terms or their children.'));
  187. }
  188. }
  189. }
  190. /**
  191. * Submit handler for term_merge_form(). Merge terms one into another.
  192. */
  193. function term_merge_form_submit($form, &$form_state) {
  194. if (!isset($form_state['storage']['confirm'])) {
  195. // Since merging terms is an important operation, we better confirm user
  196. // really wants to do this.
  197. $form_state['storage']['confirm'] = 0;
  198. $form_state['rebuild'] = TRUE;
  199. // Before storing the submitted values we slightly preprocess them to make
  200. // sure they correspond to what is expected by submit handler of taxonomy
  201. // creation form.
  202. if (isset($form_state['values']['relations'])) {
  203. $form_state['values'] += $form_state['values']['relations'];
  204. }
  205. $form_state['storage']['info'] = $form_state['values'];
  206. $form_state['storage']['merge_settings'] = term_merge_merge_options_submit($form, $form_state, $form);
  207. $form_state['storage']['old_form'] = $form;
  208. }
  209. else {
  210. // The user has confirmed merging. We pull up the submitted values.
  211. $form_state['values'] = $form_state['storage']['info'];
  212. // If necessary, create the term trunk.
  213. if ($form_state['values']['term_trunk']['tid'] == TERM_MERGE_NEW_TERM_TRUNK) {
  214. // We try to mimic normal form submission for taxonomy module.
  215. module_load_include('inc', 'taxonomy', 'taxonomy.admin');
  216. taxonomy_form_term_submit($form_state['storage']['old_form']['term_trunk']['term_create'], $form_state);
  217. $term_trunk = $form_state['term'];
  218. }
  219. else {
  220. $term_trunk = taxonomy_term_load($form_state['values']['term_trunk']['tid']);
  221. }
  222. term_merge(array_values($form_state['values']['term_branch']), $term_trunk->tid, $form_state['storage']['merge_settings']);
  223. $form_state['redirect'] = array('taxonomy/term/' . $term_trunk->tid);
  224. }
  225. }
  226. /**
  227. * Supportive function.
  228. *
  229. * Generate form elements for select widget for term trunk element of the
  230. * term_merge_form().
  231. *
  232. * @param object $vocabulary
  233. * Fully loaded taxonomy vocabulary object
  234. */
  235. function term_merge_form_term_trunk_widget_select(&$form, &$form_state, $vocabulary) {
  236. $tree = taxonomy_get_tree($vocabulary->vid);
  237. $options = array();
  238. foreach ($tree as $v) {
  239. $options[$v->tid] = str_repeat('-', $v->depth) . $v->name . ' [tid: ' . $v->tid . ']';
  240. }
  241. $term_branch_value = array();
  242. // Firstly trying to look up selected term branches in the default value of
  243. // term branch form element.
  244. if (isset($form['term_branch']['#default_value']) && is_array($form['term_branch']['#default_value'])) {
  245. $term_branch_value = $form['term_branch']['#default_value'];
  246. }
  247. if (isset($form_state['values']['term_branch']) && is_array($form_state['values']['term_branch'])) {
  248. $term_branch_value = $form_state['values']['term_branch'];
  249. }
  250. if (!empty($term_branch_value)) {
  251. // We have to make sure among term_trunk there is no term_branch or any of
  252. // their children.
  253. foreach ($term_branch_value as $v) {
  254. unset($options[$v]);
  255. foreach (taxonomy_get_tree($vocabulary->vid, $v) as $child) {
  256. unset($options[$child->tid]);
  257. }
  258. }
  259. $options = array(TERM_MERGE_NEW_TERM_TRUNK => 'New Term') + $options;
  260. }
  261. else {
  262. // Term branch has not been selected yet.
  263. $options = array();
  264. }
  265. $form['term_trunk']['tid'] = array(
  266. '#type' => 'select',
  267. '#required' => TRUE,
  268. '#description' => t('Choose into what term you want to merge.'),
  269. '#options' => $options,
  270. '#ajax' => array(
  271. 'callback' => 'term_merge_form_term_trunk_term_create',
  272. 'wrapper' => 'term-merge-form-term-trunk-term-create',
  273. 'method' => 'replace',
  274. 'effect' => 'fade',
  275. ),
  276. );
  277. $form['term_trunk']['term_create'] = array(
  278. '#prefix' => '<div id="term-merge-form-term-trunk-term-create">',
  279. '#suffix' => '</div>',
  280. );
  281. // We throw in the Taxonomy native term create form only if the option for
  282. // creation of a new term was selected by user.
  283. if (isset($form_state['values']['term_trunk']['tid']) && $form_state['values']['term_trunk']['tid'] == TERM_MERGE_NEW_TERM_TRUNK) {
  284. module_load_include('inc', 'taxonomy', 'taxonomy.admin');
  285. $form['term_trunk']['term_create'] += array(
  286. '#type' => 'fieldset',
  287. '#title' => t('Create New Term'),
  288. );
  289. $form['term_trunk']['term_create'] += taxonomy_form_term($form['term_trunk']['term_create'], $form_state, array(), $vocabulary);
  290. // We have our own submit button, so we unset the normal one from the term
  291. // create form.
  292. unset($form['term_trunk']['term_create']['actions']);
  293. // Additionally we have to filter out from "Parent Terms" select the already
  294. // selected branch terms and their children, because we can't merge into
  295. // the term itself or its children.
  296. // We do a trick here, since we know the 1st element is the <root> option
  297. // and all others are normal taxonomy terms, we keep the 1st element as it
  298. // is while all the other elements we substitute with our $options array
  299. // which is basically identical but already has been filtered out unwanted
  300. // terms. Plus we have to unset the 'New Term' option from $options.
  301. unset($options[TERM_MERGE_NEW_TERM_TRUNK]);
  302. if (is_array($form['term_trunk']['term_create']['relations']['parent']['#options'])) {
  303. $form['term_trunk']['term_create']['relations']['parent']['#options'] = array_slice($form['term_trunk']['term_create']['relations']['parent']['#options'], 0, 1, TRUE) + $options;
  304. }
  305. // For each field attached to taxonomy term of this vocabulary that has
  306. // unlimited cardinality we have to extra process the results, otherwise
  307. // "Add another item" button doesn't work.
  308. $instances = field_info_instances($form['term_trunk']['term_create']['#entity_type'], $form['term_trunk']['term_create']['#bundle']);
  309. foreach ($instances as $instance) {
  310. $field = field_info_field($instance['field_name']);
  311. if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
  312. if (isset($form['term_trunk']['term_create'][$field['field_name']][LANGUAGE_NONE]['add_more']['#limit_validation_errors'])) {
  313. $form['term_trunk']['term_create'][$field['field_name']][LANGUAGE_NONE]['add_more']['#limit_validation_errors'] = array(array('term_branch'), array('term_trunk'));
  314. }
  315. }
  316. }
  317. }
  318. }
  319. /**
  320. * Supportive function.
  321. *
  322. * Generate form element for hierarchical select widget for term trunk element
  323. * of the term_merge_form().
  324. *
  325. * @param object $vocabulary
  326. * Fully loaded taxonomy vocabulary object
  327. */
  328. function term_merge_form_term_trunk_widget_hs_taxonomy(&$form, &$form_state, $vocabulary) {
  329. $form['term_trunk']['tid'] = array(
  330. '#type' => 'hierarchical_select',
  331. '#description' => t('Please select a term to merge into.'),
  332. '#required' => TRUE,
  333. '#element_validate' => array('term_merge_form_trunk_term_widget_hs_taxonomy_validate'),
  334. '#config' => array(
  335. 'module' => 'hs_taxonomy',
  336. 'params' => array(
  337. 'vid' => $vocabulary->vid,
  338. 'exclude_tid' => NULL,
  339. 'root_term' => FALSE,
  340. ),
  341. 'enforce_deepest' => 0,
  342. 'entity_count' => 0,
  343. 'require_entity' => 0,
  344. 'save_lineage' => 0,
  345. 'level_labels' => array(
  346. 'status' => 0,
  347. ),
  348. 'dropbox' => array(
  349. 'status' => 0,
  350. ),
  351. 'editability' => array(
  352. 'status' => 0,
  353. ),
  354. 'resizable' => TRUE,
  355. 'render_flat_select' => 0,
  356. ),
  357. );
  358. }
  359. /**
  360. * Supportive function.
  361. *
  362. * Generate form elements for autocomplete widget for term trunk element of the
  363. * term_merge_form().
  364. *
  365. * @param object $vocabulary
  366. * Fully loaded taxonomy vocabulary object
  367. */
  368. function term_merge_form_term_trunk_widget_autocomplete(&$form, &$form_state, $vocabulary) {
  369. $form['term_trunk']['tid'] = array(
  370. '#type' => 'textfield',
  371. '#description' => t("Start typing in a term's name in order to get some suggestions."),
  372. '#required' => TRUE,
  373. '#autocomplete_path' => 'term-merge/autocomplete/term-trunk/' . $vocabulary->machine_name,
  374. '#element_validate' => array('term_merge_form_trunk_term_widget_autocomplete_validate'),
  375. );
  376. }
  377. /**
  378. * Supportive function.
  379. *
  380. * Validate form element of the autocomplete widget of term trunk element of
  381. * the form term_merge_form(). Make sure the entered string is a name of one of
  382. * the existing terms in the vocabulary where the merge occurs. If term is found
  383. * the function substitutes the name with its {taxonomy_term_data}.tid as it is
  384. * what is expected from a term trunk widget to provide in its value.
  385. */
  386. function term_merge_form_trunk_term_widget_autocomplete_validate($element, &$form_state, $form) {
  387. $term = taxonomy_get_term_by_name($element['#value'], $form['#vocabulary']->machine_name);
  388. if (!is_array($term) || empty($term)) {
  389. // Seems like the user has entered a non existing name in the autocomplete
  390. // textfield.
  391. form_error($element, t('There are no terms with name %name in the %vocabulary vocabulary.', array(
  392. '%name' => $element['#value'],
  393. '%vocabulary' => $form['#vocabulary']->name,
  394. )));
  395. }
  396. else {
  397. // We have to substitute the term's name with its tid in order to make this
  398. // widget consistent with the interface.
  399. $term = array_pop($term);
  400. form_set_value($element, $term->tid, $form_state);
  401. }
  402. }
  403. /**
  404. * Supportive function.
  405. *
  406. * Validate form element of the Hierarchical Select widget of term trunk element
  407. * of the form term_merge_form(). Convert the value from array to a single tid
  408. * integer value.
  409. */
  410. function term_merge_form_trunk_term_widget_hs_taxonomy_validate($element, &$form_state, $form) {
  411. $tid = 0;
  412. if (is_array($element['#value']) && !empty($element['#value'])) {
  413. $tid = (int) array_pop($element['#value']);
  414. }
  415. form_set_value($element, $tid, $form_state);
  416. }
  417. /**
  418. * Menu page callback function.
  419. *
  420. * Autocomplete callback function for the trunk term form element in the widget
  421. * of autocomplete. The code of this function was mainly copy-pasted from
  422. * Taxonomy autocomplete widget menu callback function.
  423. *
  424. * @param object $vocabulary
  425. * Fully loaded vocabulary object inside of which the terms are about to be
  426. * merged
  427. */
  428. function term_merge_form_term_trunk_widget_autocomplete_autocomplete($vocabulary) {
  429. // If the request has a '/' in the search text, then the menu system will have
  430. // split it into multiple arguments, recover the intended $tags_typed.
  431. $args = func_get_args();
  432. // Shift off the $vocabulary argument.
  433. array_shift($args);
  434. $tags_typed = implode('/', $args);
  435. // Querying database for suggestions.
  436. $query = db_select('taxonomy_term_data', 't');
  437. $tags_return = $query->addTag('translatable')
  438. ->addTag('term_access')
  439. ->fields('t', array('tid', 'name'))
  440. ->condition('t.vid', $vocabulary->vid)
  441. ->condition('t.name', '%' . db_like($tags_typed) . '%', 'LIKE')
  442. ->range(0, 10)
  443. ->execute()
  444. ->fetchAllKeyed();
  445. $term_matches = array();
  446. foreach ($tags_return as $tid => $name) {
  447. $n = $name;
  448. // Term names containing commas or quotes must be wrapped in quotes.
  449. if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
  450. $n = '"' . str_replace('"', '""', $name) . '"';
  451. }
  452. $term_matches[$n] = check_plain($name . ' [tid: ' . $tid . ']');
  453. }
  454. drupal_json_output($term_matches);
  455. }
  456. /**
  457. * Ajax callback function.
  458. *
  459. * Used in term_merge_term_merge_form() to replace the term_trunk element
  460. * depending on already selected term_branch values.
  461. */
  462. function term_merge_form_term_trunk($form, $form_state) {
  463. return $form['term_trunk'];
  464. }
  465. /**
  466. * Ajax callback function.
  467. *
  468. * Used in term_merge_term_merge_form() to replace the term create fieldset
  469. * depending on already selected term_branch values and the term_trunk value.
  470. */
  471. function term_merge_form_term_trunk_term_create($form, $form_state) {
  472. return $form['term_trunk']['term_create'];
  473. }
  474. /**
  475. * Generate 'term_merge_duplicates_form'.
  476. *
  477. * Allow merging terms with the same or similar names.
  478. *
  479. * @param object $vocabulary
  480. * Fully loaded taxonomy vocabulary object inside of which term merging
  481. * occurs, if this argument is omitted, then $term is required and will be
  482. * used to obtain information about Taxonomy vocabulary
  483. * @param object $parent_term
  484. * Fully loaded taxonomy term object using which the function will pull up
  485. * the vocabulary inside of which term merging occurs. Duplicate terms will be
  486. * sought only among children of this term
  487. */
  488. function term_merge_duplicates_form($form, &$form_state, $vocabulary = NULL, $parent_term = NULL) {
  489. // TODO: make this JavaScript #attached.
  490. drupal_add_js(drupal_get_path('module', 'term_merge') . '/js/duplicate.form.js');
  491. // Checking if we were not given vocabulary object, we will use term object to
  492. // obtain the former.
  493. if (!is_null($parent_term) && is_null($vocabulary)) {
  494. $vocabulary = taxonomy_vocabulary_load($parent_term->vid);
  495. }
  496. $tree = taxonomy_get_tree($vocabulary->vid, is_null($parent_term) ? 0 : $parent_term->tid);
  497. // Helpful and self explaining text that should help people understand what's
  498. // up.
  499. $form['help'] = array(
  500. '#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>',
  501. );
  502. $form['scaling'] = array(
  503. '#type' => 'fieldset',
  504. '#title' => t('Scaling for large vocabularies'),
  505. '#description' => t('Adjust these settings if your vocabulary is very large.'),
  506. '#tree' => TRUE,
  507. '#collapsible' => TRUE,
  508. );
  509. $form['scaling']['help'] = array(
  510. '#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(
  511. '%vocabulary' => $vocabulary->name,
  512. )) . '</p>',
  513. );
  514. $form['scaling']['max_duplicates'] = array(
  515. '#type' => 'textfield',
  516. '#title' => t('Show N duplicates'),
  517. '#description' => t('Input an integer here - this many duplicates will be show on the form. Once this amount of possible duplicates is found, the search process terminates.'),
  518. '#required' => TRUE,
  519. '#default_value' => isset($form_state['values']['scaling']['max_duplicates']) ? $form_state['values']['scaling']['max_duplicates'] : 300,
  520. '#element_validate' => array('element_validate_integer_positive'),
  521. );
  522. $form['scaling']['update'] = array(
  523. '#type' => 'button',
  524. '#value' => t('Re-run duplicate search'),
  525. '#ajax' => array(
  526. 'callback' => 'term_merge_duplicates_form_scaling',
  527. 'wrapper' => 'term-merge-duplicate-wrapper',
  528. 'method' => 'replace',
  529. 'effect' => 'fade',
  530. ),
  531. );
  532. // Amount of found duplicates.
  533. $count = 0;
  534. // Array of groups of terms with the same name. Each group is an array of
  535. // duplicates. Trunk term of each group will be chosen by user.
  536. $groups = array();
  537. foreach ($tree as $term) {
  538. if ($count >= $form['scaling']['max_duplicates']['#default_value']) {
  539. // We have reached the limit of possible duplicates to be found.
  540. break;
  541. }
  542. $name = term_merge_duplicates_process_name($term->name);
  543. if (!isset($groups[$name])) {
  544. $groups[$name] = array();
  545. }
  546. else {
  547. // We increment count by one for the just encountered duplicate. Plus, if
  548. // it is the second duplicate in this group, we also increment it by one
  549. // for the 1st duplicate in the group.
  550. $count++;
  551. if (count($groups[$name]) == 1) {
  552. $count++;
  553. }
  554. }
  555. $groups[$name][$term->tid] = $term;
  556. }
  557. $form['wrapper'] = array(
  558. '#prefix' => '<div id="term-merge-duplicate-wrapper">',
  559. '#suffix' => '</div>',
  560. );
  561. if ($count > 0) {
  562. $form['wrapper']['global_switch'] = array(
  563. '#type' => 'checkbox',
  564. '#title' => t('Select All Terms'),
  565. '#description' => t('Checking here will select for merging all the encountered duplicate terms.'),
  566. '#attributes' => array(
  567. 'class' => array('term-merge-duplicate-general-switch'),
  568. ),
  569. );
  570. }
  571. $form['wrapper']['group'] = array(
  572. '#tree' => TRUE,
  573. );
  574. foreach ($groups as $i => $group) {
  575. if (count($group) > 1) {
  576. // Sorting terms by tid for better usage experience.
  577. ksort($group);
  578. $first_term = reset($group);
  579. $options = array();
  580. foreach ($group as $term) {
  581. $parents = array();
  582. // Adding Root to the hierarchy.
  583. $parents[] = t('Vocabulary Root');
  584. foreach (taxonomy_get_parents_all($term->tid) as $parent) {
  585. // We do not include the current term in the hierarchy.
  586. if ($parent->tid != $term->tid) {
  587. $parents[] = $parent->name;
  588. }
  589. }
  590. $language = isset($term->language) ? $term->language : LANGUAGE_NONE;
  591. if ($language == LANGUAGE_NONE) {
  592. $language = t('Not Specified');
  593. }
  594. $options[$term->tid] = array(
  595. 'id' => $term->tid,
  596. 'title' => l($term->name, 'taxonomy/term/' . $term->tid),
  597. 'language' => $language,
  598. 'description' => check_markup($term->description, $term->format),
  599. 'parents' => implode(' &raquo; ', $parents),
  600. );
  601. }
  602. $form['wrapper']['group'][$i] = array(
  603. '#type' => 'fieldset',
  604. '#title' => check_plain($first_term->name),
  605. '#collapsible' => TRUE,
  606. '#pre_render' => array('term_merge_duplicates_fieldset_preprocess'),
  607. '#element_validate' => array('term_merge_duplicates_fieldset_validate'),
  608. );
  609. $form['wrapper']['group'][$i]['duplicates'] = array(
  610. '#type' => 'tableselect',
  611. '#title' => 'Duplicates',
  612. '#header' => array(
  613. 'id' => t('ID'),
  614. 'title' => t('Title'),
  615. 'description' => t('Description'),
  616. 'language' => t('Language'),
  617. 'parents' => t('Parents'),
  618. ),
  619. '#options' => $options,
  620. );
  621. $options = array();
  622. foreach ($group as $term) {
  623. $options[$term->tid] = $term->name;
  624. }
  625. $form['wrapper']['group'][$i]['trunk_tid'] = array(
  626. '#type' => 'radios',
  627. '#title' => t('Merge Into'),
  628. '#options' => $options,
  629. '#attributes' => array(
  630. 'class' => array('term-merge-duplicate-trunk'),
  631. ),
  632. );
  633. }
  634. }
  635. if ($count > 0) {
  636. // Adding necessary options of merging.
  637. $form += term_merge_merge_options_elements($vocabulary);
  638. $form['actions'] = array(
  639. '#type' => 'actions',
  640. );
  641. $form['actions']['submit'] = array(
  642. '#type' => 'submit',
  643. '#value' => t('Submit'),
  644. );
  645. }
  646. else {
  647. if (is_null($parent_term)) {
  648. $no_match_text = t('Sorry, seems like we were not able to find any possible duplicate terms in %vocabulary vocabulary.', array(
  649. '%vocabulary' => $vocabulary->name,
  650. ));
  651. }
  652. else {
  653. $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(
  654. '%term' => $parent_term->name,
  655. '!url' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/merge/duplicates'),
  656. ));
  657. }
  658. $form['nothing_found'] = array(
  659. '#markup' => '<p><b>' . $no_match_text . '</b></p>',
  660. );
  661. }
  662. return $form;
  663. }
  664. /**
  665. * Submit handler for 'term_merge_duplicates_form'.
  666. *
  667. * Actually merge duplicate terms.
  668. */
  669. function term_merge_duplicates_form_submit($form, &$form_state) {
  670. $batch = array(
  671. 'title' => t('Merging terms'),
  672. 'operations' => array(),
  673. 'finished' => 'term_merge_batch_finished',
  674. 'file' => drupal_get_path('module', 'term_merge') . '/term_merge.batch.inc',
  675. );
  676. // Processing general options for merging.
  677. $merge_settings = term_merge_merge_options_submit($form, $form_state, $form);
  678. if (isset($form_state['values']['group'])) {
  679. foreach ($form_state['values']['group'] as $values) {
  680. // Filtering out only the selected duplicate terms.
  681. $term_branches = array_filter($values['duplicates']);
  682. // We also do not want to have trunk term to be among the branch terms.
  683. unset($term_branches[$values['trunk_tid']]);
  684. if (!empty($term_branches)) {
  685. // If something has been selected in this group we schedule its merging.
  686. $batch['operations'][] = array('_term_merge_batch_process', array(
  687. $term_branches,
  688. $values['trunk_tid'],
  689. $merge_settings,
  690. ));
  691. }
  692. }
  693. }
  694. if (empty($batch['operations'])) {
  695. drupal_set_message(t('No merging has been made, because you have not selected any duplicate term to merge.'));
  696. }
  697. else {
  698. batch_set($batch);
  699. }
  700. }
  701. /**
  702. * String process function.
  703. *
  704. * Manipulate supplied var $name and by the output of this function terms in a
  705. * vocabulary are grouped as duplicates.
  706. *
  707. * @param string $name
  708. * String that needs to be manipulated
  709. *
  710. * @return string
  711. * Processed string (normally it implies making it upper case, stripping down
  712. * any special chars, etc.)
  713. */
  714. function term_merge_duplicates_process_name($name) {
  715. // Making upper case.
  716. $name = drupal_strtoupper($name);
  717. // Trying transliteration, if available.
  718. if (module_exists('transliteration')) {
  719. $name = transliteration_get($name);
  720. // Keeping only ASCII chars.
  721. $name = preg_replace('#\W#', '', $name);
  722. }
  723. return $name;
  724. }
  725. /**
  726. * Form element preprocess function.
  727. *
  728. * Insert extra column for choosing term trunk into tableselect of terms to be
  729. * merged.
  730. */
  731. function term_merge_duplicates_fieldset_preprocess($element) {
  732. $options = &$element['duplicates']['#options'];
  733. foreach ($options as $tid => $row) {
  734. $element['trunk_tid'][$tid]['#title_display'] = 'invisible';
  735. $options[$tid] = array(
  736. 'trunk' => drupal_render($element['trunk_tid'][$tid]),
  737. ) + $options[$tid];
  738. }
  739. $element['trunk_tid']['#title_display'] = 'invisible';
  740. $element['duplicates']['#header'] = array(
  741. 'trunk' => $element['trunk_tid']['#title'],
  742. ) + $element['duplicates']['#header'];
  743. return $element;
  744. }
  745. /**
  746. * FAPI element validation callback.
  747. *
  748. * Validate fieldset of a 'term_merge_duplicates_form' form, if any duplicate
  749. * has been selected for merging, it makes sure the trunk term has been
  750. * selected. We can't allow merging without knowing the explicit trunk term.
  751. */
  752. function term_merge_duplicates_fieldset_validate($element, &$form_state, $form) {
  753. if (!empty($element['duplicates']['#value']) && !is_numeric($element['trunk_tid']['#value'])) {
  754. form_error($element, t('Please, choose %trunk_tid_label for the group %group_label', array(
  755. '%trunk_tid_label' => $element['trunk_tid']['#title'],
  756. '%group_label' => $element['#title'],
  757. )));
  758. }
  759. }
  760. /**
  761. * Ajax callback function.
  762. *
  763. * Used in term_merge_duplicates_form() to replace the duplicates tables with
  764. * new data per current scaling settings.
  765. */
  766. function term_merge_duplicates_form_scaling($form, &$form_state) {
  767. return $form['wrapper'];
  768. }