Simpletest Coverage - modules/taxonomy/taxonomy.admin.inc

1 <?php
2 // $Id: taxonomy.admin.inc,v 1.64 2009/08/04 06:50:07 webchick Exp $
3
4 /**
5 * @file
6 * Administrative page callbacks for the taxonomy module.
7 */
8
9 /**
10 * Form builder to list and manage vocabularies.
11 *
12 * @ingroup forms
13 * @see taxonomy_overview_vocabularies_submit()
14 * @see theme_taxonomy_overview_vocabularies()
15 */
16 function taxonomy_overview_vocabularies() {
17 $vocabularies = taxonomy_get_vocabularies();
18 $form = array('#tree' => TRUE);
19 foreach ($vocabularies as $vocabulary) {
20 $types = array();
21 foreach ($vocabulary->nodes as $type) {
22 $node_type = node_type_get_name($type);
23 $types[] = $node_type ? check_plain($node_type) : check_plain($type);
24 }
25 $form[$vocabulary->vid]['#vocabulary'] = $vocabulary;
26 $form[$vocabulary->vid]['name'] = array('#markup' => check_plain($vocabulary->name));
27 $form[$vocabulary->vid]['types'] = array('#markup' => implode(', ', $types));
28 $form[$vocabulary->vid]['weight'] = array('#type' => 'weight', '#delta' => 10, '#default_value' => $vocabulary->weight);
29 $form[$vocabulary->vid]['edit'] = array('#markup' => l(t('edit vocabulary'), "admin/structure/taxonomy/$vocabulary->vid"));
30 $form[$vocabulary->vid]['list'] = array('#markup' => l(t('list terms'), "admin/structure/taxonomy/$vocabulary->vid/list"));
31 $form[$vocabulary->vid]['add'] = array('#markup' => l(t('add terms'), "admin/structure/taxonomy/$vocabulary->vid/add"));
32 }
33
34 // Only make this form include a submit button and weight if more than one
35 // vocabulary exists.
36 if (count($vocabularies) > 1) {
37 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
38 }
39 elseif (isset($vocabulary)) {
40 unset($form[$vocabulary->vid]['weight']);
41 }
42 return $form;
43 }
44
45 /**
46 * Submit handler for vocabularies overview. Updates changed vocabulary weights.
47 *
48 * @see taxonomy_overview_vocabularies()
49 */
50 function taxonomy_overview_vocabularies_submit($form, &$form_state) {
51 foreach ($form_state['values'] as $vid => $vocabulary) {
52 if (is_numeric($vid) && $form[$vid]['#vocabulary']->weight != $form_state['values'][$vid]['weight']) {
53 $form[$vid]['#vocabulary']->weight = $form_state['values'][$vid]['weight'];
54 taxonomy_vocabulary_save($form[$vid]['#vocabulary']);
55 }
56 }
57 }
58
59 /**
60 * Theme the vocabulary overview as a sortable list of vocabularies.
61 *
62 * @ingroup themeable
63 * @see taxonomy_overview_vocabularies()
64 */
65 function theme_taxonomy_overview_vocabularies($form) {
66 $rows = array();
67
68 foreach (element_children($form) as $key) {
69 if (isset($form[$key]['name'])) {
70 $vocabulary = &$form[$key];
71
72 $row = array();
73 $row[] = drupal_render($vocabulary['name']);
74 $row[] = drupal_render($vocabulary['types']);
75 if (isset($vocabulary['weight'])) {
76 $vocabulary['weight']['#attributes']['class'] = 'vocabulary-weight';
77 $row[] = drupal_render($vocabulary['weight']);
78 }
79 $row[] = drupal_render($vocabulary['edit']);
80 $row[] = drupal_render($vocabulary['list']);
81 $row[] = drupal_render($vocabulary['add']);
82 $rows[] = array('data' => $row, 'class' => 'draggable');
83 }
84 }
85
86 if (empty($rows)) {
87 $rows[] = array(array('data' => t('No vocabularies available. <a href="@link">Add vocabulary</a>.', array('@link' => url('admin/structure/taxonomy/add'))), 'colspan' => '5'));
88 }
89
90 $header = array(t('Vocabulary name'), t('Content types'));
91 if (isset($form['submit'])) {
92 $header[] = t('Weight');
93 drupal_add_tabledrag('taxonomy', 'order', 'sibling', 'vocabulary-weight');
94 }
95 $header[] = array('data' => t('Operations'), 'colspan' => '3');
96 return theme('table', $header, $rows, array('id' => 'taxonomy')) . drupal_render_children($form);
97 }
98
99 /**
100 * Display form for adding and editing vocabularies.
101 *
102 * @ingroup forms
103 * @see taxonomy_form_vocabulary_submit()
104 */
105 function taxonomy_form_vocabulary(&$form_state, $edit = array()) {
106 drupal_add_js(drupal_get_path('module', 'taxonomy') . '/vocabulary.js');
107 if (!is_array($edit)) {
108 $edit = (array)$edit;
109 }
110 $edit += array(
111 'name' => '',
112 'machine_name' => '',
113 'description' => '',
114 'help' => '',
115 'nodes' => array(),
116 'hierarchy' => 0,
117 'tags' => 0,
118 'multiple' => 0,
119 'required' => 0,
120 'weight' => 0,
121 );
122 $form['#vocabulary'] = (object) $edit;
123 // Check whether we need a deletion confirmation form.
124 if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
125 return taxonomy_vocabulary_confirm_delete($form_state, $form_state['values']['vid']);
126 }
127 $form['name'] = array(
128 '#type' => 'textfield',
129 '#title' => t('Name'),
130 '#default_value' => $edit['name'],
131 '#maxlength' => 255,
132 '#required' => TRUE,
133 '#field_suffix' => ' <small id="vocabulary-name-suffix">&nbsp;</small>',
134 );
135 $form['machine_name'] = array(
136 '#type' => 'textfield',
137 '#title' => t('Machine readable name'),
138 '#default_value' => $edit['machine_name'],
139 '#maxlength' => 255,
140 '#description' => t('The unique machine readable name for this vocabulary, used for theme templates, can only contain lowercase letters, numbers and underscores.'),
141 '#required' => TRUE,
142 );
143 $form['help'] = array(
144 '#type' => 'textfield',
145 '#title' => t('Help text'),
146 '#maxlength' => 255,
147 '#default_value' => $edit['help'],
148 '#description' => t('Instructions to present to the user when selecting terms, e.g., <em>"Enter a comma separated list of words"</em>.'),
149 );
150 $form['description'] = array(
151 '#type' => 'textfield',
152 '#title' => t('Description'),
153 '#default_value' => $edit['description'],
154 );
155 $form['nodes'] = array(
156 '#type' => 'checkboxes',
157 '#title' => t('Apply to content types'),
158 '#default_value' => $edit['nodes'],
159 '#options' => array_map('check_plain', node_type_get_names()),
160 );
161 $form['settings'] = array(
162 '#type' => 'fieldset',
163 '#title' => t('Settings'),
164 '#collapsible' => TRUE,
165 );
166 $form['settings']['tags'] = array(
167 '#type' => 'checkbox',
168 '#title' => t('Tags'),
169 '#default_value' => $edit['tags'],
170 '#description' => t('Terms are created by users when submitting posts by typing a comma separated list.'),
171 );
172 $form['settings']['multiple'] = array(
173 '#type' => 'checkbox',
174 '#title' => t('Multiple select'),
175 '#default_value' => $edit['multiple'],
176 '#description' => t('Allows posts to have more than one term from this vocabulary (always true for tags).'),
177 );
178 $form['settings']['required'] = array(
179 '#type' => 'checkbox',
180 '#title' => t('Required'),
181 '#default_value' => $edit['required'],
182 '#description' => t('At least one term in this vocabulary must be selected when submitting a post.'),
183 );
184 // Set the hierarchy to "multiple parents" by default. This simplifies the
185 // vocabulary form and standardizes the term form.
186 $form['hierarchy'] = array(
187 '#type' => 'value',
188 '#value' => '0',
189 );
190
191 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
192 if (isset($edit['vid'])) {
193 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
194 $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']);
195 $form['module'] = array('#type' => 'value', '#value' => $edit['module']);
196 }
197 return $form;
198 }
199
200 /**
201 * Validation handler for the vocabulary form.
202 *
203 * @see taxonomy_form_vocabulary()
204 */
205 function taxonomy_form_vocabulary_validate($form, &$form_state) {
206 if ($form_state['clicked_button']['#value'] != t('Delete') && isset($form_state['values']['machine_name'])) {
207
208 // Restrict machine names to appropriate characters.
209 $machine_name = $form_state['values']['machine_name'];
210 if (!preg_match('!^[a-z0-9_]+$!', $form_state['values']['machine_name'])) {
211 form_set_error('machine_name', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
212 }
213
214 // Do not allow duplicate machine names.
215 $vocabularies = taxonomy_get_vocabularies();
216 foreach ($vocabularies as $vocabulary) {
217 if ($machine_name == $vocabulary->machine_name && (!isset($form_state['values']['vid']) || $vocabulary->vid != $form_state['values']['vid'])) {
218 form_set_error('machine_name', t('This machine-readable name is already in use by another vocabulary and must be unique.'));
219 }
220 }
221 }
222 }
223 /**
224 * Accept the form submission for a vocabulary and save the results.
225 */
226 function taxonomy_form_vocabulary_submit($form, &$form_state) {
227 $old_vocabulary = $form['#vocabulary'];
228 if ($form_state['clicked_button']['#value'] == t('Delete')) {
229 // Rebuild the form to confirm vocabulary deletion.
230 $form_state['rebuild'] = TRUE;
231 $form_state['confirm_delete'] = TRUE;
232 return;
233 }
234 // Fix up the nodes array to remove unchecked nodes.
235 $form_state['values']['nodes'] = array_filter($form_state['values']['nodes']);
236 $vocabulary = (object) $form_state['values'];
237 if ($vocabulary->machine_name != $old_vocabulary->machine_name) {
238 field_attach_rename_bundle($old_vocabulary->machine_name, $vocabulary->machine_name);
239 }
240 switch (taxonomy_vocabulary_save($vocabulary)) {
241 case SAVED_NEW:
242 drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
243 watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->vid));
244 break;
245 case SAVED_UPDATED:
246 drupal_set_message(t('Updated vocabulary %name.', array('%name' => $vocabulary->name)));
247 watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->vid));
248 break;
249 }
250
251 $form_state['vid'] = $vocabulary->vid;
252 $form_state['redirect'] = 'admin/structure/taxonomy';
253 return;
254 }
255
256 /**
257 * Form builder for the taxonomy terms overview.
258 *
259 * Display a tree of all the terms in a vocabulary, with options to edit
260 * each one. The form is made drag and drop by the theme function.
261 *
262 * @ingroup forms
263 * @see taxonomy_overview_terms_submit()
264 * @see theme_taxonomy_overview_terms()
265 */
266 function taxonomy_overview_terms(&$form_state, $vocabulary) {
267 global $pager_page_array, $pager_total, $pager_total_items;
268
269 // Check for confirmation forms.
270 if (isset($form_state['confirm_reset_alphabetical'])) {
271 return taxonomy_vocabulary_confirm_reset_alphabetical($form_state, $vocabulary->vid);
272 }
273
274 $form = array(
275 '#vocabulary' => $vocabulary,
276 '#tree' => TRUE,
277 '#parent_fields' => FALSE,
278 );
279
280 $page = isset($_GET['page']) ? $_GET['page'] : 0;
281 $page_increment = variable_get('taxonomy_terms_per_page_admin', 100); // Number of terms per page.
282 $page_entries = 0; // Elements shown on this page.
283 $before_entries = 0; // Elements at the root level before this page.
284 $after_entries = 0; // Elements at the root level after this page.
285 $root_entries = 0; // Elements at the root level on this page.
286
287 // Terms from previous and next pages are shown if the term tree would have
288 // been cut in the middle. Keep track of how many extra terms we show on each
289 // page of terms.
290 $back_peddle = NULL;
291 $forward_peddle = 0;
292
293 // An array of the terms to be displayed on this page.
294 $current_page = array();
295
296 // Case for free tagging.
297 if ($vocabulary->tags) {
298 // We are not calling taxonomy_get_tree because that might fail with a big
299 // number of tags in the freetagging vocabulary.
300 $query = db_select('taxonomy_term_data', 't')->extend('PagerDefault');
301 $query->join('taxonomy_term_hierarchy', 'h', 't.tid = h.tid');
302 $query->addTag('term_access');
303 $query->condition('t.vid', $vocabulary->vid);
304
305 // Store count in total entries and use this as count query.
306 $count_query = db_select('taxonomy_term_data', 't');
307 $count_query->join('taxonomy_term_hierarchy', 'h', 't.tid = h.tid');
308 $count_query->addTag('term_access');
309 $count_query->condition('t.vid', $vocabulary->vid);
310 $count_query->addExpression('COUNT(t.tid)');
311 $total_entries = $count_query->execute();
312 $query->setCountQuery($count_query);
313
314 $result = $query
315 ->fields('t')
316 ->fields('h', array('parent'))
317 ->orderBy('weight')
318 ->orderBy('name')
319 ->limit($page_increment)
320 ->execute();
321
322 foreach ($result as $term) {
323 $key = 'tid:' . $term->tid . ':0';
324 $current_page[$key] = $term;
325 $page_entries++;
326 }
327 }
328 // Case for restricted vocabulary.
329 else {
330 $term_deltas = array();
331 $tree = taxonomy_get_tree($vocabulary->vid);
332 $term = current($tree);
333 do {
334 // In case this tree is completely empty.
335 if (empty($term)) {
336 break;
337 }
338 // Count entries before the current page.
339 if ($page && ($page * $page_increment) > $before_entries && !isset($back_peddle)) {
340 $before_entries++;
341 continue;
342 }
343 // Count entries after the current page.
344 elseif ($page_entries > $page_increment && isset($complete_tree)) {
345 $after_entries++;
346 continue;
347 }
348
349 // Do not let a term start the page that is not at the root.
350 if (isset($term->depth) && ($term->depth > 0) && !isset($back_peddle)) {
351 $back_peddle = 0;
352 while ($pterm = prev($tree)) {
353 $before_entries--;
354 $back_peddle++;
355 if ($pterm->depth == 0) {
356 prev($tree);
357 continue 2; // Jump back to the start of the root level parent.
358 }
359 }
360 }
361 $back_peddle = isset($back_peddle) ? $back_peddle : 0;
362
363 // Continue rendering the tree until we reach the a new root item.
364 if ($page_entries >= $page_increment + $back_peddle + 1 && $term->depth == 0 && $root_entries > 1) {
365 $complete_tree = TRUE;
366 // This new item at the root level is the first item on the next page.
367 $after_entries++;
368 continue;
369 }
370 if ($page_entries >= $page_increment + $back_peddle) {
371 $forward_peddle++;
372 }
373
374 // Finally, if we've gotten down this far, we're rendering a term on this page.
375 $page_entries++;
376 $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
377 $key = 'tid:' . $term->tid . ':' . $term_deltas[$term->tid];
378
379 // Keep track of the first term displayed on this page.
380 if ($page_entries == 1) {
381 $form['#first_tid'] = $term->tid;
382 }
383 // Keep a variable to make sure at least 2 root elements are displayed.
384 if ($term->parents[0] == 0) {
385 $root_entries++;
386 }
387 $current_page[$key] = $term;
388 } while ($term = next($tree));
389
390 // Because we didn't use a pager query, set the necessary pager variables.
391 $total_entries = $before_entries + $page_entries + $after_entries;
392 $pager_total_items[0] = $total_entries;
393 $pager_page_array[0] = $page;
394 $pager_total[0] = ceil($total_entries / $page_increment);
395 }
396
397 // If this form was already submitted once, it's probably hit a validation
398 // error. Ensure the form is rebuilt in the same order as the user submitted.
399 if (!empty($form_state['input'])) {
400 $order = array_flip(array_keys($form_state['input'])); // Get the $_POST order.
401 $current_page = array_merge($order, $current_page); // Update our form with the new order.
402 foreach ($current_page as $key => $term) {
403 // Verify this is a term for the current page and set at the current depth.
404 if (is_array($form_state['input'][$key]) && is_numeric($form_state['input'][$key]['tid'])) {
405 $current_page[$key]->depth = $form_state['input'][$key]['depth'];
406 }
407 else {
408 unset($current_page[$key]);
409 }
410 }
411 }
412
413 // Build the actual form.
414 foreach ($current_page as $key => $term) {
415 // Save the term for the current page so we don't have to load it a second time.
416 $form[$key]['#term'] = (array)$term;
417 if (isset($term->parents)) {
418 $form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
419 unset($form[$key]['#term']['parents'], $term->parents);
420 }
421
422 $form[$key]['view'] = array('#markup' => l($term->name, "taxonomy/term/$term->tid"));
423 if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
424 $form['#parent_fields'] = TRUE;
425 $form[$key]['tid'] = array(
426 '#type' => 'hidden',
427 '#value' => $term->tid
428 );
429 $form[$key]['parent'] = array(
430 '#type' => 'hidden',
431 // Yes, default_value on a hidden. It needs to be changeable by the javascript.
432 '#default_value' => $term->parent,
433 );
434 $form[$key]['depth'] = array(
435 '#type' => 'hidden',
436 // Same as above, the depth is modified by javascript, so it's a default_value.
437 '#default_value' => $term->depth,
438 );
439 }
440 $form[$key]['edit'] = array('#markup' => l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit', array('query' => drupal_get_destination())));
441 }
442
443 $form['#total_entries'] = $total_entries;
444 $form['#page_increment'] = $page_increment;
445 $form['#page_entries'] = $page_entries;
446 $form['#back_peddle'] = $back_peddle;
447 $form['#forward_peddle'] = $forward_peddle;
448 $form['#empty_text'] = t('No terms available. <a href="@link">Add term</a>.', array('@link' => url('admin/structure/taxonomy/' . $vocabulary->vid . '/add')));
449
450 if (!$vocabulary->tags && $vocabulary->hierarchy < 2 && count($tree) > 1) {
451 $form['submit'] = array(
452 '#type' => 'submit',
453 '#value' => t('Save')
454 );
455 $form['reset_alphabetical'] = array(
456 '#type' => 'submit',
457 '#value' => t('Reset to alphabetical')
458 );
459 $form['destination'] = array(
460 '#type' => 'hidden',
461 '#value' => $_GET['q'] . (isset($_GET['page']) ? '?page=' . $_GET['page'] : '')
462 );
463 }
464
465 return $form;
466 }
467
468 /**
469 * Submit handler for terms overview form.
470 *
471 * Rather than using a textfield or weight field, this form depends entirely
472 * upon the order of form elements on the page to determine new weights.
473 *
474 * Because there might be hundreds or thousands of taxonomy terms that need to
475 * be ordered, terms are weighted from 0 to the number of terms in the
476 * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
477 * lowest to highest, but are not necessarily sequential. Numbers may be skipped
478 * when a term has children so that reordering is minimal when a child is
479 * added or removed from a term.
480 *
481 * @see taxonomy_overview_terms()
482 */
483 function taxonomy_overview_terms_submit($form, &$form_state) {
484 if ($form_state['clicked_button']['#value'] == t('Reset to alphabetical')) {
485 // Execute the reset action.
486 if ($form_state['values']['reset_alphabetical'] === TRUE) {
487 return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
488 }
489 // Rebuild the form to confirm the reset action.
490 $form_state['rebuild'] = TRUE;
491 $form_state['confirm_reset_alphabetical'] = TRUE;
492 return;
493 }
494
495 $order = array_flip(array_keys($form_state['input'])); // Get the $_POST order.
496 $form_state['values'] = array_merge($order, $form_state['values']); // Update our original form with the new order.
497
498 $vocabulary = $form['#vocabulary'];
499 $hierarchy = 0; // Update the current hierarchy type as we go.
500
501 $changed_terms = array();
502 $tree = taxonomy_get_tree($vocabulary->vid);
503
504 if (empty($tree)) {
505 return;
506 }
507
508 // Build a list of all terms that need to be updated on previous pages.
509 $weight = 0;
510 $term = (array)$tree[0];
511 while ($term['tid'] != $form['#first_tid']) {
512 if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
513 $term['parent'] = $term['parents'][0];
514 $term['weight'] = $weight;
515 $changed_terms[$term['tid']] = $term;
516 }
517 $weight++;
518 $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
519 $term = (array)$tree[$weight];
520 }
521
522 // Renumber the current page weights and assign any new parents.
523 $level_weights = array();
524 foreach ($form_state['values'] as $tid => $values) {
525 if (isset($form[$tid]['#term'])) {
526 $term = $form[$tid]['#term'];
527 // Give terms at the root level a weight in sequence with terms on previous pages.
528 if ($values['parent'] == 0 && $term['weight'] != $weight) {
529 $term['weight'] = $weight;
530 $changed_terms[$term['tid']] = $term;
531 }
532 // Terms not at the root level can safely start from 0 because they're all on this page.
533 elseif ($values['parent'] > 0) {
534 $level_weights[$values['parent']] = isset($level_weights[$values['parent']]) ? $level_weights[$values['parent']] + 1 : 0;
535 if ($level_weights[$values['parent']] != $term['weight']) {
536 $term['weight'] = $level_weights[$values['parent']];
537 $changed_terms[$term['tid']] = $term;
538 }
539 }
540 // Update any changed parents.
541 if ($values['parent'] != $term['parent']) {
542 $term['parent'] = $values['parent'];
543 $changed_terms[$term['tid']] = $term;
544 }
545 $hierarchy = $term['parent'] != 0 ? 1 : $hierarchy;
546 $weight++;
547 }
548 }
549
550 // Build a list of all terms that need to be updated on following pages.
551 for ($weight; $weight < count($tree); $weight++) {
552 $term = (array)$tree[$weight];
553 if ($term['parents'][0] == 0 && $term['weight'] != $weight) {
554 $term['parent'] = $term['parents'][0];
555 $term['weight'] = $weight;
556 $changed_terms[$term['tid']] = $term;
557 }
558 $hierarchy = $term['parents'][0] != 0 ? 1 : $hierarchy;
559 }
560
561 // Save all updated terms.
562 foreach ($changed_terms as $changed) {
563 $term = (object)$changed;
564 taxonomy_term_save($term);
565 }
566
567 // Update the vocabulary hierarchy to flat or single hierarchy.
568 if ($vocabulary->hierarchy != $hierarchy) {
569 $vocabulary->hierarchy = $hierarchy;
570 taxonomy_vocabulary_save($vocabulary);
571 }
572 }
573
574 /**
575 * Theme the terms overview as a sortable list of terms.
576 *
577 * @ingroup themeable
578 * @see taxonomy_overview_terms()
579 */
580 function theme_taxonomy_overview_terms($form) {
581 $page_increment = $form['#page_increment'];
582 $page_entries = $form['#page_entries'];
583 $back_peddle = $form['#back_peddle'];
584 $forward_peddle = $form['#forward_peddle'];
585
586 // Add drag and drop if parent fields are present in the form.
587 if ($form['#parent_fields']) {
588 drupal_add_tabledrag('taxonomy', 'match', 'parent', 'term-parent', 'term-parent', 'term-id', FALSE);
589 drupal_add_tabledrag('taxonomy', 'depth', 'group', 'term-depth', NULL, NULL, FALSE);
590 drupal_add_js(drupal_get_path('module', 'taxonomy') . '/taxonomy.js');
591 drupal_add_js(array('taxonomy' => array('backPeddle' => $back_peddle, 'forwardPeddle' => $forward_peddle)), 'setting');
592 drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
593 }
594
595 $errors = form_get_errors() != FALSE ? form_get_errors() : array();
596 $rows = array();
597 foreach (element_children($form) as $key) {
598 if (isset($form[$key]['#term'])) {
599 $term = &$form[$key];
600
601 $row = array();
602 $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', $term['#term']['depth']) : '') . drupal_render($term['view']);
603 if ($form['#parent_fields']) {
604 $term['tid']['#attributes']['class'] = 'term-id';
605 $term['parent']['#attributes']['class'] = 'term-parent';
606 $term['depth']['#attributes']['class'] = 'term-depth';
607 $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
608 }
609 $row[] = drupal_render($term['edit']);
610
611 $row = array('data' => $row);
612 $rows[$key] = $row;
613 }
614 }
615
616 // Add necessary classes to rows.
617 $row_position = 0;
618 foreach ($rows as $key => $row) {
619 $classes = array();
620 if (isset($form['#parent_fields'])) {
621 $classes[] = 'draggable';
622 }
623
624 // Add classes that mark which terms belong to previous and next pages.
625 if ($row_position < $back_peddle || $row_position >= $page_entries - $forward_peddle) {
626 $classes[] = 'taxonomy-term-preview';
627 }
628
629 if ($row_position !== 0 && $row_position !== count($rows) - 1) {
630 if ($row_position == $back_peddle - 1 || $row_position == $page_entries - $forward_peddle - 1) {
631 $classes[] = 'taxonomy-term-divider-top';
632 }
633 elseif ($row_position == $back_peddle || $row_position == $page_entries - $forward_peddle) {
634 $classes[] = 'taxonomy-term-divider-bottom';
635 }
636 }
637
638 // Add an error class if this row contains a form error.
639 foreach ($errors as $error_key => $error) {
640 if (strpos($error_key, $key) === 0) {
641 $classes[] = 'error';
642 }
643 }
644 $rows[$key]['class'] = implode(' ', $classes);
645 $row_position++;
646 }
647
648 if (empty($rows)) {
649 $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '2'));
650 }
651
652 $header = array(t('Name'), t('Operations'));
653 $output = theme('table', $header, $rows, array('id' => 'taxonomy'));
654 $output .= drupal_render_children($form);
655 $output .= theme('pager', NULL);
656
657 return $output;
658 }
659
660 /**
661 * Form function for the term edit form.
662 *
663 * @ingroup forms
664 * @see taxonomy_form_term_submit()
665 */
666 function taxonomy_form_term(&$form_state, $vocabulary, $edit = array()) {
667 $edit += array(
668 'name' => '',
669 'description' => '',
670 'vocabulary_machine_name' => $vocabulary->machine_name,
671 'tid' => NULL,
672 'weight' => 0,
673 );
674
675 // Take into account multi-step rebuilding.
676 if (isset($form_state['term'])) {
677 $edit = $form_state['term'] + $edit;
678 }
679
680 $parent = array_keys(taxonomy_get_parents($edit['tid']));
681 $form['#term'] = $edit;
682 $form['#term']['parent'] = $parent;
683 $form['#vocabulary'] = $vocabulary;
684 $form['#vocabulary']->nodes = drupal_map_assoc($vocabulary->nodes);
685 $form['#builder_function'] = 'taxonomy_form_term_submit_builder';
686
687 // Check for confirmation forms.
688 if (isset($form_state['confirm_delete'])) {
689 return array_merge($form, taxonomy_term_confirm_delete($form_state, $edit['tid']));
690 }
691 elseif (isset($form_state['confirm_parents'])) {
692 return array_merge($form, taxonomy_term_confirm_parents($form_state, $vocabulary));
693 }
694
695 $form['identification'] = array(
696 '#type' => 'fieldset',
697 '#title' => t('Identification'),
698 '#collapsible' => TRUE,
699 );
700 $form['identification']['name'] = array(
701 '#type' => 'textfield',
702 '#title' => t('Term name'),
703 '#default_value' => $edit['name'],
704 '#maxlength' => 255,
705 '#required' => TRUE);
706 $form['identification']['description'] = array(
707 '#type' => 'textarea',
708 '#title' => t('Description'),
709 '#default_value' => $edit['description'],
710 '#description' => t('A description of the term. To be displayed on taxonomy/term pages and RSS feeds.'));
711
712 $form['vocabulary_machine_name'] = array(
713 '#type' => 'textfield',
714 '#access' => FALSE,
715 '#value' => isset($edit['vocabulary_machine_name']) ? $edit['vocabulary_machine_name'] : $vocabulary->name,
716 );
717
718 field_attach_form('taxonomy_term', (object) $edit, $form, $form_state);
719
720 $form['advanced'] = array(
721 '#type' => 'fieldset',
722 '#title' => t('Advanced options'),
723 '#collapsible' => TRUE,
724 '#collapsed' => $vocabulary->hierarchy < 2,
725 );
726
727 // taxonomy_get_tree and taxonomy_get_parents may contain large numbers of
728 // items so we check for taxonomy_override_selector before loading the
729 // full vocabulary. Contrib modules can then intercept before
730 // hook_form_alter to provide scalable alternatives.
731 if (!variable_get('taxonomy_override_selector', FALSE)) {
732 $parent = array_keys(taxonomy_get_parents($edit['tid']));
733 $children = taxonomy_get_tree($vocabulary->vid, $edit['tid']);
734
735 // A term can't be the child of itself, nor of its children.
736 foreach ($children as $child) {
737 $exclude[] = $child->tid;
738 }
739 $exclude[] = $edit['tid'];
740
741 $form['advanced']['parent'] = _taxonomy_term_select(t('Parents'), $parent, $vocabulary->vid, t('Parent terms') . '.', 1, '<' . t('root') . '>', $exclude);
742 }
743 $form['advanced']['synonyms'] = array(
744 '#type' => 'textarea',
745 '#title' => t('Synonyms'),
746 '#default_value' => implode("\n", taxonomy_get_synonyms($edit['tid'])),
747 '#description' => t('One synonym per line. Text input term selection widgets will show terms whose synonyms match the entered value as suggestions.'));
748 $form['advanced']['weight'] = array(
749 '#type' => 'textfield',
750 '#title' => t('Weight'),
751 '#size' => 6,
752 '#default_value' => $edit['weight'],
753 '#description' => t('Terms are displayed in ascending order by weight.'),
754 '#required' => TRUE);
755 $form['vid'] = array(
756 '#type' => 'value',
757 '#value' => $vocabulary->vid);
758 $form['submit'] = array(
759 '#type' => 'submit',
760 '#value' => t('Save'));
761
762 if ($edit['tid']) {
763 $form['delete'] = array(
764 '#type' => 'submit',
765 '#value' => t('Delete'));
766 $form['tid'] = array(
767 '#type' => 'value',
768 '#value' => $edit['tid']);
769 }
770 else {
771 $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
772 }
773
774 return $form;
775 }
776
777 /**
778 * Validation handler for the term form.
779 *
780 * @see taxonomy_form_term()
781 */
782 function taxonomy_form_term_validate($form, &$form_state) {
783 field_attach_form_validate('taxonomy_term', (object) $form_state['values'], $form, $form_state);
784
785 // Ensure numeric values.
786 if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
787 form_set_error('weight', t('Weight value must be numeric.'));
788 }
789 }
790
791 /**
792 * Submit handler to insert or update a term.
793 *
794 * @see taxonomy_form_term()
795 */
796 function taxonomy_form_term_submit($form, &$form_state) {
797 if ($form_state['clicked_button']['#value'] == t('Delete')) {
798 // Execute the term deletion.
799 if ($form_state['values']['delete'] === TRUE) {
800 return taxonomy_term_confirm_delete_submit($form, $form_state);
801 }
802 // Rebuild the form to confirm term deletion.
803 $form_state['rebuild'] = TRUE;
804 $form_state['confirm_delete'] = TRUE;
805 return;
806 }
807 // Rebuild the form to confirm enabling multiple parents.
808 elseif ($form_state['clicked_button']['#value'] == t('Save') && !$form['#vocabulary']->tags && count($form_state['values']['parent']) > 1 && $form['#vocabulary']->hierarchy < 2) {
809 $form_state['rebuild'] = TRUE;
810 $form_state['confirm_parents'] = TRUE;
811 return;
812 }
813
814 $term = taxonomy_form_term_submit_builder($form, $form_state);
815
816 $status = taxonomy_term_save($term);
817 switch ($status) {
818 case SAVED_NEW:
819 drupal_set_message(t('Created new term %term.', array('%term' => $term->name)));
820 watchdog('taxonomy', 'Created new term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
821 break;
822 case SAVED_UPDATED:
823 drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
824 watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
825 break;
826 }
827
828 if (!$form['#vocabulary']->tags) {
829 $current_parent_count = count($form_state['values']['parent']);
830 $previous_parent_count = count($form['#term']['parent']);
831 // Root doesn't count if it's the only parent.
832 if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) {
833 $current_parent_count = 0;
834 $form_state['values']['parent'] = array();
835 }
836
837 // If the number of parents has been reduced to one or none, do a check on the
838 // parents of every term in the vocabulary value.
839 if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
840 taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
841 }
842 // If we've increased the number of parents and this is a single or flat
843 // hierarchy, update the vocabulary immediately.
844 elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']->hierarchy < 2) {
845 $form['#vocabulary']->hierarchy = $current_parent_count == 1 ? 1 : 2;
846 taxonomy_vocabulary_save($form['#vocabulary']);
847 }
848 }
849
850 $form_state['tid'] = $term->tid;
851 $form_state['redirect'] = 'admin/structure/taxonomy';
852 return;
853 }
854
855 /**
856 * Build a term by processing form values and prepare for a form rebuild.
857 */
858 function taxonomy_form_term_submit_builder($form, &$form_state) {
859 $term = (object) $form_state['values'];
860 field_attach_submit('taxonomy_term', $term, $form, $form_state);
861
862 $form_state['term'] = (array)$term;
863 $form_state['rebuild'] = TRUE;
864
865 return $term;
866 }
867
868 /**
869 * Form builder for the confirmation of multiple term parents.
870 *
871 * @ingroup forms
872 * @see taxonomy_form_term()
873 */
874 function taxonomy_term_confirm_parents(&$form_state, $vocabulary) {
875 $form = array();
876 foreach (element_children($form_state['values']) as $key) {
877 $form[$key] = array(
878 '#type' => 'value',
879 '#value' => $form_state['values'][$key],
880 );
881 }
882 $question = t('Set multiple term parents?');
883 $description = '<p>' . t("Adding multiple parents to a term will cause the %vocabulary vocabulary to look for multiple parents on every term. Because multiple parents are not supported when using the drag and drop outline interface, drag and drop will be disabled if you enable this option. If you choose to have multiple parents, you will only be able to set parents by using the term edit form.", array('%vocabulary' => $vocabulary->name)) . '</p>';
884 $description .= '<p>' . t("You may re-enable the drag and drop interface at any time by reducing multiple parents to a single parent for the terms in this vocabulary.") . '</p>';
885 return confirm_form($form, $question, drupal_get_destination(), $description, t('Set multiple parents'));
886 }
887
888 /**
889 * Form builder for the term delete form.
890 *
891 * @ingroup forms
892 * @see taxonomy_term_confirm_delete_submit()
893 */
894 function taxonomy_term_confirm_delete(&$form_state, $tid) {
895 $term = taxonomy_term_load($tid);
896
897 $form['type'] = array('#type' => 'value', '#value' => 'term');
898 $form['name'] = array('#type' => 'value', '#value' => $term->name);
899 $form['tid'] = array('#type' => 'value', '#value' => $tid);
900 $form['vocabulary_machine_name'] = array('#type' => 'value', '#value' => $term->vocabulary_machine_name);
901 $form['delete'] = array('#type' => 'value', '#value' => TRUE);
902 return confirm_form($form,
903 t('Are you sure you want to delete the term %title?',
904 array('%title' => $term->name)),
905 'admin/structure/taxonomy',
906 t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
907 t('Delete'),
908 t('Cancel'));
909 }
910
911 /**
912 * Submit handler to delete a term after confirmation.
913 *
914 * @see taxonomy_term_confirm_delete()
915 */
916 function taxonomy_term_confirm_delete_submit($form, &$form_state) {
917 taxonomy_term_delete($form_state['values']['tid']);
918 taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
919 drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
920 watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
921 $form_state['redirect'] = 'admin/structure/taxonomy';
922 return;
923 }
924
925 /**
926 * Form builder for the vocabulary delete confirmation form.
927 *
928 * @ingroup forms
929 * @see taxonomy_vocabulary_confirm_delete_submit()
930 */
931 function taxonomy_vocabulary_confirm_delete(&$form_state, $vid) {
932 $vocabulary = taxonomy_vocabulary_load($vid);
933
934 $form['#id'] = 'taxonomy_vocabulary_confirm_delete';
935 $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
936 $form['vid'] = array('#type' => 'value', '#value' => $vid);
937 $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
938 $form['#submit'] = array('taxonomy_vocabulary_confirm_delete_submit');
939 return confirm_form($form,
940 t('Are you sure you want to delete the vocabulary %title?',
941 array('%title' => $vocabulary->name)),
942 'admin/structure/taxonomy',
943 t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
944 t('Delete'),
945 t('Cancel'));
946 }
947
948 /**
949 * Submit handler to delete a vocabulary after confirmation.
950 *
951 * @see taxonomy_vocabulary_confirm_delete()
952 */
953 function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
954 $status = taxonomy_vocabulary_delete($form_state['values']['vid']);
955 drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
956 watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
957 $form_state['redirect'] = 'admin/structure/taxonomy';
958 return;
959 }
960
961 /**
962 * Form builder to confirm resetting a vocabulary to alphabetical order.
963 *
964 * @ingroup forms
965 * @see taxonomy_vocabulary_confirm_reset_alphabetical_submit()
966 */
967 function taxonomy_vocabulary_confirm_reset_alphabetical(&$form_state, $vid) {
968 $vocabulary = taxonomy_vocabulary_load($vid);
969
970 $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
971 $form['vid'] = array('#type' => 'value', '#value' => $vid);
972 $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
973 $form['reset_alphabetical'] = array('#type' => 'value', '#value' => TRUE);
974 return confirm_form($form,
975 t('Are you sure you want to reset the vocabulary %title to alphabetical order?',
976 array('%title' => $vocabulary->name)),
977 'admin/structure/taxonomy/' . $vid,
978 t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.'),
979 t('Reset to alphabetical'),
980 t('Cancel'));
981 }
982
983 /**
984 * Submit handler to reset a vocabulary to alphabetical order after confirmation.
985 *
986 * @see taxonomy_vocabulary_confirm_reset_alphabetical()
987 */
988 function taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, &$form_state) {
989 db_update('taxonomy_term_data')
990 ->fields(array('weight' => 0))
991 ->condition('vid', $form_state['values']['vid'])
992 ->execute();
993 drupal_set_message(t('Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name'])));
994 watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
995 $form_state['redirect'] = 'admin/structure/taxonomy/' . $form_state['values']['vid'];
996 }
997

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.