Skip to content
Snippets Groups Projects
Verified Commit c92d75cc authored by Lee Rowlands's avatar Lee Rowlands Committed by Lee Rowlands
Browse files

Issue #2877633 by larowlan: Add a 'is parent of' argument plugin

parent 9a0acde5
Branches 2877633-add-parent-of
Tags 8.x-2.0-alpha17
No related merge requests found
Showing with 323 additions and 133 deletions
......@@ -7,5 +7,16 @@ views.argument.entity_hierarchy_argument_is_child_of_entity:
type: integer
label: 'Depth'
views.argument.entity_hierarchy_argument_is_parent_of_entity:
type: views_argument
label: 'Parent entities'
mapping:
depth:
type: integer
label: 'Depth'
views.argument.entity_hierarchy_argument_is_parent_of_entity_revision:
type: views.argument.entity_hierarchy_argument_is_parent_of_entity
views.argument.entity_hierarchy_argument_is_child_of_entity_revision:
type: views.argument.entity_hierarchy_argument_is_child_of_entity
......@@ -67,6 +67,15 @@ function entity_hierarchy_views_data() {
'id' => $has_revisions ? 'entity_hierarchy_argument_is_child_of_entity_revision' : 'entity_hierarchy_argument_is_child_of_entity',
],
];
// Contextual filter for filtering to parent of a given child.
$data[$table_name]['is_parent'] = [
'title' => t('Hierarchy: Is Parent of'),
'help' => t('Limit to parent of given entity'),
'real field' => 'left_pos',
'argument' => [
'id' => $has_revisions ? 'entity_hierarchy_argument_is_parent_of_entity_revision' : 'entity_hierarchy_argument_is_parent_of_entity',
],
];
// Sorting and filtering on depth.
$data[$table_name]['depth'] = [
'title' => t('Hierarchy depth'),
......
......@@ -73,6 +73,10 @@ class EntityHierarchy extends AccessControlHierarchyBase implements ContainerFac
* Storage factory.
* @param \Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory $nodeKeyFactory
* Key factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* Entity type manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* Config factory.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, UserSectionStorageInterface $userSectionStorage, EntityFieldManagerInterface $entityFieldManager, NestedSetStorageFactory $nestedSetStorageFactory, NestedSetNodeKeyFactory $nodeKeyFactory, EntityTypeManagerInterface $entityTypeManager, ConfigFactoryInterface $configFactory) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $userSectionStorage, $configFactory, $entityTypeManager);
......@@ -119,7 +123,7 @@ class EntityHierarchy extends AccessControlHierarchyBase implements ContainerFac
/**
* {@inheritdoc}
*/
public function alterOptions($field, WorkbenchAccessManagerInterface $manager, array $user_sections = []) {
public function alterOptions($field, WorkbenchAccessManagerInterface $manager, array $user_sections = []) {
// @todo We need to limit the allowed options here...
return $field;
}
......
<?php
namespace Drupal\entity_hierarchy\Plugin\views\argument;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory;
use Drupal\entity_hierarchy\Storage\NestedSetStorageFactory;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a base argument plugin.
*/
abstract class EntityHierarchyArgumentPluginBase extends ArgumentPluginBase {
/**
* Set storage.
*
* @var \Drupal\entity_hierarchy\Storage\NestedSetStorageFactory
*/
protected $nestedSetStorageFactory;
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Node key factory.
*
* @var \Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory
*/
protected $nodeKeyFactory;
/**
* Table prefix for nested set.
*
* DBAL doesn't support table prefixes, so we must ensure to respect it.
*
* @var string
*/
protected $nestedSetPrefix = '';
/**
* Constructs a new HierarchyIsParentOfEntity object.
*
* @param array $configuration
* Configuration.
* @param string $plugin_id
* Plugin ID.
* @param mixed $plugin_definition
* Definition.
* @param \Drupal\entity_hierarchy\Storage\NestedSetStorageFactory $nestedSetStorageFactory
* Nested set storage.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* Entity type manager.
* @param \Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory $nodeKeyFactory
* Node key factory.
* @param \Drupal\Core\Database\Connection $database
* Database connection.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, NestedSetStorageFactory $nestedSetStorageFactory, EntityTypeManagerInterface $entityTypeManager, NestedSetNodeKeyFactory $nodeKeyFactory, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->nestedSetStorageFactory = $nestedSetStorageFactory;
$this->entityTypeManager = $entityTypeManager;
$this->nodeKeyFactory = $nodeKeyFactory;
$this->nestedSetPrefix = $database->tablePrefix();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_hierarchy.nested_set_storage_factory'),
$container->get('entity_type.manager'),
$container->get('entity_hierarchy.nested_set_node_factory'),
$container->get('database')
);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$options = range(1, 50);
$form['depth'] = [
'#type' => 'select',
'#empty_option' => $this->t('No restriction'),
'#empty_value' => 0,
'#options' => array_combine($options, $options),
'#title' => $this->t('Depth'),
'#default_value' => $this->options['depth'],
'#description' => $this->t('Filter to children that are at most this many levels deeper than their parent. E.g. for immediate children, select 1.'),
];
parent::buildOptionsForm($form, $form_state);
}
/**
* Returns the tree storage.
*
* @return \Drupal\entity_hierarchy\Storage\NestedSetStorage
* Tree storage.
*/
protected function getTreeStorage() {
return $this->nestedSetStorageFactory->fromTableName($this->nestedSetPrefix . $this->table);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
// Allow filtering depth as well.
$options['depth'] = ['default' => 0];
return $options;
}
/**
* Loads the parent entity from the argument.
*
* @return \Drupal\Core\Entity\EntityInterface
* Parent entity if exists.
*/
protected function loadEntity() {
return $this->entityTypeManager->getStorage($this->getEntityType())->load($this->argument);
}
}
......@@ -2,14 +2,6 @@
namespace Drupal\entity_hierarchy\Plugin\views\argument;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory;
use Drupal\entity_hierarchy\Storage\NestedSetStorageFactory;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Argument to limit to children of an entity.
*
......@@ -17,78 +9,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* @ViewsArgument("entity_hierarchy_argument_is_child_of_entity")
*/
class HierarchyIsChildOfEntity extends ArgumentPluginBase {
/**
* Set storage.
*
* @var \Drupal\entity_hierarchy\Storage\NestedSetStorageFactory
*/
protected $nestedSetStorageFactory;
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Node key factory.
*
* @var \Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory
*/
protected $nodeKeyFactory;
/**
* Table prefix for nested set.
*
* DBAL doesn't support table prefixes, so we must ensure to respect it.
*
* @var string
*/
protected $nestedSetPrefix = '';
/**
* Constructs a new HierarchyIsChildOfEntity object.
*
* @param array $configuration
* Configuration.
* @param string $plugin_id
* Plugin ID.
* @param mixed $plugin_definition
* Definition.
* @param \Drupal\entity_hierarchy\Storage\NestedSetStorageFactory $nestedSetStorageFactory
* Nested set storage.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* Entity type manager.
* @param \Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory $nodeKeyFactory
* Node key factory.
* @param \Drupal\Core\Database\Connection $database
* Database connection.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, NestedSetStorageFactory $nestedSetStorageFactory, EntityTypeManagerInterface $entityTypeManager, NestedSetNodeKeyFactory $nodeKeyFactory, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->nestedSetStorageFactory = $nestedSetStorageFactory;
$this->entityTypeManager = $entityTypeManager;
$this->nodeKeyFactory = $nodeKeyFactory;
$this->nestedSetPrefix = $database->tablePrefix();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_hierarchy.nested_set_storage_factory'),
$container->get('entity_type.manager'),
$container->get('entity_hierarchy.nested_set_node_factory'),
$container->get('database')
);
}
class HierarchyIsChildOfEntity extends EntityHierarchyArgumentPluginBase {
/**
* Set up the query for this argument.
......@@ -99,7 +20,7 @@ class HierarchyIsChildOfEntity extends ArgumentPluginBase {
$this->ensureMyTable();
// Load the actual entity.
$filtered = FALSE;
if ($entity = $this->loadParentEntity()) {
if ($entity = $this->loadEntity()) {
$stub = $this->nodeKeyFactory->fromEntity($entity);
if ($node = $this->getTreeStorage()->getNode($stub)) {
// Query between a range.
......@@ -124,54 +45,4 @@ class HierarchyIsChildOfEntity extends ArgumentPluginBase {
}
}
/**
* Loads the parent entity from the argument.
*
* @return \Drupal\Core\Entity\EntityInterface
* Parent entity if exists.
*/
protected function loadParentEntity() {
return $this->entityTypeManager->getStorage($this->getEntityType())->load($this->argument);
}
/**
* Returns the tree storage.
*
* @return \Drupal\entity_hierarchy\Storage\NestedSetStorage
* Tree storage.
*/
protected function getTreeStorage() {
return $this->nestedSetStorageFactory->fromTableName($this->nestedSetPrefix . $this->table);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$options = range(1, 50);
$form['depth'] = [
'#type' => 'select',
'#empty_option' => $this->t('No restriction'),
'#empty_value' => 0,
'#options' => array_combine($options, $options),
'#title' => $this->t('Depth'),
'#default_value' => $this->options['depth'],
'#description' => $this->t('Filter to children that are at most this many levels deeper than their parent. E.g. for immediate children, select 1.'),
];
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
// Allow filtering depth as well.
$options['depth'] = ['default' => 0];
return $options;
}
}
......@@ -14,7 +14,7 @@ class HierarchyIsChildOfEntityRevision extends HierarchyIsChildOfEntity {
/**
* {@inheritdoc}
*/
protected function loadParentEntity() {
protected function loadEntity() {
$storage = $this->entityTypeManager->getStorage($this->getEntityType());
return $storage->loadRevision($this->argument) ?: $storage->load($this->argument);
}
......
<?php
namespace Drupal\entity_hierarchy\Plugin\views\argument;
use Drupal\Core\Form\FormStateInterface;
/**
* Argument to limit to parent of an entity.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("entity_hierarchy_argument_is_parent_of_entity")
*/
class HierarchyIsParentOfEntity extends EntityHierarchyArgumentPluginBase {
/**
* Set up the query for this argument.
*
* The argument sent may be found at $this->argument.
*/
public function query($group_by = FALSE) {
$this->ensureMyTable();
// Load the actual entity.
$filtered = FALSE;
if ($entity = $this->loadEntity()) {
$stub = $this->nodeKeyFactory->fromEntity($entity);
if ($node = $this->getTreeStorage()->getNode($stub)) {
// Child comes between our left and right.
$filtered = TRUE;
$expression = "$this->tableAlias.$this->realField < :child_left AND $this->tableAlias.right_pos > :child_left";
$arguments = [
':child_left' => $node->getLeft(),
];
if ($depth = $this->options['depth']) {
$expression .= " AND $this->tableAlias.depth <= :depth";
$arguments[':depth'] = $node->getDepth() - $depth;
}
$this->query->addWhereExpression(0, $expression, $arguments);
}
}
// The parent entity doesn't exist, or isn't in the tree and hence has no
// children.
if (!$filtered) {
// Add a killswitch.
$this->query->addWhereExpression(0, '1 <> 1');
}
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['depth']['#description'] = $this->t('Filter to parent that are at most this many levels higher than their parent. E.g. for immediate parent, select 1.');
}
}
<?php
namespace Drupal\entity_hierarchy\Plugin\views\argument;
/**
* Argument to limit to parent of a revision.
*
* @ingroup views_argument_handlers
*
* @ViewsArgument("entity_hierarchy_argument_is_parent_of_entity_revision")
*/
class HierarchyIsParentOfEntityRevision extends HierarchyIsParentOfEntity {
/**
* {@inheritdoc}
*/
protected function loadEntity() {
$storage = $this->entityTypeManager->getStorage($this->getEntityType());
return $storage->loadRevision($this->argument) ?: $storage->load($this->argument);
}
}
......@@ -274,3 +274,56 @@ display:
- url
- url.query_args
tags: { }
block_3:
display_plugin: block
id: block_3
display_title: Block
position: 2
display_options:
display_extenders: { }
arguments:
is_parent:
id: is_parent
table: nested_set_parents_entity_test
field: is_parent
relationship: none
group_type: group
admin_label: ''
default_action: ignore
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: true
validate:
type: none
fail: 'not found'
validate_options: { }
depth: 0
plugin_id: entity_hierarchy_argument_is_parent_of_entity
defaults:
arguments: false
cache_metadata:
max-age: -1
contexts:
- entity_test_view_grants
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
tags: { }
......@@ -134,4 +134,32 @@ class ViewsIntegrationTest extends EntityHierarchyKernelTestBase {
$this->assertIdenticalResultset($executable, $expected, ['name' => 'name', 'id' => 'id']);
}
/**
* Tests views integration.
*/
public function testViewsIntegrationParents() {
$children = $this->createChildEntities($this->parent->id(), 1);
$child = reset($children);
$grandchildren = $this->createChildEntities($child->id(), 1);
// Tree is as follows
// 1 : Parent
// - 2 : Child 1
// - - 3 : Child 1
// Test showing single hierarchy.
$expected = [
[
'name' => 'Parent',
'id' => 1,
],
[
'name' => 'Child 1',
'id' => 2,
],
];
$executable = Views::getView('entity_hierarchy_test_children_view');
$executable->preview('block_3', [reset($grandchildren)->id()]);
$this->assertCount(2, $executable->result);
$this->assertIdenticalResultset($executable, $expected, ['name' => 'name', 'id' => 'id']);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment