Projet

Général

Profil

Paste
Télécharger (35,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / taxonomy / taxonomy.admin.inc @ 134c7813

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