Mercurial > hg > isophonics-drupal-site
comparison core/modules/taxonomy/src/Form/OverviewTerms.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\taxonomy\Form; | |
4 | |
5 use Drupal\Core\Entity\EntityManagerInterface; | |
6 use Drupal\Core\Form\FormBase; | |
7 use Drupal\Core\Extension\ModuleHandlerInterface; | |
8 use Drupal\Core\Form\FormStateInterface; | |
9 use Drupal\taxonomy\VocabularyInterface; | |
10 use Symfony\Component\DependencyInjection\ContainerInterface; | |
11 | |
12 /** | |
13 * Provides terms overview form for a taxonomy vocabulary. | |
14 */ | |
15 class OverviewTerms extends FormBase { | |
16 | |
17 /** | |
18 * The module handler service. | |
19 * | |
20 * @var \Drupal\Core\Extension\ModuleHandlerInterface | |
21 */ | |
22 protected $moduleHandler; | |
23 | |
24 /** | |
25 * The entity manager. | |
26 * | |
27 * @var \Drupal\Core\Entity\EntityManagerInterface | |
28 */ | |
29 protected $entityManager; | |
30 | |
31 /** | |
32 * The term storage handler. | |
33 * | |
34 * @var \Drupal\taxonomy\TermStorageInterface | |
35 */ | |
36 protected $storageController; | |
37 | |
38 /** | |
39 * Constructs an OverviewTerms object. | |
40 * | |
41 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
42 * The module handler service. | |
43 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager | |
44 * The entity manager service. | |
45 */ | |
46 public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager) { | |
47 $this->moduleHandler = $module_handler; | |
48 $this->entityManager = $entity_manager; | |
49 $this->storageController = $entity_manager->getStorage('taxonomy_term'); | |
50 } | |
51 | |
52 /** | |
53 * {@inheritdoc} | |
54 */ | |
55 public static function create(ContainerInterface $container) { | |
56 return new static( | |
57 $container->get('module_handler'), | |
58 $container->get('entity.manager') | |
59 ); | |
60 } | |
61 | |
62 /** | |
63 * {@inheritdoc} | |
64 */ | |
65 public function getFormId() { | |
66 return 'taxonomy_overview_terms'; | |
67 } | |
68 | |
69 /** | |
70 * Form constructor. | |
71 * | |
72 * Display a tree of all the terms in a vocabulary, with options to edit | |
73 * each one. The form is made drag and drop by the theme function. | |
74 * | |
75 * @param array $form | |
76 * An associative array containing the structure of the form. | |
77 * @param \Drupal\Core\Form\FormStateInterface $form_state | |
78 * The current state of the form. | |
79 * @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary | |
80 * The vocabulary to display the overview form for. | |
81 * | |
82 * @return array | |
83 * The form structure. | |
84 */ | |
85 public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL) { | |
86 // @todo Remove global variables when https://www.drupal.org/node/2044435 is | |
87 // in. | |
88 global $pager_page_array, $pager_total, $pager_total_items; | |
89 | |
90 $form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary); | |
91 $parent_fields = FALSE; | |
92 | |
93 $page = $this->getRequest()->query->get('page') ?: 0; | |
94 // Number of terms per page. | |
95 $page_increment = $this->config('taxonomy.settings')->get('terms_per_page_admin'); | |
96 // Elements shown on this page. | |
97 $page_entries = 0; | |
98 // Elements at the root level before this page. | |
99 $before_entries = 0; | |
100 // Elements at the root level after this page. | |
101 $after_entries = 0; | |
102 // Elements at the root level on this page. | |
103 $root_entries = 0; | |
104 | |
105 // Terms from previous and next pages are shown if the term tree would have | |
106 // been cut in the middle. Keep track of how many extra terms we show on | |
107 // each page of terms. | |
108 $back_step = NULL; | |
109 $forward_step = 0; | |
110 | |
111 // An array of the terms to be displayed on this page. | |
112 $current_page = []; | |
113 | |
114 $delta = 0; | |
115 $term_deltas = []; | |
116 $tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, TRUE); | |
117 $tree_index = 0; | |
118 do { | |
119 // In case this tree is completely empty. | |
120 if (empty($tree[$tree_index])) { | |
121 break; | |
122 } | |
123 $delta++; | |
124 // Count entries before the current page. | |
125 if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) { | |
126 $before_entries++; | |
127 continue; | |
128 } | |
129 // Count entries after the current page. | |
130 elseif ($page_entries > $page_increment && isset($complete_tree)) { | |
131 $after_entries++; | |
132 continue; | |
133 } | |
134 | |
135 // Do not let a term start the page that is not at the root. | |
136 $term = $tree[$tree_index]; | |
137 if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) { | |
138 $back_step = 0; | |
139 while ($pterm = $tree[--$tree_index]) { | |
140 $before_entries--; | |
141 $back_step++; | |
142 if ($pterm->depth == 0) { | |
143 $tree_index--; | |
144 // Jump back to the start of the root level parent. | |
145 continue 2; | |
146 } | |
147 } | |
148 } | |
149 $back_step = isset($back_step) ? $back_step : 0; | |
150 | |
151 // Continue rendering the tree until we reach the a new root item. | |
152 if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) { | |
153 $complete_tree = TRUE; | |
154 // This new item at the root level is the first item on the next page. | |
155 $after_entries++; | |
156 continue; | |
157 } | |
158 if ($page_entries >= $page_increment + $back_step) { | |
159 $forward_step++; | |
160 } | |
161 | |
162 // Finally, if we've gotten down this far, we're rendering a term on this | |
163 // page. | |
164 $page_entries++; | |
165 $term_deltas[$term->id()] = isset($term_deltas[$term->id()]) ? $term_deltas[$term->id()] + 1 : 0; | |
166 $key = 'tid:' . $term->id() . ':' . $term_deltas[$term->id()]; | |
167 | |
168 // Keep track of the first term displayed on this page. | |
169 if ($page_entries == 1) { | |
170 $form['#first_tid'] = $term->id(); | |
171 } | |
172 // Keep a variable to make sure at least 2 root elements are displayed. | |
173 if ($term->parents[0] == 0) { | |
174 $root_entries++; | |
175 } | |
176 $current_page[$key] = $term; | |
177 } while (isset($tree[++$tree_index])); | |
178 | |
179 // Because we didn't use a pager query, set the necessary pager variables. | |
180 $total_entries = $before_entries + $page_entries + $after_entries; | |
181 $pager_total_items[0] = $total_entries; | |
182 $pager_page_array[0] = $page; | |
183 $pager_total[0] = ceil($total_entries / $page_increment); | |
184 | |
185 // If this form was already submitted once, it's probably hit a validation | |
186 // error. Ensure the form is rebuilt in the same order as the user | |
187 // submitted. | |
188 $user_input = $form_state->getUserInput(); | |
189 if (!empty($user_input)) { | |
190 // Get the POST order. | |
191 $order = array_flip(array_keys($user_input['terms'])); | |
192 // Update our form with the new order. | |
193 $current_page = array_merge($order, $current_page); | |
194 foreach ($current_page as $key => $term) { | |
195 // Verify this is a term for the current page and set at the current | |
196 // depth. | |
197 if (is_array($user_input['terms'][$key]) && is_numeric($user_input['terms'][$key]['term']['tid'])) { | |
198 $current_page[$key]->depth = $user_input['terms'][$key]['term']['depth']; | |
199 } | |
200 else { | |
201 unset($current_page[$key]); | |
202 } | |
203 } | |
204 } | |
205 | |
206 $errors = $form_state->getErrors(); | |
207 $destination = $this->getDestinationArray(); | |
208 $row_position = 0; | |
209 // Build the actual form. | |
210 $form['terms'] = [ | |
211 '#type' => 'table', | |
212 '#header' => [$this->t('Name'), $this->t('Weight'), $this->t('Operations')], | |
213 '#empty' => $this->t('No terms available. <a href=":link">Add term</a>.', [':link' => $this->url('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $taxonomy_vocabulary->id()])]), | |
214 '#attributes' => [ | |
215 'id' => 'taxonomy', | |
216 ], | |
217 ]; | |
218 foreach ($current_page as $key => $term) { | |
219 /** @var $term \Drupal\Core\Entity\EntityInterface */ | |
220 $term = $this->entityManager->getTranslationFromContext($term); | |
221 $form['terms'][$key]['#term'] = $term; | |
222 $indentation = []; | |
223 if (isset($term->depth) && $term->depth > 0) { | |
224 $indentation = [ | |
225 '#theme' => 'indentation', | |
226 '#size' => $term->depth, | |
227 ]; | |
228 } | |
229 $form['terms'][$key]['term'] = [ | |
230 '#prefix' => !empty($indentation) ? \Drupal::service('renderer')->render($indentation) : '', | |
231 '#type' => 'link', | |
232 '#title' => $term->getName(), | |
233 '#url' => $term->urlInfo(), | |
234 ]; | |
235 if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) { | |
236 $parent_fields = TRUE; | |
237 $form['terms'][$key]['term']['tid'] = [ | |
238 '#type' => 'hidden', | |
239 '#value' => $term->id(), | |
240 '#attributes' => [ | |
241 'class' => ['term-id'], | |
242 ], | |
243 ]; | |
244 $form['terms'][$key]['term']['parent'] = [ | |
245 '#type' => 'hidden', | |
246 // Yes, default_value on a hidden. It needs to be changeable by the | |
247 // javascript. | |
248 '#default_value' => $term->parents[0], | |
249 '#attributes' => [ | |
250 'class' => ['term-parent'], | |
251 ], | |
252 ]; | |
253 $form['terms'][$key]['term']['depth'] = [ | |
254 '#type' => 'hidden', | |
255 // Same as above, the depth is modified by javascript, so it's a | |
256 // default_value. | |
257 '#default_value' => $term->depth, | |
258 '#attributes' => [ | |
259 'class' => ['term-depth'], | |
260 ], | |
261 ]; | |
262 } | |
263 $form['terms'][$key]['weight'] = [ | |
264 '#type' => 'weight', | |
265 '#delta' => $delta, | |
266 '#title' => $this->t('Weight for added term'), | |
267 '#title_display' => 'invisible', | |
268 '#default_value' => $term->getWeight(), | |
269 '#attributes' => [ | |
270 'class' => ['term-weight'], | |
271 ], | |
272 ]; | |
273 $operations = [ | |
274 'edit' => [ | |
275 'title' => $this->t('Edit'), | |
276 'query' => $destination, | |
277 'url' => $term->urlInfo('edit-form'), | |
278 ], | |
279 'delete' => [ | |
280 'title' => $this->t('Delete'), | |
281 'query' => $destination, | |
282 'url' => $term->urlInfo('delete-form'), | |
283 ], | |
284 ]; | |
285 if ($this->moduleHandler->moduleExists('content_translation') && content_translation_translate_access($term)->isAllowed()) { | |
286 $operations['translate'] = [ | |
287 'title' => $this->t('Translate'), | |
288 'query' => $destination, | |
289 'url' => $term->urlInfo('drupal:content-translation-overview'), | |
290 ]; | |
291 } | |
292 $form['terms'][$key]['operations'] = [ | |
293 '#type' => 'operations', | |
294 '#links' => $operations, | |
295 ]; | |
296 | |
297 $form['terms'][$key]['#attributes']['class'] = []; | |
298 if ($parent_fields) { | |
299 $form['terms'][$key]['#attributes']['class'][] = 'draggable'; | |
300 } | |
301 | |
302 // Add classes that mark which terms belong to previous and next pages. | |
303 if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) { | |
304 $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-preview'; | |
305 } | |
306 | |
307 if ($row_position !== 0 && $row_position !== count($tree) - 1) { | |
308 if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) { | |
309 $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-top'; | |
310 } | |
311 elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) { | |
312 $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-bottom'; | |
313 } | |
314 } | |
315 | |
316 // Add an error class if this row contains a form error. | |
317 foreach ($errors as $error_key => $error) { | |
318 if (strpos($error_key, $key) === 0) { | |
319 $form['terms'][$key]['#attributes']['class'][] = 'error'; | |
320 } | |
321 } | |
322 $row_position++; | |
323 } | |
324 | |
325 if ($parent_fields) { | |
326 $form['terms']['#tabledrag'][] = [ | |
327 'action' => 'match', | |
328 'relationship' => 'parent', | |
329 'group' => 'term-parent', | |
330 'subgroup' => 'term-parent', | |
331 'source' => 'term-id', | |
332 'hidden' => FALSE, | |
333 ]; | |
334 $form['terms']['#tabledrag'][] = [ | |
335 'action' => 'depth', | |
336 'relationship' => 'group', | |
337 'group' => 'term-depth', | |
338 'hidden' => FALSE, | |
339 ]; | |
340 $form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy'; | |
341 $form['terms']['#attached']['drupalSettings']['taxonomy'] = [ | |
342 'backStep' => $back_step, | |
343 'forwardStep' => $forward_step, | |
344 ]; | |
345 } | |
346 $form['terms']['#tabledrag'][] = [ | |
347 'action' => 'order', | |
348 'relationship' => 'sibling', | |
349 'group' => 'term-weight', | |
350 ]; | |
351 | |
352 if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) { | |
353 $form['actions'] = ['#type' => 'actions', '#tree' => FALSE]; | |
354 $form['actions']['submit'] = [ | |
355 '#type' => 'submit', | |
356 '#value' => $this->t('Save'), | |
357 '#button_type' => 'primary', | |
358 ]; | |
359 $form['actions']['reset_alphabetical'] = [ | |
360 '#type' => 'submit', | |
361 '#submit' => ['::submitReset'], | |
362 '#value' => $this->t('Reset to alphabetical'), | |
363 ]; | |
364 } | |
365 | |
366 $form['pager_pager'] = ['#type' => 'pager']; | |
367 return $form; | |
368 } | |
369 | |
370 /** | |
371 * Form submission handler. | |
372 * | |
373 * Rather than using a textfield or weight field, this form depends entirely | |
374 * upon the order of form elements on the page to determine new weights. | |
375 * | |
376 * Because there might be hundreds or thousands of taxonomy terms that need to | |
377 * be ordered, terms are weighted from 0 to the number of terms in the | |
378 * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted | |
379 * lowest to highest, but are not necessarily sequential. Numbers may be | |
380 * skipped when a term has children so that reordering is minimal when a child | |
381 * is added or removed from a term. | |
382 * | |
383 * @param array $form | |
384 * An associative array containing the structure of the form. | |
385 * @param \Drupal\Core\Form\FormStateInterface $form_state | |
386 * The current state of the form. | |
387 */ | |
388 public function submitForm(array &$form, FormStateInterface $form_state) { | |
389 // Sort term order based on weight. | |
390 uasort($form_state->getValue('terms'), ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); | |
391 | |
392 $vocabulary = $form_state->get(['taxonomy', 'vocabulary']); | |
393 // Update the current hierarchy type as we go. | |
394 $hierarchy = VocabularyInterface::HIERARCHY_DISABLED; | |
395 | |
396 $changed_terms = []; | |
397 $tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE); | |
398 | |
399 if (empty($tree)) { | |
400 return; | |
401 } | |
402 | |
403 // Build a list of all terms that need to be updated on previous pages. | |
404 $weight = 0; | |
405 $term = $tree[0]; | |
406 while ($term->id() != $form['#first_tid']) { | |
407 if ($term->parents[0] == 0 && $term->getWeight() != $weight) { | |
408 $term->setWeight($weight); | |
409 $changed_terms[$term->id()] = $term; | |
410 } | |
411 $weight++; | |
412 $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy; | |
413 $term = $tree[$weight]; | |
414 } | |
415 | |
416 // Renumber the current page weights and assign any new parents. | |
417 $level_weights = []; | |
418 foreach ($form_state->getValue('terms') as $tid => $values) { | |
419 if (isset($form['terms'][$tid]['#term'])) { | |
420 $term = $form['terms'][$tid]['#term']; | |
421 // Give terms at the root level a weight in sequence with terms on previous pages. | |
422 if ($values['term']['parent'] == 0 && $term->getWeight() != $weight) { | |
423 $term->setWeight($weight); | |
424 $changed_terms[$term->id()] = $term; | |
425 } | |
426 // Terms not at the root level can safely start from 0 because they're all on this page. | |
427 elseif ($values['term']['parent'] > 0) { | |
428 $level_weights[$values['term']['parent']] = isset($level_weights[$values['term']['parent']]) ? $level_weights[$values['term']['parent']] + 1 : 0; | |
429 if ($level_weights[$values['term']['parent']] != $term->getWeight()) { | |
430 $term->setWeight($level_weights[$values['term']['parent']]); | |
431 $changed_terms[$term->id()] = $term; | |
432 } | |
433 } | |
434 // Update any changed parents. | |
435 if ($values['term']['parent'] != $term->parents[0]) { | |
436 $term->parent->target_id = $values['term']['parent']; | |
437 $changed_terms[$term->id()] = $term; | |
438 } | |
439 $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy; | |
440 $weight++; | |
441 } | |
442 } | |
443 | |
444 // Build a list of all terms that need to be updated on following pages. | |
445 for ($weight; $weight < count($tree); $weight++) { | |
446 $term = $tree[$weight]; | |
447 if ($term->parents[0] == 0 && $term->getWeight() != $weight) { | |
448 $term->parent->target_id = $term->parents[0]; | |
449 $term->setWeight($weight); | |
450 $changed_terms[$term->id()] = $term; | |
451 } | |
452 $hierarchy = $term->parents[0] != 0 ? VocabularyInterface::HIERARCHY_SINGLE : $hierarchy; | |
453 } | |
454 | |
455 // Save all updated terms. | |
456 foreach ($changed_terms as $term) { | |
457 $term->save(); | |
458 } | |
459 | |
460 // Update the vocabulary hierarchy to flat or single hierarchy. | |
461 if ($vocabulary->getHierarchy() != $hierarchy) { | |
462 $vocabulary->setHierarchy($hierarchy); | |
463 $vocabulary->save(); | |
464 } | |
465 drupal_set_message($this->t('The configuration options have been saved.')); | |
466 } | |
467 | |
468 /** | |
469 * Redirects to confirmation form for the reset action. | |
470 */ | |
471 public function submitReset(array &$form, FormStateInterface $form_state) { | |
472 /** @var $vocabulary \Drupal\taxonomy\VocabularyInterface */ | |
473 $vocabulary = $form_state->get(['taxonomy', 'vocabulary']); | |
474 $form_state->setRedirectUrl($vocabulary->urlInfo('reset-form')); | |
475 } | |
476 | |
477 } |