Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 298 |
| OverviewTerms | |
0.00% |
0 / 1 |
|
0.00% |
0 / 6 |
5402 | |
0.00% |
0 / 298 |
| __construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| create | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 5 |
|||
| getFormId | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| buildForm | |
0.00% |
0 / 1 |
2352 | |
0.00% |
0 / 225 |
|||
| submitForm | |
0.00% |
0 / 1 |
462 | |
0.00% |
0 / 60 |
|||
| submitReset | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\taxonomy\Form\OverviewTerms. | |
| */ | |
| namespace Drupal\taxonomy\Form; | |
| use Drupal\Core\Entity\EntityManagerInterface; | |
| use Drupal\Core\Form\FormBase; | |
| use Drupal\Core\Extension\ModuleHandlerInterface; | |
| use Drupal\Core\Form\FormStateInterface; | |
| use Drupal\taxonomy\VocabularyInterface; | |
| use Symfony\Component\DependencyInjection\ContainerInterface; | |
| /* | |
| * Provides terms overview form for a taxonomy vocabulary. | |
| */ | |
| class OverviewTerms extends FormBase { | |
| /** | |
| * The module handler service. | |
| * | |
| * @var \Drupal\Core\Extension\ModuleHandlerInterface | |
| */ | |
| protected $moduleHandler; | |
| /** | |
| * The term storage controller. | |
| * | |
| * @var \Drupal\taxonomy\TermStorageInterface | |
| */ | |
| protected $storageController; | |
| /** | |
| * Constructs an OverviewTerms object. | |
| * | |
| * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
| * The module handler service. | |
| * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager | |
| * The entity manager service. | |
| */ | |
| public function __construct(ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager) { | |
| $this->moduleHandler = $module_handler; | |
| $this->storageController = $entity_manager->getStorage('taxonomy_term'); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public static function create(ContainerInterface $container) { | |
| return new static( | |
| $container->get('module_handler'), | |
| $container->get('entity.manager') | |
| ); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getFormId() { | |
| return 'taxonomy_overview_terms'; | |
| } | |
| /** | |
| * Form constructor. | |
| * | |
| * Display a tree of all the terms in a vocabulary, with options to edit | |
| * each one. The form is made drag and drop by the theme function. | |
| * | |
| * @param array $form | |
| * An associative array containing the structure of the form. | |
| * @param \Drupal\Core\Form\FormStateInterface $form_state | |
| * The current state of the form. | |
| * @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary | |
| * The vocabulary to display the overview form for. | |
| * | |
| * @return array | |
| * The form structure. | |
| */ | |
| public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL) { | |
| // @todo Remove global variables when https://www.drupal.org/node/2044435 is | |
| // in. | |
| global $pager_page_array, $pager_total, $pager_total_items; | |
| $form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary); | |
| $parent_fields = FALSE; | |
| $page = $this->getRequest()->query->get('page') ?: 0; | |
| // Number of terms per page. | |
| $page_increment = $this->config('taxonomy.settings')->get('terms_per_page_admin'); | |
| // Elements shown on this page. | |
| $page_entries = 0; | |
| // Elements at the root level before this page. | |
| $before_entries = 0; | |
| // Elements at the root level after this page. | |
| $after_entries = 0; | |
| // Elements at the root level on this page. | |
| $root_entries = 0; | |
| // Terms from previous and next pages are shown if the term tree would have | |
| // been cut in the middle. Keep track of how many extra terms we show on | |
| // each page of terms. | |
| $back_step = NULL; | |
| $forward_step = 0; | |
| // An array of the terms to be displayed on this page. | |
| $current_page = array(); | |
| $delta = 0; | |
| $term_deltas = array(); | |
| $tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, TRUE); | |
| $tree_index = 0; | |
| do { | |
| // In case this tree is completely empty. | |
| if (empty($tree[$tree_index])) { | |
| break; | |
| } | |
| $delta++; | |
| // Count entries before the current page. | |
| if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) { | |
| $before_entries++; | |
| continue; | |
| } | |
| // Count entries after the current page. | |
| elseif ($page_entries > $page_increment && isset($complete_tree)) { | |
| $after_entries++; | |
| continue; | |
| } | |
| // Do not let a term start the page that is not at the root. | |
| $term = $tree[$tree_index]; | |
| if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) { | |
| $back_step = 0; | |
| while ($pterm = $tree[--$tree_index]) { | |
| $before_entries--; | |
| $back_step++; | |
| if ($pterm->depth == 0) { | |
| $tree_index--; | |
| // Jump back to the start of the root level parent. | |
| continue 2; | |
| } | |
| } | |
| } | |
| $back_step = isset($back_step) ? $back_step : 0; | |
| // Continue rendering the tree until we reach the a new root item. | |
| if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) { | |
| $complete_tree = TRUE; | |
| // This new item at the root level is the first item on the next page. | |
| $after_entries++; | |
| continue; | |
| } | |
| if ($page_entries >= $page_increment + $back_step) { | |
| $forward_step++; | |
| } | |
| // Finally, if we've gotten down this far, we're rendering a term on this | |
| // page. | |
| $page_entries++; | |
| $term_deltas[$term->id()] = isset($term_deltas[$term->id()]) ? $term_deltas[$term->id()] + 1 : 0; | |
| $key = 'tid:' . $term->id() . ':' . $term_deltas[$term->id()]; | |
| // Keep track of the first term displayed on this page. | |
| if ($page_entries == 1) { | |
| $form['#first_tid'] = $term->id(); | |
| } | |
| // Keep a variable to make sure at least 2 root elements are displayed. | |
| if ($term->parents[0] == 0) { | |
| $root_entries++; | |
| } | |
| $current_page[$key] = $term; | |
| } while (isset($tree[++$tree_index])); | |
| // Because we didn't use a pager query, set the necessary pager variables. | |
| $total_entries = $before_entries + $page_entries + $after_entries; | |
| $pager_total_items[0] = $total_entries; | |
| $pager_page_array[0] = $page; | |
| $pager_total[0] = ceil($total_entries / $page_increment); | |
| // If this form was already submitted once, it's probably hit a validation | |
| // error. Ensure the form is rebuilt in the same order as the user | |
| // submitted. | |
| $user_input = $form_state->getUserInput(); | |
| if (!empty($user_input)) { | |
| // Get the POST order. | |
| $order = array_flip(array_keys($user_input['terms'])); | |
| // Update our form with the new order. | |
| $current_page = array_merge($order, $current_page); | |
| foreach ($current_page as $key => $term) { | |
| // Verify this is a term for the current page and set at the current | |
| // depth. | |
| if (is_array($user_input['terms'][$key]) && is_numeric($user_input['terms'][$key]['term']['tid'])) { | |
| $current_page[$key]->depth = $user_input['terms'][$key]['term']['depth']; | |
| } | |
| else { | |
| unset($current_page[$key]); | |
| } | |
| } | |
| } | |
| $errors = $form_state->getErrors(); | |
| $destination = $this->getDestinationArray(); | |
| $row_position = 0; | |
| // Build the actual form. | |
| $form['terms'] = array( | |
| '#type' => 'table', | |
| '#header' => array($this->t('Name'), $this->t('Weight'), $this->t('Operations')), | |
| '#empty' => $this->t('No terms available. <a href=":link">Add term</a>.', array(':link' => $this->url('entity.taxonomy_term.add_form', array('taxonomy_vocabulary' => $taxonomy_vocabulary->id())))), | |
| '#attributes' => array( | |
| 'id' => 'taxonomy', | |
| ), | |
| ); | |
| foreach ($current_page as $key => $term) { | |
| /** @var $term \Drupal\Core\Entity\EntityInterface */ | |
| $form['terms'][$key]['#term'] = $term; | |
| $indentation = array(); | |
| if (isset($term->depth) && $term->depth > 0) { | |
| $indentation = array( | |
| '#theme' => 'indentation', | |
| '#size' => $term->depth, | |
| ); | |
| } | |
| $form['terms'][$key]['term'] = array( | |
| '#prefix' => !empty($indentation) ? drupal_render($indentation) : '', | |
| '#type' => 'link', | |
| '#title' => $term->getName(), | |
| '#url' => $term->urlInfo(), | |
| ); | |
| if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) { | |
| $parent_fields = TRUE; | |
| $form['terms'][$key]['term']['tid'] = array( | |
| '#type' => 'hidden', | |
| '#value' => $term->id(), | |
| '#attributes' => array( | |
| 'class' => array('term-id'), | |
| ), | |
| ); | |
| $form['terms'][$key]['term']['parent'] = array( | |
| '#type' => 'hidden', | |
| // Yes, default_value on a hidden. It needs to be changeable by the | |
| // javascript. | |
| '#default_value' => $term->parents[0], | |
| '#attributes' => array( | |
| 'class' => array('term-parent'), | |
| ), | |
| ); | |
| $form['terms'][$key]['term']['depth'] = array( | |
| '#type' => 'hidden', | |
| // Same as above, the depth is modified by javascript, so it's a | |
| // default_value. | |
| '#default_value' => $term->depth, | |
| '#attributes' => array( | |
| 'class' => array('term-depth'), | |
| ), | |
| ); | |
| } | |
| $form['terms'][$key]['weight'] = array( | |
| '#type' => 'weight', | |
| '#delta' => $delta, | |
| '#title' => $this->t('Weight for added term'), | |
| '#title_display' => 'invisible', | |
| '#default_value' => $term->getWeight(), | |
| '#attributes' => array( | |
| 'class' => array('term-weight'), | |
| ), | |
| ); | |
| $operations = array( | |
| 'edit' => array( | |
| 'title' => $this->t('Edit'), | |
| 'query' => $destination, | |
| 'url' => $term->urlInfo('edit-form'), | |
| ), | |
| 'delete' => array( | |
| 'title' => $this->t('Delete'), | |
| 'query' => $destination, | |
| 'url' => $term->urlInfo('delete-form'), | |
| ), | |
| ); | |
| if ($this->moduleHandler->moduleExists('content_translation') && content_translation_translate_access($term)->isAllowed()) { | |
| $operations['translate'] = array( | |
| 'title' => $this->t('Translate'), | |
| 'query' => $destination, | |
| 'url' => $term->urlInfo('drupal:content-translation-overview'), | |
| ); | |
| } | |
| $form['terms'][$key]['operations'] = array( | |
| '#type' => 'operations', | |
| '#links' => $operations, | |
| ); | |
| $form['terms'][$key]['#attributes']['class'] = array(); | |
| if ($parent_fields) { | |
| $form['terms'][$key]['#attributes']['class'][] = 'draggable'; | |
| } | |
| // Add classes that mark which terms belong to previous and next pages. | |
| if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) { | |
| $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-preview'; | |
| } | |
| if ($row_position !== 0 && $row_position !== count($tree) - 1) { | |
| if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) { | |
| $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-top'; | |
| } | |
| elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) { | |
| $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-bottom'; | |
| } | |
| } | |
| // Add an error class if this row contains a form error. | |
| foreach ($errors as $error_key => $error) { | |
| if (strpos($error_key, $key) === 0) { | |
| $form['terms'][$key]['#attributes']['class'][] = 'error'; | |
| } | |
| } | |
| $row_position++; | |
| } | |
| if ($parent_fields) { | |
| $form['terms']['#tabledrag'][] = array( | |
| 'action' => 'match', | |
| 'relationship' => 'parent', | |
| 'group' => 'term-parent', | |
| 'subgroup' => 'term-parent', | |
| 'source' => 'term-id', | |
| 'hidden' => FALSE, | |
| ); | |
| $form['terms']['#tabledrag'][] = array( | |
| 'action' => 'depth', | |
| 'relationship' => 'group', | |
| 'group' => 'term-depth', | |
| 'hidden' => FALSE, | |
| ); | |
| $form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy'; | |
| $form['terms']['#attached']['drupalSettings']['taxonomy'] = [ | |
| 'backStep' => $back_step, | |
| 'forwardStep' => $forward_step, | |
| ]; | |
| } | |
| $form['terms']['#tabledrag'][] = array( | |
| 'action' => 'order', | |
| 'relationship' => 'sibling', | |
| 'group' => 'term-weight', | |
| ); | |
| if ($taxonomy_vocabulary->getHierarchy() != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) { | |
| $form['actions'] = array('#type' => 'actions', '#tree' => FALSE); | |
| $form['actions']['submit'] = array( | |
| '#type' => 'submit', | |
| '#value' => $this->t('Save'), | |
| '#button_type' => 'primary', | |
| ); | |
| $form['actions']['reset_alphabetical'] = array( | |
| '#type' => 'submit', | |
| '#submit' => array('::submitReset'), | |
| '#value' => $this->t('Reset to alphabetical'), | |
| ); | |
| } | |
| $form['pager_pager'] = ['#type' => 'pager']; | |
| return $form; | |
| } | |
| /** | |
| * Form submission handler. | |
| * | |
| * Rather than using a textfield or weight field, this form depends entirely | |
| * upon the order of form elements on the page to determine new weights. | |
| * | |
| * Because there might be hundreds or thousands of taxonomy terms that need to | |
| * be ordered, terms are weighted from 0 to the number of terms in the | |
| * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted | |
| * lowest to highest, but are not necessarily sequential. Numbers may be | |
| * skipped when a term has children so that reordering is minimal when a child | |
| * is added or removed from a term. | |
| * | |
| * @param array $form | |
| * An associative array containing the structure of the form. | |
| * @param \Drupal\Core\Form\FormStateInterface $form_state | |
| * The current state of the form. | |
| */ | |
| public function submitForm(array &$form, FormStateInterface $form_state) { | |
| // Sort term order based on weight. | |
| uasort($form_state->getValue('terms'), array('Drupal\Component\Utility\SortArray', 'sortByWeightElement')); | |
| $vocabulary = $form_state->get(['taxonomy', 'vocabulary']); | |
| // Update the current hierarchy type as we go. | |
| $hierarchy = TAXONOMY_HIERARCHY_DISABLED; | |
| $changed_terms = array(); | |
| $tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE); | |
| if (empty($tree)) { | |
| return; | |
| } | |
| // Build a list of all terms that need to be updated on previous pages. | |
| $weight = 0; | |
| $term = $tree[0]; | |
| while ($term->id() != $form['#first_tid']) { | |
| if ($term->parents[0] == 0 && $term->getWeight() != $weight) { | |
| $term->setWeight($weight); | |
| $changed_terms[$term->id()] = $term; | |
| } | |
| $weight++; | |
| $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy; | |
| $term = $tree[$weight]; | |
| } | |
| // Renumber the current page weights and assign any new parents. | |
| $level_weights = array(); | |
| foreach ($form_state->getValue('terms') as $tid => $values) { | |
| if (isset($form['terms'][$tid]['#term'])) { | |
| $term = $form['terms'][$tid]['#term']; | |
| // Give terms at the root level a weight in sequence with terms on previous pages. | |
| if ($values['term']['parent'] == 0 && $term->getWeight() != $weight) { | |
| $term->setWeight($weight); | |
| $changed_terms[$term->id()] = $term; | |
| } | |
| // Terms not at the root level can safely start from 0 because they're all on this page. | |
| elseif ($values['term']['parent'] > 0) { | |
| $level_weights[$values['term']['parent']] = isset($level_weights[$values['term']['parent']]) ? $level_weights[$values['term']['parent']] + 1 : 0; | |
| if ($level_weights[$values['term']['parent']] != $term->getWeight()) { | |
| $term->setWeight($level_weights[$values['term']['parent']]); | |
| $changed_terms[$term->id()] = $term; | |
| } | |
| } | |
| // Update any changed parents. | |
| if ($values['term']['parent'] != $term->parents[0]) { | |
| $term->parent->target_id = $values['term']['parent']; | |
| $changed_terms[$term->id()] = $term; | |
| } | |
| $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy; | |
| $weight++; | |
| } | |
| } | |
| // Build a list of all terms that need to be updated on following pages. | |
| for ($weight; $weight < count($tree); $weight++) { | |
| $term = $tree[$weight]; | |
| if ($term->parents[0] == 0 && $term->getWeight() != $weight) { | |
| $term->parent->target_id = $term->parents[0]; | |
| $term->setWeight($weight); | |
| $changed_terms[$term->id()] = $term; | |
| } | |
| $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy; | |
| } | |
| // Save all updated terms. | |
| foreach ($changed_terms as $term) { | |
| $term->save(); | |
| } | |
| // Update the vocabulary hierarchy to flat or single hierarchy. | |
| if ($vocabulary->getHierarchy() != $hierarchy) { | |
| $vocabulary->setHierarchy($hierarchy); | |
| $vocabulary->save(); | |
| } | |
| drupal_set_message($this->t('The configuration options have been saved.')); | |
| } | |
| /** | |
| * Redirects to confirmation form for the reset action. | |
| */ | |
| public function submitReset(array &$form, FormStateInterface $form_state) { | |
| /** @var $vocabulary \Drupal\taxonomy\VocabularyInterface */ | |
| $vocabulary = $form_state->get(['taxonomy', 'vocabulary']); | |
| $form_state->setRedirectUrl($vocabulary->urlInfo('reset-form')); | |
| } | |
| } |