Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • project/hierarchy_manager
  • issue/hierarchy_manager-3188871
  • issue/hierarchy_manager-3188833
  • issue/hierarchy_manager-3191599
  • issue/hierarchy_manager-3191605
  • issue/hierarchy_manager-3205538
  • issue/hierarchy_manager-3217994
  • issue/hierarchy_manager-3230813
  • issue/hierarchy_manager-3241543
  • issue/hierarchy_manager-3243559
  • issue/hierarchy_manager-3243579
  • issue/hierarchy_manager-3278219
  • issue/hierarchy_manager-3341369
  • issue/hierarchy_manager-3344493
  • issue/hierarchy_manager-3343978
  • issue/hierarchy_manager-3347488
  • issue/hierarchy_manager-3347499
  • issue/hierarchy_manager-3343297
  • issue/hierarchy_manager-3451974
  • issue/hierarchy_manager-3467198
20 results
Show changes
Commits on Source (38)
Showing
with 835 additions and 375 deletions
hmconfig
hmsetup
Jsoneditor
jsoneditor
Jstree
jstree
Mingsong
pluginable
\ No newline at end of file
################
# GitLabCI template for Drupal projects.
#
# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification.
# It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained.
# As long as you include the project, ref and three files below, any future updates added by the Drupal Association will be used in your
# pipelines automatically. However, you can modify this template if you have additional needs for your project.
# The full documentation is on https://project.pages.drupalcode.org/gitlab_templates/
################
# For information on alternative values for 'ref' see https://project.pages.drupalcode.org/gitlab_templates/info/templates-version/
# To test a Drupal 7 project, change the first include filename from .main.yml to .main-d7.yml
include:
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file:
- "/includes/include.drupalci.main.yml"
- "/includes/include.drupalci.variables.yml"
- "/includes/include.drupalci.workflows.yml"
################
# Pipeline configuration variables are defined with default values and descriptions in the file
# https://git.drupalcode.org/project/gitlab_templates/-/blob/main/includes/include.drupalci.variables.yml
# Uncomment the lines below if you want to override any of the variables. The following is just an example.
################
variables:
SKIP_ESLINT: '1'
SKIP_PHPSTAN: '1'
# OPT_IN_TEST_NEXT_MAJOR: '1'
# _CURL_TEMPLATES_REF: 'main'
......@@ -10,18 +10,33 @@ CONTENTS OF THIS FILE
INTRODUCTION
------------
Drupal provides a draggable table to manage the hierarchy of menu links and taxonomy terms. The Drupal draggable table is not able to present a massive hierarchy in one page.
Drupal provides a draggable table to manage the hierarchy of menu links
and taxonomy terms.
The Drupal draggable table is not able to present a massive hierarchy
in one page.
This module provides a plugin architecture to delivery a flexibility of managing hierarchy for taxonomy terms, menu links and others. There are two out of box plugins, taxonomy hierarchy management plugin and menu hierarchy plugin. The front-end JavaScript libraries is also pluginable. The out of box display plugin using jsTree to render the hierarchy tree with filter. The hierarchy tree is draggable which means you can update the hierarchy by dragging a node in the tree.
This module provides a plugin architecture to delivery a flexibility
of managing hierarchy for taxonomy terms,
menu links and others. There are two out of box plugins,
taxonomy hierarchy management plugin and menu hierarchy plugin.
The front-end JavaScript libraries is also pluginable.
The out of box display plugin using jsTree to render
the hierarchy tree with filter.
The hierarchy tree is draggable which means you can update
the hierarchy by dragging a node in the tree.
Other modules can define their own management plugin to manage hierarchy for any other entities or display plugin to render the hierarchy tree by a JavaScript library other than jsTree.
Other modules can define their own management plugin to manage
hierarchy for any other entities or display plugin to render
the hierarchy tree by a JavaScript library other than jsTree.
REQUIREMENTS
------------
This module requires the following library:
* 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)
* jsTree JS (This module will automatically load this library
from remote CDN if it wasn't hosted locally under
/libraries/jquery.jstree/3.3.8/ folder)
INSTALLATION
------------
......@@ -31,13 +46,23 @@ INSTALLATION
CONFIGURATION
-------------
* Go the hierarchy manage display management page (/admin/structure/hm_display_profile) under the Structure menu to create a display profile
* Go the hierarchy manage display management page
(/admin/structure/hm_display_profile) under
the Structure menu to create a display profile
* Go to the hierarchy management configuration page (/admin/config/user-interface/hierarchy_manager/config) to enable hierarchy management plugins, such as taxonomy plugin, and specify a display profile created in step above.
* Go to the hierarchy management configuration page
(/admin/config/user-interface/hierarchy_manager/config) to
enable hierarchy management plugins, such as taxonomy plugin,
and specify a display profile created in step above.
* Once a hierarchy mange plugin is enabled, the related edit form should be replaced with a hierarchy tree form. For instance, the taxonomy term edit form (/admin/structure/taxonomy/manage/{tid}/overview) will be replaced with a hierarchy tree implemented by the taxonomy hierarchy manage plugin.
* Once a hierarchy mange plugin is enabled, the related edit form
should be replaced with a hierarchy tree form.
For instance, the taxonomy term edit form
(/admin/structure/taxonomy/manage/{tid}/overview)
will be replaced with a hierarchy tree implemented by
the taxonomy hierarchy manage plugin.
MAINTAINERS
-----------
Mingsong Hu (Mingsong) - https://www.drupal.org/u/mingsong
\ No newline at end of file
Mingsong Hu (Mingsong) - https://www.drupal.org/u/mingsong
hierarchy_manager:
hierarchy_manager.hmconfig:
type: config_object
label: 'Hierarchy Manager Configuration'
mapping:
allowed_setup_plugins:
type: sequence
label: 'Allowed Setup Plugins'
sequence:
type: string
label: 'Setup Plugin ID'
setup_plugin_settings:
label: 'TFA validation plugin configuration'
type: sequence
sequence:
type: hierarchy_manager.HmSetupPlugin.plugin.config.[%key]
hierarchy_manager.HmSetupPlugin.plugin.config.hm_setup_taxonomy:
type: mapping
label: 'HM Setup Taxonomy Settings'
mapping:
display_profile:
type: string
label: 'Display Profile'
bundle:
type: sequence
label: 'Bundle'
sequence:
type: string
label: 'Bundle Name'
hierarchy_manager.HmSetupPlugin.plugin.config.hm_setup_menu:
type: mapping
label: 'HM Setup Menu Settings'
mapping:
display_profile:
type: string
label: 'Display Profile'
bundle:
type: sequence
label: 'Bundle'
sequence:
type: string
label: 'Bundle Item'
hierarchy_manager.hm_display_profile.*:
type: config_entity
label: 'Hierarchy Manager Display Profile'
mapping:
id:
type: string
label: 'ID'
label:
type: string
label: 'Label'
plugin:
type: string
label: 'Plugin'
config:
type: string
label: 'Config'
confirm:
type: boolean
label: 'Confirm'
hierarchy_manager.hm_display_profile.*:
type: config_entity
label: 'HM Display Profile Entity config'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
uuid:
type: string
plugin:
type: string
label: 'Display plugin'
config:
type: text
label: 'Configuration'
/* HM Tree item status (unpublished/disabled) */
.jstree-default .jstree-node.hm-tree-node-disabled > a {
color: gray;
background-color: #fff4f4;
}
/* HM menu labeling */
.hm-tree-label {
font-style: italic;
}
name: Hierarchy Manager
description: Provides API and plugins to build hierarchy views for entites such as taxonomy or menu.
description: Provides API and plugins to build hierarchy views for entities such as taxonomy or menu.
package: Administration
type: module
core: 8.x
\ No newline at end of file
core_version_requirement: ^9.2.0 || ^10 || ^11
configure: hierarchy_manager.hm_config_form
<?php
/**
* @file
* Install, update and uninstall functions for the Hierarchy Manager module.
*/
use Drupal\system\Entity\Menu;
/**
* Enable hierarchy manager for all bundles of active setup plugins.
*/
function hierarchy_manager_update_8001() {
if ($config = \Drupal::configFactory()->getEditable('hierarchy_manager.hmconfig')) {
if ($allowed_setup_plugins = $config->get('allowed_setup_plugins')) {
if (!empty($allowed_setup_plugins['hm_setup_menu'])) {
$menus = Menu::loadMultiple();
$bundles = [];
/** @var \Drupal\system\Entity\Menu $menu */
foreach ($menus as $menu) {
$id = $menu->id();
$bundles[$id] = $id;
}
$config->set('setup_plugin_settings.hm_setup_menu.bundle', $bundles);
$config->save();
}
if (!empty($allowed_setup_plugins['hm_setup_taxonomy'])) {
$vocabularies = \Drupal::service('entity_type.bundle.info')->getBundleInfo('taxonomy_term');
$bundles = [];
foreach ($vocabularies as $key => $value) {
$bundles[$key] = $key;
}
$config->set('setup_plugin_settings.hm_setup_taxonomy.bundle', $bundles);
$config->save();
}
drupal_flush_all_caches();
}
}
}
......@@ -3,10 +3,15 @@
feature.hm.jstree:
js:
js/Plugin/jstree/hm.jstree.js: {}
css:
theme:
css/Plugin/jstree/hm.jstree.css: {}
dependencies:
- hierarchy_manager/libraries.jquery.jstree
- core/drupalSettings
- core/drupal.message
- core/once
feature.hm.jsoneditor:
js:
js/Plugin/jsoneditor/hm.jsoneditor.js: {}
......@@ -17,65 +22,65 @@ feature.hm.jsoneditor:
libraries.jquery.jstree:
remote: https://github.com/vakata/jstree
version: '3.3.8'
version: '3.3.15'
license:
name: MIT
url: https://github.com/vakata/jstree/blob/master/LICENSE-MIT
gpl-compatible: true
cdn:
https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.8/
https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.15/
js:
/libraries/jquery.jstree/3.3.8/jstree.min.js: {minified: true}
/libraries/jquery.jstree/3.3.15/jstree.min.js: {minified: true}
dependencies:
- core/jquery
libraries.jquery.jstree.default:
remote: https://github.com/vakata/jstree
version: '3.3.8'
version: '3.3.15'
license:
name: MIT
url: https://github.com/vakata/jstree/blob/master/LICENSE-MIT
gpl-compatible: true
cdn:
https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.8/themes/default/
https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.15/themes/default/
css:
component:
/libraries/jquery.jstree/3.3.8/themes/default/style.min.css: {}
/libraries/jquery.jstree/3.3.15/themes/default/style.min.css: {}
libraries.jquery.jstree.default-dark:
remote: https://github.com/vakata/jstree
version: '3.3.8'
version: '3.3.15'
license:
name: MIT
url: https://github.com/vakata/jstree/blob/master/LICENSE-MIT
gpl-compatible: true
cdn:
https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.8/themes/default-dark/
https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.15/themes/default-dark/
css:
component:
/libraries/jquery.jstree/3.3.8/themes/default-dark/style.min.css: {}
/libraries/jquery.jstree/3.3.15/themes/default-dark/style.min.css: {}
libraries.jsoneditor:
remote: https://github.com/josdejong/jsoneditor
version: '7.0.4'
version: '9.9.2'
license:
name: Apache License 2.0
url: https://github.com/josdejong/jsoneditor/blob/develop/LICENSE
gpl-compatible: true
cdn:
https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/7.0.4/
https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/9.9.2/
js:
/libraries/jsoneditor/7.0.4/jsoneditor.min.js: {minified: true}
/libraries/jsoneditor/9.9.2/jsoneditor.min.js: {minified: true}
libraries.jsoneditor.default-theme:
remote: https://github.com/josdejong/jsoneditor
version: '7.0.4'
version: '9.9.2'
license:
name: Apache License 2.0
url: https://github.com/josdejong/jsoneditor/blob/develop/LICENSE
gpl-compatible: true
cdn:
https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/7.0.4/
https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/9.9.2/
css:
component:
/libraries/jsoneditor/7.0.4/jsoneditor.min.css: {minified: true}
\ No newline at end of file
/libraries/jsoneditor/9.9.2/jsoneditor.min.css: {minified: true}
......@@ -5,6 +5,22 @@
* General functions and hook implementations for Hierarchy Manager module.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function hierarchy_manager_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the hierarchy_manager module.
case 'help.page.hierarchy_manager':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Hierarchy Manager module provides a flexible solution for managing hierarchies of menu links and taxonomy terms. Unlike the default Drupal draggable table, this module supports massive hierarchies and offers a plugin architecture for customization.') . '</p>';
$output .= '<p>' . t('Out of the box, the module comes with two plugins: a taxonomy hierarchy management plugin and a menu hierarchy plugin. The front-end JavaScript libraries are also pluginable, with an out-of-the-box display plugin using jsTree to render the hierarchy tree with a filter. The hierarchy tree is draggable, allowing you to easily update the hierarchy by dragging a node in the tree.') . '</p>';
return $output;
}
}
/**
* Implements hook_library_info_alter().
*/
......@@ -26,12 +42,12 @@ function hierarchy_manager_library_info_alter(array &$libraries, $module) {
if ($cdn_library) {
$libraries['libraries.jquery.jstree.default-dark']['css']['component'] = $cdn_library;
}
// jsoneditor min js.
// Jsoneditor min js.
$cdn_library = _hierarchy_manager_use_cdn($libraries, 'libraries.jsoneditor', 'js');
if ($cdn_library) {
$libraries['libraries.jsoneditor']['js'] = $cdn_library;
}
// jsoneditor default theme.
// Jsoneditor default theme.
$cdn_library = _hierarchy_manager_use_cdn($libraries, 'libraries.jsoneditor.default-theme', 'css');
if ($cdn_library) {
$libraries['libraries.jsoneditor.default-theme']['css']['component'] = $cdn_library;
......@@ -40,15 +56,12 @@ function hierarchy_manager_library_info_alter(array &$libraries, $module) {
}
/**
* Implement hook_entity_type_alter().
*
* @param array $entity_types
* Entity type information array.
* Implements hook_entity_type_alter().
*/
function hierarchy_manager_entity_type_alter(array &$entity_types) {
// Override the menu edit form.
$entity_types['menu']
->setFormClass('edit', 'Drupal\hierarchy_manager\Form\HmMenuForm');
->setFormClass('edit', 'Drupal\hierarchy_manager\Form\HmMenuForm');
}
/**
......
......@@ -25,7 +25,7 @@ hierarchy_manager.taxonomy.tree.json:
_title: 'Taxonomy tree'
_controller: '\Drupal\hierarchy_manager\Controller\HmTaxonomyController::taxonomyTreeJson'
requirements:
_permission: 'administer taxonomy'
_custom_access: '\Drupal\hierarchy_manager\Controller\HmTaxonomyController::access'
options:
_admin_route: TRUE
hierarchy_manager.taxonomy.tree.update:
......@@ -34,7 +34,7 @@ hierarchy_manager.taxonomy.tree.update:
_title: 'Taxonomy tree'
_controller: '\Drupal\hierarchy_manager\Controller\HmTaxonomyController::updateTerms'
requirements:
_permission: 'administer taxonomy'
_custom_access: '\Drupal\hierarchy_manager\Controller\HmTaxonomyController::access'
options:
_admin_route: TRUE
......
......@@ -5,20 +5,19 @@
// Codes run both on normal page loads and when data is loaded by AJAX (or BigPipe!)
// @See https://www.drupal.org/docs/8/api/javascript-api/javascript-api-overview
(function($, Drupal) {
(function($, Drupal, once) {
Drupal.behaviors.hmJSTree = {
attach: function(context, settings) {
$(".hm-jstree", context)
.once("jstreeBehavior")
.each(function() {
const treeContainer = $(this);
const hmJstree = once('hmJSTree', '.hm-jstree', context);
// Render all trees.
hmJstree.forEach(function(hmJstree) {
const treeContainer = $(hmJstree);
const parentID = treeContainer.attr('parent-id');
const searchTextID = (parentID) ? '#hm-jstree-search-' + parentID : '#hm-jstree-search';
const optionsJson = treeContainer.attr("options");
const dataURL = treeContainer.attr('data-source') + '&parent=0';
const updateURL = treeContainer.attr('url-update');
const newWindow = false;
let $popDialog = [];
const confirm = treeContainer.attr("confirm");
let reload = true;
let rollback = false;
let themes = {
......@@ -26,8 +25,7 @@
name: 'default'
};
let options;
var offset = 0;
if (optionsJson) {
options = JSON.parse(optionsJson);
if (options.theme) {
......@@ -39,6 +37,9 @@
// Build the tree.
treeContainer.jstree({
core: {
'check_callback' : function (operation, node, node_parent, node_position, more) {
return true;
},
data: {
url: function(node) {
return node.id === '#' ?
......@@ -50,92 +51,141 @@
}
},
themes: themes,
"check_callback" : true,
"multiple": false,
},
'dnd' : {
'copy': false,
'is_draggable' : function(node) {
let can_drag = node[0].data.draggable;
if (can_drag) {
return true;
}
else {
let drupalMessages = new Drupal.Message();
drupalMessages.clear();
drupalMessages.add(Drupal.t("Cannot drag this item, possibly because it has multiple parents or ancestors."), {type: 'warning'});
return false;
}
}
},
search: {
show_only_matches: true
show_only_matches: true,
"search_callback": function(str, node) {
//search for any of the words entered
var word, words = [];
var searchFor = str.toLowerCase().replace(/^\s+/g, '').replace(/\s+$/g, '');
if (searchFor.indexOf(' ') >= 0) {
words = searchFor.split(' ');
} else {
words = [searchFor];
}
for (var i = 0; i < words.length; i++) {
word = words[i];
if ((node.text || "").toLowerCase().indexOf(word) >= 0) {
return true;
}
}
return false;
}
},
plugins: ["search", "dnd"]
'sort' : function(a, b) {
return parseInt(this.get_node(a).data.weight) > parseInt(this.get_node(b).data.weight) ? 1 : -1;
},
plugins: ["search", "dnd", "sort"]
});
// Node move event.
treeContainer.on("move_node.jstree", function(event, data) {
const thisTree = data.instance;
const movedNode = data.node;
const parent = data.parent === '#' ? 0 : data.parent;
const parent_node = thisTree.get_node(data.parent);
const old_parent = data.old_parent === '#' ? 0 : data.old_parent;
const drupalMessages = new Drupal.Message();
if (!rollback) {
let list = thisTree.get_node(data.parent).children;
let before = '';
let after = '';
if (data.position > 0) {
before = list[data.position - 1];
let parentText = Drupal.t('root');
if (parent !== 0) {
parentText = $("<div/>").html(thisTree.get_node(parent).text);
parentText.find("span").remove();
parentText = parentText.text();
}
if (data.position < list.length - 1) {
after = list[data.position + 1];
}
let parent = data.parent === '#' ? 0 : data.parent;
// Update the data on server side.
$.post(updateURL, {
keys: [movedNode.id],
target: data.position,
parent: parent,
after: after,
before: before
})
.done(function(response) {
if (response.result !== "success") {
alert("Server error:" + response.result);
// Function to move the tree item.
function moveTreeItem() {
// Update the data on server side.
$.post(updateURL, {
keys: [movedNode.id],
target: data.position,
parent: parent,
old_parent: old_parent,
old_position: data.old_position
})
.done(function(response) {
if (response.result !== "success") {
alert("Server error:" + response.result);
rollback = true;
thisTree.move_node(movedNode, data.old_parent, data.old_position);
}
else {
if (parent_node.data && !parent_node.data.draggable) {
// The parent node is not draggable.
// We have to update all duplicated nodes
// by refreshing the whole tree.
thisTree.refresh();
}
else {
// Update the nodes changed in the server side.
if (response.updated_nodes) {
let update_nodes = response.updated_nodes;
for (const id in update_nodes) {
let node = thisTree.get_node(id);
if (node) {
node.data.weight = update_nodes[id];
}
}
//Refresh the tree without reloading data from server.
thisTree.sort(parent_node, true);
thisTree.redraw(true);
}
}
let message = Drupal.t('@node is moved to position @position under @parent', {'@node': data.node.text, '@parent': parentText, '@position': data.position + 1});
// Inform user the movement.
drupalMessages.clear();
drupalMessages.add(message);
}
})
.fail(function() {
drupalMessages.clear();
drupalMessages.add(Drupal.t("Can't connect to the server."), {type: 'error'});
rollback = true;
thisTree.move_node(movedNode, data.old_parent, data.old_position);
}
})
.fail(function() {
alert("Error: Can't connect to the server.");
rollback = true;
thisTree.move_node(movedNode, data.old_parent, data.old_position);
});
});
}
// Check if confirmation dialog is enabled.
if (typeof confirm !== 'undefined' && confirm !== false) {
// Confirmation dialog enabled.
let modalTitle = Drupal.t('Confirm move?');
let modalMessage = Drupal.t('Move <em class="placeholder">@node</em> to position @position under <em class="placeholder">@parent</em>?', { '@node': data.node.text, '@parent': parentText, '@position': data.position + 1 });
modalConfirmation(modalTitle, modalMessage, moveTreeItem, function () {
// Callback when confirmation is denied.
rollback = true;
thisTree.move_node(movedNode, data.old_parent, data.old_position);
});
} else {
// Confirmation dialog disabled.
moveTreeItem()
}
}
else {
rollback = false;
}
});
// Node selected event.
treeContainer.on("select_node.jstree", function(event, data) {
var href = data.node.a_attr.href;
if (newWindow) {
window.open(href, "_self");
}
else {
Drupal.ajax({
url: href,
success: function(response) {
response.forEach(function(element) {
if (element.command && element.data) {
if (element.command === 'insert' && element.selector === null) {
$popDialog[offset] = $('<div>' + element.data + '</div>').appendTo('body');
}
}
});
if ($popDialog[offset]) {
let margin = parseInt(offset * 10 % 40);
let options = {
title: 'Edit ' + data.node.text,
minWidth: 600,
draggable: true,
resizable: true,
autoResize: false,
position: {'my': 'right bottom', 'at':'right-' + margin + ' bottom-' + margin},
};
Drupal.dialog($popDialog[offset++], options).show();
}
}
}).execute();
}
treeContainer.on('ready.jstree open_node.jstree move_node.jstree search.jstree clear_search.jstree redraw.jstree', function (event, data) {
Drupal.attachBehaviors(event.target);
});
// Search filter box.
......@@ -154,4 +204,51 @@
});
}
};
})(jQuery, Drupal);
/**
* Generic modal helper function.
*
* @param {string} title - The title for the confirm dialog.
* @param {string} message - The main message for the confirm dialog.
* @param {function} accept - Callback fired when the user answers positive.
* @param {function} deny - Callback fired when the user answers negative.
* @returns {Object} - A jQuery dialog object.
*/
function modalConfirmation(title, message, accept, deny) {
let proceed = false;
let modalConfirmationForm = $('<div></div>').appendTo('body')
.html(message)
.dialog({
modal: true,
title: title,
autoOpen: false,
width: 400,
resizable: false,
sticky: true,
closeOnEscape: true,
dialogClass: "hm-confirm",
buttons: [
{
class: 'button button--primary',
text: Drupal.t('Yes'),
click: function () {
proceed = true;
$(this).dialog('close');
}
},
{
class: 'button',
text: Drupal.t('No'),
click: function () {
$(this).dialog('close');
}
}
],
close: function () {
proceed ? accept() : deny();
}
});
return modalConfirmationForm.dialog('open');
}
})(jQuery, Drupal, once);
......@@ -2,74 +2,86 @@
namespace Drupal\hierarchy_manager\Controller;
use Drupal\Core\Url;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityRepository;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* Menu item feeding controller.
*/
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;
/**
* The entity repository object.
*
* @var \Drupal\Core\Entity\EntityRepository
*/
protected $entityRepository;
/**
* {@inheritdoc}
*/
public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager, MenuLinkTreeInterface $menu_tree, MenuLinkManagerInterface $menu_link_manager) {
public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager, MenuLinkTreeInterface $menu_tree, MenuLinkManagerInterface $menu_link_manager, EntityRepository $entity_repository) {
$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;
$this->entityRepository = $entity_repository;
}
/**
* {@inheritdoc}
*/
......@@ -79,10 +91,11 @@ class HmMenuController extends ControllerBase {
$container->get('entity_type.manager'),
$container->get('hm.plugin_type_manager'),
$container->get('menu.link_tree'),
$container->get('plugin.manager.menu.link')
$container->get('plugin.manager.menu.link'),
$container->get('entity.repository')
);
}
/**
* Callback for menu tree json.
*
......@@ -94,42 +107,42 @@ class HmMenuController extends ControllerBase {
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 = '';
}
// 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.
// 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.
......@@ -138,20 +151,20 @@ class HmMenuController extends ControllerBase {
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.
* @param string $mid
* Menu ID.
*/
public function updateMenuLinks(Request $request, string $mid) {
// Access token.
......@@ -159,31 +172,27 @@ class HmMenuController extends ControllerBase {
if (empty($token) || !$this->csrfToken->validate($token, $mid)) {
return new Response($this->t('Access denied!'));
}
$old_position = (int) $request->get('old_position');
$target_position = $request->get('target');
$parent = $request->get('parent');
$updated_links = $request->get('keys');
//$after = $request->get('after');
$before = $request->get('before');
$all_siblings = [];
$insert_after = TRUE;
if (is_array($updated_links) && !empty($updated_links)) {
if (empty($parent)) {
// Root is the parent.
$parent = '';
// All children menu links (depth = 1).
$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']);
// The parent menu doesn't exist.
return new JsonResponse(['result' => 'fail']);
}
if (empty($children)) {
......@@ -195,59 +204,57 @@ class HmMenuController extends ControllerBase {
// The parent menu has children.
$target_position = intval($target_position);
$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();
$link_id = $link->getPluginId();
$all_siblings[$link_id] = $link->getWeight();
}
}
else {
// The parent link doesn't have children.
}
$new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_links, $insert_after);
// 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.
$new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_links, $old_position);
// Update all links need to update.
foreach ($new_hierarchy as $link_id => $link_weight) {
$this->menuLinkManager->updateDefinition($link_id, ['weight' => $link_weight, 'parent' => $parent]);
}
return new JsonResponse(['result' => 'success']);
$result = [
'result' => 'success',
'updated_nodes' => $new_hierarchy,
];
return new JsonResponse($result);
}
return new JsonResponse(['result' => 'fail']);
}
/**
* Get a display plugin instance.
*
* @return NULL|object
*
* @return null|object
* The display plugin instance.
*/
protected function getDisplayPlugin() {
$display_profile = $this->hmPluginTypeManager->getDisplayProfile('hm_setup_menu');
return $this->hmPluginTypeManager->getDisplayPluginInstance($display_profile);
}
/**
* Load menu links into one array.
*
*
* @param string $mid
* The menu ID.
* @param string $parent
* parent id
* 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 = '') {
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);
......@@ -257,28 +264,29 @@ class HmMenuController extends ControllerBase {
$element['url'] = $element['url'] . '?destination=' . $destination;
}
$links[] = $this->hmPluginTypeManager->buildHierarchyItem(
$element['id'],
$element['title'],
$element['parent'],
$element['url']);
$element['id'],
$element['title'],
$element['parent'],
$element['url'],
$element['status'],
$element['weight']
);
}
return $links;
}
/**
* Load menu links into one array.
*
* @param string $mid
* The menu ID.
* @param string $parent
* parent id
* 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) {
protected function loadMenuLinkObjs(string $mid, string $parent, int $depth = 0) {
$menu_para = new MenuTreeParameters();
if (!empty($depth)) {
$menu_para->setMaxDepth($depth);
......@@ -291,10 +299,10 @@ class HmMenuController extends ControllerBase {
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
];
return $tree = $this->menuTree->transform($tree, $manipulators);
}
/**
* Recursive helper function for loadMenuTree().
*
......@@ -305,37 +313,38 @@ class HmMenuController extends ControllerBase {
* The menu links array.
*/
protected function buildMenuLinkArray($tree) {
// $tree_access_cacheability = new CacheableMetadata();
// $tree_access_cacheability = new CacheableMetadata();
foreach ($tree as $element) {
// $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($element->access));
// $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;
$this->overviewTree[$id]['status'] = $link->isEnabled();
if (!$link->isEnabled()) {
$this->overviewTree[$id]['title'] = '(' . $this->t('disabled') . ')' . $link->getTitle();
$this->overviewTree[$id]['title'] = $link->getTitle() . ' <span class="hm-tree-label hm-tree-label--disabled">(' . $this->t('disabled') . ')</span>';
}
// @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();
$this->overviewTree[$id]['title'] = $link->getTitle() . ' <span class="hm-tree-label hm-tree-label--login">(' . $this->t('<q>Log in</q> for anonymous users') . ')</span>';
}
// @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();
$this->overviewTree[$id]['title'] = $link->getTitle() . ' <span class="hm-tree-label hm-tree-label--logged-only">(' . $this->t('logged in users only') . ')</span>';
}
else {
$this->overviewTree[$id]['title'] = $link->getTitle();
}
$this->overviewTree[$id]['parent'] = $link->getParent();
$parent_id = $link->getParent();
$this->overviewTree[$id]['parent'] = $parent_id;
$this->overviewTree[$id]['weight'] = $link->getWeight();
// Build the edit url.
// Allow for a custom edit link per plugin.
$edit_route = $link->getEditRoute();
......@@ -347,17 +356,13 @@ class HmMenuController extends ControllerBase {
$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;
}
}
}
......@@ -2,18 +2,20 @@
namespace Drupal\hierarchy_manager\Controller;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\taxonomy\Entity\Term;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* Taxononmy controller class.
* Taxonomy feeding controller class.
*/
class HmTaxonomyController extends ControllerBase {
......@@ -30,7 +32,7 @@ class HmTaxonomyController extends ControllerBase {
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $storageController;
/**
* The hierarchy manager plugin type manager.
*
......@@ -38,13 +40,21 @@ class HmTaxonomyController extends ControllerBase {
*/
protected $hmPluginTypeManager;
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* {@inheritdoc}
*/
public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager) {
public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, $plugin_type_manager) {
$this->csrfToken = $csrfToken;
$this->entityTypeManager = $entity_type_manager;
$this->entityRepository = $entity_repository;
$this->storageController = $entity_type_manager->getStorage('taxonomy_term');
$this->hmPluginTypeManager = $plugin_type_manager;
}
......@@ -56,10 +66,26 @@ class HmTaxonomyController extends ControllerBase {
return new static(
$container->get('csrf_token'),
$container->get('entity_type.manager'),
$container->get('entity.repository'),
$container->get('hm.plugin_type_manager')
);
}
/**
* Access check callback for taxonomy tree json.
*
* @param \Drupal\Core\Session\AccountInterface $account
* User account.
* @param string $vid
* 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}");
}
/**
* Callback for taxonomy tree json.
*
......@@ -73,6 +99,12 @@ class HmTaxonomyController extends ControllerBase {
$token = $request->get('token');
// The term array will be returned.
$term_array = [];
// Store the number of each term id present.
$ids = [];
// Store terms that have ambiguous parents.
$am_terms = [];
// Store all terms only have single ancestor.
$single_parent = [];
if (empty($token) || !$this->csrfToken->validate($token, $vid)) {
return new Response($this->t('Access denied!'));
......@@ -80,44 +112,131 @@ class HmTaxonomyController extends ControllerBase {
$parent = $request->get('parent') ?: 0;
$depth = $request->get('depth');
$destination = $request->get('destination');
if(!empty($depth)) {
if (!empty($depth)) {
$depth = intval($depth);
}
$vocabulary_hierarchy = $this->storageController->getVocabularyHierarchyType($vid);
// Taxonomy tree must not be multiple parent tree.
if ($vocabulary_hierarchy !== VocabularyInterface::HIERARCHY_MULTIPLE) {
$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) {
if ($term instanceof 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();
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();
}
$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.
$term_array[$single_parent[$id]]['draggable'] = FALSE;
}
$ids[$id]++;
$term_id = $id . '_' . $ids[$id];
}
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,
// {term_id}_{parent_index}.
if ($count_parent > 1) {
$draggable = FALSE;
// This term has an ancestor with multiple parents.
if ($ids[$id] === $count_parent) {
// Put into the ambiguous array.
// Will solve it later.
$am_terms[] = [
'solved' => FALSE,
'id' => $term_id,
'label' => $term->label(),
'parent' => $term_parent,
'url' => $url,
'publish' => $term->isPublished(),
'weight' => $term->getWeight(),
'draggable' => FALSE,
];
continue;
}
$parent_id = $term_parent[$ids[$id]];
}
else {
if ($ids[$id]) {
// The parent has multiple grandparent.
$parent_id = $term_parent[0] . '_' . $ids[$id];
$draggable = FALSE;
}
else {
$url = $term->toUrl('edit-form', ['query' => ['destination' => $destination]])->toString();
// The parent doesn't have multiple grandparent.
$parent_id = $term_parent[0];
$draggable = TRUE;
// 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(),
$parent_id,
$url,
$term->isPublished(),
$term->getWeight(),
$draggable
);
}
}
}
// Figure out the parent id for terms in the ambiguous term array.
do {
$found = FALSE;
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(),
$term->parents[0],
$url);
$term['id'],
$term['label'],
$id . '_' . $ids[$id],
$term['url'],
$term['publish'],
$term['weight'],
$term['draggable']
);
$found = TRUE;
// Remove this parent from ids array.
$ids[$id]--;
$am_terms[$key]['solved'] = TRUE;
continue 2;
}
}
}
}
} while ($found);
// Display profile.
$display_profile = $this->hmPluginTypeManager->getDisplayProfile('hm_setup_taxonomy');
// 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.']);
}
......@@ -150,65 +269,76 @@ class HmTaxonomyController extends ControllerBase {
}
$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];
$updated_terms = $request->get('keys');
//$after = $request->get('after');
$before = $request->get('before');
$success = FALSE;
$insert_after = TRUE;
$all_siblings = [];
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];
}
// 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);
if (empty($children)) {
if (Term::load($parent_id)) {
// The parent term hasn't had any children.
}
else {
// The parent term doesn't exist.
return new JsonResponse(['result' => 'fail']);
}
}
else {
if (!empty($children)) {
// The parent term has children.
$target_position = intval($target_position);
$position = 0;
$target_position = intval($target_position);
foreach ($children as $child) {
// Figure out if the new links are inserted
// after the target position.
if ($position++ == $target_position && $child->tid !== $before) {
$insert_after = FALSE;
}
$all_siblings[$child->tid] = (int) $child->weight;
}
}
$new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_terms, $insert_after);
$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.
foreach ($terms as $term) {
if ($access_control_handler->access($term, 'update')) {
$term->set('parent', ['target_id' => $parent_id]);
$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);
}
}
$success = $term->save();
}
}
}
if ($success) {
return new JsonResponse(['result' => 'success']);
}
$result = [
'result' => $success ? 'success' : 'fail',
'updated_nodes' => $new_hierarchy,
];
return new JsonResponse(['result' => 'fail']);
return new JsonResponse($result);
}
}
......@@ -34,6 +34,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
* "label",
* "plugin",
* "config",
* "confirm",
* },
* links = {
* "canonical" = "/admin/structure/hm_display_profile/{hm_display_profile}",
......@@ -66,11 +67,19 @@ class HmDisplayProfile extends ConfigEntityBase implements HmDisplayProfileInter
* @var string
*/
protected $plugin;
/**
* The configurations
*
* The configurations.
*
* @var string
*/
protected $config;
/**
* The confirmation option.
*
* @var bool
*/
protected $confirm = FALSE;
}
......@@ -2,14 +2,12 @@
namespace Drupal\hierarchy_manager\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class HMConfigForm.
* Hierarchy Manager configuration form.
*/
class HMConfigForm extends ConfigFormBase {
......@@ -20,25 +18,13 @@ class HMConfigForm extends ConfigFormBase {
*/
protected $pluginManagerHmSetup;
/**
* Constructs a new HMConfigForm object.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
HmSetupPluginManager $plugin_manager_hm_hmsetup
) {
parent::__construct($config_factory);
$this->pluginManagerHmSetup = $plugin_manager_hm_hmsetup;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('plugin.manager.hm.hmsetup')
);
$instance = parent::create($container);
$instance->pluginManagerHmSetup = $container->get('plugin.manager.hm.hmsetup');
return $instance;
}
/**
......@@ -90,7 +76,7 @@ class HMConfigForm extends ConfigFormBase {
$form['setup_plugin_settings'] = [
'#type' => 'fieldset',
'#title' => $this->t('Setup Plugin Settings'),
'#descrption' => $this->t('Setup plugin advanced settings.'),
'#description' => $this->t('Setup plugin advanced settings.'),
'#tree' => TRUE,
];
foreach ($setup_plugins_labels as $key => $val) {
......
......@@ -5,11 +5,11 @@ namespace Drupal\hierarchy_manager\Form;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class HmDisplayProfileForm.
* HmDisplayProfile entity form class.
*/
class HmDisplayProfileForm extends EntityForm {
......@@ -24,8 +24,8 @@ class HmDisplayProfileForm extends EntityForm {
* Constructs a new HmDisplayProfileForm object.
*/
public function __construct(
HmDisplayPluginManager $plugin_manager_hm_display
) {
HmDisplayPluginManager $plugin_manager_hm_display,
) {
$this->pluginManagerHmDisplay = $plugin_manager_hm_display;
}
......@@ -83,14 +83,14 @@ class HmDisplayProfileForm extends EntityForm {
'#description' => $this->t('Display plugin that is in charge of rendering the hierarchy view.'),
'#required' => TRUE,
];
$form['config'] = array(
$form['config'] = [
'#type' => 'hidden',
'#value' => $hm_display_profile->get('config'),
'#attributes' => [
'id' => 'config-value',
],
);
];
$form['json_editor'] = [
'#type' => 'html_tag',
'#tag' => 'div',
......@@ -103,7 +103,7 @@ class HmDisplayProfileForm extends EntityForm {
'hierarchy_manager/libraries.jsoneditor',
'hierarchy_manager/feature.hm.jsoneditor',
'hierarchy_manager/libraries.jsoneditor.default-theme',
]
],
],
];
}
......@@ -114,6 +114,13 @@ class HmDisplayProfileForm extends EntityForm {
];
}
$form['confirm'] = [
'#type' => 'checkbox',
'#title' => $this->t('Confirm drag&drop'),
'#default_value' => $hm_display_profile->get("confirm"),
'#description' => $this->t('Displays a dialog when changing the hierarchy.'),
];
return $form;
}
......@@ -124,7 +131,7 @@ class HmDisplayProfileForm extends EntityForm {
$hm_display_profile = $this->entity;
// User input.
$input = $form_state->getUserInput();
if (isset($input['config'])) {
// Sanitize the user input.
$input['config'] = Xss::filter($input['config']);
......@@ -132,7 +139,10 @@ class HmDisplayProfileForm extends EntityForm {
$hm_display_profile->set('config', $input['config']);
}
if (isset($input['confirm'])) {
$hm_display_profile->set('confirm', $input['confirm']);
}
$status = $hm_display_profile->save();
switch ($status) {
......
......@@ -2,51 +2,54 @@
namespace Drupal\hierarchy_manager\Form;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\menu_ui\MenuForm;
use Drupal\system\MenuInterface;
/**
* Hierarchy manager menu plugin configuration form.
*/
class HmMenuForm extends MenuForm {
/**
* The indicator if the menu hierarchy manager is enabled.
*
* @var bool|NULL
*
* @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.
$menu = $this->entity;
// If the menu hierarchy manager plugin is enabled for this menu.
// 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);
}
if ($this->isMenuPluginEnabled($menu) && $this->loadPluginManager()) {
// 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.
*
......@@ -56,30 +59,32 @@ class HmMenuForm extends MenuForm {
* if the menu hierarchy plugin is enabled.
*/
protected function submitOverviewForm(array $complete_form, FormStateInterface $form_state) {
if (!$this->isMenuPluginEnabled()) {
if (!$this->isMenuPluginEnabled($this->entity)) {
parent::submitOverviewForm($complete_form, $form_state);
}
}
/**
* Build a menu links overview tree element.
*
*
* @param array $form
* Parent form array.
* @param FormStateInterface $form_state
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
* @return NULL|array
*
* @return null|array
* The tree elements.
*/
protected function buildOverviewTree(array $form, FormStateInterface $form_state) {
$display_profile = $this->hmPluginTypeManager->getDisplayProfile('hm_setup_menu');
if (empty($display_profile)) {
return [];
}
$display_plugin_instance = $this->hmPluginTypeManager->getDisplayPluginInstance($display_profile);
if (!empty($display_plugin_instance)) {
if (method_exists($display_plugin_instance, 'getForm')) {
// Menu ID.
......@@ -94,43 +99,65 @@ class HmMenuForm extends MenuForm {
else {
$destination = '/';
}
// Urls
$source_url = Url::fromRoute('hierarchy_manager.menu.tree.json', ['mid' => $mid], ['query' => ['token' => $token, 'destination' => $destination]])->toString();
$update_url = Url::fromRoute('hierarchy_manager.menu.tree.update', ['mid' => $mid], ['query' => ['token' => $token]])->toString();
// Urls.
$source_url = Url::fromRoute('hierarchy_manager.menu.tree.json',
['mid' => $mid],
[
'query' =>
[
'token' => $token,
'destination' => $destination,
],
])->toString();
$update_url = Url::fromRoute('hierarchy_manager.menu.tree.update',
['mid' => $mid],
['query' => ['token' => $token]]
)->toString();
$config = $display_profile->get("config");
return $display_plugin_instance->getForm($source_url, $update_url, $form, $form_state, $config);
$confirm = $display_profile->get("confirm");
return $display_plugin_instance->getForm($source_url, $update_url, $form, $form_state, $config, $confirm);
}
}
return [];
}
/**
* Create a hierarchy manager plugin manager.
*
*
* @return \Drupal\hierarchy_manager\PluginTypeManager
* The plugin manager instance.
*/
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
*
* @param \Drupal\system\MenuInterface $menu
* The menu entity.
*
* @return bool|null
* Return TRUE if the menu plugin is enabled,
* otherwise return FALSE.
*/
protected function isMenuPluginEnabled() {
protected function isMenuPluginEnabled(MenuInterface $menu) {
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;
$plugin_settings = $config->get('setup_plugin_settings');
if (!empty($plugin_settings['hm_setup_menu'])) {
$enabled_bundles = array_keys(array_filter($plugin_settings['hm_setup_menu']['bundle']));
if (in_array($menu->id(), $enabled_bundles)) {
$this->isEnabled = TRUE;
}
}
}
else {
$this->isEnabled = FALSE;
......@@ -138,8 +165,8 @@ class HmMenuForm extends MenuForm {
}
}
}
return $this->isEnabled;
}
}
}
......@@ -2,10 +2,10 @@
namespace Drupal\hierarchy_manager\Form;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Core\Url;
use Drupal\taxonomy\Form\OverviewTerms;
use Drupal\taxonomy\VocabularyInterface;
/**
* Taxonomy overview form class.
......@@ -38,11 +38,13 @@ class HmOverviewTerms extends OverviewTerms {
// Hierarchy Manager setup plugin configuration.
$plugin_settings = $config->get('setup_plugin_settings');
if (!empty($plugin_settings['hm_setup_taxonomy'])) {
// Enabled bundles.
$enabled_bundles = array_keys(array_filter($plugin_settings['hm_setup_taxonomy']['bundle']));
// Display profile ID.
$display_profile_id = $plugin_settings['hm_setup_taxonomy']['display_profile'];
// Display profile.
$display_profile = $this->entityTypeManager->getStorage('hm_display_profile')->load($display_profile_id);
if (!empty($display_profile)) {
if (!empty($display_profile) && in_array($taxonomy_vocabulary->id(), $enabled_bundles)) {
// Display plugin instance.
$instance = \Drupal::service('plugin.manager.hm.display_plugin')->createInstance($display_profile->get("plugin"));
if (method_exists($instance, 'getForm')) {
......@@ -59,10 +61,22 @@ class HmOverviewTerms extends OverviewTerms {
$destination = '/';
}
// Urls.
$source_url = Url::fromRoute('hierarchy_manager.taxonomy.tree.json', ['vid' => $vid], ['query' => ['token' => $token, 'destination' => $destination]])->toString();
$update_url = Url::fromRoute('hierarchy_manager.taxonomy.tree.update', ['vid' => $vid], ['query' => ['token' => $token]])->toString();
$source_url = Url::fromRoute('hierarchy_manager.taxonomy.tree.json',
['vid' => $vid],
[
'query' => [
'token' => $token,
'destination' => $destination,
],
]
)->toString();
$update_url = Url::fromRoute('hierarchy_manager.taxonomy.tree.update',
['vid' => $vid],
['query' => ['token' => $token]]
)->toString();
$config = $display_profile->get("config");
return $instance->getForm($source_url, $update_url, $form, $form_state, $config);
$confirm = $display_profile->get('confirm');
return $instance->getForm($source_url, $update_url, $form, $form_state, $config, $confirm);
}
}
}
......@@ -93,9 +107,14 @@ class HmOverviewTerms extends OverviewTerms {
// If the taxonomy setup plugin is enabled,
// override the submitForm function.
if (!empty($allowed_setup_plugins['hm_setup_taxonomy'])) {
// We don't need to do anything here,
// as the taxonomy plugin take it over.
return;
$plugin_settings = $config->get('setup_plugin_settings');
$enabled_bundles = array_keys(array_filter($plugin_settings['hm_setup_taxonomy']['bundle']));
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
if (in_array($vocabulary->id(), $enabled_bundles)) {
// We don't need to do anything here,
// as the taxonomy plugin take it over.
return;
}
}
}
}
......