Skip to content
Snippets Groups Projects
HmTaxonomyController.php 11 KiB
Newer Older
Mingsong Hu's avatar
Mingsong Hu committed
<?php

namespace Drupal\hierarchy_manager\Controller;

Mingsong Hu's avatar
Mingsong Hu committed
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityRepositoryInterface;
Mingsong Hu's avatar
Mingsong Hu committed
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
Mingsong Hu's avatar
Mingsong Hu committed
use Drupal\taxonomy\Entity\Term;
use Symfony\Component\DependencyInjection\ContainerInterface;
Mingsong's avatar
Mingsong committed
use Symfony\Component\HttpFoundation\JsonResponse;
Mingsong Hu's avatar
Mingsong Hu committed
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
Mingsong's avatar
Mingsong committed
 * Taxonomy feeding controller class.
Mingsong Hu's avatar
Mingsong Hu committed
 */
class HmTaxonomyController extends ControllerBase {

  /**
   * CSRF Token.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  protected $csrfToken;

  /**
   * The term storage handler.
   *
   * @var \Drupal\taxonomy\TermStorageInterface
   */
  protected $storageController;
Mingsong Hu's avatar
Mingsong Hu committed
  /**
   * The hierarchy manager plugin type manager.
Mingsong Hu's avatar
Mingsong Hu committed
   *
   * @var \Drupal\hierarchy_manager\PluginTypeManager
Mingsong Hu's avatar
Mingsong Hu committed
   */
  protected $hmPluginTypeManager;
Mingsong Hu's avatar
Mingsong Hu committed

  /**
   * The entity repository.
   *
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
   */
  protected $entityRepository;

Mingsong Hu's avatar
Mingsong Hu committed
  /**
   * {@inheritdoc}
   */
  public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, $plugin_type_manager) {
Mingsong Hu's avatar
Mingsong Hu committed

    $this->csrfToken = $csrfToken;
    $this->entityTypeManager = $entity_type_manager;
    $this->entityRepository = $entity_repository;
Mingsong Hu's avatar
Mingsong Hu committed
    $this->storageController = $entity_type_manager->getStorage('taxonomy_term');
    $this->hmPluginTypeManager = $plugin_type_manager;
Mingsong Hu's avatar
Mingsong Hu committed
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
        $container->get('csrf_token'),
        $container->get('entity_type.manager'),
        $container->get('entity.repository'),
        $container->get('hm.plugin_type_manager')
Mingsong Hu's avatar
Mingsong Hu committed
        );
  }

  /**
   * Access check callback for taxonomy tree json.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
Mingsong's avatar
Mingsong committed
   *   User account.
Mingsong's avatar
Mingsong committed
   *   Vocabulary ID.
   */
  public function access(AccountInterface $account, string $vid) {
    if ($account->hasPermission('administer taxonomy')) {
      return AccessResult::allowed();
    }
    return AccessResult::allowedIfHasPermission($account, "edit terms in {$vid}");
  }

Mingsong Hu's avatar
Mingsong Hu committed
  /**
   * Callback for taxonomy tree json.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   Http request object.
   * @param string $vid
   *   Vocabulary ID.
   */
  public function taxonomyTreeJson(Request $request, string $vid) {
    // Access token.
    $token = $request->get('token');
    // The term array will be returned.
    $term_array = [];
Mingsong Hu's avatar
Mingsong Hu committed
    // Store the number of each term id present.
    $ids = [];
Mingsong's avatar
Mingsong committed
    // Store terms that have ambiguous parents.
Mingsong Hu's avatar
Mingsong Hu committed
    $am_terms = [];
    // Store all terms only have single ancestor.
    $single_parent = [];
Mingsong Hu's avatar
Mingsong Hu committed

    if (empty($token) || !$this->csrfToken->validate($token, $vid)) {
      return new Response($this->t('Access denied!'));
    }
    $parent = $request->get('parent') ?: 0;
    $depth = $request->get('depth');
    $destination = $request->get('destination');
Mingsong's avatar
Mingsong committed

    if (!empty($depth)) {
      $depth = intval($depth);
    }
Mingsong Hu's avatar
Mingsong Hu committed

    $tree = $this->storageController->loadTree($vid, $parent, $depth, TRUE);
    $access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');

    foreach ($tree as $term) {
      if ($term instanceof Term) {
        $term = $this->entityRepository->getTranslationFromContext($term);
        // User can only access the terms that they can update.
        if ($access_control_handler->access($term, 'update')) {
          if (empty($destination)) {
            $url = $term->toUrl('edit-form')->toString();
          }
          else {
            $url = $term->toUrl('edit-form', ['query' => ['destination' => $destination]])->toString();
          }
Mingsong Hu's avatar
Mingsong Hu committed
          $term_parent = $term->parents;
          $id = $term->id();
          $count_parent = count($term_parent);
          if (isset($ids[$id])) {
            if ($ids[$id] === 0 && isset($single_parent[$id])) {
              // Update previous term in the term array
              // which has the same ID. Make it not draggable.
Mingsong's avatar
Mingsong committed
              $term_array[$single_parent[$id]]['draggable'] = FALSE;
Mingsong Hu's avatar
Mingsong Hu committed
            }
            $ids[$id]++;
Mingsong's avatar
Mingsong committed
            $term_id = $id . '_' . $ids[$id];
Mingsong Hu's avatar
Mingsong Hu committed
          }
          else {
            $ids[$id] = 0;
            $term_id = $id;
          }
          // If a taxonomy term has multiple parents,
          // It will present multiple times under different parents.
          // So the term id will be duplicated.
          // The solution is to format the term id as following,
Mingsong's avatar
Mingsong committed
          // {term_id}_{parent_index}.
          if ($count_parent > 1) {
            $draggable = FALSE;
Mingsong Hu's avatar
Mingsong Hu committed
            // This term has an ancestor with multiple parents.
            if ($ids[$id] === $count_parent) {
              // Put into the ambiguous array.
              // Will solve it later.
              $am_terms[] = [
Mingsong's avatar
Mingsong committed
                'solved' => FALSE,
Mingsong Hu's avatar
Mingsong Hu committed
                'id' => $term_id,
                'label' => $term->label(),
                'parent' => $term_parent,
                'url' => $url,
                'publish' => $term->isPublished(),
                'weight' => $term->getWeight(),
Mingsong's avatar
Mingsong committed
                'draggable' => FALSE,
Mingsong Hu's avatar
Mingsong Hu committed
              ];
              continue;
            }
            $parent_id = $term_parent[$ids[$id]];
Mingsong Hu's avatar
Mingsong Hu committed
          }
Mingsong Hu's avatar
Mingsong Hu committed
            if ($ids[$id]) {
              // The parent has multiple grandparent.
              $parent_id = $term_parent[0] . '_' . $ids[$id];
Mingsong's avatar
Mingsong committed
              $draggable = FALSE;
Mingsong Hu's avatar
Mingsong Hu committed
            }
            else {
              // The parent doesn't have multiple grandparent.
              $parent_id = $term_parent[0];
Mingsong's avatar
Mingsong committed
              $draggable = TRUE;
Mingsong Hu's avatar
Mingsong Hu committed
              // At this point, we still don't know
              // if this term has multiple ancestors or not.
              // So keep the index of term array for later update
              // if needed.
              $single_parent[$id] = count($term_array);
            }
          }

          $term_array[] = $this->hmPluginTypeManager->buildHierarchyItem(
            $term_id,
            $term->label(),
Mingsong Hu's avatar
Mingsong Hu committed
            $parent_id,
Mingsong Hu's avatar
Mingsong Hu committed
            $term->isPublished(),
            $term->getWeight(),
            $draggable
Mingsong Hu's avatar
Mingsong Hu committed
        }
      }
    }
Mingsong Hu's avatar
Mingsong Hu committed
    // Figure out the parent id for terms in the ambiguous term array.
    do {
Mingsong's avatar
Mingsong committed
      $found = FALSE;
Mingsong Hu's avatar
Mingsong Hu committed
      foreach ($am_terms as $key => $term) {
        if ($term['solved']) {
          continue;
        }
        $parent_ids = $term['parent'];
        foreach ($parent_ids as $id) {
          if ($ids[$id]) {
            // Found the parent with multiple grandparent.
            $term_array[] = $this->hmPluginTypeManager->buildHierarchyItem(
                $term['id'],
                $term['label'],
                $id . '_' . $ids[$id],
                $term['url'],
                $term['publish'],
                $term['weight'],
                $term['draggable']
            );
Mingsong's avatar
Mingsong committed
            $found = TRUE;
Mingsong Hu's avatar
Mingsong Hu committed
            // Remove this parent from ids array.
            $ids[$id]--;
Mingsong's avatar
Mingsong committed
            $am_terms[$key]['solved'] = TRUE;
Mingsong Hu's avatar
Mingsong Hu committed
            continue 2;
          }
        }
      }
Mingsong's avatar
Mingsong committed
    } while ($found);
    // Display profile.
    $display_profile = $this->hmPluginTypeManager->getDisplayProfile('hm_setup_taxonomy');
Mingsong Hu's avatar
Mingsong Hu committed
    // Display plugin instance.
    $display_plugin = $this->hmPluginTypeManager->getDisplayPluginInstance($display_profile);
    if (empty($display_plugin)) {
      return new JsonResponse(['result' => 'Display profile has not been set up.']);
    }
Mingsong Hu's avatar
Mingsong Hu committed

    if (method_exists($display_plugin, 'treeData')) {
      // Convert the tree data to the structure
      // that display plugin accepts.
      $tree_data = $display_plugin->treeData($term_array);
    }
    else {
      $tree_data = $term_array;
    }

    return new JsonResponse($tree_data);
  }

  /**
   * Callback for taxonomy tree json.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   Http request object.
   * @param string $vid
   *   Vocabulary ID.
   */
  public function updateTerms(Request $request, string $vid) {
    // Access token.
    $token = $request->get('token');
    if (empty($token) || !$this->csrfToken->validate($token, $vid)) {
      return new Response($this->t('Access denied!'));
    }

    $target_position = $request->get('target');
    $old_position = (int) $request->get('old_position');
    $old_parent_id = $request->get('old_parent');
    // Remove the parent index from the parent id.
    $old_parent_id = explode('_', $old_parent_id)[0];
    $parent_id = $request->get('parent');
    // Remove the parent index from the parent id.
    $parent_id = explode('_', $parent_id)[0];
Mingsong Hu's avatar
Mingsong Hu committed
    $updated_terms = $request->get('keys');
    $success = FALSE;
    $all_siblings = [];
Mingsong Hu's avatar
Mingsong Hu committed

    if (is_array($updated_terms) && !empty($updated_terms)) {
      // Remove the parent index from the term id.
      for ($i = 0; $i < count($updated_terms); $i++) {
        $updated_terms[$i] = explode('_', $updated_terms[$i])[0];
      }
Mingsong Hu's avatar
Mingsong Hu committed
      // Taxonomy access control.
      $access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');

      // Children of the parent term in weight and name alphabetically order.
      $children = $this->storageController->loadTree($vid, $parent_id, 1);
Mingsong Hu's avatar
Mingsong Hu committed
      if (!empty($children)) {
Mingsong Hu's avatar
Mingsong Hu committed
        // The parent term has children.
Mingsong Hu's avatar
Mingsong Hu committed
        $target_position = intval($target_position);
        foreach ($children as $child) {
          $all_siblings[$child->tid] = (int) $child->weight;
        }
Mingsong Hu's avatar
Mingsong Hu committed
      }
      $new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_terms, $old_position);
      $tids = array_keys($new_hierarchy);
      // Load all terms needed to update.
      $terms = Term::loadMultiple($tids);
      // Update all terms.
Mingsong Hu's avatar
Mingsong Hu committed
      foreach ($terms as $term) {
        if ($access_control_handler->access($term, 'update')) {
          $term->setWeight($new_hierarchy[$term->id()]);
          // Update the parent IDs.
          if (in_array($term->id(), $updated_terms)) {
            $parents = [];
            $same_parent = $old_parent_id === $parent_id;
            // Update the parent only if it is changed.
            if (!$same_parent) {
              foreach ($term->get('parent') as $parent) {
                $tid = $parent->get('target_id')->getValue();
                if ($tid === $old_parent_id) {
                  $tid = $parent_id;
                }
                elseif ($tid === $parent_id) {
                  continue;
                }
                $parents[] = ['target_id' => $tid];
              }
              // Set the new parent.
              $term->set('parent', $parents);
            }
          }
Mingsong Hu's avatar
Mingsong Hu committed
          $success = $term->save();
        }
      }
    }

    $result = [
      'result' => $success ? 'success' : 'fail',
      'updated_nodes' => $new_hierarchy,
    ];
Mingsong Hu's avatar
Mingsong Hu committed