From a349984a3daac72f03f9d6c42eff6e865bdc8670 Mon Sep 17 00:00:00 2001 From: pookmish Date: Mon, 2 Oct 2023 11:36:49 -0500 Subject: [PATCH] HSD8-1416 Media usage page with links to the the parent content. (#139) --- src/Controller/MediaUsageController.php | 158 ++++++++++++++++++ .../StanfordMediaSubscriber.php | 45 +++++ src/Plugin/views/field/EntityUsageLink.php | 96 ----------- stanford_media.links.task.yml | 10 ++ stanford_media.module | 26 +++ stanford_media.routing.yml | 15 ++ stanford_media.services.yml | 5 + .../views/field/EntityUsageLinkTest.php | 99 ----------- 8 files changed, 259 insertions(+), 195 deletions(-) create mode 100644 src/Controller/MediaUsageController.php create mode 100644 src/EventSubscriber/StanfordMediaSubscriber.php delete mode 100644 src/Plugin/views/field/EntityUsageLink.php create mode 100644 stanford_media.links.task.yml delete mode 100644 tests/src/Kernel/Plugin/views/field/EntityUsageLinkTest.php diff --git a/src/Controller/MediaUsageController.php b/src/Controller/MediaUsageController.php new file mode 100644 index 00000000..91d00951 --- /dev/null +++ b/src/Controller/MediaUsageController.php @@ -0,0 +1,158 @@ +get('entity_usage.usage') + ); + } + + /** + * Controller constructor. + * + * @param \Drupal\entity_usage\EntityUsageInterface $entityUsage + * Entity usage service. + */ + public function __construct(protected EntityUsageInterface $entityUsage) {} + + /** + * @param \Drupal\media\MediaInterface $media + * Media entity. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * Page title. + */ + public function title(MediaInterface $media) { + return $this->t('Media Usage: @title', ['@title' => $media->label()]); + } + + /** + * Controller table display to show the list of entities using a media item. + * + * @param \Drupal\media\MediaInterface $media + * Media entity. + * + * @return array + * Render array. + */ + public function view(MediaInterface $media) { + // Create a table to display a link to each of the sources from the entity usage. + $build = [ + '#theme' => 'table', + '#header' => [ + $this->t('Title'), + $this->t('Operations'), + ], + '#rows' => [], + ]; + // Cache exists, use that. + $cache = $this->cache()->get('stanford_media:media_usage:' . $media->id()); + if ($cache) { + $build['#rows'] = $cache->data; + return $build; + } + + $cache_tags = ['media_usage:media:' . $media->id()]; + + foreach ($this->getParentEntityUses($media) as $parent_entity) { + $row = []; + $row['title']['data'] = [ + '#type' => 'link', + '#title' => $parent_entity->label(), + '#url' => $parent_entity->toUrl(), + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => $this->getOperationLinks($parent_entity), + ]; + $build['#rows'][] = $row; + } + $this->cache() + ->set('stanford_media:media_usage:' . $media->id(), $build['#rows'], CacheBackendInterface::CACHE_PERMANENT, $cache_tags); + + return $build; + } + + protected function getOperationLinks(EntityInterface $entity) { + $links = []; + if ($entity->hasLinkTemplate('canonical') && $entity->access('view')) { + $links['view'] = [ + 'title' => $this->t('View'), + 'url' => $entity->toUrl(), + ]; + } + + if ($entity->hasLinkTemplate('edit-form') && $entity->access('update')) { + $links['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => $entity->toUrl('edit-form'), + ]; + } + return $links; + } + + /** + * Get the parent entity of a media item. + * + * @param \Drupal\media\MediaInterface $media + * Media entity. + * + * @return \Drupal\Core\Entity\EntityInterface[] + * Array of parent entities. + */ + protected function getParentEntityUses(MediaInterface $media) { + $sources = $this->entityUsage->listSources($media); + $deduped = []; + $parent_entities = []; + + /// Loop through the sources and create a link to the entity. + foreach ($sources as $entity_type => $entity_ids) { + $entities = $this->entityTypeManager() + ->getStorage($entity_type) + ->loadMultiple(array_keys($entity_ids)); + + foreach ($entities as $entity) { + $parent_entity = $this->getParentEntity($entity); + + // Check if we've loaded this entity already. + if (!$parent_entity || isset($deduped[$parent_entity->id()])) { + continue; + } + + $deduped[$parent_entity->id()] = TRUE; + $parent_entities[] = $parent_entity; + } + } + return $parent_entities; + } + + /** + * Get the parent entity of an entity like a paragraph. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * Entity object. + * + * @return \Drupal\Core\Entity\EntityInterface[] + * Parent entity, or the original entity if it has no parent. + */ + protected function getParentEntity(EntityInterface $entity) { + if (method_exists($entity, 'getParentEntity') && $entity->getParentEntity()) { + return $this->getParentEntity($entity->getParentEntity()); + } + return $entity->hasLinkTemplate('canonical') ? $entity : NULL; + } + +} diff --git a/src/EventSubscriber/StanfordMediaSubscriber.php b/src/EventSubscriber/StanfordMediaSubscriber.php new file mode 100644 index 00000000..882862b3 --- /dev/null +++ b/src/EventSubscriber/StanfordMediaSubscriber.php @@ -0,0 +1,45 @@ + 'onRegisterEntityUsage', + Events::DELETE_BY_SOURCE_ENTITY => 'onRegisterEntityUsage', + Events::DELETE_BY_TARGET_ENTITY => 'onRegisterEntityUsage', + ]; + } + + /** + * Invalidate the media usage cache tag when a media entity is used. + * + * @param \Drupal\entity_usage\Events\EntityUsageEvent $event + * The entity usage event. + */ + public function onRegisterEntityUsage(EntityUsageEvent $event) { + if ($event->getTargetEntityId() && $event->getTargetEntityType() == 'media') { + Cache::invalidateTags(["media_usage:media:{$event->getTargetEntityId()}"]); + } + + $source_type = $event->getSourceEntityType(); + $source_id = $event->getSourceEntityId(); + + if ($source_type && $source_id) { + Cache::invalidateTags(["media_usage:$source_type:$source_id"]); + } + } + +} diff --git a/src/Plugin/views/field/EntityUsageLink.php b/src/Plugin/views/field/EntityUsageLink.php deleted file mode 100644 index 8c96a9e2..00000000 --- a/src/Plugin/views/field/EntityUsageLink.php +++ /dev/null @@ -1,96 +0,0 @@ -get('entity_type.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->entityTypeManager = $entity_type_manager; - } - - /** - * {@inheritdoc} - */ - public function query() { - $this->ensureMyTable(); - // Add the field. - $params = $this->options['group_type'] != 'group' ? ['function' => $this->options['group_type']] : []; - - $this->field_alias = $this->query->addField($this->tableAlias, 'source_type', NULL, $params); - $this->field_alias_id = $this->query->addField($this->tableAlias, 'source_id', NULL, $params); - $this->addAdditionalFields(); - } - - /** - * {@inheritdoc} - */ - public function render(ResultRow $values) { - $type = $values->{$this->field_alias}; - $id = $values->{$this->field_alias_id}; - if (!$type || !$id) { - return ''; - } - - $child = $this->entityTypeManager->getStorage($type)->load($id); - $parent = $this->getParent($child); - if ($parent->hasLinkTemplate('canonical')) { - return $parent->toLink()->toRenderable(); - } - return $parent->getEntityType()->getLabel(); - } - - /** - * Get the parent entity from a child entity. - * - * @param \Drupal\Core\Entity\EntityInterface $child - * Child entity. - * - * @return \Drupal\Core\Entity\EntityInterface - * Parent entity of the child. - */ - protected function getParent(EntityInterface $child) { - if (method_exists($child, 'getParentEntity')) { - if ($sub_parent = $child->getParentEntity()) { - return $this->getParent($sub_parent); - } - } - return $child; - } - -} diff --git a/stanford_media.links.task.yml b/stanford_media.links.task.yml new file mode 100644 index 00000000..b3ca69af --- /dev/null +++ b/stanford_media.links.task.yml @@ -0,0 +1,10 @@ +# Media usage local task. +entity.media.usage: + route_name: entity.media.usage + title: 'Usage' + base_route: entity.media.canonical + weight: 10 + options: + parameters: + media: + type: entity:media diff --git a/stanford_media.module b/stanford_media.module index e713c905..d6069c0e 100644 --- a/stanford_media.module +++ b/stanford_media.module @@ -19,6 +19,32 @@ use Drupal\stanford_media\Form\MediaLibraryEmbeddableForm; use Drupal\stanford_media\Form\MediaLibraryFileUploadForm; use Drupal\stanford_media\Form\MediaLibraryGoogleFormForm; +/** + * Implements hook_entity_type_alter(). + */ +function stanford_media_entity_type_alter(array &$entity_types) { + // Add route for media entity type to view the usage details. + $entity_types['media']->setLinkTemplate('usage', '/media/{media}/usage'); +} + +/** + * Implements hook_entity_operation_alter(). + */ +/** + * Implements hook_entity_operation(). + */ +function stanford_media_entity_operation(EntityInterface $entity) { + $operations = []; + if ($entity->getEntityTypeId() == 'media' && $entity->access('update')) { + $operations['usage'] = [ + 'title' => t('Usage'), + 'weight' => 100, + 'url' => $entity->toUrl('usage'), + ]; + } + return $operations; +} + /** * Implements hook_ENTITY_TYPE_delete(). */ diff --git a/stanford_media.routing.yml b/stanford_media.routing.yml index f2a5dfb4..c3b8916b 100644 --- a/stanford_media.routing.yml +++ b/stanford_media.routing.yml @@ -7,3 +7,18 @@ stanford_media.bulk_upload: _custom_access: '\Drupal\stanford_media\Form\BulkUpload::access' options: _admin_route: TRUE + +# Media entity usage route. +entity.media.usage: + path: '/media/{media}/usage' + defaults: + _controller: '\Drupal\stanford_media\Controller\MediaUsageController::view' + _title_callback: '\Drupal\stanford_media\Controller\MediaUsageController::title' + options: + _admin_route: TRUE + parameters: + media: + type: entity:media + requirements: + _entity_access: 'media.update' + media: \d+ diff --git a/stanford_media.services.yml b/stanford_media.services.yml index d3fe07f1..2b6acb02 100644 --- a/stanford_media.services.yml +++ b/stanford_media.services.yml @@ -16,3 +16,8 @@ services: plugin.manager.embed_validator_plugin_manager: class: Drupal\stanford_media\Plugin\EmbedValidatorPluginManager parent: default_plugin_manager + + stanford_media.event_subscriber: + class: Drupal\stanford_media\EventSubscriber\StanfordMediaSubscriber + tags: + - { name: event_subscriber } diff --git a/tests/src/Kernel/Plugin/views/field/EntityUsageLinkTest.php b/tests/src/Kernel/Plugin/views/field/EntityUsageLinkTest.php deleted file mode 100644 index 8a298e9b..00000000 --- a/tests/src/Kernel/Plugin/views/field/EntityUsageLinkTest.php +++ /dev/null @@ -1,99 +0,0 @@ -installEntitySchema('node'); - $this->installEntitySchema('user'); - - NodeType::create(['type' => 'article']) - ->save(); - - $this->node = Node::create([ - 'title' => $this->randomString(), - 'type' => 'article', - ]); - $this->node->save(); - } - - /** - * Test the field returns a rendered text. - */ - public function testViewField() { - $plugin = EntityUsageLink::create(\Drupal::getContainer(), [], '', []); - $view = $this->createMock(ViewExecutable::class); - - $query = $this->createMock(Sql::class); - $query->method('ensureTable')->willReturn('foo'); - $query->method('addField')->will($this->returnCallback([ - $this, - 'addFieldCallback', - ])); - $view->query = $query; - - $display = $this->createMock(DisplayPluginBase::class); - $plugin->init($view, $display); - - $plugin->query(); - $this->assertEquals('foo_source_type', $plugin->field_alias); - $this->assertEquals('foo_source_id', $plugin->field_alias_id); - - $values = $this->createMock(ResultRow::class); - $values->foo_source_type = 'node'; - $values->foo_source_id = NULL; - $this->assertEmpty($plugin->render($values)); - - $values->foo_source_id = $this->node->id(); - $result = $plugin->render($values); - $result = \Drupal::service('renderer')->renderPlain($result); - - $expression = sprintf('/