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
......@@ -3,10 +3,11 @@
namespace Drupal\hierarchy_manager\Plugin\HmDisplayPlugin;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\hierarchy_manager\Plugin\HmDisplayPluginInterface;
use Drupal\hierarchy_manager\Plugin\HmDisplayPluginBase;
use Drupal\hierarchy_manager\Plugin\HmDisplayPluginInterface;
/**
* JsTree display plugin.
......@@ -19,10 +20,10 @@ use Drupal\hierarchy_manager\Plugin\HmDisplayPluginBase;
class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInterface {
use StringTranslationTrait;
/*
/**
* Build the tree form.
*/
public function getForm(string $url_source, string $url_update, array &$form = [], FormStateInterface &$form_state = NULL, $options = NULL) {
public function getForm(string $url_source, string $url_update, array &$form = [], FormStateInterface &$form_state = NULL, $options = NULL, $confirm = FALSE) {
if (!empty($url_source)) {
if (!empty(($form_state))) {
$parent_formObj = $form_state->getFormObject();
......@@ -43,12 +44,12 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
$form['search'] = [
'#type' => 'textfield',
'#title' => $this
->t('Search'),
->t('Search'),
'#description' => $this->t('Type in the search keyword here to filter the tree below. Multiple keywords separated by spaces. Empty the keyword to reset the tree.'),
'#attributes' => [
'name' => 'jstree-search',
'id' => isset($parent_id) ? 'hm-jstree-search-' . $parent_id : 'hm-jstree-search',
'parent-id' => isset($parent_id) ? $parent_id : '',
'parent-id' => $parent_id ?? '',
'class' => [
'hm-jstree-search',
],
......@@ -59,7 +60,7 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
$form['jstree'] = [
'#type' => 'html_tag',
'#suffix' => '<div class="description">' . $this->t('Click a tree node to edit it.') . '<br>' . $this->t('The tree node is draggable and droppable') . '</div>',
'#suffix' => '<div class="description">' . $this->t('Click an item to edit it. Drag and drop items to change their position in the tree.') . '</div>',
'#tag' => 'div',
'#value' => '',
'#attributes' => [
......@@ -67,8 +68,9 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
'hm-jstree',
],
'id' => isset($parent_id) ? 'hm-jstree-' . $parent_id : 'hm-jstree',
'parent-id' => isset($parent_id) ? $parent_id : '',
'parent-id' => $parent_id ?? '',
'options' => $options,
'confirm' => $confirm,
'data-source' => $url_source,
'url-update' => $url_update,
],
......@@ -92,6 +94,8 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
// The array key of jsTree is different from the data source.
// So we need to translate them.
foreach ($data as $tree_node) {
// Applies a very permissive XSS/HTML filter for node text.
$tree_node['text'] = Xss::filterAdmin($tree_node['text']);
$jstree_node = $tree_node;
// The root id for jsTree is #.
if (empty($tree_node['parent'])) {
......@@ -109,13 +113,17 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
'width' => '960',
'title' => $this->t('Edit') . ' ' . preg_replace('~<span(.*?)</span>~Usi', '', $tree_node['text']),
];
// Custom data
// Custom data.
$jstree_node['a_attr'] = [
'href' => $jstree_node['edit_url'],
'class' => 'use-ajax',
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode($dialog_options),
];
$jstree_node['data'] = [
'weight' => $tree_node['weight'],
'draggable' => $tree_node['draggable'],
];
unset($jstree_node['edit_url']);
// Add this node into the data array.
$jstree_data[] = $jstree_node;
......@@ -123,4 +131,5 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
return $jstree_data;
}
}
......@@ -10,13 +10,14 @@ use Drupal\Core\Form\FormStateInterface;
*/
interface HmDisplayPluginInterface extends PluginInspectionInterface {
/*
/**
* Build the tree form.
*/
public function getForm(string $url_source, string $url_update, array &$form = [], FormStateInterface &$form_state = NULL, $options = NULL);
/**
* Build the data array that JS library accepts.
*/
public function treeData(array $data);
}
......@@ -2,9 +2,9 @@
namespace Drupal\hierarchy_manager\Plugin;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Provides the Hierarchy manager display plugin plugin manager.
......
......@@ -2,8 +2,9 @@
namespace Drupal\hierarchy_manager\Plugin\HmSetupPlugin;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginInterface;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginBase;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginInterface;
use Drupal\system\Entity\Menu;
/**
* Menu link hierarchy setup plugin.
......@@ -14,5 +15,18 @@ use Drupal\hierarchy_manager\Plugin\HmSetupPluginBase;
* )
*/
class HmMenu extends HmSetupPluginBase implements HmSetupPluginInterface {
}
/**
* {@inheritdoc}
*/
public function getBundleOptions() {
$menus = Menu::loadMultiple();
$options = [];
/** @var \Drupal\system\Entity\Menu $menu */
foreach ($menus as $menu) {
$options[$menu->id()] = $menu->label();
}
return $options;
}
}
......@@ -2,8 +2,8 @@
namespace Drupal\hierarchy_manager\Plugin\HmSetupPlugin;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginInterface;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginBase;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginInterface;
/**
* Taxonomy hierarchy setup plugin.
......@@ -14,4 +14,17 @@ use Drupal\hierarchy_manager\Plugin\HmSetupPluginBase;
* )
*/
class HmTaxonomy extends HmSetupPluginBase implements HmSetupPluginInterface {
/**
* {@inheritdoc}
*/
public function getBundleOptions() {
$bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('taxonomy_term');
$options = [];
foreach ($bundles as $key => $value) {
$options[$key] = $value['label'];
}
return $options;
}
}
......@@ -16,7 +16,14 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte
*
* @var string
*/
protected $displayProfile;
protected $displayProfile;
/**
* Enabled entity bundles.
*
* @var array
*/
protected $enabledBundles;
/**
* Constructs a new setup plugin object.
......@@ -34,9 +41,11 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte
$plugin_settings = \Drupal::config('hierarchy_manager.hmconfig')->get('setup_plugin_settings');
if (isset($plugin_settings[$this->pluginId])) {
$this->displayProfile = $plugin_settings[$this->pluginId]['display_profile'];
$this->enabledBundles = $plugin_settings[$this->pluginId]['bundle'];
}
else {
$this->displayProfile = '';
$this->enabledBundles = [];
}
}
......@@ -54,10 +63,17 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte
'#type' => 'select',
'#title' => $this->t('Display Profile'),
'#options' => $display_options,
'#description' => 'Specify the display profile to render the hierarchy tree.',
'#description' => $this->t('Specify the display profile to render the hierarchy tree.'),
'#default_value' => $this->displayProfile,
'#required' => TRUE,
];
$settings_form['bundle'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Enabled bundles'),
'#options' => $this->getBundleOptions(),
'#default_value' => $this->enabledBundles,
'#description' => $this->t('Specify bundles for which hierarchy manager should be enabled.'),
];
return $settings_form;
}
......@@ -68,7 +84,7 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte
* @return string
* The profile ID.
*/
public function getDispalyProfileId() {
public function getDisplayProfileId() {
return $this->displayProfile;
}
......
......@@ -9,6 +9,9 @@ use Drupal\Component\Plugin\PluginInspectionInterface;
*/
interface HmSetupPluginInterface extends PluginInspectionInterface {
/**
* Get a list of bundles supported by the Setup Plugin.
*/
public function getBundleOptions();
// Add get/set methods for your plugin type here.
}
......@@ -2,9 +2,9 @@
namespace Drupal\hierarchy_manager\Plugin;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
/**
* Provides the Hierarchy Manager Setup Plugin plugin manager.
......
......@@ -7,29 +7,32 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager;
use Drupal\hierarchy_manager\Plugin\HmSetupPluginManager;
/**
* Hierarchy Manager plugin manager class.
*/
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}
*/
......@@ -38,10 +41,10 @@ class PluginTypeManager {
$this->displayManager = $display_manager;
$this->setupManager = $setup_manager;
}
/**
* Construct an item inside the hierarchy.
*
*
* @param string|int $id
* Item id.
* @param string $label
......@@ -50,28 +53,35 @@ class PluginTypeManager {
* Parent id of the item.
* @param string $edit_url
* The URL where to edit this item.
* @param boolean $status
* @param bool $status
* The item status.
* @param int $weight
* The weight of the node.
* @param bool $draggable
* If this item draggable.
*
* @return array
* The hierarchy item array.
*/
public function buildHierarchyItem($id, $label, $parent, $edit_url, $status = TRUE) {
return
[
public function buildHierarchyItem($id, $label, $parent, $edit_url, $status = TRUE, $weight = 0, $draggable = TRUE) {
return [
'id' => $id,
'text' => $label,
'parent' => $parent,
'edit_url' => $edit_url,
'status' => $status
'status' => $status,
'weight' => $weight,
'draggable' => $draggable,
];
}
/**
* Get a display plugin instance according to a setup plugin.
*
*
* @param \Drupal\Core\Config\Entity\ConfigEntityBase $display_profile
* Display profile entity.
* @return NULL|object
*
* @return null|object
* The display plugin instance.
*/
public function getDisplayPluginInstance(ConfigEntityBase $display_profile) {
......@@ -80,145 +90,135 @@ class PluginTypeManager {
}
// Display plugin ID.
$display_plugin_id = $display_profile->get("plugin");
return $this->displayManager->createInstance($display_plugin_id);
}
/**
* Get a display profile entity according to a setup plugin.
*
* @param string $setup_plugin_id
* setup plugin ID.
* @return NULL|\Drupal\Core\Config\Entity\ConfigEntityBase
*
* @return null|\Drupal\Core\Config\Entity\ConfigEntityBase
* The display profile entity.
*/
public function getDisplayProfile(string $setup_plugin_id) {
// The setup plugin instance.
$setup_plugin = $this->setupManager->createInstance($setup_plugin_id);
// Return the display profile.
return $this->entityTypeManager->getStorage('hm_display_profile')->load($setup_plugin->getDispalyProfileId());
return $this->entityTypeManager->getStorage('hm_display_profile')->load($setup_plugin->getDisplayProfileId());
}
/**
* Update the items for a hierarchy
*
* 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]
* 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.
* @param int $weight
* The initial weight.
*
* @param int $old_position
* The old position of moving items.
*
* @return array
* All siblings needed to move and their new weights.
*/
public function updateHierarchy(int $target_position, array $all_siblings, array $updated_items, $after, int $weight = 0) {
public function updateHierarchy(int $target_position, array $all_siblings, array $updated_items, int $old_position) {
$new_hierarchy = [];
$first_half = TRUE;
$weight = 0;
if (!empty($all_siblings)) {
$total = count($all_siblings);
if ($target_position === 0) {
// The insert postion is the first position.
$num_new = count(array_diff($updated_items, array_keys($all_siblings)));
if ($target_position <= 0) {
// The insert position is into the first.
// we don't need to move any siblings.
$weight = (int) reset($all_siblings) - 1;
$weight = reset($all_siblings) - 1;
}
elseif ($target_position >= $total - 1) {
// The insert postion is the end,
elseif ($target_position >= $total + $num_new - 1) {
// The insert position is at 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;
$last_item = array_slice($all_siblings, -1, 1, TRUE);
$weight = reset($last_item) + 1;
}
else {
$target_item = array_slice($all_siblings, $target_position, 1, TRUE);
$weight = (int) reset($target_item);
$target_item = array_slice($all_siblings, $target_position, 1);
$target_weight = reset($target_item);
$weight = $target_weight;
$total_insert = count($updated_items);
// Figure out if the target element should move forward.
if ($num_new || ($old_position > $target_position)) {
$move_forward = TRUE;
}
else {
$move_forward = FALSE;
}
// 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);
if ($target_position - $num_new >= $total / 2) {
if ($move_forward) {
$moving_siblings = array_slice($all_siblings, $target_position, NULL, TRUE);
$weight = $target_weight;
}
else {
// Insert before the target position.
// The target need to move forwards.
$moving_siblings = array_slice($all_siblings, $target_position, NULL, TRUE);
$moving_siblings = array_slice($all_siblings, $target_position + 1, NULL, TRUE);
$weight = $target_weight + 1;
}
$step = $weight + count($updated_items);
$after = TRUE;
$expected_weight = $weight + $total_insert;
}
// Move the first bundle.
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);
if ($move_forward) {
$moving_siblings = array_slice($all_siblings, 0, $target_position, TRUE);
$weight = $target_weight - 1;
}
else {
// Insert before the target position.
// The target stay where it is.
$weight -= 1;
$moving_siblings = array_slice($all_siblings, 0, $target_position, TRUE);
$moving_siblings = array_slice($all_siblings, 0, $target_position + 1, TRUE);
$weight = $target_weight;
}
$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);
$after = FALSE;
$expected_weight = $weight - count($moving_siblings) - $total_insert + 1;
}
// 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.
foreach ($moving_siblings as $item_id => $item_weight) {
// Skip all items that are 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;
if ($after) {
if ($item_weight < $expected_weight) {
$new_hierarchy[$item_id] = $expected_weight;
}
else {
// Update the weight.
$new_hierarchy[$item_id] = $step;
// The weight is bigger than expected.
// No need to move the rest of siblings.
break;
}
}
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.
$new_hierarchy[$item_id] = $step;
}
else {
// There is planty room, no need to move anymore.
break;
if ($item_weight > $expected_weight) {
$new_hierarchy[$item_id] = $expected_weight;
}
}
// Move to next sibling.
$expected_weight++;
}
}
}
foreach ($updated_items as $item) {
$new_hierarchy[$item] = $weight++;
}
return $new_hierarchy;
}
}
}
<?php
namespace Drupal\Tests\hierarchy_manager\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the JSTree rendering in the taxonomy manage page.
*
* @group hierarchy_manager
*/
class JsTreeRenderTest extends WebDriverTestBase {
/**
* The admin user.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected static $modules = [
'taxonomy',
'hierarchy_manager',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Test term array.
*
* @var array
*/
protected $testTerms = [
'Term 1',
'Term 2',
'Term 3',
];
/**
* Set up the test environment.
*/
protected function setUp(): void {
parent::setUp();
// Create a user with necessary permissions.
$this->adminUser = $this->drupalCreateUser([
'administer taxonomy',
'access taxonomy overview',
'administer site configuration',
]);
// Log in the user.
$this->drupalLogin($this->adminUser);
// Create a taxonomy vocabulary.
Vocabulary::create([
'vid' => 'tags',
'description' => 'A vocabulary for testing.',
'name' => 'Tags',
])->save();
// Add terms to the 'Tags' vocabulary.
foreach ($this->testTerms as $term_name) {
Term::create([
'vid' => 'tags',
'name' => $term_name,
])->save();
}
// Set up the JsTree profile.
$this->drupalGet('/admin/structure/hm_display_profile/add');
$this->submitForm(['label' => 'test jstree'], 'Save');
}
/**
* Test the JSTree rendering.
*/
public function testJsTreeRendering() {
$assertSession = $this->assertSession();
// Manage taxonomy by using JsTree.
$this->drupalGet('/admin/config/user-interface/hierarchy_manager/config');
$edit = [
'hm_allowed_setup_plugins[hm_setup_taxonomy]' => 'checked',
'setup_plugin_settings[hm_setup_taxonomy][bundle][tags]' => 'checked',
];
$this->submitForm($edit, 'Save configuration');
// Navigate to the taxonomy manage page.
$this->drupalGet('/admin/structure/taxonomy/manage/tags/overview');
// Wait for the JSTree to be fully initialized and rendered.
$assertSession->waitForElement('css', 'div.jstree-default');
$term_tree_items = [];
// Wait for all tree items.
foreach ($this->testTerms as $term_name) {
$term_tree_items[] = $assertSession->waitForLink($term_name);
}
// Check if all tree item exist.
foreach ($term_tree_items as $item) {
$this->assertNotEmpty($item);
}
// Check if the description text.
$assertSession->pageTextContains('Click an item to edit it. Drag and drop items to change their position in the tree.');
}
}