diff --git a/apps/cms/config/sync/entity_usage.settings.yml b/apps/cms/config/sync/entity_usage.settings.yml index 575153e65..8f69040e1 100644 --- a/apps/cms/config/sync/entity_usage.settings.yml +++ b/apps/cms/config/sync/entity_usage.settings.yml @@ -5,8 +5,8 @@ local_task_enabled_entity_types: - node track_enabled_source_entity_types: - node - - content_moderation_state - block_content + - content_moderation_state - menu_link_content - media - redirect @@ -14,8 +14,8 @@ track_enabled_source_entity_types: - path_alias track_enabled_target_entity_types: - node - - content_moderation_state - block_content + - content_moderation_state - menu_link_content - media - redirect @@ -32,6 +32,8 @@ track_enabled_plugins: - html_link - ckeditor_image - block_field + - gutenberg_linked_content + - gutenberg_referenced_content - gutenberg_media_embed track_enabled_base_fields: false site_domains: { } diff --git a/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml b/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml index 844075fdc..4eded13a4 100644 --- a/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml +++ b/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml @@ -28,6 +28,6 @@ matchers: bundles: document: document group_by_bundle: 0 - substitution_type: null + substitution_type: canonical limit: '100' weight: -9 diff --git a/packages/drupal/custom/custom.module b/packages/drupal/custom/custom.module index 030c5f087..07ac7bc44 100644 --- a/packages/drupal/custom/custom.module +++ b/packages/drupal/custom/custom.module @@ -6,6 +6,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; use Drupal\Core\Render\RenderContext; +use Drupal\Core\StreamWrapper\LocalStream; +use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\Utility\Error as ErrorUtil; use Drupal\file\Entity\File; use Drupal\media\Entity\Media; @@ -90,6 +92,75 @@ function custom_silverback_gutenberg_link_processor_outbound_url_alter( } } +/** + * Implements hook_silverback_gutenberg_link_processor_inbound_link_alter(). + * @param \DOMElement $link + * @param \Drupal\silverback_gutenberg\LinkProcessor $linkProcessor + * + * @return void + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ +function custom_silverback_gutenberg_link_processor_inbound_link_alter( + \DOMElement $link, + LinkProcessor $linkProcessor +) { + // For inbound links (when data gets saved), if the link points to a public + // file, then we want to replace the href value with "media/uuid/edit" and + // also make sure the data-id and data-entity-type attributes have the proper + // values (the uuid and the 'media' value). This is a special case for media, + // because the url of the media item is replaced by + // custom_silverback_gutenberg_link_processor_outbound_url_alter() with the + // file path, so now on inbound we need to basically do the opposite. This is + // needed so that the entity usage integration works properly (where the + // data-id and data-entity-type attributes are checked). + $href = $link->getAttribute('href'); + /* @var \Drupal\Core\StreamWrapper\StreamWrapperManager $wrapperManager */ + $wrapperManager = \Drupal::service('stream_wrapper_manager'); + /* @var \Drupal\Core\StreamWrapper\StreamWrapperInterface[] $visibleWrappers */ + $visibleWrappers = $wrapperManager->getWrappers(StreamWrapperInterface::VISIBLE); + foreach ($visibleWrappers as $scheme => $wrapperInfo) { + $wrapper = $wrapperManager->getViaScheme($scheme); + // We are only handle local streams for now. + if (!$wrapper instanceof LocalStream) { + continue; + } + if (!str_starts_with($href, '/' . $wrapper->getDirectoryPath() . '/')) { + continue; + } + // When searching for a file inside the database, the wrapper uri is used + // instead of the directory path. That is why we need the wrapper in the + // first place. + $fileuri = str_replace('/' . $wrapper->getDirectoryPath() . '/', $wrapper->getUri(), urldecode($href)); + $files = \Drupal::entityTypeManager() + ->getStorage('file') + ->loadByProperties(['uri' => $fileuri]); + // No files found, just continue to the next wrapper. + if (empty($files)) { + continue; + } + $file = array_shift($files); + $usageList = \Drupal::service('file.usage')->listUsage($file); + // If the media file usage list is empty, then this is probably some kind of + // orphan file, or tracked by some other entity type. + if (empty($usageList['file']['media'])) { + continue; + } + $mids = array_keys($usageList['file']['media']); + $mid = reset($mids); + $media = Media::load($mid); + if (empty($media)) { + continue; + } + // If we got here, we found a matching media item, so we can populate the + // link metadata with its values and just break out of the wrappers loop. + $link->setAttribute('href', '/media/' . $media->uuid() . '/edit'); + $link->setAttribute('data-id', $media->uuid()); + $link->setAttribute('data-entity-type', 'media'); + break; + } +} + /** * Implements hook_form_alter(). * diff --git a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml index 5561d002f..0cceba025 100644 --- a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml +++ b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml @@ -4,6 +4,8 @@ _meta: uuid: 25086be7-ca5f-4ff8-9695-b9c71a676d4e bundle: page default_langcode: en + depends: + b998ae5e-8b56-40ca-8f80-fd4404e7e716: media default: revision_uid: - @@ -47,7 +49,7 @@ default: -

link to file

+

link to file

diff --git a/tests/e2e/specs/drupal/entity-usage.spec.ts b/tests/e2e/specs/drupal/entity-usage.spec.ts new file mode 100644 index 000000000..118ac4421 --- /dev/null +++ b/tests/e2e/specs/drupal/entity-usage.spec.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; + +import { cmsUrl } from '../../helpers/url'; + +test.describe('entity-usage', () => { + test.use({ storageState: '.auth/admin.json' }); + + test('media usage works with inline doc links', async ({ page }) => { + await page.goto(cmsUrl('/admin/content/media')); + page.locator('div.view-content').getByText('Example DOCX document').click(); + page.locator('a.tabs__link').getByText('Usage').click(); + await expect( + page.locator('table').getByText('Page with links'), + ).toBeVisible(); + }); +}); diff --git a/tests/schema/specs/links.spec.ts b/tests/schema/specs/links.spec.ts index f1196bc00..2262cb2b1 100644 --- a/tests/schema/specs/links.spec.ts +++ b/tests/schema/specs/links.spec.ts @@ -29,7 +29,7 @@ test('Links', async () => { { "__typename": "BlockMarkup", "markup": " -

link to file

+

link to file

link to page

",