View source
<?php
namespace Drupal\entity_hierarchy\Plugin\Field\FieldType;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\entity_hierarchy\Storage\InsertPosition;
use Drupal\entity_hierarchy\Storage\NestedSetStorage;
use Drupal\entity_hierarchy\Storage\TreeLockTrait;
use PNX\NestedSet\Node;
use PNX\NestedSet\NodeKey;
class EntityReferenceHierarchy extends EntityReferenceItem {
use TreeLockTrait;
const HIERARCHY_MIN_CHILD_WEIGHT = -50;
const HIERARCHY_MAX_CHILD_WEIGHT = 50;
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties = parent::propertyDefinitions($field_definition);
$weight_definition = DataDefinition::create('integer')
->setLabel($field_definition
->getSetting('weight_label'));
$weight_definition
->addConstraint('Range', [
'min' => self::HIERARCHY_MIN_CHILD_WEIGHT,
]);
$weight_definition
->addConstraint('Range', [
'max' => self::HIERARCHY_MAX_CHILD_WEIGHT,
]);
$properties['weight'] = $weight_definition;
return $properties;
}
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = parent::schema($field_definition);
$schema['columns']['weight'] = [
'type' => 'int',
'unsigned' => FALSE,
];
$schema['indexes']['weight'] = [
'weight',
];
return $schema;
}
public static function defaultFieldSettings() {
return [
'weight_label' => t('Weight'),
'weight_min' => self::HIERARCHY_MIN_CHILD_WEIGHT,
'weight_max' => self::HIERARCHY_MAX_CHILD_WEIGHT,
] + parent::defaultFieldSettings();
}
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::fieldSettingsForm($form, $form_state);
$elements['weight_min'] = [
'#type' => 'number',
'#title' => t('Minimum'),
'#default_value' => $this
->getSetting('weight_min'),
];
$elements['weight_max'] = [
'#type' => 'number',
'#title' => t('Maximum'),
'#default_value' => $this
->getSetting('weight_max'),
];
$elements['weight_label'] = [
'#type' => 'textfield',
'#title' => t('Weight Label'),
'#default_value' => $this
->getSetting('weight_label'),
'#description' => t('The weight of this child with respect to other children.'),
];
return $elements;
}
public static function getPreconfiguredOptions() {
return [];
}
public function postSave($update) {
if (\Drupal::state()
->get('entity_hierarchy_disable_writes', FALSE)) {
return;
}
$nodeKeyFactory = $this
->getNodeKeyFactory();
$storage = $this
->getTreeStorage();
$fieldDefinition = $this
->getFieldDefinition();
$fieldName = $fieldDefinition
->getName();
$entityTypeId = $fieldDefinition
->getTargetEntityTypeId();
$this
->lockTree($fieldName, $entityTypeId);
$parentEntity = $this
->get('entity')
->getValue();
if (!$parentEntity) {
$stubNode = $nodeKeyFactory
->fromEntity($this
->getEntity());
if (($existingNode = $storage
->getNode($stubNode)) && $existingNode
->getDepth() > 0) {
$storage
->moveSubTreeToRoot($existingNode);
}
$this
->releaseLock($fieldName, $entityTypeId);
return;
}
$parentKey = $nodeKeyFactory
->fromEntity($parentEntity);
$childEntity = $this
->getEntity();
$childKey = $nodeKeyFactory
->fromEntity($childEntity);
$isNewNode = FALSE;
if (!($childNode = $storage
->getNode($childKey))) {
$isNewNode = TRUE;
$childNode = $childKey;
}
if ($existingParent = $storage
->getNode($parentKey)) {
$insertPosition = new InsertPosition($existingParent, $isNewNode, InsertPosition::DIRECTION_BELOW);
if ($siblingEntities = $this
->getSiblingEntityWeights($storage, $existingParent, $childNode)) {
$weightOrderedSiblings = $this
->groupSiblingsByWeight($siblingEntities, $fieldName);
$weight = $this
->get('weight')
->getValue();
$insertPosition = $this
->getInsertPosition($weightOrderedSiblings, $weight, $isNewNode) ?: $insertPosition;
}
$insertPosition
->performInsert($storage, $childNode);
$this
->releaseLock($fieldName, $entityTypeId);
return;
}
$parentNode = $storage
->addRootNode($parentKey);
(new InsertPosition($parentNode, $isNewNode, InsertPosition::DIRECTION_BELOW))
->performInsert($storage, $childNode);
$this
->releaseLock($fieldName, $entityTypeId);
}
protected function entityTypeStorage($entity_type_id) {
return \Drupal::entityTypeManager()
->getStorage($entity_type_id);
}
protected function getTreeStorage() {
$fieldDefinition = $this
->getFieldDefinition();
return \Drupal::service('entity_hierarchy.nested_set_storage_factory')
->get($fieldDefinition
->getName(), $fieldDefinition
->getTargetEntityTypeId());
}
protected function getNodeKeyFactory() {
return \Drupal::service('entity_hierarchy.nested_set_node_factory');
}
protected function entityTypeDefinition() {
return \Drupal::entityTypeManager()
->getDefinition($this
->getFieldDefinition()
->getTargetEntityTypeId());
}
protected function loadSiblingEntityWeights(array $siblings) {
$fieldDefinition = $this
->getFieldDefinition();
$entityType = $this
->entityTypeDefinition();
$entityTypeId = $fieldDefinition
->getTargetEntityTypeId();
$entityStorage = $this
->entityTypeStorage($entityTypeId);
$siblingEntities = new \SplObjectStorage();
$key = $entityType
->hasKey('revision') ? $entityType
->getKey('revision') : $entityType
->getKey('id');
$parentField = $fieldDefinition
->getName();
$query = $entityStorage
->getAggregateQuery();
$ids = array_map(function (Node $item) {
return $item
->getRevisionId();
}, $siblings);
$entities = $query
->groupBy($key)
->sort($key, 'ASC')
->groupBy($parentField . '.weight')
->condition($key, $ids, 'IN')
->execute();
$weightSeparator = $fieldDefinition instanceof BaseFieldDefinition ? '__' : '_';
$entities = array_combine(array_column($entities, $key), array_column($entities, $parentField . $weightSeparator . 'weight'));
foreach ($siblings as $node) {
if (!isset($entities[$node
->getRevisionId()])) {
continue;
}
$siblingEntities[$node] = (int) $entities[$node
->getRevisionId()];
}
return $siblingEntities;
}
protected function getSiblingEntityWeights(NestedSetStorage $storage, Node $parentNode, $childNode) {
if ($siblingNodes = array_filter($storage
->findChildren($parentNode
->getNodeKey()), function (Node $node) use ($childNode) {
if ($childNode instanceof NodeKey) {
return $childNode
->getId() !== $node
->getNodeKey()
->getId();
}
return $childNode
->getNodeKey()
->getId() !== $node
->getNodeKey()
->getId();
})) {
return $this
->loadSiblingEntityWeights($siblingNodes);
}
return FALSE;
}
public function groupSiblingsByWeight(\SplObjectStorage $siblingEntities, $fieldName) {
$weightMap = [];
foreach ($siblingEntities as $node) {
if (!$siblingEntities
->offsetExists($node)) {
continue;
}
$weightMap[$siblingEntities
->offsetGet($node)][] = $node;
}
ksort($weightMap);
return $weightMap;
}
public function getInsertPosition(array $weightOrderedSiblings, $weight, $isNewNode) {
if (isset($weightOrderedSiblings[$weight])) {
return new InsertPosition(end($weightOrderedSiblings[$weight]), $isNewNode, InsertPosition::DIRECTION_AFTER);
}
$firstGroup = reset($weightOrderedSiblings);
$start = key($weightOrderedSiblings);
if ($weight < $start) {
return new InsertPosition(reset($firstGroup), $isNewNode);
}
foreach (array_keys($weightOrderedSiblings) as $weightPosition) {
if ($weight < $weightPosition) {
return new InsertPosition(reset($weightOrderedSiblings[$weightPosition]), $isNewNode);
}
}
$lastGroup = end($weightOrderedSiblings);
if (!$lastGroup) {
return FALSE;
}
return new InsertPosition(end($lastGroup), $isNewNode, InsertPosition::DIRECTION_AFTER);
}
}