Skip to content
Snippets Groups Projects
Commit 23867166 authored by Mark Fullmer's avatar Mark Fullmer
Browse files

Issue #2825602 by mark_fullmer, jjpost, segovia94, ricksta, Eduardo Morales...

Issue #2825602 by mark_fullmer, jjpost, segovia94, ricksta, Eduardo Morales Alberti, Rob230, hawkeye.twolf, manuel.adan, phjou: Add option to make embeds responsive
parent ecc1f574
No related branches found
No related tags found
1 merge request!9Issue #2825602 by segovia94, Eduardo Morales Alberti, Rob230, hawkeye.twolf,...
Pipeline #256127 passed with warnings
......@@ -15,3 +15,24 @@ filter_settings.url_embed_convert_links:
url_prefix:
type: string
label: 'URL Prefix'
filter_settings.url_embed:
type: filter
label: 'URL Embed settings'
mapping:
enable_responsive:
type: boolean
label: 'Enable responsive wrappers'
default_ratio:
type: string
label: 'A ratio that should be used if none provided'
field.formatter.settings.url_embed:
type: mapping
label: 'URL Embed wrapped in a responsive wrapper'
mapping:
enable_responsive:
type: boolean
label: 'Enable responsive wrappers'
default_ratio:
type: string
label: 'A ratio that should be used if none provided'
.responsive-embed {
/* Default 16:9 aspect ratio will be overridden with an inline style */
--responsive-embed-ratio: 56.5;
}
.responsive-embed :where(iframe, object, embed) {
width: 100%;
height: auto;
aspect-ratio: 100 / var(--responsive-embed-ratio);
}
......@@ -10,6 +10,7 @@ namespace Drupal\url_embed\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\DeprecationHelper;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Utility\Error;
use Drupal\url_embed\UrlEmbedHelperTrait;
......@@ -30,20 +31,80 @@ class LinkEmbedFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$element = array();
public static function defaultSettings() {
return [
'enable_responsive' => FALSE,
'default_ratio' => '66.669'
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = [];
$element['enable_responsive'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable responsive wrappers'),
'#description' => $this->t('Automatically wrap embedded iframes with a container which will allow the embedded media to scale appropriately to the size of the page.'),
'#default_value' => $this->getSetting('enable_responsive'),
];
$element['default_ratio'] = [
'#type' => 'textfield',
'#title' => $this->t('Default ratio (as a percent)'),
'#description' => $this->t('Embeds normally derive their aspect ratio from information from the provider. In cases where this information is not present, provide a fallback. Example: 56.25'),
'#default_value' => $this->getSetting('default_ratio'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
return [$this->t('Responsive: @responsive', ['@responsive' => $this->getSetting('enable_responsive') ? $this->t('enabled') : $this->t('disabled')])];
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$default_settings = self::defaultSettings();
$default_ratio = $this->getSetting('default_ratio');
$element = [];
foreach ($items as $delta => $item) {
$ratio = !empty($default_ratio) ? $default_ratio : $default_settings['default_ratio'];
if ($url = $item->getUrl()->toString()) {
try {
$html = $this->urlEmbed()->getEmbed($url)->code->html ?? NULL;
if (!$html) {
if (($info = $this->urlEmbed()->getEmbed($url)) && !empty($info->code->html)) {
$url_output = $info->code->html;
if (!empty($info->code->ratio)) {
$ratio = $info->code->ratio;
}
}
if (!$url_output) {
throw new \Exception('Could not retrieve HTML content for ' . $url);
}
$element[$delta] = array(
// Wrap the embed code in a container to make it responsive.
if ($this->getSetting('enable_responsive') === TRUE) {
$element[$delta] = [
'#theme' => 'responsive_embed',
'#ratio' => $ratio,
'#url_output' => $url_output,
'#attached' => [
'library' => [
'url_embed/responsive_styles',
],
],
];
}
else {
$element[$delta] = [
'#type' => 'inline_template',
'#template' => $html,
);
'#template' => $url_output,
];
}
}
catch (\Exception $e) {
DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.1.0', fn() => Error::logException(\Drupal::logger('url_embed'), $e), fn() => watchdog_exception('url_embed', $e));
......
......@@ -41,7 +41,7 @@ class ConvertUrlToEmbedFilter extends FilterBase {
'#type' => 'textfield',
'#title' => $this->t('URL prefix'),
'#default_value' => $this->settings['url_prefix'],
'#description' => $this->t('Optional prefix that will be used to indicate which URLs that apply. All URLs that are supported will be converted if empty. Example: EMBED-https://twitter.com/drupal/status/735873777683320832'),
'#description' => $this->t('Optional prefix that will be used to indicate which URLs that apply. All URLs that are supported will be converted if empty. Example: EMBED-https://www.youtube.com/watch?v=I95hSyocMlg'),
];
return $form;
}
......
......@@ -9,7 +9,9 @@ namespace Drupal\url_embed\Plugin\Filter;
use Drupal\Component\Utility\DeprecationHelper;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Utility\Error;
use Drupal\embed\DomHelperTrait;
......@@ -41,6 +43,13 @@ class UrlEmbedFilter extends FilterBase implements ContainerFactoryPluginInterfa
use DomHelperTrait;
use UrlEmbedHelperTrait;
/**
* The Renderer service.
*
* @var \Drupal\Core\Render\Renderer.
*/
protected $renderer;
/**
* Constructs a UrlEmbedFilter object.
*
......@@ -53,9 +62,10 @@ class UrlEmbedFilter extends FilterBase implements ContainerFactoryPluginInterfa
* @param \Drupal\url_embed\UrlEmbedInterface $url_embed
* The URL embed service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, UrlEmbedInterface $url_embed) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, UrlEmbedInterface $url_embed, Renderer $renderer) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->setUrlEmbed($url_embed);
$this->renderer = $renderer;
}
/**
......@@ -66,7 +76,8 @@ class UrlEmbedFilter extends FilterBase implements ContainerFactoryPluginInterfa
$configuration,
$plugin_id,
$plugin_definition,
$container->get('url_embed')
$container->get('url_embed'),
$container->get('renderer')
);
}
......@@ -78,20 +89,39 @@ class UrlEmbedFilter extends FilterBase implements ContainerFactoryPluginInterfa
if (strpos($text, 'data-embed-url') !== FALSE) {
$dom = Html::load($text);
$xpath = new \DOMXPath($dom);
$config = $this->getConfiguration();
foreach ($xpath->query('//drupal-url[@data-embed-url]') as $node) {
/** @var \DOMElement $node */
$url = $node->getAttribute('data-embed-url');
$url_output = '';
$ratio = !empty($this->settings['default_ratio']) ? $this->settings['default_ratio'] : '66.7';
try {
if (($info = $this->urlEmbed()->getEmbed($url)) && !empty($info->code->html)) {
$url_output = $info->code->html;
if (!empty($info->code->ratio)) {
$ratio = $info->code->ratio;
}
}
}
catch (\Exception $e) {
DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.1.0', fn() => Error::logException(\Drupal::logger('url_embed'), $e), fn() => watchdog_exception('url_embed', $e));
}
if (!empty($this->settings['enable_responsive'])) {
// Wrap the embed code in a container to make it responsive.
$responsive_embed = [
'#theme' => 'responsive_embed',
'#ratio' => $ratio,
'#url_output' => $url_output,
];
$url_output = $this->renderer->render($responsive_embed);
$result
->setAttachments([
'library' => [
'url_embed/responsive_styles',
],
]);
}
$this->replaceNodeContent($node, $url_output);
}
......@@ -116,4 +146,23 @@ class UrlEmbedFilter extends FilterBase implements ContainerFactoryPluginInterfa
}
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form['enable_responsive'] = [
'#type' => 'checkbox',
'#title' => $this->t('Embeds fill container (responsive behavior)'),
'#description' => $this->t('Automatically scale the embed to fill the size of the container.'),
'#default_value' => $this->settings['enable_responsive'] ?? TRUE,
];
$form['default_ratio'] = [
'#type' => 'textfield',
'#title' => $this->t('Default ratio (as a percent)'),
'#description' => $this->t('Embeds normally derive their aspect ratio from information from the provider. In cases where this information is not present, provide a fallback. Example: 56.25'),
'#default_value' => $this->settings['default_ratio'] ?? '66.7',
];
return $form;
}
}
<div class="responsive-embed" style="--responsive-embed-ratio: {{ ratio }}">{{ url_output|raw }}</div>
......@@ -68,14 +68,14 @@ class ConvertUrlToEmbedFilterTest extends BrowserTestBase {
* are passed. Also tests situations when embed fails.
*/
public function testFilter() {
$content = 'before https://twitter.com/drupal/status/735873777683320832 after';
$content = 'before https://www.youtube.com/watch?v=I95hSyocMlg after';
$settings = [];
$settings['type'] = 'page';
$settings['title'] = 'Test convert url to embed with sample Twitter url';
$settings['title'] = 'Test convert url to embed with sample YouTube url';
$settings['body'] = [['value' => $content, 'format' => 'custom_format']];
$node = $this->drupalCreateNode($settings);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->responseContains('<drupal-url data-embed-url="https://twitter.com/drupal/status/735873777683320832"></drupal-url>');
$this->assertSession()->responseContains('<drupal-url data-embed-url="https://www.youtube.com/watch?v=I95hSyocMlg"></drupal-url>');
$this->assertSession()->pageTextNotContains(strip_tags($content));
$content = 'before /not-valid/url after';
......@@ -94,35 +94,35 @@ class ConvertUrlToEmbedFilterTest extends BrowserTestBase {
$format->setFilterConfig('url_embed_convert_links', $configuration);
$format->save();
$content = 'before https://twitter.com/drupal/status/735873777683320832 after';
$content = 'before https://www.youtube.com/watch?v=I95hSyocMlg after';
$settings = [];
$settings['type'] = 'page';
$settings['title'] = 'Test convert url to embed with sample Twitter url and no prefix';
$settings['title'] = 'Test convert url to embed with sample YouTube url and no prefix';
$settings['body'] = [['value' => $content, 'format' => 'custom_format']];
$node = $this->drupalCreateNode($settings);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->responseContains(strip_tags($content));
$this->assertSession()->responseNotContains('<drupal-url data-embed-url="https://twitter.com/drupal/status/735873777683320832"></drupal-url>');
$this->assertSession()->responseNotContains('<drupal-url data-embed-url="https://www.youtube.com/watch?v=I95hSyocMlg"></drupal-url>');
$content = 'before EMBED https://twitter.com/drupal/status/735873777683320832 after';
$content = 'before EMBED https://www.youtube.com/watch?v=I95hSyocMlg after';
$settings = [];
$settings['type'] = 'page';
$settings['title'] = 'Test convert url to embed with sample Twitter url with the prefix';
$settings['title'] = 'Test convert url to embed with sample YouTube url with the prefix';
$settings['body'] = [['value' => $content, 'format' => 'custom_format']];
$node = $this->drupalCreateNode($settings);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->responseContains('<drupal-url data-embed-url="https://twitter.com/drupal/status/735873777683320832"></drupal-url>');
$this->assertSession()->responseContains('<drupal-url data-embed-url="https://www.youtube.com/watch?v=I95hSyocMlg"></drupal-url>');
$this->assertSession()->pageTextNotContains(strip_tags($content));
$content = 'before Embed https://twitter.com/drupal/status/735873777683320832 after';
$content = 'before Embed https://www.youtube.com/watch?v=I95hSyocMlg after';
$settings = [];
$settings['type'] = 'page';
$settings['title'] = 'Test convert url to embed with sample Twitter url with wrong prefix';
$settings['title'] = 'Test convert url to embed with sample YouTube url with wrong prefix';
$settings['body'] = [['value' => $content, 'format' => 'custom_format']];
$node = $this->drupalCreateNode($settings);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->responseContains(strip_tags($content));
$this->assertSession()->responseNotContains('<drupal-url data-embed-url="https://twitter.com/drupal/status/735873777683320832"></drupal-url>');
$this->assertSession()->responseNotContains('<drupal-url data-embed-url="https://www.youtube.com/watch?v=I95hSyocMlg"></drupal-url>');
}
}
......@@ -34,10 +34,64 @@ class LinkEmbedFormatterTest extends BrowserTestBase {
protected $fieldStorage;
/**
* Tests the 'url_embed' formatter.
* Tests the default url_embed link field formatter.
*/
function testLinkEmbedFormatter() {
function testDefaultEmbedFormatter() {
$field_name = mb_strtolower($this->randomMachineName());
$display_options = [
'type' => 'url_embed',
'label' => 'hidden',
'settings' => [
'enable_responsive' => FALSE,
'default_ratio' => '',
],
];
$this->createLinkEmbedFormatter($field_name, $display_options);
// Create an entity to test the embed formatter.
$url = UrlEmbedTestBase::FLICKR_URL;
$entity = EntityTest::create();
$entity->set($field_name, $url);
$entity->save();
// Render the entity and verify that the link is output as an embed.
$output = $this->renderTestEntity($entity->id());
$this->assertStringContainsString(UrlEmbedTestBase::FLICKR_OUTPUT_FIELD, $output);
}
/**
* Tests the responsive url_embed link field formatter.
*/
function testResponsiveEmbedFormatter() {
$field_name = mb_strtolower($this->randomMachineName());
$display_options = [
'type' => 'url_embed',
'label' => 'hidden',
'settings' => [
'enable_responsive' => TRUE,
'default_ratio' => '66.669',
],
];
$this->createLinkEmbedFormatter($field_name, $display_options);
// Create an entity to test the embed formatter.
$url = UrlEmbedTestBase::FLICKR_URL;
$entity = EntityTest::create();
$entity->set($field_name, $url);
$entity->save();
// Render the entity and verify that the link is output as an embed.
$output = $this->renderTestEntity($entity->id());
$this->assertStringContainsString(UrlEmbedTestBase::FLICKR_OUTPUT_FIELD_RESPONSIVE, $output);
}
/**
* Provides a 'url_embed' formatter.
*
* @param string $field_name.
* A machine name for a field.
* @param array $display_options.
* Field formatter options.
*/
function createLinkEmbedFormatter($field_name, $display_options) {
// Create a field with settings to validate.
$this->fieldStorage = FieldStorageConfig::create(array(
'field_name' => $field_name,
......@@ -59,23 +113,9 @@ class LinkEmbedFormatterTest extends BrowserTestBase {
'type' => 'link_default',
))
->save();
$display_options = array(
'type' => 'url_embed',
'label' => 'hidden',
);
\Drupal::service('entity_display.repository')->getViewDisplay('entity_test', 'entity_test', 'full')
->setComponent($field_name, $display_options)
->save();
// Create an entity to test the embed formatter.
$url = UrlEmbedTestBase::FLICKR_URL;
$entity = EntityTest::create();
$entity->set($field_name, $url);
$entity->save();
// Render the entity and verify that the link is output as an embed.
$output = $this->renderTestEntity($entity->id());
$this->assertStringContainsString(UrlEmbedTestBase::FLICKR_OUTPUT_FIELD, $output);
}
/**
......
......@@ -7,6 +7,8 @@
namespace Drupal\Tests\url_embed\Functional;
use Drupal\filter\Entity\FilterFormat;
/**
* Tests the url_embed filter.
*
......@@ -60,6 +62,23 @@ class UrlEmbedFilterTest extends UrlEmbedTestBase {
$node = $this->drupalCreateNode($settings);
$this->drupalget('node/' . $node->id());
$this->assertSession()->responseContains('<div data-embed-url="' . static::FLICKR_URL . '"');
// Enable the settings option to use a responsive wrapper.
$format = FilterFormat::load('custom_format');
$configuration = $format->filters('url_embed')->getConfiguration();
$configuration['settings']['enable_responsive'] = '1';
$format->setFilterConfig('url_embed', $configuration);
$format->save();
// Tests responsive url embed using sample flickr url.
$content = '<drupal-url data-embed-url="' . static::FLICKR_URL . '">This placeholder should not be rendered.</drupal-url>';
$settings = [];
$settings['type'] = 'page';
$settings['title'] = 'Test responsive url embed with sample Flickr url';
$settings['body'] = [['value' => $content, 'format' => 'custom_format']];
$node = $this->drupalCreateNode($settings);
$this->drupalGet('node/' . $node->id());
$this->assertSession()->responseContains('<div class="responsive-embed" style="--responsive-embed-ratio: 66.699">' . static::FLICKR_OUTPUT_WYSIWYG . '</div>');
$this->assertSession()->pageTextNotContains(strip_tags($content));
}
}
......@@ -45,6 +45,13 @@ abstract class UrlEmbedTestBase extends BrowserTestBase {
*/
const FLICKR_OUTPUT_FIELD = '<a data-flickr-embed="true" href="https://www.flickr.com/photos/peste76/49945030047/" title="Ephemeral by der_peste (on/off), on Flickr"><img src="https://live.staticflickr.com/65535/49945030047_413c0dd459_b.jpg" width="1024" height="683" alt="Ephemeral"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>';
/**
* The expected output of the Flickr URL in a link field.
*/
const FLICKR_OUTPUT_FIELD_RESPONSIVE = '<div class="responsive-embed" style="--responsive-embed-ratio: 66.699"><a data-flickr-embed="true" href="https://www.flickr.com/photos/peste76/49945030047/" title="Ephemeral by der_peste (on/off), on Flickr"><img src="https://live.staticflickr.com/65535/49945030047_413c0dd459_b.jpg" width="1024" height="683" alt="Ephemeral"></a><script async src="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>';
/**
* The expected output of the Flickr URL in a WYSIWYG.
*/
......
......@@ -3,6 +3,8 @@ type: module
description: 'Allows URLs to be embedded using a text editor.'
core_version_requirement: ^10.1 || ^11
package: Filters
ckeditor5-stylesheets:
- css/url_embed.responsive.css
dependencies:
- drupal:editor
- embed:embed
......
# CKEditor 5
plugin:
js:
js/ckeditor5_plugins/urlembed/build/urlembed.js: { preprocess: false, minified: true }
......@@ -10,3 +9,7 @@ admin:
css:
theme:
css/ckeditor5.css: { }
responsive_styles:
css:
component:
css/url_embed.responsive.css: { }
......@@ -6,6 +6,72 @@
*/
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\UrlHelper;
use Drupal\editor\Entity\Editor;
/**
* Implements hook_theme().
*/
function url_embed_theme($existing, $type, $theme, $path) {
return [
'responsive_embed' => [
'variables' => [
'url_output' => NULL,
'ratio' => NULL,
],
],
];
}
/**
* Implements hook_ckeditor_css_alter().
*/
function url_embed_ckeditor_css_alter(array &$css, Editor $editor) {
$filter_format = $editor->getFilterFormat();
if ($filter_format) {
$filter = $filter_format->filters('url_embed');
$config = $filter->getConfiguration();
// Add the responsive embed css if the url_embed module has it enabled
if (!empty($config['settings']['enable_responsive'])) {
$css[] = \Drupal::service('extension.list.module')->getPath('url_embed') . '/css/url_embed.responsive.css';
}
}
}
/**
* Implements hook_library_info_alter().
*/
function url_embed_library_info_alter(&$libraries, $extension) {
if ($extension === 'ckeditor5') {
// Add paths to stylesheets specified by a modules's ckeditor5-stylesheets
// config property.
$module = 'url_embed';
$module_path = \Drupal::service('extension.list.module')->getPath($module);
$info = \Drupal::service('extension.list.module')->getExtensionInfo($module);
if (isset($info['ckeditor5-stylesheets']) && $info['ckeditor5-stylesheets'] !== FALSE) {
$css = $info['ckeditor5-stylesheets'];
foreach ($css as $key => $url) {
// CSS URL is external or relative to Drupal root.
if (UrlHelper::isExternal($url) || $url[0] === '/') {
$css[$key] = $url;
}
// CSS URL is relative to theme.
else {
$css[$key] = '/' . $module_path . '/' . $url;
}
}
}
$existing = $libraries['internal.drupal.ckeditor5.stylesheets']['css']['theme'] ?? [];
$url_embed = array_fill_keys(array_values($css), []);
$merged = array_merge($existing, $url_embed);
$libraries['internal.drupal.ckeditor5.stylesheets'] = [
'css' => [
'theme' => $merged,
],
];
}
}
/**
* Performs a GET request to Facebook's graph API to retrieve token debug info.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment