taxonomy.admin.inc 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. <?php
  2. /**
  3. * @file
  4. * Administrative page callbacks for the taxonomy module.
  5. */
  6. /**
  7. * Form builder to list and manage vocabularies.
  8. *
  9. * @ingroup forms
  10. * @see taxonomy_overview_vocabularies_submit()
  11. * @see theme_taxonomy_overview_vocabularies()
  12. */
  13. function taxonomy_overview_vocabularies($form) {
  14. $vocabularies = taxonomy_get_vocabularies();
  15. $form['#tree'] = TRUE;
  16. foreach ($vocabularies as $vocabulary) {
  17. $form[$vocabulary->vid]['#vocabulary'] = $vocabulary;
  18. $form[$vocabulary->vid]['name'] = array('#markup' => check_plain($vocabulary->name));
  19. $form[$vocabulary->vid]['weight'] = array(
  20. '#type' => 'weight',
  21. '#title' => t('Weight for @title', array('@title' => $vocabulary->name)),
  22. '#title_display' => 'invisible',
  23. '#delta' => 10,
  24. '#default_value' => $vocabulary->weight,
  25. );
  26. $form[$vocabulary->vid]['edit'] = array('#type' => 'link', '#title' => t('edit vocabulary'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name/edit");
  27. $form[$vocabulary->vid]['list'] = array('#type' => 'link', '#title' => t('list terms'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name");
  28. $form[$vocabulary->vid]['add'] = array('#type' => 'link', '#title' => t('add terms'), '#href' => "admin/structure/taxonomy/$vocabulary->machine_name/add");
  29. }
  30. // Only make this form include a submit button and weight if more than one
  31. // vocabulary exists.
  32. if (count($vocabularies) > 1) {
  33. $form['actions'] = array('#type' => 'actions');
  34. $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
  35. }
  36. elseif (isset($vocabulary)) {
  37. unset($form[$vocabulary->vid]['weight']);
  38. }
  39. return $form;
  40. }
  41. /**
  42. * Submit handler for vocabularies overview. Updates changed vocabulary weights.
  43. *
  44. * @see taxonomy_overview_vocabularies()
  45. */
  46. function taxonomy_overview_vocabularies_submit($form, &$form_state) {
  47. foreach ($form_state['values'] as $vid => $vocabulary) {
  48. if (is_numeric($vid) && $form[$vid]['#vocabulary']->weight != $form_state['values'][$vid]['weight']) {
  49. $form[$vid]['#vocabulary']->weight = $form_state['values'][$vid]['weight'];
  50. taxonomy_vocabulary_save($form[$vid]['#vocabulary']);
  51. }
  52. }
  53. drupal_set_message(t('The configuration options have been saved.'));
  54. }
  55. /**
  56. * Returns HTML for the vocabulary overview form as a sortable list of vocabularies.
  57. *
  58. * @param $variables
  59. * An associative array containing:
  60. * - form: A render element representing the form.
  61. *
  62. * @see taxonomy_overview_vocabularies()
  63. * @ingroup themeable
  64. */
  65. function theme_taxonomy_overview_vocabularies($variables) {
  66. $form = $variables['form'];
  67. $rows = array();
  68. foreach (element_children($form) as $key) {
  69. if (isset($form[$key]['name'])) {
  70. $vocabulary = &$form[$key];
  71. $row = array();
  72. $row[] = drupal_render($vocabulary['name']);
  73. if (isset($vocabulary['weight'])) {
  74. $vocabulary['weight']['#attributes']['class'] = array('vocabulary-weight');
  75. $row[] = drupal_render($vocabulary['weight']);
  76. }
  77. $row[] = drupal_render($vocabulary['edit']);
  78. $row[] = drupal_render($vocabulary['list']);
  79. $row[] = drupal_render($vocabulary['add']);
  80. $rows[] = array('data' => $row, 'class' => array('draggable'));
  81. }
  82. }
  83. $header = array(t('Vocabulary name'));
  84. if (isset($form['actions'])) {
  85. $header[] = t('Weight');
  86. drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
  87. }
  88. $header[] = array('data' => t('Operations'), 'colspan' => '3');
  89. return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No vocabularies available. <a href="@link">Add vocabulary</a>.', array('@link' => url('admin/structure/taxonomy/add'))), 'attributes' => array('id' => 'taxonomy'))) . drupal_render_children($form);
  90. }
  91. /**
  92. * Form builder for the vocabulary editing form.
  93. *
  94. * @ingroup forms
  95. * @see taxonomy_form_vocabulary_submit()
  96. * @see taxonomy_form_vocabulary_validate()
  97. */
  98. function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
  99. // During initial form build, add the entity to the form state for use
  100. // during form building and processing. During a rebuild, use what is in the
  101. // form state.
  102. if (!isset($form_state['vocabulary'])) {
  103. $vocabulary = is_object($edit) ? $edit : (object) $edit;
  104. $defaults = array(
  105. 'name' => '',
  106. 'machine_name' => '',
  107. 'description' => '',
  108. 'hierarchy' => 0,
  109. 'weight' => 0,
  110. );
  111. foreach ($defaults as $key => $value) {
  112. if (!isset($vocabulary->$key)) {
  113. $vocabulary->$key = $value;
  114. }
  115. }
  116. $form_state['vocabulary'] = $vocabulary;
  117. }
  118. else {
  119. $vocabulary = $form_state['vocabulary'];
  120. }
  121. // @todo Legacy support. Modules are encouraged to access the entity using
  122. // $form_state. Remove in Drupal 8.
  123. $form['#vocabulary'] = $form_state['vocabulary'];
  124. // Check whether we need a deletion confirmation form.
  125. if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
  126. return taxonomy_vocabulary_confirm_delete($form, $form_state, $form_state['values']['vid']);
  127. }
  128. $form['name'] = array(
  129. '#type' => 'textfield',
  130. '#title' => t('Name'),
  131. '#default_value' => $vocabulary->name,
  132. '#maxlength' => 255,
  133. '#required' => TRUE,
  134. );
  135. $form['machine_name'] = array(
  136. '#type' => 'machine_name',
  137. '#default_value' => $vocabulary->machine_name,
  138. '#maxlength' => 255,
  139. '#machine_name' => array(
  140. 'exists' => 'taxonomy_vocabulary_machine_name_load',
  141. ),
  142. );
  143. $form['old_machine_name'] = array(
  144. '#type' => 'value',
  145. '#value' => $vocabulary->machine_name,
  146. );
  147. $form['description'] = array(
  148. '#type' => 'textfield',
  149. '#title' => t('Description'),
  150. '#default_value' => $vocabulary->description,
  151. );
  152. // Set the hierarchy to "multiple parents" by default. This simplifies the
  153. // vocabulary form and standardizes the term form.
  154. $form['hierarchy'] = array(
  155. '#type' => 'value',
  156. '#value' => '0',
  157. );
  158. $form['actions'] = array('#type' => 'actions');
  159. $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
  160. if (isset($vocabulary->vid)) {
  161. $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
  162. $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
  163. $form['module'] = array('#type' => 'value', '#value' => $vocabulary->module);
  164. }
  165. $form['#validate'][] = 'taxonomy_form_vocabulary_validate';
  166. return $form;
  167. }
  168. /**
  169. * Form validation handler for taxonomy_form_vocabulary().
  170. *
  171. * Makes sure that the machine name of the vocabulary is not in the
  172. * disallowed list (names that conflict with menu items, such as 'list'
  173. * and 'add').
  174. *
  175. * @see taxonomy_form_vocabulary()
  176. * @see taxonomy_form_vocabulary_submit()
  177. */
  178. function taxonomy_form_vocabulary_validate($form, &$form_state) {
  179. // During the deletion there is no 'machine_name' key
  180. if (isset($form_state['values']['machine_name'])) {
  181. // Do not allow machine names to conflict with taxonomy path arguments.
  182. $machine_name = $form_state['values']['machine_name'];
  183. $disallowed = array('add', 'list');
  184. if (in_array($machine_name, $disallowed)) {
  185. form_set_error('machine_name', t('The machine-readable name cannot be "add" or "list".'));
  186. }
  187. }
  188. }
  189. /**
  190. * Form submission handler for taxonomy_form_vocabulary().
  191. *
  192. * @see taxonomy_form_vocabulary()
  193. * @see taxonomy_form_vocabulary_validate()
  194. */
  195. function taxonomy_form_vocabulary_submit($form, &$form_state) {
  196. if ($form_state['triggering_element']['#value'] == t('Delete')) {
  197. // Rebuild the form to confirm vocabulary deletion.
  198. $form_state['rebuild'] = TRUE;
  199. $form_state['confirm_delete'] = TRUE;
  200. return;
  201. }
  202. $vocabulary = $form_state['vocabulary'];
  203. entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state);
  204. switch (taxonomy_vocabulary_save($vocabulary)) {
  205. case SAVED_NEW:
  206. drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
  207. watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
  208. break;
  209. case SAVED_UPDATED:
  210. drupal_set_message(t('Updated vocabulary %name.', array('%name' => $vocabulary->name)));
  211. watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
  212. break;
  213. }
  214. $form_state['values']['vid'] = $vocabulary->vid;
  215. $form_state['vid'] = $vocabulary->vid;
  216. $form_state['redirect'] = 'admin/structure/taxonomy';
  217. }
  218. /**
  219. * Form builder for the taxonomy terms overview.
  220. *
  221. * Display a tree of all the terms in a vocabulary, with options to edit
  222. * each one. The form is made drag and drop by the theme function.
  223. *
  224. * @ingroup forms
  225. * @see taxonomy_overview_terms_submit()
  226. * @see theme_taxonomy_overview_terms()
  227. */
  228. function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
  229. global $pager_page_array, $pager_total, $pager_total_items;
  230. // Check for confirmation forms.
  231. if (isset($form_state['confirm_reset_alphabetical'])) {
  232. return taxonomy_vocabulary_confirm_reset_alphabetical($form, $form_state, $vocabulary->vid);
  233. }
  234. $form['#vocabulary'] = $vocabulary;
  235. $form['#tree'] = TRUE;
  236. $form['#parent_fields'] = FALSE;
  237. $page = isset($_GET['page']) ? $_GET['page'] : 0;
  238. $page_increment = variable_get('taxonomy_terms_per_page_admin', 100); // Number of terms per page.
  239. $page_entries = 0; // Elements shown on this page.
  240. $before_entries = 0; // Elements at the root level before this page.
  241. $after_entries = 0; // Elements at the root level after this page.
  242. $root_entries = 0; // Elements at the root level on this page.
  243. // Terms from previous and next pages are shown if the term tree would have
  244. // been cut in the middle. Keep track of how many extra terms we show on each
  245. // page of terms.
  246. $back_step = NULL;
  247. $forward_step = 0;
  248. // An array of the terms to be displayed on this page.
  249. $current_page = array();
  250. $delta = 0;
  251. $term_deltas = array();
  252. $tree = taxonomy_get_tree($vocabulary->vid);
  253. $term = current($tree);
  254. do {
  255. // In case this tree is completely empty.
  256. if (empty($term)) {
  257. break;
  258. }
  259. $delta++;
  260. // Count entries before the current page.
  261. if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) {
  262. $before_entries++;
  263. continue;
  264. }
  265. // Count entries after the current page.
  266. elseif ($page_entries > $page_increment && isset($complete_tree)) {
  267. $after_entries++;
  268. continue;
  269. }
  270. // Do not let a term start the page that is not at the root.
  271. if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) {
  272. $back_step = 0;
  273. while ($pterm = prev($tree)) {
  274. $before_entries--;
  275. $back_step++;
  276. if ($pterm->depth == 0) {
  277. prev($tree);
  278. continue 2; // Jump back to the start of the root level parent.
  279. }
  280. }
  281. }
  282. $back_step = isset($back_step) ? $back_step : 0;
  283. // Continue rendering the tree until we reach the a new root item.
  284. if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) {
  285. $complete_tree = TRUE;
  286. // This new item at the root level is the first item on the next page.
  287. $after_entries++;
  288. continue;
  289. }
  290. if ($page_entries >= $page_increment + $back_step) {
  291. $forward_step++;
  292. }
  293. // Finally, if we've gotten down this far, we're rendering a term on this page.
  294. $page_entries++;
  295. $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
  296. $key = 'tid:' . $term->tid . ':' . $term_deltas[$term->tid];
  297. // Keep track of the first term displayed on this page.
  298. if ($page_entries == 1) {
  299. $form['#first_tid'] = $term->tid;
  300. }
  301. // Keep a variable to make sure at least 2 root elements are displayed.
  302. if ($term->parents[0] == 0) {
  303. $root_entries++;
  304. }
  305. $current_page[$key] = $term;
  306. } while ($term = next($tree));
  307. // Because we didn't use a pager query, set the necessary pager variables.
  308. $total_entries = $before_entries + $page_entries + $after_entries;
  309. $pager_total_items[0] = $total_entries;
  310. $pager_page_array[0] = $page;
  311. $pager_total[0] = ceil($total_entries / $page_increment);
  312. // If this form was already submitted once, it's probably hit a validation
  313. // error. Ensure the form is rebuilt in the same order as the user submitted.
  314. if (!empty($form_state['input'])) {
  315. $order = array_flip(array_keys($form_state['input'])); // Get the $_POST order.
  316. $current_page = array_merge($order, $current_page); // Update our form with the new order.
  317. foreach ($current_page as $key => $term) {
  318. // Verify this is a term for the current page and set at the current depth.
  319. if (is_array($form_state['input'][$key]) && is_numeric($form_state['input'][$key]['tid'])) {
  320. $current_page[$key]->depth = $form_state['input'][$key]['depth'];
  321. }
  322. else {
  323. unset($current_page[$key]);
  324. }
  325. }
  326. }
  327. // Build the actual form.
  328. foreach ($current_page as $key => $term) {
  329. // Save the term for the current page so we don't have to load it a second time.
  330. $form[$key]['#term'] = (array) $term;
  331. if (isset($term->parents)) {
  332. $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
  333. unset($form[$key]['#term']['parents'], $term->parents);
  334. }
  335. $form[$key]['view'] = array('#type' => 'link', '#title' => $term->name, '#href' => "taxonomy/term/$term->tid");
  336. if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
  337. $form['#parent_fields'] = TRUE;
  338. $form[$key]['tid'] = array(
  339. '#type' => 'hidden',
  340. '#value' => $term->tid
  341. );
  342. $form[$key]['parent'] = array(
  343. '#type' => 'hidden',
  344. // Yes, default_value on a hidden. It needs to be changeable by the javascript.
  345. '#default_value' => $term->parent,
  346. );
  347. $form[$key]['depth'] = array(
  348. '#type' => 'hidden',
  349. // Same as above, the depth is modified by javascript, so it's a default_value.
  350. '#default_value' => $term->depth,
  351. );
  352. $form[$key]['weight'] = array(
  353. '#type' => 'weight',
  354. '#delta' => $delta,
  355. '#title_display' => 'invisible',
  356. '#title' => t('Weight for added term'),
  357. '#default_value' => $term->weight,
  358. );
  359. }
  360. $form[$key]['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'taxonomy/term/' . $term->tid . '/edit', '#options' => array('query' => drupal_get_destination()));
  361. }
  362. $form['#total_entries'] = $total_entries;
  363. $form['#page_increment'] = $page_increment;
  364. $form['#page_entries'] = $page_entries;
  365. $form['#back_step'] = $back_step;
  366. $form['#forward_step'] = $forward_step;
  367. $form['#empty_text'] = t('No terms available. <a href="@link">Add term</a>.', array('@link' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add')));
  368. if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
  369. $form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
  370. $form['actions']['submit'] = array(
  371. '#type' => 'submit',
  372. '#value' => t('Save')
  373. );
  374. $form['actions']['reset_alphabetical'] = array(
  375. '#type' => 'submit',
  376. '#value' => t('Reset to alphabetical')
  377. );
  378. $form_state['redirect'] = array($_GET['q'], (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array()));
  379. }
  380. return $form;
  381. }
  382. /**
  383. * Submit handler for terms overview form.
  384. *
  385. * Rather than using a textfield or weight field, this form depends entirely
  386. * upon the order of form elements on the page to determine new weights.
  387. *
  388. * Because there might be hundreds or thousands of taxonomy terms that need to
  389. * be ordered, terms are weighted from 0 to the number of terms in the
  390. * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
  391. * lowest to highest, but are not necessarily sequential. Numbers may be skipped
  392. * when a term has children so that reordering is minimal when a child is
  393. * added or removed from a term.
  394. *
  395. * @see taxonomy_overview_terms()
  396. */
  397. function taxonomy_overview_terms_submit($form, &$form_state) {
  398. if ($form_state['triggering_element']['#value'] == t('Reset to alphabetical')) {
  399. // Execute the reset action.
  400. if ($form_state['values']['reset_alphabetical'] === TRUE) {
  401. return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
  402. }
  403. // Rebuild the form to confirm the reset action.
  404. $form_state['rebuild'] = TRUE;
  405. $form_state['confirm_reset_alphabetical'] = TRUE;
  406. return;
  407. }
  408. // Sort term order based on weight.
  409. uasort($form_state['values'], 'drupal_sort_weight');
  410. $vocabulary = $form['#vocabulary'];
  411. $hierarchy = 0; // Update the current hierarchy type as we go.
  412. $changed_terms = array();
  413. $tree = taxonomy_get_tree($vocabulary->vid);
  414. if (empty($tree)) {
  415. return;
  416. }
  417. // Build a list of all terms that need to be updated on previous pages.
  418. $weight = 0;
  419. $term = (array) $tree[0];
  420. while ($term['tid'] != $form['#first_tid']) {
  421. if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
  422. $term['parent'] = $term['parents'][0];
  423. $term['weight'] = $weight;
  424. $changed_terms[$term['tid']] = $term;
  425. }
  426. $weight++;
  427. $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
  428. $term = (array) $tree[$weight];
  429. }
  430. // Renumber the current page weights and assign any new parents.
  431. $level_weights = array();
  432. foreach ($form_state['values'] as $tid => $values) {
  433. if (isset($form[$tid]['#term'])) {
  434. $term = $form[$tid]['#term'];
  435. // Give terms at the root level a weight in sequence with terms on previous pages.
  436. if ($values['parent'] == 0 && $term['weight'] != $weight) {
  437. $term['weight'] = $weight;
  438. $changed_terms[$term['tid']] = $term;
  439. }
  440. // Terms not at the root level can safely start from 0 because they're all on this page.
  441. elseif ($values['parent'] > 0) {
  442. $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
  443. if ($level_weights[$values['parent']] != $term['weight']) {
  444. $term['weight'] = $level_weights[$values['parent']];
  445. $changed_terms[$term['tid']] = $term;
  446. }
  447. }
  448. // Update any changed parents.
  449. if ($values['parent'] != $term['parent']) {
  450. $term['parent'] = $values['parent'];
  451. $changed_terms[$term['tid']] = $term;
  452. }
  453. $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
  454. $weight++;
  455. }
  456. }
  457. // Build a list of all terms that need to be updated on following pages.
  458. for ($weight; $weight < count($tree); $weight++) {
  459. $term = (array) $tree[$weight];
  460. if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
  461. $term['parent'] = $term['parents'][0];
  462. $term['weight'] = $weight;
  463. $changed_terms[$term['tid']] = $term;
  464. }
  465. $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
  466. }
  467. // Save all updated terms.
  468. foreach ($changed_terms as $changed) {
  469. $term = (object) $changed;
  470. // Update term_hierachy and term_data directly since we don't have a
  471. // fully populated term object to save.
  472. db_update('taxonomy_term_hierarchy')
  473. ->fields(array('parent' => $term->parent))
  474. ->condition('tid', $term->tid, '=')
  475. ->execute();
  476. db_update('taxonomy_term_data')
  477. ->fields(array('weight' => $term->weight))
  478. ->condition('tid', $term->tid, '=')
  479. ->execute();
  480. }
  481. // Update the vocabulary hierarchy to flat or single hierarchy.
  482. if ($vocabulary->hierarchy != $hierarchy) {
  483. $vocabulary->hierarchy = $hierarchy;
  484. taxonomy_vocabulary_save($vocabulary);
  485. }
  486. drupal_set_message(t('The configuration options have been saved.'));
  487. }
  488. /**
  489. * Returns HTML for a terms overview form as a sortable list of terms.
  490. *
  491. * @param $variables
  492. * An associative array containing:
  493. * - form: A render element representing the form.
  494. *
  495. * @see taxonomy_overview_terms()
  496. * @ingroup themeable
  497. */
  498. function theme_taxonomy_overview_terms($variables) {
  499. $form = $variables['form'];
  500. $page_increment = $form['#page_increment'];
  501. $page_entries = $form['#page_entries'];
  502. $back_step = $form['#back_step'];
  503. $forward_step = $form['#forward_step'];
  504. // Add drag and drop if parent fields are present in the form.
  505. if ($form['#parent_fields']) {
  506. drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
  507. drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
  508. drupal_add_js(drupal_get_path('module', 'taxonomy') . '/taxonomy.js');
  509. drupal_add_js(array('taxonomy' => array('backStep' => $back_step, 'forwardStep' => $forward_step)), 'setting');
  510. drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
  511. }
  512. drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'term-weight');
  513. $errors = form_get_errors() != FALSE ? form_get_errors() : array();
  514. $rows = array();
  515. foreach (element_children($form) as $key) {
  516. if (isset($form[$key]['#term'])) {
  517. $term = &$form[$key];
  518. $row = array();
  519. $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', array('size' => $term['#term']['depth'])) : ''). drupal_render($term['view']);
  520. if ($form['#parent_fields']) {
  521. $term['tid']['#attributes']['class'] = array('term-id');
  522. $term['parent']['#attributes']['class'] = array('term-parent');
  523. $term['depth']['#attributes']['class'] = array('term-depth');
  524. $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
  525. }
  526. $term['weight']['#attributes']['class'] = array('term-weight');
  527. $row[] = drupal_render($term['weight']);
  528. $row[] = drupal_render($term['edit']);
  529. $row = array('data' => $row);
  530. $rows[$key] = $row;
  531. }
  532. }
  533. // Add necessary classes to rows.
  534. $row_position = 0;
  535. foreach ($rows as $key => $row) {
  536. $rows[$key]['class'] = array();
  537. if (isset($form['#parent_fields'])) {
  538. $rows[$key]['class'][] = 'draggable';
  539. }
  540. // Add classes that mark which terms belong to previous and next pages.
  541. if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) {
  542. $rows[$key]['class'][] = 'taxonomy-term-preview';
  543. }
  544. if ($row_position !== 0 && $row_position !== count($rows) - 1) {
  545. if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) {
  546. $rows[$key]['class'][] = 'taxonomy-term-divider-top';
  547. }
  548. elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) {
  549. $rows[$key]['class'][] = 'taxonomy-term-divider-bottom';
  550. }
  551. }
  552. // Add an error class if this row contains a form error.
  553. foreach ($errors as $error_key => $error) {
  554. if (strpos($error_key, $key) === 0) {
  555. $rows[$key]['class'][] = 'error';
  556. }
  557. }
  558. $row_position++;
  559. }
  560. if (empty($rows)) {
  561. $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '3'));
  562. }
  563. $header = array(t('Name'), t('Weight'), t('Operations'));
  564. $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'taxonomy')));
  565. $output .= drupal_render_children($form);
  566. $output .= theme('pager');
  567. return $output;
  568. }
  569. /**
  570. * Form function for the term edit form.
  571. *
  572. * @ingroup forms
  573. * @see taxonomy_form_term_submit()
  574. */
  575. function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = NULL) {
  576. // During initial form build, add the term entity to the form state for use
  577. // during form building and processing. During a rebuild, use what is in the
  578. // form state.
  579. if (!isset($form_state['term'])) {
  580. $term = is_object($edit) ? $edit : (object) $edit;
  581. if (!isset($vocabulary) && isset($term->vid)) {
  582. $vocabulary = taxonomy_vocabulary_load($term->vid);
  583. }
  584. $defaults = array(
  585. 'name' => '',
  586. 'description' => '',
  587. 'format' => NULL,
  588. 'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
  589. 'tid' => NULL,
  590. 'weight' => 0,
  591. );
  592. foreach ($defaults as $key => $value) {
  593. if (!isset($term->$key)) {
  594. $term->$key = $value;
  595. }
  596. }
  597. $form_state['term'] = $term;
  598. }
  599. else {
  600. $term = $form_state['term'];
  601. if (!isset($vocabulary) && isset($term->vid)) {
  602. $vocabulary = taxonomy_vocabulary_load($term->vid);
  603. }
  604. }
  605. $parent = array_keys(taxonomy_get_parents($term->tid));
  606. $form['#term'] = (array) $term;
  607. $form['#term']['parent'] = $parent;
  608. $form['#vocabulary'] = $vocabulary;
  609. // Check for confirmation forms.
  610. if (isset($form_state['confirm_delete'])) {
  611. return array_merge($form, taxonomy_term_confirm_delete($form, $form_state, $term->tid));
  612. }
  613. $form['name'] = array(
  614. '#type' => 'textfield',
  615. '#title' => t('Name'),
  616. '#default_value' => $term->name,
  617. '#maxlength' => 255,
  618. '#required' => TRUE,
  619. '#weight' => -5,
  620. );
  621. $form['description'] = array(
  622. '#type' => 'text_format',
  623. '#title' => t('Description'),
  624. '#default_value' => $term->description,
  625. '#format' => $term->format,
  626. '#weight' => 0,
  627. );
  628. $form['vocabulary_machine_name'] = array(
  629. '#type' => 'value',
  630. '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name,
  631. );
  632. $langcode = entity_language('taxonomy_term', $term);
  633. field_attach_form('taxonomy_term', $term, $form, $form_state, $langcode);
  634. $form['relations'] = array(
  635. '#type' => 'fieldset',
  636. '#title' => t('Relations'),
  637. '#collapsible' => TRUE,
  638. '#collapsed' => $vocabulary->hierarchy < 2,
  639. '#weight' => 10,
  640. );
  641. // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
  642. // items so we check for taxonomy_override_selector before loading the
  643. // full vocabulary. Contrib modules can then intercept before
  644. // hook_form_alter to provide scalable alternatives.
  645. if (!variable_get('taxonomy_override_selector', FALSE)) {
  646. $parent = array_keys(taxonomy_get_parents($term->tid));
  647. $children = taxonomy_get_tree($vocabulary->vid, $term->tid);
  648. // A term can't be the child of itself, nor of its children.
  649. foreach ($children as $child) {
  650. $exclude[] = $child->tid;
  651. }
  652. $exclude[] = $term->tid;
  653. $tree = taxonomy_get_tree($vocabulary->vid);
  654. $options = array('<' . t('root') . '>');
  655. if (empty($parent)) {
  656. $parent = array(0);
  657. }
  658. foreach ($tree as $item) {
  659. if (!in_array($item->tid, $exclude)) {
  660. $options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
  661. }
  662. }
  663. $form['relations']['parent'] = array(
  664. '#type' => 'select',
  665. '#title' => t('Parent terms'),
  666. '#options' => $options,
  667. '#default_value' => $parent,
  668. '#multiple' => TRUE,
  669. );
  670. }
  671. $form['relations']['weight'] = array(
  672. '#type' => 'textfield',
  673. '#title' => t('Weight'),
  674. '#size' => 6,
  675. '#default_value' => $term->weight,
  676. '#description' => t('Terms are displayed in ascending order by weight.'),
  677. '#required' => TRUE,
  678. );
  679. $form['vid'] = array(
  680. '#type' => 'value',
  681. '#value' => $vocabulary->vid,
  682. );
  683. $form['tid'] = array(
  684. '#type' => 'value',
  685. '#value' => $term->tid,
  686. );
  687. $form['actions'] = array('#type' => 'actions');
  688. $form['actions']['submit'] = array(
  689. '#type' => 'submit',
  690. '#value' => t('Save'),
  691. '#weight' => 5,
  692. );
  693. if ($term->tid) {
  694. $form['actions']['delete'] = array(
  695. '#type' => 'submit',
  696. '#value' => t('Delete'),
  697. '#access' => user_access("delete terms in $vocabulary->vid") || user_access('administer taxonomy'),
  698. '#weight' => 10,
  699. );
  700. }
  701. else {
  702. $form_state['redirect'] = $_GET['q'];
  703. }
  704. return $form;
  705. }
  706. /**
  707. * Validation handler for the term form.
  708. *
  709. * @see taxonomy_form_term()
  710. */
  711. function taxonomy_form_term_validate($form, &$form_state) {
  712. entity_form_field_validate('taxonomy_term', $form, $form_state);
  713. // Ensure numeric values.
  714. if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
  715. form_set_error('weight', t('Weight value must be numeric.'));
  716. }
  717. }
  718. /**
  719. * Submit handler to insert or update a term.
  720. *
  721. * @see taxonomy_form_term()
  722. */
  723. function taxonomy_form_term_submit($form, &$form_state) {
  724. if ($form_state['triggering_element']['#value'] == t('Delete')) {
  725. // Execute the term deletion.
  726. if ($form_state['values']['delete'] === TRUE) {
  727. return taxonomy_term_confirm_delete_submit($form, $form_state);
  728. }
  729. // Rebuild the form to confirm term deletion.
  730. $form_state['rebuild'] = TRUE;
  731. $form_state['confirm_delete'] = TRUE;
  732. return;
  733. }
  734. $term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state);
  735. $status = taxonomy_term_save($term);
  736. switch ($status) {
  737. case SAVED_NEW:
  738. drupal_set_message(t('Created new term %term.', array('%term' => $term->name)));
  739. watchdog('taxonomy', 'Created new term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
  740. break;
  741. case SAVED_UPDATED:
  742. drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
  743. watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
  744. // Clear the page and block caches to avoid stale data.
  745. cache_clear_all();
  746. break;
  747. }
  748. $current_parent_count = count($form_state['values']['parent']);
  749. $previous_parent_count = count($form['#term']['parent']);
  750. // Root doesn't count if it's the only parent.
  751. if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) {
  752. $current_parent_count = 0;
  753. $form_state['values']['parent'] = array();
  754. }
  755. // If the number of parents has been reduced to one or none, do a check on the
  756. // parents of every term in the vocabulary value.
  757. if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
  758. taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
  759. }
  760. // If we've increased the number of parents and this is a single or flat
  761. // hierarchy, update the vocabulary immediately.
  762. elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']->hierarchy < 2) {
  763. $form['#vocabulary']->hierarchy = $current_parent_count == 1 ? 1 : 2;
  764. taxonomy_vocabulary_save($form['#vocabulary']);
  765. }
  766. $form_state['values']['tid'] = $term->tid;
  767. $form_state['tid'] = $term->tid;
  768. }
  769. /**
  770. * Updates the form state's term entity by processing this submission's values.
  771. */
  772. function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) {
  773. $term = $form_state['term'];
  774. entity_form_submit_build_entity('taxonomy_term', $term, $form, $form_state);
  775. // Convert text_format field into values expected by taxonomy_term_save().
  776. $description = $form_state['values']['description'];
  777. $term->description = $description['value'];
  778. $term->format = $description['format'];
  779. return $term;
  780. }
  781. /**
  782. * Form builder for the term delete form.
  783. *
  784. * @ingroup forms
  785. * @see taxonomy_term_confirm_delete_submit()
  786. */
  787. function taxonomy_term_confirm_delete($form, &$form_state, $tid) {
  788. $term = taxonomy_term_load($tid);
  789. // Always provide entity id in the same form key as in the entity edit form.
  790. $form['tid'] = array('#type' => 'value', '#value' => $tid);
  791. $form['#term'] = $term;
  792. $form['type'] = array('#type' => 'value', '#value' => 'term');
  793. $form['name'] = array('#type' => 'value', '#value' => $term->name);
  794. $form['vocabulary_machine_name'] = array('#type' => 'value', '#value' => $term->vocabulary_machine_name);
  795. $form['delete'] = array('#type' => 'value', '#value' => TRUE);
  796. return confirm_form($form,
  797. t('Are you sure you want to delete the term %title?',
  798. array('%title' => $term->name)),
  799. 'admin/structure/taxonomy',
  800. t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
  801. t('Delete'),
  802. t('Cancel'));
  803. }
  804. /**
  805. * Submit handler to delete a term after confirmation.
  806. *
  807. * @see taxonomy_term_confirm_delete()
  808. */
  809. function taxonomy_term_confirm_delete_submit($form, &$form_state) {
  810. taxonomy_term_delete($form_state['values']['tid']);
  811. taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
  812. drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
  813. watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
  814. $form_state['redirect'] = 'admin/structure/taxonomy';
  815. cache_clear_all();
  816. return;
  817. }
  818. /**
  819. * Form builder for the vocabulary delete confirmation form.
  820. *
  821. * @ingroup forms
  822. * @see taxonomy_vocabulary_confirm_delete_submit()
  823. */
  824. function taxonomy_vocabulary_confirm_delete($form, &$form_state, $vid) {
  825. $vocabulary = taxonomy_vocabulary_load($vid);
  826. // Always provide entity id in the same form key as in the entity edit form.
  827. $form['vid'] = array('#type' => 'value', '#value' => $vid);
  828. $form['#vocabulary'] = $vocabulary;
  829. $form['#id'] = 'taxonomy_vocabulary_confirm_delete';
  830. $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
  831. $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
  832. $form['#submit'] = array('taxonomy_vocabulary_confirm_delete_submit');
  833. return confirm_form($form,
  834. t('Are you sure you want to delete the vocabulary %title?',
  835. array('%title' => $vocabulary->name)),
  836. 'admin/structure/taxonomy',
  837. t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
  838. t('Delete'),
  839. t('Cancel'));
  840. }
  841. /**
  842. * Submit handler to delete a vocabulary after confirmation.
  843. *
  844. * @see taxonomy_vocabulary_confirm_delete()
  845. */
  846. function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
  847. $status = taxonomy_vocabulary_delete($form_state['values']['vid']);
  848. drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
  849. watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
  850. $form_state['redirect'] = 'admin/structure/taxonomy';
  851. cache_clear_all();
  852. return;
  853. }
  854. /**
  855. * Form builder to confirm resetting a vocabulary to alphabetical order.
  856. *
  857. * @ingroup forms
  858. * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit()
  859. */
  860. function taxonomy_vocabulary_confirm_reset_alphabetical($form, &$form_state, $vid) {
  861. $vocabulary = taxonomy_vocabulary_load($vid);
  862. $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
  863. $form['vid'] = array('#type' => 'value', '#value' => $vid);
  864. $form['machine_name'] = array('#type' => 'value', '#value' => $vocabulary->machine_name);
  865. $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
  866. $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
  867. return confirm_form($form,
  868. t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
  869. array('%title' => $vocabulary->name)),
  870. 'admin/structure/taxonomy/' . $vocabulary->machine_name,
  871. t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.'),
  872. t('Reset to alphabetical'),
  873. t('Cancel'));
  874. }
  875. /**
  876. * Submit handler to reset a vocabulary to alphabetical order after confirmation.
  877. *
  878. * @see taxonomy_vocabulary_confirm_reset_alphabetical()
  879. */
  880. function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
  881. db_update('taxonomy_term_data')
  882. ->fields(array('weight' => 0))
  883. ->condition('vid', $form_state['values']['vid'])
  884. ->execute();
  885. drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
  886. watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
  887. $form_state['redirect'] = 'admin/structure/taxonomy/' . $form_state['values']['machine_name'];
  888. }