View source
<?php
namespace Drupal\media\Plugin\media\Source;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\media\IFrameUrlHelper;
use Drupal\media\OEmbed\Resource;
use Drupal\media\OEmbed\ResourceException;
use Drupal\media\MediaSourceBase;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Drupal\media\OEmbed\ResourceFetcherInterface;
use Drupal\media\OEmbed\UrlResolverInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class OEmbed extends MediaSourceBase implements OEmbedInterface {
protected $logger;
protected $messenger;
protected $httpClient;
protected $resourceFetcher;
protected $urlResolver;
protected $iFrameUrlHelper;
protected $fileSystem;
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 = 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;
$this->httpClient = $http_client;
$this->resourceFetcher = $resource_fetcher;
$this->urlResolver = $url_resolver;
$this->iFrameUrlHelper = $iframe_url_helper;
if (!$file_system) {
@trigger_error('The file_system service must be passed to OEmbed::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED);
$file_system = \Drupal::service('file_system');
}
$this->fileSystem = $file_system;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('entity_type.manager'), $container
->get('entity_field.manager'), $container
->get('config.factory'), $container
->get('plugin.manager.field.field_type'), $container
->get('logger.factory')
->get('media'), $container
->get('messenger'), $container
->get('http_client'), $container
->get('media.oembed.resource_fetcher'), $container
->get('media.oembed.url_resolver'), $container
->get('media.oembed.iframe_url_helper'), $container
->get('file_system'));
}
public function getMetadataAttributes() {
return [
'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 name of the provider"),
'provider_url' => $this
->t('The URL of the provider'),
'cache_age' => $this
->t('Suggested cache lifetime'),
'default_name' => $this
->t('Default name of the media item'),
'thumbnail_uri' => $this
->t('Local URI 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'),
];
}
public function getMetadata(MediaInterface $media, $name) {
$media_url = $this
->getSourceFieldValue($media);
if (empty($media_url)) {
return NULL;
}
try {
$resource_url = $this->urlResolver
->getResourceUrl($media_url);
$resource = $this->resourceFetcher
->fetchResource($resource_url);
} catch (ResourceException $e) {
$this->messenger
->addError($e
->getMessage());
return NULL;
}
switch ($name) {
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 $this
->getLocalThumbnailUri($resource) ?: parent::getMetadata($media, 'thumbnail_uri');
case 'type':
return $resource
->getType();
case 'title':
return $resource
->getTitle();
case 'author_name':
return $resource
->getAuthorName();
case 'author_url':
return $resource
->getAuthorUrl();
case 'provider_name':
$provider = $resource
->getProvider();
return $provider ? $provider
->getName() : '';
case 'provider_url':
$provider = $resource
->getProvider();
return $provider ? $provider
->getUrl() : NULL;
case 'cache_age':
return $resource
->getCacheMaxAge();
case 'thumbnail_width':
return $resource
->getThumbnailWidth();
case 'thumbnail_height':
return $resource
->getThumbnailHeight();
case 'url':
$url = $resource
->getUrl();
return $url ? $url
->toString() : NULL;
case 'width':
return $resource
->getWidth();
case 'height':
return $resource
->getHeight();
case 'html':
return $resource
->getHtml();
default:
break;
}
return NULL;
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$domain = $this->configFactory
->get('media.settings')
->get('iframe_domain');
if (!$this->iFrameUrlHelper
->isSecure($domain)) {
array_unshift($form, [
'#markup' => '<p>' . $this
->t('It is potentially insecure to display oEmbed content in a frame that is served from the same domain as your main Drupal site, as this may allow execution of third-party code. <a href=":url" target="_blank">You can specify a different domain for serving oEmbed content here</a> (opens in a new window).', [
':url' => Url::fromRoute('media.settings')
->setAbsolute()
->toString(),
]) . '</p>',
]);
}
$form['thumbnails_directory'] = [
'#type' => 'textfield',
'#title' => $this
->t('Thumbnails location'),
'#default_value' => $this->configuration['thumbnails_directory'],
'#description' => $this
->t('Thumbnails will be fetched from the provider for local usage. This is the URI of the directory where they will be placed.'),
'#required' => TRUE,
];
$configuration = $this
->getConfiguration();
$plugin_definition = $this
->getPluginDefinition();
$form['providers'] = [
'#type' => 'checkboxes',
'#title' => $this
->t('Allowed providers'),
'#default_value' => $configuration['providers'],
'#options' => array_combine($plugin_definition['providers'], $plugin_definition['providers']),
'#description' => $this
->t('Optionally select the allowed oEmbed providers for this media type. If left blank, all providers will be allowed.'),
];
return $form;
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$configuration = $this
->getConfiguration();
$configuration['providers'] = array_filter(array_values($configuration['providers']));
$this
->setConfiguration($configuration);
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$thumbnails_directory = $form_state
->getValue('thumbnails_directory');
$stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
if (!$stream_wrapper_manager
->isValidUri($thumbnails_directory)) {
$form_state
->setErrorByName('thumbnails_directory', $this
->t('@path is not a valid path.', [
'@path' => $thumbnails_directory,
]));
}
}
public function defaultConfiguration() {
return [
'thumbnails_directory' => 'public://oembed_thumbnails',
'providers' => [],
] + parent::defaultConfiguration();
}
protected function getLocalThumbnailUri(Resource $resource) {
$remote_thumbnail_url = $resource
->getThumbnailUrl();
if (!$remote_thumbnail_url) {
return NULL;
}
$remote_thumbnail_url = $remote_thumbnail_url
->toString();
$local_thumbnail_url = parse_url($remote_thumbnail_url, PHP_URL_PATH);
$configuration = $this
->getConfiguration();
$directory = $configuration['thumbnails_directory'];
$local_thumbnail_uri = "{$directory}/" . Crypt::hashBase64($local_thumbnail_url) . '.' . pathinfo($local_thumbnail_url, PATHINFO_EXTENSION);
if (file_exists($local_thumbnail_uri)) {
return $local_thumbnail_uri;
}
if (!$this->fileSystem
->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
$this->logger
->warning('Could not prepare thumbnail destination directory @dir for oEmbed media.', [
'@dir' => $directory,
]);
return NULL;
}
try {
$response = $this->httpClient
->get($remote_thumbnail_url);
if ($response
->getStatusCode() === 200) {
$this->fileSystem
->saveData((string) $response
->getBody(), $local_thumbnail_uri, FileSystemInterface::EXISTS_REPLACE);
return $local_thumbnail_uri;
}
} catch (RequestException $e) {
$this->logger
->warning($e
->getMessage());
} catch (FileException $e) {
$this->logger
->warning('Could not download remote thumbnail from {url}.', [
'url' => $remote_thumbnail_url,
]);
}
return NULL;
}
public function getSourceFieldConstraints() {
return [
'oembed_resource' => [],
];
}
public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) {
$display
->setComponent($this
->getSourceFieldDefinition($type)
->getName(), [
'type' => 'oembed',
'label' => 'visually_hidden',
]);
}
public function prepareFormDisplay(MediaTypeInterface $type, EntityFormDisplayInterface $display) {
parent::prepareFormDisplay($type, $display);
$source_field = $this
->getSourceFieldDefinition($type)
->getName();
$display
->setComponent($source_field, [
'type' => 'oembed_textfield',
'weight' => $display
->getComponent($source_field)['weight'],
]);
$display
->removeComponent('name');
}
public function getProviders() {
$configuration = $this
->getConfiguration();
return $configuration['providers'] ?: $this
->getPluginDefinition()['providers'];
}
public function createSourceField(MediaTypeInterface $type) {
$plugin_definition = $this
->getPluginDefinition();
$label = (string) $this
->t('@type URL', [
'@type' => $plugin_definition['label'],
]);
return parent::createSourceField($type)
->set('label', $label);
}
}