diff --git a/core/modules/media/src/Plugin/media/Source/OEmbed.php b/core/modules/media/src/Plugin/media/Source/OEmbed.php index 0a21e925f60..fd6bcf11a51 100644 --- a/core/modules/media/src/Plugin/media/Source/OEmbed.php +++ b/core/modules/media/src/Plugin/media/Source/OEmbed.php @@ -2,6 +2,7 @@ namespace Drupal\media\Plugin\media\Source; +use Drupal\Component\Render\PlainTextOutput; use Drupal\Component\Utility\Crypt; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\Display\EntityFormDisplayInterface; @@ -14,6 +15,7 @@ use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Url; +use Drupal\Core\Utility\Token; use Drupal\media\IFrameUrlHelper; use Drupal\media\OEmbed\Resource; use Drupal\media\OEmbed\ResourceException; @@ -126,6 +128,13 @@ class OEmbed extends MediaSourceBase implements OEmbedInterface { */ protected $fileSystem; + /** + * The token replacement service. + * + * @var \Drupal\Core\Utility\Token + */ + protected $token; + /** * Constructs a new OEmbed instance. * @@ -157,8 +166,10 @@ class OEmbed extends MediaSourceBase implements OEmbedInterface { * The iFrame URL helper service. * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system. + * @param \Drupal\Core\Utility\Token $token + * The token replacement service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, FieldTypePluginManagerInterface $field_type_manager, LoggerInterface $logger, MessengerInterface $messenger, ClientInterface $http_client, ResourceFetcherInterface $resource_fetcher, UrlResolverInterface $url_resolver, IFrameUrlHelper $iframe_url_helper, FileSystemInterface $file_system) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, FieldTypePluginManagerInterface $field_type_manager, LoggerInterface $logger, MessengerInterface $messenger, ClientInterface $http_client, ResourceFetcherInterface $resource_fetcher, UrlResolverInterface $url_resolver, IFrameUrlHelper $iframe_url_helper, FileSystemInterface $file_system, Token $token = NULL) { parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $field_type_manager, $config_factory); $this->logger = $logger; $this->messenger = $messenger; @@ -167,6 +178,11 @@ class OEmbed extends MediaSourceBase implements OEmbedInterface { $this->urlResolver = $url_resolver; $this->iFrameUrlHelper = $iframe_url_helper; $this->fileSystem = $file_system; + if (empty($token)) { + @trigger_error('The token service should be passed to ' . __METHOD__ . '() and is required in drupal:10.0.0. See https://www.drupal.org/node/3240036', E_USER_DEPRECATED); + $token = \Drupal::token(); + } + $this->token = $token; } /** @@ -187,7 +203,8 @@ class OEmbed extends MediaSourceBase implements OEmbedInterface { $container->get('media.oembed.resource_fetcher'), $container->get('media.oembed.url_resolver'), $container->get('media.oembed.iframe_url_helper'), - $container->get('file_system') + $container->get('file_system'), + $container->get('token') ); } @@ -362,7 +379,7 @@ class OEmbed extends MediaSourceBase implements OEmbedInterface { */ public function defaultConfiguration() { return [ - 'thumbnails_directory' => 'public://oembed_thumbnails', + 'thumbnails_directory' => 'public://oembed_thumbnails/[date:custom:Y-m]', 'providers' => [], ] + parent::defaultConfiguration(); } @@ -392,10 +409,13 @@ class OEmbed extends MediaSourceBase implements OEmbedInterface { return NULL; } - // Ensure that we can write to the local directory where thumbnails are - // stored. + // Use the configured directory to store thumbnails. The directory can + // contain basic (i.e., global) tokens. If any of the replaced tokens + // contain HTML, the tags will be removed and XML entities will be decoded. $configuration = $this->getConfiguration(); $directory = $configuration['thumbnails_directory']; + $directory = $this->token->replace($directory); + $directory = PlainTextOutput::renderFromHtml($directory); // The local thumbnail doesn't exist yet, so try to download it. First, // ensure that the destination directory is writable, and if it's not, diff --git a/core/modules/media/tests/src/Kernel/OEmbedSourceTest.php b/core/modules/media/tests/src/Kernel/OEmbedSourceTest.php index 02b481d9e3a..be3b092508f 100644 --- a/core/modules/media/tests/src/Kernel/OEmbedSourceTest.php +++ b/core/modules/media/tests/src/Kernel/OEmbedSourceTest.php @@ -135,19 +135,36 @@ class OEmbedSourceTest extends MediaKernelTestBase { $media_type = $this->createMediaType('oembed:video'); $source = $media_type->getSource(); + // Add some HTML to the global site slogan, and use the site:slogan token in + // the thumbnail path, in order to prove that the final thumbnail path is + // stripped of HTML tags, and XML entities are decoded. + $this->config('system.site') + ->set('slogan', '

this&that

') + ->save(); + $configuration = $source->getConfiguration(); + $configuration['thumbnails_directory'] .= '/[site:slogan]'; + $source->setConfiguration($configuration); + $media_type->save(); + $media = Media::create([ 'bundle' => $media_type->id(), $source->getSourceFieldDefinition($media_type)->getName() => $this->randomString(), ]); $media->save(); + // The thumbnail directory should include the current date, as per the + // default configuration of the oEmbed source plugin. + $date = date('Y-m', $this->container->get('datetime.time')->getRequestTime()); + // The thumbnail should have a file extension, even if it wasn't in the URL. - $expected_uri = 'public://oembed_thumbnails/' . Crypt::hashBase64($thumbnail_url) . ".$expected_extension"; + $expected_uri = "public://oembed_thumbnails/$date/this&that/" . Crypt::hashBase64($thumbnail_url) . ".$expected_extension"; $this->assertSame($expected_uri, $source->getMetadata($media, 'thumbnail_uri')); + // Even if we get the thumbnail_uri more than once, it should only be // downloaded once. The HTTP client will throw an exception if we try to // do another request without setting up another response. $source->getMetadata($media, 'thumbnail_uri'); + // The downloaded thumbnail should be usable by the image toolkit. $this->assertFileExists($expected_uri); /** @var \Drupal\Core\Image\Image $image */ diff --git a/core/profiles/demo_umami/config/install/media.type.remote_video.yml b/core/profiles/demo_umami/config/install/media.type.remote_video.yml index f2e7b330ea3..ad299681f39 100644 --- a/core/profiles/demo_umami/config/install/media.type.remote_video.yml +++ b/core/profiles/demo_umami/config/install/media.type.remote_video.yml @@ -8,7 +8,7 @@ source: 'oembed:video' queue_thumbnail_downloads: false new_revision: true source_configuration: - thumbnails_directory: 'public://oembed_thumbnails' + thumbnails_directory: 'public://oembed_thumbnails/[date:custom:Y-m]' providers: - YouTube - Vimeo diff --git a/core/profiles/standard/config/optional/media.type.remote_video.yml b/core/profiles/standard/config/optional/media.type.remote_video.yml index f2e7b330ea3..ad299681f39 100644 --- a/core/profiles/standard/config/optional/media.type.remote_video.yml +++ b/core/profiles/standard/config/optional/media.type.remote_video.yml @@ -8,7 +8,7 @@ source: 'oembed:video' queue_thumbnail_downloads: false new_revision: true source_configuration: - thumbnails_directory: 'public://oembed_thumbnails' + thumbnails_directory: 'public://oembed_thumbnails/[date:custom:Y-m]' providers: - YouTube - Vimeo