Skip to content
Snippets Groups Projects
Commit d2c9e782 authored by Mingsong Hu's avatar Mingsong Hu
Browse files

Menu hierarchy plugin and jsTree plugin

parent bc335d23
No related merge requests found
...@@ -21,7 +21,7 @@ REQUIREMENTS ...@@ -21,7 +21,7 @@ REQUIREMENTS
This module requires the following library: This module requires the following library:
* Fancytree JS (This module will automatically load this library from romte CDN if it wasn't hosted locally under /libraries/jquery.fancytree/ folder) * jsTree JS (This module will automatically load this library from romte CDN if it wasn't hosted locally under /libraries/jquery.jstree/3.3.8/ folder)
INSTALLATION INSTALLATION
------------ ------------
......
...@@ -29,6 +29,18 @@ function hierarchy_manager_library_info_alter(array &$libraries, $module) { ...@@ -29,6 +29,18 @@ function hierarchy_manager_library_info_alter(array &$libraries, $module) {
} }
} }
/**
* Implement hook_entity_type_alter().
*
* @param array $entity_types
* Entity type information array.
*/
function hierarchy_manager_entity_type_alter(array &$entity_types) {
// Override the menu edit form.
$entity_types['menu']
->setFormClass('edit', 'Drupal\hierarchy_manager\Form\HmMenuForm');
}
/** /**
* Replace local library with CDN. * Replace local library with CDN.
* *
......
...@@ -9,7 +9,7 @@ hierarchy_manager.hm_config_form: ...@@ -9,7 +9,7 @@ hierarchy_manager.hm_config_form:
options: options:
_admin_route: TRUE _admin_route: TRUE
# Taxonomy display plugin. # Taxonomy hierarchy plugin.
hierarchy_manager.taxonomy.tree.json: hierarchy_manager.taxonomy.tree.json:
path: '/admin/hierarchy_manager/taxonomy/json/{vid}' path: '/admin/hierarchy_manager/taxonomy/json/{vid}'
defaults: defaults:
...@@ -29,3 +29,22 @@ hierarchy_manager.taxonomy.tree.update: ...@@ -29,3 +29,22 @@ hierarchy_manager.taxonomy.tree.update:
options: options:
_admin_route: TRUE _admin_route: TRUE
# Menu hierarchy plugin.
hierarchy_manager.menu.tree.json:
path: '/admin/hierarchy_manager/menu/json/{mid}'
defaults:
_title: 'Menu tree'
_controller: '\Drupal\hierarchy_manager\Controller\HmMenuController::menuTreeJson'
requirements:
_permission: 'administer menu'
options:
_admin_route: TRUE
hierarchy_manager.menu.tree.update:
path: '/admin/hierarchy_manager/menu/update/{mid}'
defaults:
_title: 'Menu tree'
_controller: '\Drupal\hierarchy_manager\Controller\HmMenuController::updateMenuLinks'
requirements:
_permission: 'administer menu'
options:
_admin_route: TRUE
services: services:
# Plugins
plugin.manager.hm.hmsetup: plugin.manager.hm.hmsetup:
class: Drupal\hierarchy_manager\Plugin\HmSetupPluginManager class: Drupal\hierarchy_manager\Plugin\HmSetupPluginManager
parent: default_plugin_manager parent: default_plugin_manager
plugin.manager.hm.display_plugin: plugin.manager.hm.display_plugin:
class: Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager class: Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager
parent: default_plugin_manager parent: default_plugin_manager
# Event subscriber
hm.route_subscriber: hm.route_subscriber:
class: Drupal\hierarchy_manager\Routing\HmRouteSubscriber class: Drupal\hierarchy_manager\Routing\HmRouteSubscriber
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
# Custom services
hm.plugin_type_manager:
class: Drupal\hierarchy_manager\PluginTypeManager
arguments: ['@entity_type.manager', '@plugin.manager.hm.display_plugin', '@plugin.manager.hm.hmsetup']
tags:
- { name: hm_plugin_type_manager, priority: 1000 }
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
const updateURL = treeContainer.attr('url-update') const updateURL = treeContainer.attr('url-update')
let reload = true; let reload = true;
let rollback = false; let rollback = false;
let after = 1;
// Ajax callback to refresh the tree. // Ajax callback to refresh the tree.
if (reload) { if (reload) {
// Build the tree. // Build the tree.
...@@ -54,12 +55,26 @@ ...@@ -54,12 +55,26 @@
const thisTree = data.instance; const thisTree = data.instance;
const movedNode = data.node; const movedNode = data.node;
if (!rollback) { if (!rollback) {
let list = thisTree.get_node(data.parent).children;
let before = '';
let after = '';
if (data.position > 0) {
before = list[data.position - 1];
}
if (data.position < list.length - 1) {
after = list[data.position + 1];
}
let parent = data.parent === '#' ? 0 : data.parent;
// Update the data on server side. // Update the data on server side.
$.post(updateURL, { $.post(updateURL, {
keys: [movedNode.id], keys: [movedNode.id],
target: data.position, target: data.position,
parent: data.parent parent: parent,
after: after,
before: before
}) })
.done(function(response) { .done(function(response) {
if (response.result !== "success") { if (response.result !== "success") {
......
<?php
namespace Drupal\hierarchy_manager\Controller;
use Drupal\Core\Url;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class HmMenuController extends ControllerBase {
/**
* CSRF Token.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfToken;
/**
* The menu_link_content storage handler.
*
* @var \Drupal\menu_link_content\MenuLinkContentStorageInterface
*/
protected $storageController;
/**
* The hierarchy manager plugin type manager.
*
* @var \Drupal\hierarchy_manager\PluginTypeManager
*/
protected $hmPluginTypeManager;
/**
* The menu tree service.
*
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
protected $menuTree;
/**
* The menu tree array.
*
* @var array
*/
protected $overviewTree = [];
/**
* The menu link manager.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
*/
protected $menuLinkManager;
/**
* {@inheritdoc}
*/
public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager, MenuLinkTreeInterface $menu_tree, MenuLinkManagerInterface $menu_link_manager) {
$this->csrfToken = $csrfToken;
$this->entityTypeManager = $entity_type_manager;
$this->storageController = $entity_type_manager->getStorage('menu_link_content');
$this->hmPluginTypeManager = $plugin_type_manager;
$this->menuTree = $menu_tree;
$this->menuLinkManager = $menu_link_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('csrf_token'),
$container->get('entity_type.manager'),
$container->get('hm.plugin_type_manager'),
$container->get('menu.link_tree'),
$container->get('plugin.manager.menu.link')
);
}
/**
* Callback for menu tree json.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* Http request object.
* @param string $mid
* Menu ID.
*/
public function menuTreeJson(Request $request, string $mid) {
// Access token.
$token = $request->get('token');
if (empty($token) || !$this->csrfToken->validate($token, $mid)) {
return new Response($this->t('Access denied!'));
}
$parent = $request->get('parent');
$depth = $request->get('depth');
$destination = $request->get('destination');
if (empty($depth)) {
$depth = 0;
}
else {
$depth = intval($depth);
}
if (empty($parent)) {
$parent = '';
}
if (empty($destination)) {
$destination = '';
}
// We indicate that a menu administrator is running the menu access check.
$request->attributes->set('_menu_admin', TRUE);
$tree = $this->loadMenuTree($mid, $parent, $depth, $destination);
// menu access check done.
$request->attributes->set('_menu_admin', FALSE);
if ($tree) {
// Display plugin instance.
$display_plugin = $this->getDisplayPlugin();
if (empty($display_plugin)) {
return new JsonResponse(['result' => 'Display profile has not been set up.']);
}
if (method_exists($display_plugin, 'treeData')) {
// Transform the tree data to the structure
// that display plugin accepts.
$tree_data = $display_plugin->treeData($tree);
}
else {
$tree_data = $tree;
}
return new JsonResponse($tree_data);
}
return new JsonResponse([]);
}
/**
* Callback for taxonomy tree json.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* Http request object.
* @param string $vid
* Vocabulary ID.
*/
public function updateMenuLinks(Request $request, string $mid) {
// Access token.
$token = $request->get('token');
if (empty($token) || !$this->csrfToken->validate($token, $mid)) {
return new Response($this->t('Access denied!'));
}
$target_position = $request->get('target');
$parent = $request->get('parent');
$updated_links = $request->get('keys');
//$after = $request->get('after');
$before = $request->get('before');
if (is_array($updated_links) && !empty($updated_links)) {
if (empty($parent)) {
// Root is the parent.
$parent = '';
$parent_links = $children = $this->loadMenuLinkObjs($mid, $parent, 1);
}
else {
// All children menu links (depth = 1).
$parent_links = $this->loadMenuLinkObjs($mid, $parent, 1);
}
// In order to make room for menu links inserted,
// we need to move all children links forward,
// and work out the weight for links inserted.
if (empty($parent_links)) {
// The parent menu doesn't exist.
return new JsonResponse(['result' => 'fail']);
}
else {
if (empty($children)) {
$parent_link = reset($parent_links);
$children = $parent_link->subtree;
}
if ($children) {
// The parent menu has children.
$target_position = intval($target_position);
$all_siblings = [];
$insert_after = TRUE;
$position = 0;
foreach ($children as $child) {
$link = $child->link;
$link_id = $link->getPLuginId();
// Figure out if the new links are inserted
// after the target position.
if ($position++ == $target_position && $link_id !== $before) {
$insert_after = FALSE;
}
$all_siblings[$link_id] = (int) $link->getWeight();
}
$new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_links, $insert_after);
$weight = $new_hierarchy['start_weight'];
$moving_siblings = $new_hierarchy['moving_siblings'];
// Update all sibling links needed to update.
foreach ($moving_siblings as $link_id => $link_weight) {
$this->menuLinkManager->updateDefinition($link_id, ['weight' => $link_weight]);
}
}
else {
// The parent link doesn't have children.
$weight = 0;
}
// Move all links updated.
foreach ($updated_links as $link_id) {
$this->menuLinkManager->updateDefinition($link_id, ['weight' => $weight++, 'parent' => $parent]);
}
}
return new JsonResponse(['result' => 'success']);
}
return new JsonResponse(['result' => 'fail']);
}
/**
* Get a display plugin instance.
*
* @return NULL|object
*/
protected function getDisplayPlugin() {
return $this->hmPluginTypeManager->getDisplayPluginInstance('hm_setup_menu');
}
/**
* Load menu links into one array.
*
* @param string $mid
* The menu ID.
* @param string $parent
* parent id
* @param int $depth
* The max depth loaded.
* @param string $destination
* The destination of edit link.
*/
protected function loadMenuTree(string $mid, string $parent, int $depth = 0, string $destination = '') {
$tree = $this->loadMenuLinkObjs($mid, $parent, $depth);
// Load all menu links into one array.
$tree = $this->buildMenuLinkArray($tree);
$links = [];
foreach ($tree as $element) {
if (!empty($destination)) {
$element['url'] = $element['url'] . '?destination=' . $destination;
}
$links[] = $this->hmPluginTypeManager->buildHierarchyItem(
$element['id'],
$element['title'],
$element['parent'],
$element['url']);
}
return $links;
}
/**
* Load menu links into one array.
*
* @param string $mid
* The menu ID.
* @param string $parent
* parent id
* @param int $depth
* The max depth loaded.
* @param string $destination
* The destination of edit link.
*/
protected function loadMenuLinkObjs(string $mid, string $parent, int $depth = 0) {
$menu_para = new MenuTreeParameters();
if (!empty($depth)) {
$menu_para->setMaxDepth($depth);
}
if (!empty($parent)) {
$menu_para->setRoot($parent);
}
$tree = $this->menuTree->load($mid, $menu_para);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
];
return $tree = $this->menuTree->transform($tree, $manipulators);
}
/**
* Recursive helper function for loadMenuTree().
*
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
* The tree retrieved by \Drupal\Core\Menu\MenuLinkTreeInterface::load().
*
* @return array
* The menu links array.
*/
protected function buildMenuLinkArray($tree) {
// $tree_access_cacheability = new CacheableMetadata();
foreach ($tree as $element) {
// $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($element->access));
// Only load accessible links.
if (!$element->access->isAllowed()) {
continue;
}
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
$link = $element->link;
if ($link) {
// The id consistes of plugin ID and link ID.
$id = $link->getPluginId();
$this->overviewTree[$id]['id'] = $id;
if (!$link->isEnabled()) {
$this->overviewTree[$id]['title'] = '(' . $this->t('disabled') . ')' . $link->getTitle();
}
// @todo Remove this in https://www.drupal.org/node/2568785.
elseif ($id === 'user.logout') {
$this->overviewTree[$id]['title'] = ' (' . $this->t('<q>Log in</q> for anonymous users') . ')' . $link->getTitle();
}
// @todo Remove this in https://www.drupal.org/node/2568785.
elseif (($url = $link->getUrlObject()) && $url->isRouted() && $url->getRouteName() == 'user.page') {
$this->overviewTree[$id]['title'] = ' (' . $this->t('logged in users only') . ')' . $link->getTitle();
}
else {
$this->overviewTree[$id]['title'] = $link->getTitle();
}
$this->overviewTree[$id]['parent'] = $link->getParent();
// Build the edit url.
// Allow for a custom edit link per plugin.
$edit_route = $link->getEditRoute();
if ($edit_route) {
$this->overviewTree[$id]['url'] = $edit_route->toString();
}
else {
// Fall back to the standard edit link.
$this->overviewTree[$id]['url'] = Url::fromRoute('menu_ui.link_edit', ['menu_link_plugin' => $link->getPluginId()])->toString();
}
}
if ($element->subtree) {
$this->buildMenuLinkArray($element->subtree);
}
}
/* $tree_access_cacheability
->merge(CacheableMetadata::createFromRenderArray($this->overviewTree))
->applyTo($form); */
return $this->overviewTree;
}
}
...@@ -30,31 +30,23 @@ class HmTaxonomyController extends ControllerBase { ...@@ -30,31 +30,23 @@ class HmTaxonomyController extends ControllerBase {
* @var \Drupal\taxonomy\TermStorageInterface * @var \Drupal\taxonomy\TermStorageInterface
*/ */
protected $storageController; protected $storageController;
/** /**
* Display plugin manager. * The hierarchy manager plugin type manager.
* *
* @var \Drupal\hierarchy_manager\Plugin\HmDisplayPluginInterface * @var \Drupal\hierarchy_manager\PluginTypeManager
*/ */
protected $displayManager; protected $hmPluginTypeManager;
/**
* Setup plugin manager.
*
* @var \Drupal\hierarchy_manager\Plugin\HmSetupPluginManager
*/
protected $setupManager;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $display_manager, $setup_manager) { public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager) {
$this->csrfToken = $csrfToken; $this->csrfToken = $csrfToken;
$this->entityTypeManager = $entity_type_manager; $this->entityTypeManager = $entity_type_manager;
$this->storageController = $entity_type_manager->getStorage('taxonomy_term'); $this->storageController = $entity_type_manager->getStorage('taxonomy_term');
$this->displayManager = $display_manager; $this->hmPluginTypeManager = $plugin_type_manager;
$this->setupManager = $setup_manager;
} }
/** /**
...@@ -64,8 +56,7 @@ class HmTaxonomyController extends ControllerBase { ...@@ -64,8 +56,7 @@ class HmTaxonomyController extends ControllerBase {
return new static( return new static(
$container->get('csrf_token'), $container->get('csrf_token'),
$container->get('entity_type.manager'), $container->get('entity_type.manager'),
$container->get('plugin.manager.hm.display_plugin'), $container->get('hm.plugin_type_manager')
$container->get('plugin.manager.hm.hmsetup')
); );
} }
...@@ -99,31 +90,28 @@ class HmTaxonomyController extends ControllerBase { ...@@ -99,31 +90,28 @@ class HmTaxonomyController extends ControllerBase {
$tree = $this->storageController->loadTree($vid, $parent, $depth, TRUE); $tree = $this->storageController->loadTree($vid, $parent, $depth, TRUE);
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term'); $access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');
foreach ($tree as $term) { foreach ($tree as $term) {
if ($term instanceof Term) { if ($term instanceof Term) {
// User can only access the terms that they can update. // User can only access the terms that they can update.
if ($access_control_handler->access($term, 'update')) { if ($access_control_handler->access($term, 'update')) {
$term_array[] = [ $term_array[] = $this->hmPluginTypeManager->buildHierarchyItem(
'id' => $term->id(), $term->id(),
'text' => $term->label(), $term->label(),
'parent' => $term->parents[0], $term->parents[0],
'edit_url' => $term->toUrl('edit-form')->toString(), $term->toUrl('edit-form')->toString());
];
} }
} }
} }
} }
// Taxonomy setup plugin instance.
$taxonomy_setup_plugin = $this->setupManager->createInstance('hm_setup_taxonomy');
// Display profile.
$display_profile = $this->entityTypeManager->getStorage('hm_display_profile')->load($taxonomy_setup_plugin->getDispalyProfileId());
// Display plugin ID.
$display_plugin_id = $display_profile->get("plugin");
// Display plugin instance. // Display plugin instance.
$display_plugin = $this->displayManager->createInstance($display_plugin_id); $display_plugin = $this->hmPluginTypeManager->getDisplayPluginInstance('hm_setup_taxonomy');
if (empty($display_plugin)) {
return new JsonResponse(['result' => 'Display profile has not been set up.']);
}
if (method_exists($display_plugin, 'treeData')) { if (method_exists($display_plugin, 'treeData')) {
// Convert the tree data to the structure // Convert the tree data to the structure
...@@ -153,8 +141,10 @@ class HmTaxonomyController extends ControllerBase { ...@@ -153,8 +141,10 @@ class HmTaxonomyController extends ControllerBase {
} }
$target_position = $request->get('target'); $target_position = $request->get('target');
$parent_id = intval($request->get('parent')); $parent_id = $request->get('parent');
$updated_terms = $request->get('keys'); $updated_terms = $request->get('keys');
//$after = $request->get('after');
$before = $request->get('before');
$success = FALSE; $success = FALSE;
if (is_array($updated_terms) && !empty($updated_terms)) { if (is_array($updated_terms) && !empty($updated_terms)) {
...@@ -175,36 +165,34 @@ class HmTaxonomyController extends ControllerBase { ...@@ -175,36 +165,34 @@ class HmTaxonomyController extends ControllerBase {
} }
else { else {
// The parent term has children. // The parent term has children.
$target_position = intval($target_position); $target_position = intval($target_position);
$total = count($children); $all_siblings = [];
// Move all terms after the target position forward. $insert_after = TRUE;
if (isset($children[$target_position])) { $position = 0;
$weight = (int) $children[$target_position]->weight;
$tids = []; foreach ($children as $child) {
$step = $weight + count($updated_terms); // Figure out if the new links are inserted
for ($i = $target_position; $i < $total; $i++) { // after the target position.
if ($children[$i]->weight < $step++) { if ($position++ == $target_position && $child->tid !== $before) {
$tids[] = $children[$i]->tid; $insert_after = FALSE;
}
else {
// There is planty room, no need to move anymore.
break;
}
} }
$step = $weight + count($updated_terms);
$all_siblings[$child->tid] = (int) $child->weight;
}
$new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_terms, $insert_after);
$weight = $new_hierarchy['start_weight'];
$moving_siblings = $new_hierarchy['moving_siblings'];
$tids = array_keys($moving_siblings);
if (!empty($tids)) {
// Update siblings.
$term_siblings = Term::loadMultiple($tids); $term_siblings = Term::loadMultiple($tids);
foreach ($term_siblings as $term) { foreach ($term_siblings as $term) {
$term->setWeight($step++); $term->setWeight($moving_siblings[$term->id()]);
$success = $term->save(); $success = $term->save();
} }
} }
elseif ($target_position === $total) {
// Insert into the end.
$weight = intval(array_slice($children, -1)[0]->weight) + 1;
}
else {
return new JsonResponse(['result' => 'The term is not found.']);
}
} }
// Load all terms needed to update. // Load all terms needed to update.
......
<?php
namespace Drupal\hierarchy_manager\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\menu_ui\MenuForm;
class HmMenuForm extends MenuForm {
/**
* The indicator if the menu hierarchy manager is enabled.
*
* @var bool|NULL
*/
private $isEnabled = NULL;
/**
* The hierarchy manager plugin type manager.
*
* @var \Drupal\hierarchy_manager\PluginTypeManager
*/
private $hmPluginTypeManager = NULL;
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// If the menu hierarchy manager plugin is enabled.
// Override the menu overview form.
if ($this->isMenuPluginEnabled() && $this->loadPluginManager()) {
$menu = $this->entity;
// Add menu links administration form for existing menus.
if (!$menu->isNew() || $menu->isLocked()) {
// We are removing the menu link overview form
// and using our own hierarchy manager tree instead.
// The overview form implemented by Drupal Menu UI module
// @see \Drupal\menu_ui\MenuForm::form()
unset($form['links']);
$form['hm_links'] = $this->buildOverviewTree([], $form_state);
}
}
return $form;
}
/**
* Submit handler for the menu overview form.
*
* The hierarchy manager tree is a pure front-end solution in which
* we don't need to deal with the submission data from the back-end.
* Therefore nothing need to do,
* if the menu hierarchy plugin is enabled.
*/
protected function submitOverviewForm(array $complete_form, FormStateInterface $form_state) {
if (!$this->isMenuPluginEnabled()) {
parent::submitOverviewForm($complete_form, $form_state);
}
}
/**
* Build a menu links overview tree element.
*
* @param array $form
* Parent form array.
* @param FormStateInterface $form_state
* Form state object.
* @return NULL|array
*/
protected function buildOverviewTree(array $form, FormStateInterface $form_state) {
global $base_path;
$display_plugin_instance = $this->hmPluginTypeManager->getDisplayPluginInstance('hm_setup_menu');
if (!empty($display_plugin_instance)) {
if (method_exists($display_plugin_instance, 'getForm')) {
// Menu ID.
$mid = $this->entity->id();
// CSRF token.
$token = \Drupal::csrfToken()->get($mid);
// Get current language.
$language = \Drupal::languageManager()->getCurrentLanguage();
// Destination for edit link.
$destination = $this->getDestinationArray();
if (isset($destination['destination'])) {
$destination = $destination['destination'];
}
else {
$destination = '';
}
if ($language->isDefault()) {
$source_url = $base_path . 'admin/hierarchy_manager/menu/json/' . $mid . '?token=' . $token . '&destination=' . $destination;
$update_url = $base_path . 'admin/hierarchy_manager/menu/update/' . $mid . '?token=' . $token;
}
else {
$source_url = $base_path . $language->getId() . '/admin/hierarchy_manager/menu/json/' . $mid . '?token=' . $token . '&destination=' . $destination;
$update_url = $base_path . $language->getId() . '/admin/hierarchy_manager/menu/update/' . $mid . '?token=' . $token;
}
return $display_plugin_instance->getForm($source_url, $update_url, $form, $form_state);
}
}
return [];
}
/**
* Create a hierarchy manager plugin manager.
*
* @return \Drupal\hierarchy_manager\PluginTypeManager
*/
protected function loadPluginManager() {
if (empty($this->hmPluginTypeManager)) {
$this->hmPluginTypeManager = \Drupal::service('hm.plugin_type_manager');
}
return $this->hmPluginTypeManager;
}
/**
* Check if the menu hierarchy plugin is enabled.
*
* @return boolean|NULL
* Return TRUE if the menu plugin is enabled,
* otherwise return FALSE.
*/
protected function isMenuPluginEnabled() {
if ($this->isEnabled === NULL) {
if ($config = \Drupal::config('hierarchy_manager.hmconfig')) {
if ($allowed_setup_plugins = $config->get('allowed_setup_plugins')) {
if (!empty($allowed_setup_plugins['hm_setup_menu'])) {
$this->isEnabled = TRUE;
}
else {
$this->isEnabled = FALSE;
}
}
}
}
return $this->isEnabled;
}
}
...@@ -85,7 +85,7 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte ...@@ -85,7 +85,7 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
foreach ($data as $tree_node) { foreach ($data as $tree_node) {
$jstree_node = $tree_node; $jstree_node = $tree_node;
// The root id for jsTree is #. // The root id for jsTree is #.
if ($tree_node['parent'] === '0') { if (empty($tree_node['parent'])) {
$jstree_node['parent'] = '#'; $jstree_node['parent'] = '#';
} }
// Custom data // Custom data
......
<?php
namespace DRupal\hierarchy_manager\Plugin\HmSetupPlugin;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginInterface;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginBase;
/**
* Menu link hierarchy setup plugin.
*
* @HmSetupPlugin(
* id = "hm_setup_menu",
* label = @Translation("Menu link hierarchy setup plugin")
* )
*/
class HmMenu extends HmSetupPluginBase implements HmSetupPluginInterface {
}
...@@ -32,8 +32,12 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte ...@@ -32,8 +32,12 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte
parent::__construct($configuration, $plugin_id, $plugin_definition); parent::__construct($configuration, $plugin_id, $plugin_definition);
$plugin_settings = \Drupal::config('hierarchy_manager.hmconfig')->get('setup_plugin_settings'); $plugin_settings = \Drupal::config('hierarchy_manager.hmconfig')->get('setup_plugin_settings');
$settings = $plugin_settings[$this->pluginId] ?: []; if (isset($plugin_settings[$this->pluginId])) {
$this->displayProfile = $settings['display_profile']; $this->displayProfile = $plugin_settings[$this->pluginId]['display_profile'];
}
else {
$this->displayProfile = '';
}
} }
/** /**
......
<?php
namespace Drupal\hierarchy_manager;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginManager;
class PluginTypeManager {
/**
* Display plugin manager.
*
* @var \Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager
*/
protected $displayManager;
/**
* Setup plugin manager.
*
* @var \Drupal\hierarchy_manager\Plugin\HmSetupPluginManager
*/
protected $setupManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, HmDisplayPluginManager $display_manager, HmSetupPluginManager $setup_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->displayManager = $display_manager;
$this->setupManager = $setup_manager;
}
/**
* Construct an item inside the hierarchy.
*
* @param string|int $id
* Item id.
* @param string $label
* Item text.
* @param string $parent
* Parent id of the item.
* @param string $edit_url
* The URL where to edit this item.
* @return array
* The hierarchy item array.
*/
public function buildHierarchyItem($id, $label, $parent, $edit_url) {
return
[
'id' => $id,
'text' => $label,
'parent' => $parent,
'edit_url' => $edit_url,
];
}
/**
* Get a display plugin instance according to a setup plugin.
*
* @param string $setup_plugin_id
* setup plugin ID.
* @return NULL|object
* The display plugin instance.
*/
public function getDisplayPluginInstance(string $setup_plugin_id) {
// The setup plugin instance.
$setup_plugin = $this->setupManager->createInstance($setup_plugin_id);
// Display profile.
$display_profile = $this->entityTypeManager->getStorage('hm_display_profile')->load($setup_plugin->getDispalyProfileId());
if (empty($display_profile)) {
return NULL;
}
// Display plugin ID.
$display_plugin_id = $display_profile->get("plugin");
return $this->displayManager->createInstance($display_plugin_id);
}
/**
* Update the items for a hierarchy
*
* @param int $target_position
* Which position the new items will be insert.
* @param array $all_siblings
* All siblings of the new items in an array[$item_id => (int)$weight]
* @param array $updated_items
* IDs of new items inserted.
* @param int|bool $after
* Indicator if new items are inserted after target position.
*
* @return array
* All siblings needed to move and their new weights.
*/
public function updateHierarchy(int $target_position, array $all_siblings, array $updated_items, $after) {
$filtered_moving_siblings = [];
$first_half = TRUE;
$total = count($all_siblings);
if ($target_position === 0) {
// The insert postion is the first position.
// we don't need to move any siblings.
$weight = (int) reset($all_siblings) - 1;
}
elseif ($target_position >= $total - 1) {
// The insert postion is the end,
// we don't need to move any siblings.
$last_item= array_slice($all_siblings, -1, 1, TRUE);
$weight = (int) reset($last_item) + 1;
}
else {
$target_item = array_slice($all_siblings, $target_position, 1, TRUE);
$weight = (int) reset($target_item);
// If the target position is in the second half,
// we will move all siblings
// after the target position forward.
// Otherwise, we will move siblings
// before the target position backwards.
if ($target_position >= $total / 2) {
$first_half = FALSE;
if ($after) {
// Insert after the target position.
// The target stay where it is.
$weight += 1;
$moving_siblings = array_slice($all_siblings, $target_position + 1, NULL, TRUE);
}
else {
// Insert before the target position.
// The target need to move forwards.
$moving_siblings = array_slice($all_siblings, $target_position, NULL, TRUE);
}
$step = $weight + count($updated_items);
}
else {
if ($after) {
// Insert after the target position.
// The target need to move backwards.
$moving_siblings = array_slice($all_siblings, 0, $target_position + 1, TRUE);
}
else {
// Insert before the target position.
// The target stay where it is.
$weight -= 1;
$moving_siblings = array_slice($all_siblings, 0, $target_position, TRUE);
}
$weight = $step = $weight - count($updated_items);
// Reverse the siblings_moved array
// as we will decrease the weight
// starting from the first element
// and the new weight should be in
// an opposite order.
$moving_siblings = array_reverse($moving_siblings, TRUE);
}
// Move all siblings that need to move.
foreach($moving_siblings as $item_id => $item_weight) {
// Skip all links in the updated array. They will be moved later.
if (in_array($item_id, $updated_items)) {
continue;
}
if ($first_half) {
// While moving the first half of the siblings,
// all moving siblings' weight are decreased,
// if they are greater than the step.
if ((int)$item_weight < --$step) {
// There is planty room, no need to move anymore.
break;
}
else {
// Update the weight.
$filtered_moving_siblings[$item_id] = $step;
}
}
else {
// While moving the second half of the siblings,
// all moving siblings' weight are increased,
// if they are less than the step.
if ((int)$item_weight < ++$step) {
// Update the weight.
$filtered_moving_siblings[$item_id] = $step;
}
else {
// There is planty room, no need to move anymore.
break;
}
}
}
}
return ['start_weight' => $weight, 'moving_siblings' => $filtered_moving_siblings];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment