From a5a75d821954e3d53a23f5be2e60b7c7437da731 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 13:18:00 +0200 Subject: [PATCH 01/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../src/OEmbed/ResourceFetcherDecorator.php | 46 ++++++ .../src/XrayAuditInsightReport.php | 89 ++++++++++ .../xray_audit_insight.install | 58 +++++++ .../xray_audit_insight.services.yml | 11 ++ .../XrayAuditExternalResourcesPlugin.php | 154 ++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php create mode 100644 modules/xray_audit_insight/src/XrayAuditInsightReport.php create mode 100644 modules/xray_audit_insight/xray_audit_insight.install create mode 100644 src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php diff --git a/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php b/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php new file mode 100644 index 0000000..9002117 --- /dev/null +++ b/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\xray_audit_insight\OEmbed; + +use Drupal\media\OEmbed\ResourceFetcherInterface; +use Drupal\media\OEmbed\ResourceException; +use Drupal\xray_audit_insight\XrayAuditInsightReport; + +/** + * Decorates the original ResourceFetcher service to add logging capabilities. + */ +class ResourceFetcherDecorator implements ResourceFetcherInterface { + + /** + * Constructs a new ResourceFetcherDecorator. + * + * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher + * The original resource fetcher. + * @param \Drupal\xray_audit_insight\XrayAuditInsightReport $insight_report + * The XrayAuditInsight report service. + */ + public function __construct( + protected ResourceFetcherInterface $resourceFetcher, + protected XrayAuditInsightReport $insightReport, + ) {} + + /** + * {@inheritdoc} + */ + public function fetchResource($url) { + try { + return $this->resourceFetcher->fetchResource($url); + } + catch (ResourceException $e) { + // Log the exception to the xray_audit_insight table. + $this->insightReport->addInsightData( + 'external_resource', + $e->getMessage(), + $url + ); + + throw $e; + } + } + +} diff --git a/modules/xray_audit_insight/src/XrayAuditInsightReport.php b/modules/xray_audit_insight/src/XrayAuditInsightReport.php new file mode 100644 index 0000000..d48462a --- /dev/null +++ b/modules/xray_audit_insight/src/XrayAuditInsightReport.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\xray_audit_insight; + +use Drupal\Core\Database\Connection; + +/** + * Service for managing the xray_audit_insight table. + */ +class XrayAuditInsightReport { + + /** + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** + * Constructs a new XrayAuditInsightReport object. + * + * @param \Drupal\Core\Database\Connection $database + * The database connection. + */ + public function __construct(Connection $database) { + $this->database = $database; + } + + /** + * Retrieves insights by type. + * + * @param string $type + * The type of insights to retrieve. + * + * @return array + * An array of insights. + */ + public function getInsightsByType($type) { + $query = $this->database->select('xray_audit_insight', 'xai') + ->fields('xai') + ->condition('type', $type); + + return $query->execute()->fetchAll(); + } + + /** + * Adds a new insight with text data to the xray_audit_insight table. + * + * Only adds the record if no record with the same type and data exists. + * + * @param string $type + * The type of the insight. + * @param string $message + * The message associated with the insight. + * @param string $url + * The URL to be stored. + * + * @return bool + * TRUE if a new record was added, FALSE if a duplicate was found. + */ + public function addInsightData(string $type, string $message, string $url) { + $data = $url; + + // Check if a record with the same type and data already exists. + $exists = $this->database->select('xray_audit_insight', 'xai') + ->fields('xai', ['id']) + ->condition('type', $type) + ->condition('data', $data) + ->condition('message', $message) + ->range(0, 1) + ->execute() + ->fetchField(); + + // Only insert if no matching record exists. + if (!$exists) { + $this->database->insert('xray_audit_insight') + ->fields([ + 'type' => $type, + 'data' => $data, + 'message' => $message, + ]) + ->execute(); + return TRUE; + } + + return FALSE; + } + +} diff --git a/modules/xray_audit_insight/xray_audit_insight.install b/modules/xray_audit_insight/xray_audit_insight.install new file mode 100644 index 0000000..89204df --- /dev/null +++ b/modules/xray_audit_insight/xray_audit_insight.install @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the xray_audit_insight module. + */ + +/** + * Implements hook_schema(). + */ +function xray_audit_insight_schema() { + $schema['xray_audit_insight'] = [ + 'description' => 'Stores xray audit insight data', + 'fields' => [ + 'id' => [ + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique insight ID.', + ], + 'type' => [ + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'Insight type.', + ], + 'message' => [ + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + 'description' => 'Text data containing insight information.', + ], + 'data' => [ + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + 'description' => 'Text data containing insight information.', + ], + ], + 'primary key' => ['id'], + 'indexes' => [ + 'type' => ['type'], + ], + ]; + + return $schema; +} + +/** + * Create xray_audit_insight table. + */ +function xray_audit_insight_update_9001() { + $schema = \Drupal::database()->schema(); + $table_name = 'xray_audit_insight'; + + if (!$schema->tableExists($table_name)) { + $schema->createTable($table_name, xray_audit_insight_schema()[$table_name]); + } +} diff --git a/modules/xray_audit_insight/xray_audit_insight.services.yml b/modules/xray_audit_insight/xray_audit_insight.services.yml index 0a6dcb5..fec69cc 100644 --- a/modules/xray_audit_insight/xray_audit_insight.services.yml +++ b/modules/xray_audit_insight/xray_audit_insight.services.yml @@ -2,3 +2,14 @@ services: plugin_manager.xray_audit_insight: class: Drupal\xray_audit_insight\Plugin\XrayAuditInsightPluginManager parent: default_plugin_manager + + xray_audit_insight.report: + class: Drupal\xray_audit_insight\XrayAuditInsightReport + arguments: ['@database'] + + xray_audit_insight.resource_fetcher: + class: Drupal\xray_audit_insight\OEmbed\ResourceFetcherDecorator + arguments: [ '@xray_audit_insight.resource_fetcher.inner', '@xray_audit_insight.report'] + decorates: media.oembed.resource_fetcher + decoration_priority: 10 + public: false diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php new file mode 100644 index 0000000..5ca2e31 --- /dev/null +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -0,0 +1,154 @@ +<?php + +namespace Drupal\xray_audit\Plugin\xray_audit\tasks\ContentDisplay; + +use Drupal\xray_audit\Plugin\XrayAuditTaskPluginBase; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @XrayAuditTaskPlugin ( + * id = "external_resources", + * label = @Translation("External Resources"), + * description = @Translation("External resources referenced in content."), + * group = "content_display", + * sort = 2, + * operations = { + * "external_resources" = { + * "label" = "External Resources", + * "description" = "Lists external resources referenced in content" + * } + * } + * ) + */ +class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { + + /** + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected $entityFieldManager; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = new static( + $configuration, + $plugin_id, + $plugin_definition + ); + $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->entityFieldManager = $container->get('entity_field.manager'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getDataOperationResult(string $operation = '') { + $results = []; + $media_storage = $this->entityTypeManager->getStorage('media'); + $display_storage = $this->entityTypeManager->getStorage('entity_view_display'); + + // Get all media bundles. + $bundles = $this->entityTypeManager->getStorage('media_type')->loadMultiple(); + + // Track fields using OEmbedFormatter. + $oembed_fields = []; + + // Find all view displays using OEmbedFormatter. + foreach ($bundles as $bundle_id => $bundle) { + // Check all view modes for this bundle. + $displays = $display_storage->loadByProperties([ + 'targetEntityType' => 'media', + 'bundle' => $bundle_id, + ]); + + foreach ($displays as $display) { + $components = $display->getComponents(); + foreach ($components as $field_name => $component) { + if (isset($component['type']) && str_contains($component['type'], 'oembed')) { + $oembed_fields[$bundle_id][$field_name] = $field_name; + } + } + } + } + + // Process each bundle that has oembed fields. + foreach ($oembed_fields as $bundle_id => $field_names) { + // Get all media entities of this bundle. + $media_ids = $media_storage->getQuery() + ->condition('bundle', $bundle_id) + ->accessCheck(FALSE) + ->execute(); + + if (empty($media_ids)) { + continue; + } + + $media_entities = $media_storage->loadMultiple($media_ids); + + // Process each media entity. + foreach ($media_entities as $media_id => $media) { + foreach ($field_names as $field_name) { + if ($media->hasField($field_name) && !$media->get($field_name)->isEmpty()) { + // For oembed fields, the URL is typically in the 'value' property. + $field_items = $media->get($field_name); + + foreach ($field_items as $field_item) { + if ($field_item->getValue() && isset($field_item->getValue()['value'])) { + $results[] = (object) [ + 'entity_id' => $media_id, + 'entity_type' => 'media', + 'bundle' => $bundle_id, + 'resource' => $field_item->getValue()['value'], + 'edit_url' => $media->toUrl('edit-form'), + ]; + } + } + } + } + } + } + + return $results; + } + + /** + * {@inheritdoc} + */ + public function buildDataRenderArray(array $data, string $operation = '') { + $build = []; + + $rows = []; + foreach ($data as $row) { + $rows[] = [ + 'entity_id' => $row->entity_id, + 'entity_type' => $row->entity_type, + 'bundle' => $row->bundle, + 'resource' => $row->resource, + 'edit_link' => \Drupal::linkGenerator()->generate($this->t('Edit'), $row->edit_url), + ]; + } + + $build['table'] = [ + '#theme' => 'table', + '#header' => [ + 'Entity ID', + 'Entity Type', + 'Bundle', + 'Resource URL', + 'Edit', + ], + '#rows' => $rows, + '#empty' => $this->t('No external resources found'), + ]; + + return $build; + } + +} -- GitLab From 43419df86d4026b7188b53e031a32f107c25dbc5 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 13:40:05 +0200 Subject: [PATCH 02/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../XrayAuditExternalResourcesPlugin.php | 101 ++++++++++++++++-- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php index 5ca2e31..314422e 100644 --- a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -6,6 +6,8 @@ use Drupal\xray_audit\Plugin\XrayAuditTaskPluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** + * Plugin implementation of external resources. + * * @XrayAuditTaskPlugin ( * id = "external_resources", * label = @Translation("External Resources"), @@ -23,15 +25,40 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { /** + * The entity type manager service. + * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** + * The entity field manager service. + * * @var \Drupal\Core\Entity\EntityFieldManagerInterface */ protected $entityFieldManager; + /** + * The module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The XrayAuditInsightReport service. + * + * @var \Drupal\xray_audit_insight\XrayAuditInsightReport|null + */ + protected $insightReport; + + /** + * The URL resolver service. + * + * @var \Drupal\media\OEmbed\UrlResolverInterface + */ + protected $urlResolver; + /** * {@inheritdoc} */ @@ -43,6 +70,17 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { ); $instance->entityTypeManager = $container->get('entity_type.manager'); $instance->entityFieldManager = $container->get('entity_field.manager'); + $instance->moduleHandler = $container->get('module_handler'); + + if ($instance->moduleHandler->moduleExists('media')) { + $instance->urlResolver = $container->get('media.oembed.url_resolver'); + } + // Check if xray_audit_insight module is enabled. + if ($instance->moduleHandler->moduleExists('xray_audit_insight')) { + $instance->insightReport = $container->get('xray_audit_insight.report'); + + } + return $instance; } @@ -51,6 +89,9 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { */ public function getDataOperationResult(string $operation = '') { $results = []; + if (!$this->moduleHandler->moduleExists('media')) { + return $results; + } $media_storage = $this->entityTypeManager->getStorage('media'); $display_storage = $this->entityTypeManager->getStorage('entity_view_display'); @@ -101,13 +142,37 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { foreach ($field_items as $field_item) { if ($field_item->getValue() && isset($field_item->getValue()['value'])) { - $results[] = (object) [ + $resource_url = $field_item->getValue()['value']; + $result = [ 'entity_id' => $media_id, 'entity_type' => 'media', 'bundle' => $bundle_id, - 'resource' => $field_item->getValue()['value'], + 'resource' => $resource_url, 'edit_url' => $media->toUrl('edit-form'), + 'needs_review' => FALSE, + 'insight_message' => '', ]; + + // Check if xray_audit_insight module is enabled + // and query for insights. + if ($this->insightReport) { + // Get insights of type 'external_resource' where + // data matches the normalized resource URL. + $normalized_url = $this->urlResolver->getResourceUrl($resource_url); + $query = \Drupal::database()->select('xray_audit_insight', 'xai') + ->fields('xai') + ->condition('type', 'external_resource') + ->condition('data', $normalized_url); + $insights = $query->execute()->fetchAll(); + + if (!empty($insights)) { + $result['needs_review'] = TRUE; + // Use the message from the most recent insight. + $result['insight_message'] = end($insights)->message; + } + } + + $results[] = (object) $result; } } } @@ -126,24 +191,40 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { $rows = []; foreach ($data as $row) { - $rows[] = [ + $row_data = [ 'entity_id' => $row->entity_id, 'entity_type' => $row->entity_type, 'bundle' => $row->bundle, 'resource' => $row->resource, 'edit_link' => \Drupal::linkGenerator()->generate($this->t('Edit'), $row->edit_url), ]; + + // Add insight columns if xray_audit_insight module is enabled. + if ($this->moduleHandler->moduleExists('xray_audit_insight')) { + $row_data['needs_review'] = isset($row->needs_review) && $row->needs_review ? $this->t('Yes') : $this->t('No'); + $row_data['insight_message'] = $row->insight_message ?? ''; + } + + $rows[] = $row_data; + } + + $header = [ + 'Entity ID', + 'Entity Type', + 'Bundle', + 'Resource URL', + 'Edit', + ]; + + // Add insight headers if xray_audit_insight module is enabled. + if ($this->moduleHandler->moduleExists('xray_audit_insight')) { + $header[] = $this->t('Needs Review'); + $header[] = $this->t('Message'); } $build['table'] = [ '#theme' => 'table', - '#header' => [ - 'Entity ID', - 'Entity Type', - 'Bundle', - 'Resource URL', - 'Edit', - ], + '#header' => $header, '#rows' => $rows, '#empty' => $this->t('No external resources found'), ]; -- GitLab From 95aba9cc29861f269ceffd0458b156117c80a9c3 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 14:23:29 +0200 Subject: [PATCH 03/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../XrayAuditExternalResourcesInsight.php | 85 +++++++++++++++++++ .../XrayAuditExternalResourcesPlugin.php | 32 +++++++ 2 files changed, 117 insertions(+) create mode 100644 modules/xray_audit_insight/src/Plugin/insights/XrayAuditExternalResourcesInsight.php diff --git a/modules/xray_audit_insight/src/Plugin/insights/XrayAuditExternalResourcesInsight.php b/modules/xray_audit_insight/src/Plugin/insights/XrayAuditExternalResourcesInsight.php new file mode 100644 index 0000000..23ebd00 --- /dev/null +++ b/modules/xray_audit_insight/src/Plugin/insights/XrayAuditExternalResourcesInsight.php @@ -0,0 +1,85 @@ +<?php + +namespace Drupal\xray_audit_insight\Plugin\insights; + +use Drupal\Core\Url; +use Drupal\Core\Link; +use Drupal\xray_audit_insight\Plugin\XrayAuditInsightPluginBase; +use Drupal\xray_audit\Plugin\XrayAuditTaskPluginInterface; + +/** + * Plugin implementation for external resources needing review. + * + * @XrayAuditInsightPlugin ( + * id = "external_resources_review", + * label = @Translation("External resources needing review"), + * description = @Translation("Identified external resources that should be reviewed"), + * sort = 7 + * ) + */ +class XrayAuditExternalResourcesInsight extends XrayAuditInsightPluginBase { + + /** + * {@inheritdoc} + */ + protected $taskPluginId = 'external_resources'; + + /** + * {@inheritdoc} + */ + protected $operation = 'external_resources'; + + /** + * {@inheritdoc} + */ + public function getInsightsForDrupalReport(): array { + $insights = $this->getInsights(); + $needs_review = $insights['external_resources_review']; + if ($needs_review) { + $url = Url::fromUserInput($this->getPathReport($this->taskPluginId, $this->operation)); + $reports_link = Link::fromTextAndUrl($this->t('task report'), $url); + $value = $this->t('External resources should be reviewed. Please check the @report_link for details', [ + '@report_link' => $reports_link->toString(), + ]); + $severity = REQUIREMENT_WARNING; + } + else { + $value = $this->t("No external resources need review."); + $severity = NULL; + } + + return [ + 'external_resources_review' => $this->buildInsightForDrupalReport( + $this->label(), + $value, + '', + $severity + ), + ]; + } + + /** + * {@inheritdoc} + */ + public function getInsights(): array { + return ['external_resources_review' => $this->existResourcesNeedingReview()]; + } + + /** + * Check if there are external resources needing review. + * + * @return bool + * True if there are resources needing review. + */ + protected function existResourcesNeedingReview(): bool { + $task_plugin = $this->getInstancedPlugin($this->taskPluginId, $this->operation); + $result = $task_plugin instanceof XrayAuditTaskPluginInterface ? $task_plugin->getDataOperationResult($this->operation) : []; + foreach ($result as $resource) { + if (!empty($resource->needs_review)) { + return TRUE; + } + } + return FALSE; + } + +} diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php index 314422e..816278c 100644 --- a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -59,6 +59,13 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { */ protected $urlResolver; + /** + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + /** * {@inheritdoc} */ @@ -71,6 +78,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { $instance->entityTypeManager = $container->get('entity_type.manager'); $instance->entityFieldManager = $container->get('entity_field.manager'); $instance->moduleHandler = $container->get('module_handler'); + $instance->database = $container->get('database'); if ($instance->moduleHandler->moduleExists('media')) { $instance->urlResolver = $container->get('media.oembed.url_resolver'); @@ -119,6 +127,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } } + $normalized_urls = []; // Process each bundle that has oembed fields. foreach ($oembed_fields as $bundle_id => $field_names) { // Get all media entities of this bundle. @@ -163,6 +172,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { ->fields('xai') ->condition('type', 'external_resource') ->condition('data', $normalized_url); + $normalized_urls[] = $normalized_url; $insights = $query->execute()->fetchAll(); if (!empty($insights)) { @@ -180,9 +190,31 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } } + $this->cleanupOrphanedInsights($normalized_urls); + return $results; } + /** + * Cleans up orphaned external resource insights that don't match any media. + */ + protected function cleanupOrphanedInsights(array $normalized_urls) { + $query = $this->database->select('xray_audit_insight', 'xai') + ->fields('xai', ['id', 'data']) + ->condition('type', 'external_resource'); + $insights = $query->execute()->fetchAll(); + + foreach ($insights as $insight) { + $normalized_url_insight = $insight->data; + + if (!in_array($normalized_url_insight, $normalized_urls)) { + $this->database->delete('xray_audit_insight') + ->condition('id', $insight->id) + ->execute(); + } + } + } + /** * {@inheritdoc} */ -- GitLab From 9aa25a01e7c365e538ee87883c6f7180fea89393 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 14:34:43 +0200 Subject: [PATCH 04/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../src/OEmbed/ResourceFetcherDecorator.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php b/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php index 9002117..d6ea11f 100644 --- a/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php +++ b/modules/xray_audit_insight/src/OEmbed/ResourceFetcherDecorator.php @@ -14,10 +14,10 @@ class ResourceFetcherDecorator implements ResourceFetcherInterface { /** * Constructs a new ResourceFetcherDecorator. * - * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher - * The original resource fetcher. - * @param \Drupal\xray_audit_insight\XrayAuditInsightReport $insight_report - * The XrayAuditInsight report service. + * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resourceFetcher + * The original resource fetcher service. + * @param \Drupal\xray_audit_insight\XrayAuditInsightReport $insightReport + * The XrayAuditInsightReport service for logging insights. */ public function __construct( protected ResourceFetcherInterface $resourceFetcher, -- GitLab From 4664b2ed75b6979255e9025a3c97403c8ed66d0c Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 14:54:12 +0200 Subject: [PATCH 05/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../src/XrayAuditInsightReport.php | 55 +++++++++++++------ .../XrayAuditExternalResourcesPlugin.php | 40 +++----------- 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/modules/xray_audit_insight/src/XrayAuditInsightReport.php b/modules/xray_audit_insight/src/XrayAuditInsightReport.php index d48462a..ac1d6e8 100644 --- a/modules/xray_audit_insight/src/XrayAuditInsightReport.php +++ b/modules/xray_audit_insight/src/XrayAuditInsightReport.php @@ -26,23 +26,6 @@ class XrayAuditInsightReport { $this->database = $database; } - /** - * Retrieves insights by type. - * - * @param string $type - * The type of insights to retrieve. - * - * @return array - * An array of insights. - */ - public function getInsightsByType($type) { - $query = $this->database->select('xray_audit_insight', 'xai') - ->fields('xai') - ->condition('type', $type); - - return $query->execute()->fetchAll(); - } - /** * Adds a new insight with text data to the xray_audit_insight table. * @@ -86,4 +69,42 @@ class XrayAuditInsightReport { return FALSE; } + /** + * Gets insights for a specific external resource URL. + * + * @param string $normalized_url + * The normalized URL to check. + * + * @return array + * Array of matching insights. + */ + public function getExternalResourceInsights(string $normalized_url) { + $query = $this->database->select('xray_audit_insight', 'xai') + ->fields('xai') + ->condition('type', 'external_resource') + ->condition('data', $normalized_url); + return $query->execute()->fetchAll(); + } + + /** + * Cleans up orphaned external resource insights that don't match any media. + * + * @param array $normalized_urls + * Array of active normalized URLs. + */ + public function cleanupOrphanedExternalResourceInsights(array $normalized_urls) { + $query = $this->database->select('xray_audit_insight', 'xai') + ->fields('xai', ['id', 'data']) + ->condition('type', 'external_resource'); + $insights = $query->execute()->fetchAll(); + + foreach ($insights as $insight) { + if (!in_array($insight->data, $normalized_urls)) { + $this->database->delete('xray_audit_insight') + ->condition('id', $insight->id) + ->execute(); + } + } + } + } diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php index 816278c..f38c6c2 100644 --- a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -60,11 +60,11 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { protected $urlResolver; /** - * The database connection. + * The link generator service. * - * @var \Drupal\Core\Database\Connection + * @var \Drupal\Core\Utility\LinkGeneratorInterface */ - protected $database; + protected $linkGenerator; /** * {@inheritdoc} @@ -78,7 +78,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { $instance->entityTypeManager = $container->get('entity_type.manager'); $instance->entityFieldManager = $container->get('entity_field.manager'); $instance->moduleHandler = $container->get('module_handler'); - $instance->database = $container->get('database'); + $instance->linkGenerator = $container->get('link_generator'); if ($instance->moduleHandler->moduleExists('media')) { $instance->urlResolver = $container->get('media.oembed.url_resolver'); @@ -168,12 +168,8 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { // Get insights of type 'external_resource' where // data matches the normalized resource URL. $normalized_url = $this->urlResolver->getResourceUrl($resource_url); - $query = \Drupal::database()->select('xray_audit_insight', 'xai') - ->fields('xai') - ->condition('type', 'external_resource') - ->condition('data', $normalized_url); $normalized_urls[] = $normalized_url; - $insights = $query->execute()->fetchAll(); + $insights = $this->insightReport->getExternalResourceInsights($normalized_url); if (!empty($insights)) { $result['needs_review'] = TRUE; @@ -190,31 +186,13 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } } - $this->cleanupOrphanedInsights($normalized_urls); + if ($this->insightReport) { + $this->insightReport->cleanupOrphanedExternalResourceInsights($normalized_urls); + } return $results; } - /** - * Cleans up orphaned external resource insights that don't match any media. - */ - protected function cleanupOrphanedInsights(array $normalized_urls) { - $query = $this->database->select('xray_audit_insight', 'xai') - ->fields('xai', ['id', 'data']) - ->condition('type', 'external_resource'); - $insights = $query->execute()->fetchAll(); - - foreach ($insights as $insight) { - $normalized_url_insight = $insight->data; - - if (!in_array($normalized_url_insight, $normalized_urls)) { - $this->database->delete('xray_audit_insight') - ->condition('id', $insight->id) - ->execute(); - } - } - } - /** * {@inheritdoc} */ @@ -228,7 +206,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { 'entity_type' => $row->entity_type, 'bundle' => $row->bundle, 'resource' => $row->resource, - 'edit_link' => \Drupal::linkGenerator()->generate($this->t('Edit'), $row->edit_url), + 'edit_link' => $this->linkGenerator->generate($this->t('Edit'), $row->edit_url), ]; // Add insight columns if xray_audit_insight module is enabled. -- GitLab From 967e0abaa363f4ccf08b852fc94f011bd2d677de Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 15:25:55 +0200 Subject: [PATCH 06/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../XrayAuditExternalResourcesPlugin.php | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php index f38c6c2..929b963 100644 --- a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -127,7 +127,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } } - $normalized_urls = []; + $media_urls = []; // Process each bundle that has oembed fields. foreach ($oembed_fields as $bundle_id => $field_names) { // Get all media entities of this bundle. @@ -167,9 +167,24 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { if ($this->insightReport) { // Get insights of type 'external_resource' where // data matches the normalized resource URL. - $normalized_url = $this->urlResolver->getResourceUrl($resource_url); - $normalized_urls[] = $normalized_url; - $insights = $this->insightReport->getExternalResourceInsights($normalized_url); + $media_urls[] = $resource_url; + try { + $normalized_url = $this->urlResolver->getResourceUrl($resource_url); + $media_urls[] = $normalized_url; + $insights = $this->insightReport->getExternalResourceInsights($normalized_url); + } + catch (\Exception $e) { + // Log the exception to the xray_audit_insight table. + $this->insightReport->addInsightData( + 'external_resource', + $e->getMessage(), + $resource_url + ); + + $result['needs_review'] = TRUE; + // Use the message from the most recent insight. + $result['insight_message'] = $e->getMessage(); + } if (!empty($insights)) { $result['needs_review'] = TRUE; @@ -187,7 +202,7 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } if ($this->insightReport) { - $this->insightReport->cleanupOrphanedExternalResourceInsights($normalized_urls); + $this->insightReport->cleanupOrphanedExternalResourceInsights($media_urls); } return $results; -- GitLab From 983f712afed63a91118fcf0cdc8970bdf615280a Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Wed, 2 Apr 2025 16:03:03 +0200 Subject: [PATCH 07/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../XrayAuditExternalResourcesPlugin.php | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php index 929b963..b63a214 100644 --- a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -2,7 +2,9 @@ namespace Drupal\xray_audit\Plugin\xray_audit\tasks\ContentDisplay; +use Drupal\Component\Utility\Html; use Drupal\xray_audit\Plugin\XrayAuditTaskPluginBase; +use Drupal\xray_audit\Utils\XrayAuditTableFilter; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -215,45 +217,63 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { $build = []; $rows = []; + + $headers = [ + $this->t('Entity ID'), + $this->t('Entity Type'), + $this->t('Bundle'), + $this->t('Resource URL'), + $this->t('Edit'), + ]; + foreach ($data as $row) { + $highlight = FALSE; $row_data = [ - 'entity_id' => $row->entity_id, - 'entity_type' => $row->entity_type, - 'bundle' => $row->bundle, - 'resource' => $row->resource, - 'edit_link' => $this->linkGenerator->generate($this->t('Edit'), $row->edit_url), + $row->entity_id ?? '', + $row->entity_type ?? '', + $row->bundle ?? '', + $row->resource ?? '', + $this->linkGenerator->generate($this->t('Edit'), $row->edit_url) ?? '', ]; // Add insight columns if xray_audit_insight module is enabled. if ($this->moduleHandler->moduleExists('xray_audit_insight')) { - $row_data['needs_review'] = isset($row->needs_review) && $row->needs_review ? $this->t('Yes') : $this->t('No'); - $row_data['insight_message'] = $row->insight_message ?? ''; + $row_data[] = isset($row->needs_review) && $row->needs_review ? $this->t('Yes') : $this->t('No'); + $row_data[] = $row->insight_message ?? ''; + $highlight = isset($row->needs_review) && $row->needs_review; } - $rows[] = $row_data; + $rows[] = ['data' => $row_data, 'class' => $highlight ? ['xray-audit--highlighted'] : []]; } - $header = [ - 'Entity ID', - 'Entity Type', - 'Bundle', - 'Resource URL', - 'Edit', - ]; - // Add insight headers if xray_audit_insight module is enabled. if ($this->moduleHandler->moduleExists('xray_audit_insight')) { - $header[] = $this->t('Needs Review'); - $header[] = $this->t('Message'); + $headers[] = $this->t('Needs Review'); + $headers[] = $this->t('Message'); } - + $unique_id = Html::getUniqueId('xray-audit-external-resources'); $build['table'] = [ '#theme' => 'table', - '#header' => $header, + '#header' => $headers, + '#sticky' => TRUE, + '#weight' => 10, '#rows' => $rows, '#empty' => $this->t('No external resources found'), + '#attributes' => [ + 'class' => ['xray-audit__table'], + 'id' => $unique_id, + ], + '#attached' => [ + 'library' => [ + 'xray_audit/xray_audit', + ], + ], ]; + $columns_indexes = [0, 1, 2, 3, 4, 5, 6]; + $build['filter'] = XrayAuditTableFilter::generateRenderableFilterInput($unique_id, $columns_indexes, NULL, $headers); + $build['filter']['#weight'] = 6; + return $build; } -- GitLab From 66a488fcd3f34636aeb536bf0099a4fa3bec6662 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Thu, 3 Apr 2025 08:17:49 +0200 Subject: [PATCH 08/10] Issue #3482085 by eduardo morales alberti: Remove not necesary query --- .../src/XrayAuditInsightReport.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/modules/xray_audit_insight/src/XrayAuditInsightReport.php b/modules/xray_audit_insight/src/XrayAuditInsightReport.php index ac1d6e8..3911e3e 100644 --- a/modules/xray_audit_insight/src/XrayAuditInsightReport.php +++ b/modules/xray_audit_insight/src/XrayAuditInsightReport.php @@ -93,18 +93,11 @@ class XrayAuditInsightReport { * Array of active normalized URLs. */ public function cleanupOrphanedExternalResourceInsights(array $normalized_urls) { - $query = $this->database->select('xray_audit_insight', 'xai') - ->fields('xai', ['id', 'data']) - ->condition('type', 'external_resource'); - $insights = $query->execute()->fetchAll(); - foreach ($insights as $insight) { - if (!in_array($insight->data, $normalized_urls)) { - $this->database->delete('xray_audit_insight') - ->condition('id', $insight->id) - ->execute(); - } - } + $this->database->delete('xray_audit_insight') + ->condition('data', $normalized_urls, 'IN') + ->execute(); + } } -- GitLab From adef209b3fa6fb7bc41f2042e72cbdcdd79d035d Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Thu, 3 Apr 2025 08:19:24 +0200 Subject: [PATCH 09/10] Issue #3482085 by eduardo morales alberti: Solve condition --- modules/xray_audit_insight/src/XrayAuditInsightReport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xray_audit_insight/src/XrayAuditInsightReport.php b/modules/xray_audit_insight/src/XrayAuditInsightReport.php index 3911e3e..e0cabe9 100644 --- a/modules/xray_audit_insight/src/XrayAuditInsightReport.php +++ b/modules/xray_audit_insight/src/XrayAuditInsightReport.php @@ -95,7 +95,7 @@ class XrayAuditInsightReport { public function cleanupOrphanedExternalResourceInsights(array $normalized_urls) { $this->database->delete('xray_audit_insight') - ->condition('data', $normalized_urls, 'IN') + ->condition('data', $normalized_urls, 'NOT IN') ->execute(); } -- GitLab From 1d1e3d4535e0050970908e937b4aacbd6e70fd40 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Thu, 3 Apr 2025 08:41:42 +0200 Subject: [PATCH 10/10] Issue #3482085 by eduardo morales alberti: List medias that could not be retrieve --- .../XrayAuditExternalResourcesPlugin.php | 178 +++++++++++------- 1 file changed, 106 insertions(+), 72 deletions(-) diff --git a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php index b63a214..adaad2e 100644 --- a/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php +++ b/src/Plugin/xray_audit/tasks/ContentDisplay/XrayAuditExternalResourcesPlugin.php @@ -98,30 +98,33 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { * {@inheritdoc} */ public function getDataOperationResult(string $operation = '') { - $results = []; if (!$this->moduleHandler->moduleExists('media')) { - return $results; + return []; } - $media_storage = $this->entityTypeManager->getStorage('media'); - $display_storage = $this->entityTypeManager->getStorage('entity_view_display'); - // Get all media bundles. - $bundles = $this->entityTypeManager->getStorage('media_type')->loadMultiple(); + $oembed_fields = $this->getOembedFields(); + $results = $this->processMediaEntities($oembed_fields); + $this->cleanupInsights($results); + + return $results; + } - // Track fields using OEmbedFormatter. + /** + * Gets all OEmbed fields from media bundles. + */ + private function getOembedFields(): array { $oembed_fields = []; + $bundles = $this->entityTypeManager->getStorage('media_type')->loadMultiple(); + $display_storage = $this->entityTypeManager->getStorage('entity_view_display'); - // Find all view displays using OEmbedFormatter. foreach ($bundles as $bundle_id => $bundle) { - // Check all view modes for this bundle. $displays = $display_storage->loadByProperties([ 'targetEntityType' => 'media', 'bundle' => $bundle_id, ]); foreach ($displays as $display) { - $components = $display->getComponents(); - foreach ($components as $field_name => $component) { + foreach ($display->getComponents() as $field_name => $component) { if (isset($component['type']) && str_contains($component['type'], 'oembed')) { $oembed_fields[$bundle_id][$field_name] = $field_name; } @@ -129,10 +132,17 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } } - $media_urls = []; - // Process each bundle that has oembed fields. + return $oembed_fields; + } + + /** + * Process media entities and collect results. + */ + private function processMediaEntities(array $oembed_fields): array { + $results = []; + $media_storage = $this->entityTypeManager->getStorage('media'); + foreach ($oembed_fields as $bundle_id => $field_names) { - // Get all media entities of this bundle. $media_ids = $media_storage->getQuery() ->condition('bundle', $bundle_id) ->accessCheck(FALSE) @@ -143,71 +153,95 @@ class XrayAuditExternalResourcesPlugin extends XrayAuditTaskPluginBase { } $media_entities = $media_storage->loadMultiple($media_ids); - - // Process each media entity. foreach ($media_entities as $media_id => $media) { - foreach ($field_names as $field_name) { - if ($media->hasField($field_name) && !$media->get($field_name)->isEmpty()) { - // For oembed fields, the URL is typically in the 'value' property. - $field_items = $media->get($field_name); - - foreach ($field_items as $field_item) { - if ($field_item->getValue() && isset($field_item->getValue()['value'])) { - $resource_url = $field_item->getValue()['value']; - $result = [ - 'entity_id' => $media_id, - 'entity_type' => 'media', - 'bundle' => $bundle_id, - 'resource' => $resource_url, - 'edit_url' => $media->toUrl('edit-form'), - 'needs_review' => FALSE, - 'insight_message' => '', - ]; - - // Check if xray_audit_insight module is enabled - // and query for insights. - if ($this->insightReport) { - // Get insights of type 'external_resource' where - // data matches the normalized resource URL. - $media_urls[] = $resource_url; - try { - $normalized_url = $this->urlResolver->getResourceUrl($resource_url); - $media_urls[] = $normalized_url; - $insights = $this->insightReport->getExternalResourceInsights($normalized_url); - } - catch (\Exception $e) { - // Log the exception to the xray_audit_insight table. - $this->insightReport->addInsightData( - 'external_resource', - $e->getMessage(), - $resource_url - ); - - $result['needs_review'] = TRUE; - // Use the message from the most recent insight. - $result['insight_message'] = $e->getMessage(); - } - - if (!empty($insights)) { - $result['needs_review'] = TRUE; - // Use the message from the most recent insight. - $result['insight_message'] = end($insights)->message; - } - } - - $results[] = (object) $result; - } - } - } + $this->processMediaEntity($media, $media_id, $bundle_id, $field_names, $results); + } + } + + return $results; + } + + /** + * Process a single media entity. + */ + private function processMediaEntity($media, $media_id, $bundle_id, array $field_names, array &$results): void { + foreach ($field_names as $field_name) { + if (!$media->hasField($field_name) || $media->get($field_name)->isEmpty()) { + continue; + } + + foreach ($media->get($field_name) as $field_item) { + $value = $field_item->getValue(); + if (!isset($value['value'])) { + continue; } + + $result = $this->createBaseResult($media_id, $bundle_id, $value['value'], $media); + $this->addInsightData($result, $value['value']); + $results[] = (object) $result; } } + } + + /** + * Creates base result array for a media item. + */ + private function createBaseResult($media_id, $bundle_id, $resource_url, $media): array { + return [ + 'entity_id' => $media_id, + 'entity_type' => 'media', + 'bundle' => $bundle_id, + 'resource' => $resource_url, + 'edit_url' => $media->toUrl('edit-form'), + 'needs_review' => FALSE, + 'insight_message' => '', + ]; + } - if ($this->insightReport) { - $this->insightReport->cleanupOrphanedExternalResourceInsights($media_urls); + /** + * Adds insight data to the result. + */ + private function addInsightData(array &$result, string $resource_url): void { + if (!$this->insightReport) { + return; } - return $results; + try { + $normalized_url = $this->urlResolver->getResourceUrl($resource_url); + $insights = $this->insightReport->getExternalResourceInsights($normalized_url); + + if (!empty($insights)) { + $result['needs_review'] = TRUE; + $result['insight_message'] = end($insights)->message; + } + } + catch (\Exception $e) { + $this->insightReport->addInsightData('external_resource', $e->getMessage(), $resource_url); + $result['needs_review'] = TRUE; + $result['insight_message'] = $e->getMessage(); + } + } + + /** + * Cleanup orphaned insights. + */ + private function cleanupInsights(array $results): void { + if (!$this->insightReport) { + return; + } + + $media_urls = array_reduce($results, function ($urls, $result) { + $urls[] = $result->resource; + try { + $urls[] = $this->urlResolver->getResourceUrl($result->resource); + } + catch (\Exception) { + // Ignore URL resolution errors. + } + return $urls; + }, []); + + $this->insightReport->cleanupOrphanedExternalResourceInsights($media_urls); } /** -- GitLab