You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
754 lines
23 KiB
754 lines
23 KiB
<?php |
|
|
|
namespace Drupal\islandora; |
|
|
|
use Drupal\context\ContextManager; |
|
use Drupal\Core\Entity\ContentEntityInterface; |
|
use Drupal\Core\Entity\EntityFieldManagerInterface; |
|
use Drupal\Core\Entity\EntityInterface; |
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
|
use Drupal\Core\Entity\Query\QueryException; |
|
use Drupal\Core\Entity\Query\QueryInterface; |
|
use Drupal\Core\Language\LanguageManagerInterface; |
|
use Drupal\Core\Site\Settings; |
|
use Drupal\Core\Url; |
|
use Drupal\file\FileInterface; |
|
use Drupal\flysystem\FlysystemFactory; |
|
use Drupal\islandora\ContextProvider\FileContextProvider; |
|
use Drupal\islandora\ContextProvider\MediaContextProvider; |
|
use Drupal\islandora\ContextProvider\NodeContextProvider; |
|
use Drupal\islandora\ContextProvider\TermContextProvider; |
|
use Drupal\media\MediaInterface; |
|
use Drupal\node\NodeInterface; |
|
use Drupal\taxonomy\TermInterface; |
|
|
|
/** |
|
* Utility functions for figuring out when to fire derivative reactions. |
|
*/ |
|
class IslandoraUtils { |
|
|
|
const EXTERNAL_URI_FIELD = 'field_external_uri'; |
|
|
|
const MEDIA_OF_FIELD = 'field_media_of'; |
|
|
|
const MEDIA_USAGE_FIELD = 'field_media_use'; |
|
const MEMBER_OF_FIELD = 'field_member_of'; |
|
const MODEL_FIELD = 'field_model'; |
|
|
|
/** |
|
* The entity type manager. |
|
* |
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
|
*/ |
|
protected $entityTypeManager; |
|
|
|
/** |
|
* The entity field manager. |
|
* |
|
* @var \Drupal\Core\Entity\EntityFieldManagerInterface |
|
*/ |
|
protected $entityFieldManager; |
|
|
|
/** |
|
* Context manager. |
|
* |
|
* @var \Drupal\context\ContextManager |
|
*/ |
|
protected $contextManager; |
|
|
|
/** |
|
* Flysystem factory. |
|
* |
|
* @var \Drupal\flysystem\FlysystemFactory |
|
*/ |
|
protected $flysystemFactory; |
|
|
|
/** |
|
* Language manager. |
|
* |
|
* @var \Drupal\Core\Language\LanguageManagerInterface |
|
*/ |
|
protected $languageManager; |
|
|
|
/** |
|
* Constructor. |
|
* |
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
|
* The entity type manager. |
|
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager |
|
* The entity field manager. |
|
* @param \Drupal\context\ContextManager $context_manager |
|
* Context manager. |
|
* @param \Drupal\flysystem\FlysystemFactory $flysystem_factory |
|
* Flysystem factory. |
|
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager |
|
* Language manager. |
|
*/ |
|
public function __construct( |
|
EntityTypeManagerInterface $entity_type_manager, |
|
EntityFieldManagerInterface $entity_field_manager, |
|
ContextManager $context_manager, |
|
FlysystemFactory $flysystem_factory, |
|
LanguageManagerInterface $language_manager |
|
) { |
|
$this->entityTypeManager = $entity_type_manager; |
|
$this->entityFieldManager = $entity_field_manager; |
|
$this->contextManager = $context_manager; |
|
$this->flysystemFactory = $flysystem_factory; |
|
$this->languageManager = $language_manager; |
|
} |
|
|
|
/** |
|
* Gets nodes that a media belongs to. |
|
* |
|
* @param \Drupal\media\MediaInterface $media |
|
* The Media whose node you are searching for. |
|
* |
|
* @return \Drupal\node\NodeInterface |
|
* Parent node. |
|
* |
|
* @throws \Drupal\Core\TypedData\Exception\MissingDataException |
|
* Method $field->first() throws if data structure is unset and no item can |
|
* be created. |
|
*/ |
|
public function getParentNode(MediaInterface $media) { |
|
if (!$media->hasField(self::MEDIA_OF_FIELD)) { |
|
return NULL; |
|
} |
|
$field = $media->get(self::MEDIA_OF_FIELD); |
|
if ($field->isEmpty()) { |
|
return NULL; |
|
} |
|
$parent = $field->first() |
|
->get('entity') |
|
->getTarget(); |
|
if (!is_null($parent)) { |
|
return $parent->getValue(); |
|
} |
|
return NULL; |
|
} |
|
|
|
/** |
|
* Gets media that belong to a node. |
|
* |
|
* @param \Drupal\node\NodeInterface $node |
|
* The parent node. |
|
* |
|
* @return \Drupal\media\MediaInterface[] |
|
* The children Media. |
|
* |
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException |
|
* Calling getStorage() throws if the entity type doesn't exist. |
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
|
* Calling getStorage() throws if the storage handler couldn't be loaded. |
|
*/ |
|
public function getMedia(NodeInterface $node) { |
|
if (!$this->entityTypeManager->getStorage('field_storage_config') |
|
->load('media.' . self::MEDIA_OF_FIELD)) { |
|
return []; |
|
} |
|
$mids = $this->entityTypeManager->getStorage('media')->getQuery() |
|
->condition(self::MEDIA_OF_FIELD, $node->id()) |
|
->execute(); |
|
if (empty($mids)) { |
|
return []; |
|
} |
|
return $this->entityTypeManager->getStorage('media')->loadMultiple($mids); |
|
} |
|
|
|
/** |
|
* Gets media that belong to a node with the specified term. |
|
* |
|
* @param \Drupal\node\NodeInterface $node |
|
* The parent node. |
|
* @param \Drupal\taxonomy\TermInterface $term |
|
* Taxonomy term. |
|
* |
|
* @return \Drupal\media\MediaInterface |
|
* The child Media. |
|
* |
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException |
|
* Calling getStorage() throws if the entity type doesn't exist. |
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
|
* Calling getStorage() throws if the storage handler couldn't be loaded. |
|
*/ |
|
public function getMediaWithTerm(NodeInterface $node, TermInterface $term) { |
|
$mids = $this->getMediaReferencingNodeAndTerm($node, $term); |
|
if (empty($mids)) { |
|
return NULL; |
|
} |
|
return $this->entityTypeManager->getStorage('media')->load(reset($mids)); |
|
} |
|
|
|
/** |
|
* Gets Media that reference a File. |
|
* |
|
* @param int $fid |
|
* File id. |
|
* |
|
* @return \Drupal\media\MediaInterface[] |
|
* Array of media. |
|
* |
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException |
|
* Calling getStorage() throws if the entity type doesn't exist. |
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
|
* Calling getStorage() throws if the storage handler couldn't be loaded. |
|
*/ |
|
public function getReferencingMedia($fid) { |
|
// Get media fields that reference files. |
|
$fields = $this->getReferencingFields('media', 'file'); |
|
|
|
// Process field names, stripping off 'media.' and appending 'target_id'. |
|
$conditions = array_map( |
|
function ($field) { |
|
return ltrim($field, 'media.') . '.target_id'; |
|
}, |
|
$fields |
|
); |
|
|
|
// Query for media that reference this file. |
|
$query = $this->entityTypeManager->getStorage('media')->getQuery(); |
|
$group = $query->orConditionGroup(); |
|
foreach ($conditions as $condition) { |
|
$group->condition($condition, $fid); |
|
} |
|
$query->condition($group); |
|
|
|
return $this->entityTypeManager->getStorage('media') |
|
->loadMultiple($query->execute()); |
|
} |
|
|
|
/** |
|
* Gets the taxonomy term associated with an external uri. |
|
* |
|
* @param string $uri |
|
* External uri. |
|
* |
|
* @return \Drupal\taxonomy\TermInterface|null |
|
* Term or NULL if not found. |
|
* |
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException |
|
* Calling getStorage() throws if the entity type doesn't exist. |
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException |
|
* Calling getStorage() throws if the storage handler couldn't be loaded. |
|
*/ |
|
public function getTermForUri($uri) { |
|
// Get authority link fields to search. |
|
$field_map = $this->entityFieldManager->getFieldMap(); |
|
$fields = []; |
|
foreach ($field_map['taxonomy_term'] as $field_name => $field_data) { |
|
if ($field_data['type'] == 'authority_link') { |
|
$fields[] = $field_name; |
|
} |
|
} |
|
// Add field_external_uri. |
|
$fields[] = self::EXTERNAL_URI_FIELD; |
|
|
|
$query = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery(); |
|
|
|
$orGroup = $query->orConditionGroup(); |
|
foreach ($fields as $field) { |
|
$orGroup->condition("$field.uri", $uri); |
|
} |
|
|
|
$results = $query |
|
->condition($orGroup) |
|
->execute(); |
|
|
|
if (empty($results)) { |
|
return NULL; |
|
} |
|
|
|
return $this->entityTypeManager->getStorage('taxonomy_term') |
|
->load(reset($results)); |
|
} |
|
|
|
/** |
|
* Gets the taxonomy term associated with an external uri. |
|
* |
|
* @param \Drupal\taxonomy\TermInterface $term |
|
* Taxonomy term. |
|
* |
|
* @return string|null |
|
* URI or NULL if not found. |
|
* |
|
* @throws \Drupal\Core\TypedData\Exception\MissingDataException |
|
* Method $field->first() throws if data structure is unset and no item can |
|
* be created. |
|
*/ |
|
public function getUriForTerm(TermInterface $term) { |
|
$fields = $this->getUriFieldNamesForTerms(); |
|
foreach ($fields as $field_name) { |
|
if ($term && $term->hasField($field_name)) { |
|
$field = $term->get($field_name); |
|
if (!$field->isEmpty()) { |
|
$link = $field->first()->getValue(); |
|
return $link['uri']; |
|
} |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
/** |
|
* Gets every field name that might contain an external uri for a term. |
|
* |
|
* @return string[] |
|
* Field names for fields that a term may have as an external uri. |
|
*/ |
|
public function getUriFieldNamesForTerms() { |
|
// Get authority link fields to search. |
|
$field_map = $this->entityFieldManager->getFieldMap(); |
|
$fields = []; |
|
foreach ($field_map['taxonomy_term'] as $field_name => $field_data) { |
|
$data_types = ['authority_link', 'field_external_authority_link']; |
|
if (in_array($field_data['type'], $data_types)) { |
|
$fields[] = $field_name; |
|
} |
|
} |
|
// Add field_external_uri. |
|
$fields[] = self::EXTERNAL_URI_FIELD; |
|
return $fields; |
|
} |
|
|
|
/** |
|
* Executes context reactions for a Node. |
|
* |
|
* @param string $reaction_type |
|
* Reaction type. |
|
* @param \Drupal\node\NodeInterface $node |
|
* Node to evaluate contexts and pass to reaction. |
|
*/ |
|
public function executeNodeReactions($reaction_type, NodeInterface $node) { |
|
$provider = new NodeContextProvider($node); |
|
$provided = $provider->getRuntimeContexts([]); |
|
$this->contextManager->evaluateContexts($provided); |
|
|
|
// Fire off index reactions. |
|
foreach ($this->contextManager->getActiveReactions($reaction_type) as $reaction) { |
|
$reaction->execute($node); |
|
} |
|
} |
|
|
|
/** |
|
* Executes context reactions for a Media. |
|
* |
|
* @param string $reaction_type |
|
* Reaction type. |
|
* @param \Drupal\media\MediaInterface $media |
|
* Media to evaluate contexts and pass to reaction. |
|
*/ |
|
public function executeMediaReactions($reaction_type, MediaInterface $media) { |
|
$provider = new MediaContextProvider($media); |
|
$provided = $provider->getRuntimeContexts([]); |
|
$this->contextManager->evaluateContexts($provided); |
|
|
|
// Fire off index reactions. |
|
foreach ($this->contextManager->getActiveReactions($reaction_type) as $reaction) { |
|
$reaction->execute($media); |
|
} |
|
} |
|
|
|
/** |
|
* Executes context reactions for a File. |
|
* |
|
* @param string $reaction_type |
|
* Reaction type. |
|
* @param \Drupal\file\FileInterface $file |
|
* File to evaluate contexts and pass to reaction. |
|
*/ |
|
public function executeFileReactions($reaction_type, FileInterface $file) { |
|
$provider = new FileContextProvider($file); |
|
$provided = $provider->getRuntimeContexts([]); |
|
$this->contextManager->evaluateContexts($provided); |
|
|
|
// Fire off index reactions. |
|
foreach ($this->contextManager->getActiveReactions($reaction_type) as $reaction) { |
|
$reaction->execute($file); |
|
} |
|
} |
|
|
|
/** |
|
* Executes context reactions for a File. |
|
* |
|
* @param string $reaction_type |
|
* Reaction type. |
|
* @param \Drupal\taxonomy\TermInterface $term |
|
* Term to evaluate contexts and pass to reaction. |
|
*/ |
|
public function executeTermReactions($reaction_type, TermInterface $term) { |
|
$provider = new TermContextProvider($term); |
|
$provided = $provider->getRuntimeContexts([]); |
|
$this->contextManager->evaluateContexts($provided); |
|
|
|
// Fire off index reactions. |
|
foreach ($this->contextManager->getActiveReactions($reaction_type) as $reaction) { |
|
$reaction->execute($term); |
|
} |
|
} |
|
|
|
/** |
|
* Executes derivative reactions for a Media and Node. |
|
* |
|
* @param string $reaction_type |
|
* Reaction type. |
|
* @param \Drupal\node\NodeInterface $node |
|
* Node to pass to reaction. |
|
* @param \Drupal\media\MediaInterface $media |
|
* Media to evaluate contexts. |
|
*/ |
|
public function executeDerivativeReactions($reaction_type, NodeInterface $node, MediaInterface $media) { |
|
$provider = new MediaContextProvider($media); |
|
$provided = $provider->getRuntimeContexts([]); |
|
$this->contextManager->evaluateContexts($provided); |
|
|
|
// Fire off index reactions. |
|
foreach ($this->contextManager->getActiveReactions($reaction_type) as $reaction) { |
|
$reaction->execute($node); |
|
} |
|
} |
|
|
|
/** |
|
* Evaluates if fields have changed between two instances of a ContentEntity. |
|
* |
|
* @param \Drupal\Core\Entity\ContentEntityInterface $entity |
|
* The updated entity. |
|
* @param \Drupal\Core\Entity\ContentEntityInterface $original |
|
* The original entity. |
|
* |
|
* @return bool |
|
* TRUE if the fields have changed. |
|
*/ |
|
public function haveFieldsChanged(ContentEntityInterface $entity, ContentEntityInterface $original) { |
|
|
|
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()); |
|
|
|
$ignore_list = ['vid' => 1, 'changed' => 1, 'path' => 1]; |
|
$field_definitions = array_diff_key($field_definitions, $ignore_list); |
|
|
|
foreach ($field_definitions as $field_name => $field_definition) { |
|
$langcodes = array_keys($entity->getTranslationLanguages()); |
|
|
|
if ($langcodes !== array_keys($original->getTranslationLanguages())) { |
|
// If the list of langcodes has changed, we need to save. |
|
return TRUE; |
|
} |
|
|
|
foreach ($langcodes as $langcode) { |
|
$items = $entity |
|
->getTranslation($langcode) |
|
->get($field_name) |
|
->filterEmptyItems(); |
|
$original_items = $original |
|
->getTranslation($langcode) |
|
->get($field_name) |
|
->filterEmptyItems(); |
|
|
|
// If the field items are not equal, we need to save. |
|
if (!$items->equals($original_items)) { |
|
return TRUE; |
|
} |
|
} |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
/** |
|
* Returns a list of all available filesystem schemes. |
|
* |
|
* @return String[] |
|
* List of all available filesystem schemes. |
|
*/ |
|
public function getFilesystemSchemes() { |
|
$schemes = ['public']; |
|
if (!empty(Settings::get('file_private_path'))) { |
|
$schemes[] = 'private'; |
|
} |
|
return array_merge($schemes, $this->flysystemFactory->getSchemes()); |
|
} |
|
|
|
/** |
|
* Get array of media ids that have fields that reference $node and $term. |
|
* |
|
* @param \Drupal\node\NodeInterface $node |
|
* The node to reference. |
|
* @param \Drupal\taxonomy\TermInterface $term |
|
* The term to reference. |
|
* |
|
* @return array|int|null |
|
* Array of media IDs or NULL. |
|
*/ |
|
public function getMediaReferencingNodeAndTerm(NodeInterface $node, TermInterface $term) { |
|
$term_fields = $this->getReferencingFields('media', 'taxonomy_term'); |
|
if (count($term_fields) <= 0) { |
|
\Drupal::logger("No media fields reference a taxonomy term"); |
|
return NULL; |
|
} |
|
$node_fields = $this->getReferencingFields('media', 'node'); |
|
if (count($node_fields) <= 0) { |
|
\Drupal::logger("No media fields reference a node."); |
|
return NULL; |
|
} |
|
|
|
$remove_entity = function (&$o) { |
|
$o = substr($o, strpos($o, '.') + 1); |
|
}; |
|
array_walk($term_fields, $remove_entity); |
|
array_walk($node_fields, $remove_entity); |
|
|
|
$query = $this->entityTypeManager->getStorage('media')->getQuery(); |
|
$taxon_condition = $this->getEntityQueryOrCondition($query, $term_fields, $term->id()); |
|
$query->condition($taxon_condition); |
|
$node_condition = $this->getEntityQueryOrCondition($query, $node_fields, $node->id()); |
|
$query->condition($node_condition); |
|
// Does the tags field exist? |
|
try { |
|
$mids = $query->execute(); |
|
} |
|
catch (QueryException $e) { |
|
$mids = []; |
|
} |
|
return $mids; |
|
} |
|
|
|
/** |
|
* Get the fields on an entity of $entity_type that reference a $target_type. |
|
* |
|
* @param string $entity_type |
|
* Type of entity to search for. |
|
* @param string $target_type |
|
* Type of entity the field references. |
|
* |
|
* @return array |
|
* Array of fields. |
|
*/ |
|
public function getReferencingFields($entity_type, $target_type) { |
|
$fields = $this->entityTypeManager->getStorage('field_storage_config')->getQuery() |
|
->condition('entity_type', $entity_type) |
|
->condition('settings.target_type', $target_type) |
|
->execute(); |
|
if (!is_array($fields)) { |
|
$fields = [$fields]; |
|
} |
|
return $fields; |
|
} |
|
|
|
/** |
|
* Make an OR condition for an array of fields and a value. |
|
* |
|
* @param \Drupal\Core\Entity\Query\QueryInterface $query |
|
* The QueryInterface for the query. |
|
* @param array $fields |
|
* The array of field names. |
|
* @param string $value |
|
* The value to search the fields for. |
|
* |
|
* @return \Drupal\Core\Entity\Query\ConditionInterface |
|
* The OR condition to add to your query. |
|
*/ |
|
private function getEntityQueryOrCondition(QueryInterface $query, array $fields, $value) { |
|
$condition = $query->orConditionGroup(); |
|
foreach ($fields as $field) { |
|
$condition->condition($field, $value); |
|
} |
|
return $condition; |
|
} |
|
|
|
/** |
|
* Gets the id URL of an entity. |
|
* |
|
* @param \Drupal\Core\Entity\EntityInterface $entity |
|
* The entity whose URL you want. |
|
* |
|
* @return string |
|
* The entity URL. |
|
* |
|
* @throws \Drupal\Core\Entity\Exception\UndefinedLinkTemplateException |
|
* Thrown if the given entity does not specify a "canonical" template. |
|
* @throws \Drupal\Core\Entity\EntityMalformedException |
|
*/ |
|
public function getEntityUrl(EntityInterface $entity) { |
|
$undefined = $this->languageManager->getLanguage('und'); |
|
return $entity->toUrl('canonical', [ |
|
'absolute' => TRUE, |
|
'language' => $undefined, |
|
])->toString(); |
|
} |
|
|
|
/** |
|
* Gets the downloadable URL for a file. |
|
* |
|
* @param \Drupal\file\FileInterface $file |
|
* The file whose URL you want. |
|
* |
|
* @return string |
|
* The file URL. |
|
*/ |
|
public function getDownloadUrl(FileInterface $file) { |
|
return $file->createFileUrl(FALSE); |
|
} |
|
|
|
/** |
|
* Gets the URL for an entity's REST endpoint. |
|
* |
|
* @param \Drupal\Core\Entity\EntityInterface $entity |
|
* The entity whose REST endpoint you want. |
|
* @param string $format |
|
* REST serialization format. |
|
* |
|
* @return string |
|
* The REST URL. |
|
*/ |
|
public function getRestUrl(EntityInterface $entity, $format = '') { |
|
$undefined = $this->languageManager->getLanguage('und'); |
|
$entity_type = $entity->getEntityTypeId(); |
|
$rest_url = Url::fromRoute( |
|
"rest.entity.$entity_type.GET", |
|
[$entity_type => $entity->id()], |
|
['absolute' => TRUE, 'language' => $undefined] |
|
)->toString(); |
|
if (!empty($format)) { |
|
$rest_url .= "?_format=$format"; |
|
} |
|
return $rest_url; |
|
} |
|
|
|
/** |
|
* Determines if an entity type and bundle make an 'Islandora' type entity. |
|
* |
|
* @param string $entity_type |
|
* The entity type ('node', 'media', etc...). |
|
* @param string $bundle |
|
* Entity bundle ('article', 'page', etc...). |
|
* |
|
* @return bool |
|
* TRUE if the bundle has the correct fields to be an 'Islandora' type. |
|
*/ |
|
public function isIslandoraType($entity_type, $bundle) { |
|
$fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle); |
|
switch ($entity_type) { |
|
case 'media': |
|
return isset($fields[self::MEDIA_OF_FIELD]) && isset($fields[self::MEDIA_USAGE_FIELD]); |
|
|
|
case 'taxonomy_term': |
|
return isset($fields[self::EXTERNAL_URI_FIELD]); |
|
|
|
default: |
|
return isset($fields[self::MEMBER_OF_FIELD]); |
|
} |
|
} |
|
|
|
/** |
|
* Util function for access handlers . |
|
* |
|
* @param string $entity_type |
|
* Entity type such as 'node', 'media', 'taxonomy_term', etc.. |
|
* @param string $bundle_type |
|
* Bundle type such as 'node_type', 'media_type', 'vocabulary', etc... |
|
* |
|
* @return bool |
|
* If user can create _at least one_ of the 'Islandora' types requested. |
|
*/ |
|
public function canCreateIslandoraEntity($entity_type, $bundle_type) { |
|
$bundles = $this->entityTypeManager->getStorage($bundle_type)->loadMultiple(); |
|
$access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type); |
|
|
|
$allowed = []; |
|
foreach (array_keys($bundles) as $bundle) { |
|
// Skip bundles that aren't 'Islandora' types. |
|
if (!$this->isIslandoraType($entity_type, $bundle)) { |
|
continue; |
|
} |
|
|
|
$access = $access_control_handler->createAccess($bundle, NULL, [], TRUE); |
|
if (!$access->isAllowed()) { |
|
continue; |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
/** |
|
* Recursively finds ancestors of an entity. |
|
* |
|
* @param \Drupal\Core\Entity\ContentEntityInterface $entity |
|
* The entity being checked. |
|
* @param array $fields |
|
* An optional array where the values are the field names to be used for |
|
* retrieval. |
|
* @param int|bool $max_height |
|
* How many levels of checking should be done when retrieving ancestors. |
|
* |
|
* @return array |
|
* An array where the keys and values are the node IDs of the ancestors. |
|
*/ |
|
public function findAncestors(ContentEntityInterface $entity, array $fields = [self::MEMBER_OF_FIELD], $max_height = FALSE): array { |
|
// XXX: If a negative integer is passed assume it's false. |
|
if ($max_height < 0) { |
|
$max_height = FALSE; |
|
} |
|
$context = [ |
|
'max_height' => $max_height, |
|
'ancestors' => [], |
|
]; |
|
$this->findAncestorsByEntityReference($entity, $context, $fields); |
|
return $context['ancestors']; |
|
} |
|
|
|
/** |
|
* Helper that builds up the ancestors. |
|
* |
|
* @param \Drupal\Core\Entity\ContentEntityInterface $entity |
|
* The entity being checked. |
|
* @param array $context |
|
* An array containing: |
|
* -ancestors: The ancestors that have been found. |
|
* -max_height: How far up the chain to go. |
|
* @param array $fields |
|
* An optional array where the values are the field names to be used for |
|
* retrieval. |
|
* @param int $current_height |
|
* The current height of the recursion. |
|
*/ |
|
protected function findAncestorsByEntityReference(ContentEntityInterface $entity, array &$context, array $fields = [self::MEMBER_OF_FIELD], int $current_height = 1): void { |
|
$parents = $this->getParentsByEntityReference($entity, $fields); |
|
foreach ($parents as $parent) { |
|
if (isset($context['ancestors'][$parent->id()])) { |
|
continue; |
|
} |
|
$context['ancestors'][$parent->id()] = $parent->id(); |
|
if ($context['max_height'] === FALSE || $current_height < $context['max_height']) { |
|
$this->findAncestorsByEntityReference($parent, $context, $fields, $current_height + 1); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Helper that gets the immediate parents of a node. |
|
* |
|
* @param \Drupal\Core\Entity\ContentEntityInterface $entity |
|
* The entity being checked. |
|
* @param array $fields |
|
* An array where the values are the field names to be used. |
|
* |
|
* @return array |
|
* An array of entity objects keyed by field item deltas. |
|
*/ |
|
protected function getParentsByEntityReference(ContentEntityInterface $entity, array $fields): array { |
|
$parents = []; |
|
foreach ($fields as $field) { |
|
if ($entity->hasField($field)) { |
|
$reference_field = $entity->get($field); |
|
if (!$reference_field->isEmpty()) { |
|
$parents = array_merge($parents, $reference_field->referencedEntities()); |
|
} |
|
} |
|
} |
|
return $parents; |
|
} |
|
|
|
}
|
|
|