diff --git a/core/profiles/standard/config/optional/core.entity_form_display.media.youtube.default.yml b/core/modules/media/config/install/core.entity_form_display.media.youtube.default.yml similarity index 100% rename from core/profiles/standard/config/optional/core.entity_form_display.media.youtube.default.yml rename to core/modules/media/config/install/core.entity_form_display.media.youtube.default.yml diff --git a/core/modules/media/config/install/core.entity_view_display.media.youtube.default.yml b/core/modules/media/config/install/core.entity_view_display.media.youtube.default.yml new file mode 100644 index 0000000..08d2525 --- /dev/null +++ b/core/modules/media/config/install/core.entity_view_display.media.youtube.default.yml @@ -0,0 +1,28 @@ +langcode: en +status: true +dependencies: + config: + - field.field.media.youtube.field_media_oembed + - image.style.thumbnail + - media.type.youtube + module: + - image + - media + - user +id: media.youtube.default +targetEntityType: media +bundle: youtube +mode: default +content: + field_media_oembed: + label: hidden + settings: { } + third_party_settings: { } + type: oembed + weight: 0 + region: content +hidden: + created: true + langcode: true + thumbnail: true + uid: true diff --git a/core/profiles/standard/config/optional/field.field.media.youtube.field_media_oembed.yml b/core/modules/media/config/install/field.field.media.youtube.field_media_oembed.yml similarity index 100% rename from core/profiles/standard/config/optional/field.field.media.youtube.field_media_oembed.yml rename to core/modules/media/config/install/field.field.media.youtube.field_media_oembed.yml diff --git a/core/profiles/standard/config/optional/field.storage.media.field_media_oembed.yml b/core/modules/media/config/install/field.storage.media.field_media_oembed.yml similarity index 100% rename from core/profiles/standard/config/optional/field.storage.media.field_media_oembed.yml rename to core/modules/media/config/install/field.storage.media.field_media_oembed.yml diff --git a/core/profiles/standard/config/optional/media.type.youtube.yml b/core/modules/media/config/install/media.type.youtube.yml similarity index 59% rename from core/profiles/standard/config/optional/media.type.youtube.yml rename to core/modules/media/config/install/media.type.youtube.yml index 6bf0836..5276a7e 100644 --- a/core/profiles/standard/config/optional/media.type.youtube.yml +++ b/core/modules/media/config/install/media.type.youtube.yml @@ -3,11 +3,11 @@ status: true dependencies: { } id: youtube label: YouTube -description: 'YouTube videos.' -handler: oembed +description: "Use thie media type to store remote content such as youtube videos." +source: oembed queue_thumbnail_downloads: false -new_revision: false -handler_configuration: +new_revision: true +source_configuration: thumbnails_location: 'public://oembed_thumbnails' source_field: field_media_oembed field_map: { } diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml index 9ac76f3..9130309 100644 --- a/core/modules/media/config/schema/media.schema.yml +++ b/core/modules/media/config/schema/media.schema.yml @@ -52,17 +52,13 @@ media.source.image: type: media.source.field_aware label: '"Image" media source configuration' +media.source.oembed: + type: media.source.field_aware + label: '"oEmbed" media source configuration' + media.source.field_aware: type: mapping mapping: source_field: type: string label: 'Source field' - -media.handler.oembed: - type: media.handler.field_aware - label: 'oEmbed handler configuration' - mapping: - thumbnails_location: - type: string - label: 'oEmbed thumbnails location' diff --git a/core/modules/media/config/schema/media.schema.yml.orig b/core/modules/media/config/schema/media.schema.yml.orig deleted file mode 100644 index dad5189..0000000 --- a/core/modules/media/config/schema/media.schema.yml.orig +++ /dev/null @@ -1,60 +0,0 @@ -media.settings: - type: config_object - label: 'Media settings' - mapping: - icon_base_uri: - type: string - label: 'Full URI to a folder where the media icons will be installed' - -media.type.*: - type: config_entity - label: 'Media type' - mapping: - id: - type: string - label: 'Machine name' - label: - type: label - label: 'Name' - description: - type: text - label: 'Description' - source: - type: string - label: 'Source' - source_configuration: - type: media.source.[%parent.source] - queue_thumbnail_downloads: - type: boolean - label: 'Whether the thumbnail downloads should be queued' - new_revision: - type: boolean - label: 'Whether a new revision should be created by default' - field_map: - type: sequence - label: 'Field map' - sequence: - type: string - -field.formatter.settings.media_thumbnail: - type: field.formatter.settings.image - label: 'Media thumbnail field display format settings' - -media.source.*: - type: mapping - label: 'Media source settings' - -media.source.file: - type: media.source.field_aware - label: '"File" media source configuration' - -media.source.image: - type: media.source.field_aware - label: '"Image" media source configuration' - -media.source.field_aware: - type: mapping - mapping: - source_field: - type: string - label: 'Source field' diff --git a/core/modules/media/media.services.yml b/core/modules/media/media.services.yml index 8feecad..3a1f3f5 100644 --- a/core/modules/media/media.services.yml +++ b/core/modules/media/media.services.yml @@ -9,4 +9,4 @@ services: - { name: access_check, applies_to: _access_media_revision } media.oembed: class: Drupal\media\OEmbed - arguments: ['@http_client_factory', '@cache.default'] + arguments: ['@http_client_factory', '@cache.default', '@logger.factory'] diff --git a/core/modules/media/src/OEmbed.php b/core/modules/media/src/OEmbed.php index 1835166..bf2a6b4 100644 --- a/core/modules/media/src/OEmbed.php +++ b/core/modules/media/src/OEmbed.php @@ -4,12 +4,16 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Http\ClientFactory; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; /** * OEmbed service. */ class OEmbed implements OEmbedInterface { + use StringTranslationTrait; + /** * Cache key to be used to store providers info into cache. * @@ -65,23 +69,33 @@ class OEmbed implements OEmbedInterface { protected $discovered; /** + * The logger channel for media. + * + * @var \Drupal\Core\Logger\LoggerChannelInterface + */ + protected $logger; + + /** * Constructs OEmbed class. * * @param \Drupal\Core\Http\ClientFactory $client_factory * The HTTP client factory service. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger channel for media. */ - public function __construct(ClientFactory $client_factory, CacheBackendInterface $cache) { + public function __construct(ClientFactory $client_factory, CacheBackendInterface $cache, LoggerChannelFactoryInterface $logger_factory) { $this->clientFactory = $client_factory; $this->cache = $cache; + $this->logger = $logger_factory->get('media'); } /** * {@inheritdoc} */ public function getProviders() { - if ($this->providers) { + if (!empty($this->providers)) { return $this->providers; } elseif ($data = $this->cache->get(static::$providersCacheKey)) { @@ -91,17 +105,34 @@ public function getProviders() { else { $response = $this->clientFactory->fromOptions()->get(static::$providersUrl); if ($response->getStatusCode() !== 200) { - // TODO Handle errors. + drupal_set_message($this->t('Could not retrieve the providers list from the remote oEmbed database.'), 'error'); + $this->logger->error('Remote oEmbed providers database returned status code @code.', [ + '@code' => $response->getStatusCode(), + ]); + return FALSE; } $providers = \GuzzleHttp\json_decode($response->getBody()->getContents(), TRUE); if (!is_array($providers)) { - // TODO Handle errors. + drupal_set_message($this->t('An error occurred while trying to retrieve the providers list from the remote oEmbed database.'), 'error'); + $this->logger->error('Remote oEmbed providers database returned incorrect response content. Providers: @response', [ + '@response' => json_encode($providers), + ]); + return FALSE; } - $this->cache->set(self::$providersCacheKey, $providers, static::$providersCacheLifetime); - $this->providers = $providers; + // Some provider names may contain dot chars ("."), which are not allowed + // in config keys. We store them in an array where keys are hex-converted + // names, in order to allow an easy conversion back to their original + // names when necessary. + $keyed_providers = []; + foreach ($providers as $provider) { + $keyed_providers[bin2hex($provider['provider_name'])] = $provider; + } + + $this->cache->set(self::$providersCacheKey, $keyed_providers, static::$providersCacheLifetime); + $this->providers = $keyed_providers; return $this->providers; } } @@ -109,13 +140,21 @@ public function getProviders() { /** * {@inheritdoc} */ - public function matchUrl($url, $providers_limit = NULL) { + public function matchUrl($url, array $providers_limit = []) { $providers = $this->getProviders(); - if (isset($providers)) { + if (!empty($providers_limit)) { $providers = array_intersect_key($providers, $providers_limit); } foreach ($providers as $provider_name => $provider_info) { + // @TODO Figure out a way of making this validation more robust. Youtube, + // for instance, does not come with schemes... + if ($provider_info['provider_name'] === 'YouTube') { + $provider_info['endpoints'][0]['schemes'] = [ + 'http*://*youtube.com/*', + 'http*://*youtu.be/*', + ]; + } if (!empty($provider_info['endpoints'])) { foreach ($provider_info['endpoints'] as $endpoint) { if (!empty($endpoint['schemes'])) { @@ -146,7 +185,12 @@ public function doUrlDiscovery($url) { $response = $this->clientFactory->fromOptions()->get($url); if ($response->getStatusCode() !== 200) { - // TODO + drupal_set_message($this->t('Could not retrieve the remote URL correctly.'), 'error'); + $this->logger->error('Resource for URL @url responded with status code @code.', [ + '@url' => $url, + '@code' => $response->getStatusCode(), + ]); + return FALSE; } $content = $response->getBody()->getContents(); @@ -155,7 +199,8 @@ public function doUrlDiscovery($url) { // TODO this causes annoying warning with YT videos. Fix it: // Warning: DOMDocument::loadHTML(): htmlParseEntityRef: no name in Entity if (!@$dom->loadHTML($content)) { - // TODO + drupal_set_message($this->t('The remote URL returned an incorrect document structure.'), 'error'); + return FALSE; } $xpath = new \DOMXpath($dom); @@ -177,6 +222,19 @@ public function doUrlDiscovery($url) { /** * {@inheritdoc} */ + public function isOEmbedResource($url) { + if ($this->doUrlDiscovery($url)) { + return TRUE; + } + + // TODO handle providers that do not support discovery. + + return FALSE; + } + + /** + * {@inheritdoc} + */ public function fetchResource($endpoint_url) { if (!empty($this->resources[$endpoint_url])) { return $this->resources[$endpoint_url]; @@ -184,7 +242,11 @@ public function fetchResource($endpoint_url) { $response = $this->clientFactory->fromOptions()->get($endpoint_url); if ($response->getStatusCode() !== 200) { - // TODO Handle errors. + drupal_set_message($this->t('Could not retrieve the remote URL.'), 'error'); + $this->logger->error('Remote resource returned status code @code.', [ + '@code' => $response->getStatusCode(), + ]); + return FALSE; } $format = $response->getHeader('Content-Type'); @@ -193,7 +255,11 @@ public function fetchResource($endpoint_url) { $data = \GuzzleHttp\json_decode($content, TRUE); if (!is_array($data)) { - // TODO Handle errors. + drupal_set_message($this->t('An error occurred while trying to retrieve the remote URL content.'), 'error'); + $this->logger->error('Remote resource returned incorrect response content. Data: @response', [ + '@response' => json_encode($data), + ]); + return FALSE; } $this->resources[$endpoint_url] = $data; @@ -203,7 +269,11 @@ public function fetchResource($endpoint_url) { $data = \GuzzleHttp\json_decode(\GuzzleHttp\json_encode($content), TRUE); if (!is_array($data)) { - // TODO Handle errors. + drupal_set_message($this->t('An error occurred while trying to retrieve the remote URL content.'), 'error'); + $this->logger->error('Remote resource returned incorrect response content. Data: @response', [ + '@response' => json_encode($data), + ]); + return FALSE; } $this->resources[$endpoint_url] = $data; @@ -213,17 +283,4 @@ public function fetchResource($endpoint_url) { return FALSE; } - /** - * {@inheritdoc} - */ - public function isOEmbedResource($url) { - if ($this->doUrlDiscovery($url)) { - return TRUE; - } - - // TODO handle providers that do not support discovery. - - return FALSE; - } - -} \ No newline at end of file +} diff --git a/core/modules/media/src/OEmbedInterface.php b/core/modules/media/src/OEmbedInterface.php index bf64c2b..4a83894 100644 --- a/core/modules/media/src/OEmbedInterface.php +++ b/core/modules/media/src/OEmbedInterface.php @@ -10,19 +10,19 @@ /** * Gets oEmbed providers information. * - * Returns multi-dimensional array where each provider is represented as a - * single top-level array element. Each individual element has the following - * values: + * Returns a multi-dimensional array where each provider is represented as a + * single top-level array element. Each individual element is keyed by the + * provider's name codified with bin2hex(), and has the following values: * - provider_name: Human readable name of the provider. * - provider_url: Main URL of the provider. - * - endpoints: List of endpoints provider exposes. Each endpoint is an array - * with the following values: - * - url: Endpoint's URL. + * - endpoints: List of endpoints this provider exposes. Each endpoint is an + * array with the following values: + * - url: The endpoint's URL. * - schemes: List of URL schemes supported by the provider. * - formats: List of supported formats. Can be "json", "xml" or both. - * - discovery: Whether provider supports oEmbed discovery. + * - discovery: Whether the provider supports oEmbed discovery. * - * @return array + * @return array[] * Information about oEmbed providers. */ public function getProviders(); @@ -31,51 +31,53 @@ public function getProviders(); * Tries to match the URL against the database of oEmbed providers. * * @param string $url - * URL to be tested - * @param array|null $providers_limit - * List of provider machine names to limit too. All providers are considered - * if omitted. + * The URL to be tested. + * @param array $allowed_providers + * (optional) A list of providers to restrict to. If present, this needs to + * be an array where keys are the provider names (codified with bin2hex()), + * and the values are irrelevant. If ommitted, all providers will be + * considered. * - * @return array|false - * Associative array with two values: - * - provider: Matching provider's name. + * @return array|bool + * If a match was found, will return an associative array with two values: + * - provider: Matching provider's name (coded with bin2hex()). * - endpoint: URL of the matching endpoint. - * FALSE if a match was not found. + * Will return FALSE if no match was found. + * + * @see \Drupal\media\OEmbedInterface::getProviders() */ - public function matchUrl($url, $providers_limit = NULL); + public function matchUrl($url, array $allowed_providers = []); /** - * Runs oEmbed discovery and returns endpoint URL if successful. + * Runs oEmbed discovery and returns the endpoint URL if successful. * - * @param $url - * URL of the resource. + * @param string $url + * The resource's URL. * - * @return string|false - * Resource specific URL of the oEmbed endpoint or FALSE if the discovery - * was not successful. + * @return string|bool + * URL of the oEmbed endpoint, or FALSE if the discovery was not successful. */ public function doUrlDiscovery($url); - /** - * Determines whether it is an oEmbed resource. + * Determines whether a given URL is an oEmbed resource. * - * @param $url + * @param string $url * URL of the resource. * * @return bool - * TRUE if the URL represents a valid oEmbed resource and FALSE if not. + * TRUE if the URL represents a valid oEmbed resource, or FALSE otherwise. */ public function isOEmbedResource($url); /** * Fetches information about the oEmbed resource. * - * @param $endpoint_url - * Resource specific URL of the oEmbed endpoint. + * @param string $endpoint_url + * Resource-specific URL of the oEmbed endpoint. * - * @return array|false - * Resource information as returned from the oEmbed endpoint or FALSE if + * @return array|bool + * Resource information as returned from the oEmbed endpoint, or FALSE if * the resource could not be fetched. */ public function fetchResource($endpoint_url); diff --git a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php index aea168b..f542d44 100644 --- a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php @@ -2,8 +2,12 @@ namespace Drupal\media\Plugin\Field\FieldFormatter; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FormatterBase; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\media\OEmbedInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Plugin implementation of the 'link' formatter. @@ -12,11 +16,61 @@ * id = "oembed", * label = @Translation("oEmbed"), * field_types = { - * "link" + * "link", + * "string", + * "string_long", * } * ) */ -class OEmbedFormatter extends FormatterBase { +class OEmbedFormatter extends FormatterBase implements ContainerFactoryPluginInterface { + + /** + * The oEmbed service. + * + * @var \Drupal\media\OEmbedInterface + */ + protected $oEmbed; + + /** + * Constructs a EntityReferenceEntityFormatter instance. + * + * @param string $plugin_id + * The plugin_id for the formatter. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The definition of the field to which the formatter is associated. + * @param array $settings + * The formatter settings. + * @param string $label + * The formatter label display setting. + * @param string $view_mode + * The view mode. + * @param array $third_party_settings + * Any third party settings settings. + * @param \Drupal\media\OEmbedInterface $oembed + * The oEmbed service. + */ + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, OEmbedInterface $oembed) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); + $this->oEmbed = $oembed; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + $container->get('media.oembed') + ); + } /** * {@inheritdoc} @@ -25,16 +79,25 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $element = []; foreach ($items as $delta => $item) { - $resource = \Drupal::service('media.oembed')->fetchResource(\Drupal::service('media.oembed')->doUrlDiscovery($item->uri)); - $element[$delta] = [ - 'data' => [ + $main_property = $item->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName(); + if (!empty($item->{$main_property})) { + $resource = $this->oEmbed->fetchResource($this->oEmbed->doUrlDiscovery($item->{$main_property})); + $element[$delta] = [ '#markup' => $resource['html'], '#allowed_tags' => ['iframe'], - ], - ]; + ]; + } } return $element; } + /** + * {@inheritdoc} + */ + public static function isApplicable(FieldDefinitionInterface $field_definition) { + // This formatter is only available for fields attached to media items. + return ($field_definition->getTargetEntityTypeId() === 'media'); + } + } diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php index 2d83849..a0e1313 100644 --- a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php +++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraint.php @@ -10,7 +10,7 @@ * @Constraint( * id = "oembed_provider", * label = @Translation("oEmbed provider", context = "Validation"), - * type = { "link"} + * type = {"link", "string", "string_long"} * ) */ class OEmbedProviderConstraint extends Constraint { @@ -20,6 +20,6 @@ class OEmbedProviderConstraint extends Constraint { * * @var string */ - public $message = 'oEmbed provider @provider is not allowed.'; + public $message = 'The provider used is not allowed.'; } diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php index de0bb22..7d6eb54 100644 --- a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php +++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedProviderConstraintValidator.php @@ -3,6 +3,8 @@ namespace Drupal\media\Plugin\Validation\Constraint; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\media\MediaInterface; use Drupal\media\OEmbedInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; @@ -42,9 +44,21 @@ public static function create(ContainerInterface $container) { */ public function validate($value, Constraint $constraint) { // TODO check if resource belongs to an allowed provider. - - - //$this->context->addViolation($constraint->message, ['@provider' => 'Not allowed provider']); + /** @var FieldItemListInterface $value */ + $foo = 'ar'; + /** @var MediaInterface $media */ + $media = $value->getEntity(); + $source_config = $media->getSource()->getConfiguration(); + if (!empty($source_config['allowed_providers'])) { + $allowed_providers = array_map(function ($name) { + return hex2bin($name); + }, $source_config['allowed_providers']); + $main_property = $value->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName(); + $url_matches = $this->oEmbed->matchUrl($value->first()->get($main_property)->getString(), $allowed_providers); + if (!$url_matches) { + $this->context->addViolation($constraint->message); + } + } } } diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php index 75475b2..2b9d75d 100644 --- a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php +++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraint.php @@ -10,7 +10,7 @@ * @Constraint( * id = "oembed_resource", * label = @Translation("oEmbed resource", context = "Validation"), - * type = { "link", "string", "string_long"} + * type = {"link", "string", "string_long"} * ) */ class OEmbedResourceConstraint extends Constraint { @@ -20,6 +20,6 @@ class OEmbedResourceConstraint extends Constraint { * * @var string */ - public $message = 'Provided URL does not represent a valid oEmbed resource.'; + public $message = 'The provided URL does not represent a valid oEmbed resource.'; } diff --git a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php index 7e7d69d..bfaaa67 100644 --- a/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php +++ b/core/modules/media/src/Plugin/Validation/Constraint/OEmbedResourceConstraintValidator.php @@ -41,8 +41,8 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ public function validate($value, Constraint $constraint) { - $main_property = $value->mainPropertyName(); - if (!$this->oEmbed->isOEmbedResource($value->get($main_property)->getString())) { + $main_property = $value->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName(); + if (!$this->oEmbed->isOEmbedResource($value->first()->get($main_property)->getString())) { $this->context->addViolation($constraint->message); } } diff --git a/core/modules/media/src/Plugin/media/Handler/OEmbed.php b/core/modules/media/src/Plugin/media/Source/OEmbed.php similarity index 60% rename from core/modules/media/src/Plugin/media/Handler/OEmbed.php rename to core/modules/media/src/Plugin/media/Source/OEmbed.php index b721b3d..5b57996 100644 --- a/core/modules/media/src/Plugin/media/Handler/OEmbed.php +++ b/core/modules/media/src/Plugin/media/Source/OEmbed.php @@ -1,6 +1,6 @@ oEmbed = $oembed; $this->logger = $logger; } @@ -89,59 +91,71 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function getProvidedFields() { + public function getMetadataAttributes() { return [ - 'type' => ['label' => $this->t('Resource type')], - 'title' => ['label' => $this->t('Resource title')], - 'author_name' => ['label' => $this->t('The name of the author/owner')], - 'author_url' => ['label' => $this->t('The URL of the author/owner')], - 'provider_name' => ['label' => $this->t("The provider's name")], - 'provider_url' => ['label' => $this->t('The URL of the provider')], - 'cache_age' => ['label' => $this->t('Suggested cache lifetime')], - 'thumbnail_url' => ['label' => $this->t('The URL of the thumbnail')], - 'thumbnail_local_uri' => ['label' => $this->t('The URL of the thumbnail')], - 'thumbnail_local' => ['label' => $this->t('The URL of the thumbnail')], - 'thumbnail_width' => ['label' => $this->t('Thumbnail width')], - 'thumbnail_height' => ['label' => $this->t('Thumbnail height')], - 'url' => ['label' => $this->t('The source URL of the resource')], - 'width' => ['label' => $this->t('The width of the resource')], - 'height' => ['label' => $this->t('The height of the resource')], - 'html' => ['label' => $this->t('HTML representation of the resource')], + 'type' => $this->t('Resource type'), + 'title' => $this->t('Resource title'), + 'author_name' => $this->t('The name of the author/owner'), + 'author_url' => $this->t('The URL of the author/owner'), + 'provider_name' => $this->t("The provider's name"), + 'provider_url' => $this->t('The URL of the provider'), + 'cache_age' => $this->t('Suggested cache lifetime'), + 'thumbnail_url' => $this->t('The remote URL of the thumbnail'), + 'thumbnail_local_uri' => $this->t('The local URI of the thumbnail'), + 'thumbnail_local' => $this->t('The local URL of the thumbnail'), + 'thumbnail_width' => $this->t('Thumbnail width'), + 'thumbnail_height' => $this->t('Thumbnail height'), + 'url' => $this->t('The source URL of the resource'), + 'width' => $this->t('The width of the resource'), + 'height' => $this->t('The height of the resource'), + 'html' => $this->t('The HTML representation of the resource'), ]; } /** * {@inheritdoc} */ - public function getField(MediaInterface $media, $name) { + public function getMetadata(MediaInterface $media, $name) { $main_property = $media->get($this->configuration['source_field'])->first()->mainPropertyName(); $resource_url = $media->get($this->configuration['source_field'])->first()->get($main_property)->getString(); $oembed_data = $this->oEmbed->fetchResource($this->oEmbed->doUrlDiscovery($resource_url)); switch ($name) { case 'thumbnail_local': - $local_uri = $this->getField($media, 'thumbnail_local_uri'); + $local_uri = $this->getMetadata($media, 'thumbnail_local_uri'); if ($local_uri) { if (file_exists($local_uri)) { return $local_uri; } else { - $image_data = file_get_contents($this->getField($media, 'thumbnail_url')); + $image_data = file_get_contents($this->getMetadata($media, 'thumbnail_url')); if ($image_data) { - return file_unmanaged_save_data($image_data, $local_uri, FILE_EXISTS_REPLACE); + return file_unmanaged_save_data($image_data, $local_uri, FILE_EXISTS_REPLACE) ?: NULL; } } } - return FALSE; + return NULL; case 'thumbnail_local_uri': - $image_url = $this->getField($media, 'thumbnail_url'); + $image_url = $this->getMetadata($media, 'thumbnail_url'); if ($image_url) { - $filename = $this->getField($media, 'provider_name') . '_' . substr(md5($resource_url), 0, 5); + $filename = $this->getMetadata($media, 'provider_name') . '_' . substr(md5($resource_url), 0, 5); return $this->getLocalImageUri($filename, $image_url); } - return FALSE; + return NULL; + + case 'default_name': + if ($title = $this->getMetadata($media, 'title')) { + return $title; + } + elseif ($url = $this->getMetadata($media, 'url')) { + return $url; + } + return parent::getMetadata($media, 'default_name'); + + case 'thumbnail_uri': + return parent::getMetadata($media, 'thumbnail_uri'); default: if (!empty($oembed_data[$name])) { @@ -151,7 +165,7 @@ public function getField(MediaInterface $media, $name) { } - return FALSE; + return NULL; } /** @@ -167,14 +181,18 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description' => $this->t('Thumbnails will be fetched from the provider for local usage. This is the location where they will be placed.'), ]; - /*$form['allowed_providers'] = [ + $provider_options = array_map(function ($provider) { + return $provider['provider_name']; + }, $this->oEmbed->getProviders()); + $form['allowed_providers'] = [ '#type' => 'select', + '#title' => $this->t('Allowed providers'), '#multiple' => TRUE, '#default_value' => $this->configuration['allowed_providers'], - '#options' => array_map(function ($provider) { return $provider['provider_name']; }, $this->oEmbed->getProviders()), - '#description' => $this->t('Select allowed oEmbed providers for this media type.'), - '#attributes' => ['size' => 30], - ];*/ + '#options' => $provider_options, + '#description' => $this->t('Optionally select the allowed oEmbed providers for this media type. If left blank, all providers will be allowed.'), + '#attributes' => ['size' => 20], + ]; return $form; } @@ -185,75 +203,24 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta public function defaultConfiguration() { return [ 'thumbnails_location' => 'public://oembed_thumbnails', - //'allowed_providers' => NULL, + 'allowed_providers' => [], ] + parent::defaultConfiguration(); } /** * {@inheritdoc} */ - public function getDefaultThumbnail() { - // TODO Use oEmbed specific thumbnail? - return $this->configFactory->get('media.settings') - ->get('icon_base') . '/no-thumbnail.png'; - } - - /** - * {@inheritdoc} - */ public function getThumbnail(MediaInterface $media) { - if ($local_image = $this->getField($media, 'thumbnail_local')) { + if ($local_image = $this->getMetadata($media, 'thumbnail_local')) { return $local_image; } - return $this->getDefaultThumbnail(); - } - - /** - * {@inheritdoc} - */ - protected function createSourceField(MediaTypeInterface $type) { - $field = parent::createSourceField($type); - - $settings = $field->getSettings(); - $settings['title'] = DRUPAL_DISABLED; - $settings['link_type'] = LinkItemInterface::LINK_EXTERNAL; - $field->set('settings', $settings); - - return $field; - } - - /** - * {@inheritdoc} - */ - public function getDefaultName(MediaInterface $media) { - if ($title = $this->getField($media, 'title')) { - return $title; - } - return parent::getDefaultName($media); + return $this->getMetadata($media, 'thumbnail_uri'); } /** - * {@inheritdoc} - */ - public function attachConstraints(MediaInterface $media) { - parent::attachConstraints($media); - - if (isset($this->configuration['source_field'])) { - if ($media->hasField($this->configuration['source_field'])) { - foreach ($media->get($this->configuration['source_field']) as &$url) { - /** @var \Drupal\Core\TypedData\DataDefinitionInterface $typed_data */ - $typed_data = $url->getDataDefinition(); - $typed_data->addConstraint('oembed_resource'); - $typed_data->addConstraint('oembed_provider'); - } - } - } - } - - /** - * Computes the destination URI for a thumbnail + * Computes the destination URI for a thumbnail. * - * @param string + * @param string $filename * Filename without the extension. * @param string $remote_url * The remote URL of the thumbnail. @@ -262,6 +229,7 @@ public function attachConstraints(MediaInterface $media) { * The local URI. */ protected function getLocalImageUri($filename, $remote_url) { + $directory = $this->configuration['thumbnails_location']; // Ensure that the destination directory is writable. If not, log a warning // and return the default thumbnail. $ready = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); @@ -269,7 +237,8 @@ protected function getLocalImageUri($filename, $remote_url) { $this->logger->warning('Could not prepare thumbnail destination directory @dir for oEmbed media.', [ '@dir' => $directory, ]); - return $this->getDefaultThumbnail(); + $default_thumbnail_filename = $this->pluginDefinition['default_thumbnail_filename']; + return $this->configFactory->get('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename; } $local_uri = $this->configuration['thumbnails_location'] . '/' . $filename . '.'; @@ -278,4 +247,26 @@ protected function getLocalImageUri($filename, $remote_url) { return $local_uri; } -} \ No newline at end of file + /** + * {@inheritdoc} + */ + public function createSourceField(MediaTypeInterface $type) { + $field = parent::createSourceField($type); + + $settings = $field->getSettings(); + $settings['title'] = DRUPAL_DISABLED; + $settings['link_type'] = LinkItemInterface::LINK_EXTERNAL; + + return $field->set('settings', $settings); + } + + /** + * {@inheritdoc} + */ + public function getSourceFieldConstraints() { + return [ + 'oembed_resource' => [], + 'oembed_provider' => [], + ]; + } +} diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.youtube.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.youtube.default.yml deleted file mode 100644 index 301b726..0000000 --- a/core/profiles/standard/config/optional/core.entity_view_display.media.youtube.default.yml +++ /dev/null @@ -1,50 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.field.media.youtube.field_media_oembed - - image.style.thumbnail - - media.type.youtube - module: - - image - - media - - user -id: media.youtube.default -targetEntityType: media -bundle: youtube -mode: default -content: - created: - label: hidden - type: timestamp - weight: 0 - region: content - settings: - date_format: medium - custom_date_format: '' - timezone: '' - third_party_settings: { } - field_media_oembed: - label: above - settings: { } - third_party_settings: { } - type: oembed - weight: 6 - region: content - thumbnail: - type: image - weight: 5 - label: hidden - settings: - image_style: thumbnail - image_link: '' - region: content - third_party_settings: { } - uid: - label: hidden - type: author - weight: 0 - region: content - settings: { } - third_party_settings: { } -hidden: { }