nightlife2008 β changed the visibility of the branch 3477081-undefined-variable to active.
nightlife2008 β changed the visibility of the branch 3477081-undefined-variable to hidden.
nightlife2008 β made their first commit to this issueβs fork.
Thanks for your added information, I will keep this in mind next time!
I feel a contrib module with a dependency on https://www.drupal.org/project/draggableviews β could fill the gap?
You could then replace the default list with a Views based alternative and add filters and paging and all the other bells and whistles?
nightlife2008 β created an issue.
I discovered a side effect of the patch in #16, namely that my theme is prematurely initialized, and the default theme is used instead of the admin theme, because the route match hasn't been determined yet at that point, since it's currently determining it :D
Steps in code when this happens:
1. Find route match for path /layout_builder/add/block/overrides/node.391/0/<region>/<pluginId>
2. Routing parses the route params
3. Causes section storage's method deriveContextsFromRoute
to be triggered
4. Rendering the entity causes the Theme negotiators to run on a NullRoute
, which at that point always returns the default theme.
5. The page is rendered in the default theme instead of using the admin_theme as it should according to the _admin_route: TRUE
flag.
I'm currently doing a D10 project using Bynder and I came to the same requirement of having responsive images with different aspect ratio's for different screensizes, so I decided to create a (rough) formatter to accomodate this somehow by using different DAT queries for different breakpoints / multipliers. This formatter outputs a picture element just like core's Responsive Image module.
I reused some bits of the Responsive Image UI and the original formatter:
Formatter:
<?php
namespace Drupal\MODULE\Plugin\Field\FieldFormatter;
use Drupal\bynder\Plugin\Field\FieldFormatter\BynderFormatterBase;
use Drupal\bynder\Plugin\media\Source\Bynder;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'Bynder' formatter.
*
* @FieldFormatter(
* id = "bynder_responsive_image",
* label = @Translation("Bynder (Image, Responsive picture)"),
* field_types = {"string", "string_long", "entity_reference"}
* )
*/
class BynderFormatterResponsiveImage extends BynderFormatterBase {
/**
* The entity repository service.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* The Breakpoint manager service.
*
* @var \Drupal\breakpoint\BreakpointManagerInterface
*/
protected $breakpointManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->entityRepository = $container->get('entity.repository');
$instance->breakpointManager = $container->get('breakpoint.manager');
return $instance;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'alt_field' => '',
'title_field' => '',
'dat_query' => '',
'breakpoint_group' => 'responsive_image',
'dat_query_responsive' => [],
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$field_candidates = $this->getAttributesFieldsCandidates();
$form['alt_field'] = [
'#type' => 'select',
'#options' => $field_candidates,
'#title' => $this->t('Alt attribute field'),
'#description' => $this->t('Select the name of the field that should be used for the "alt" attribute of the image.'),
'#default_value' => $this->getSetting('alt_field'),
'#empty_value' => '',
];
$form['title_field'] = [
'#type' => 'select',
'#options' => $field_candidates,
'#title' => $this->t('Title attribute field'),
'#description' => $this->t('Select the name of the field that should be used for the "title" attribute of the image.'),
'#default_value' => $this->getSetting('title_field'),
'#empty_value' => '',
];
$dat_documentation = 'https://support.bynder.com/hc/en-us/articles/360018559260-Dynamic-Asset-Transformations-DAT-';
$form['dat_query'] = [
'#type' => 'textfield',
'#title' => $this->t('DAT query field'),
'#description' => $this->t('Attributes that should be applied to the images. See <a href=":dat_help">here</a> for explanation on possible values. Should start right after the "?", e.g. "io=transform:fill,width:100,height:200" If the following Responsive image fields are filled, this field defines the fallback image if the responsive settings are broken.', [
':dat_help' => $dat_documentation,
]),
'#default_value' => $this->getSetting('dat_query'),
'#states' => [
'visible' => [
':input.bynder-derivative' => ['value' => 'DAT'],
],
'required' => [
':input.bynder-derivative' => ['value' => 'DAT'],
],
],
];
$breakpoint_group = $this->getSettingFromFormState($form_state, 'breakpoint_group');
$form['breakpoint_group'] = [
'#type' => 'select',
'#title' => $this->t('Breakpoint group'),
'#default_value' => $breakpoint_group ?? 'responsive_image',
'#options' => $this->breakpointManager->getGroups(),
'#required' => TRUE,
'#ajax' => [
'callback' => [get_class(), 'breakpointMappingFormAjax'],
'wrapper' => 'ajax-responsive-query-settings',
'method' => 'replace',
],
];
$form['dat_query_responsive'] = [
'#type' => 'container',
'#weight' => 100,
'#tree' => TRUE,
'#attributes' => [
'id' => 'ajax-responsive-query-settings',
],
'#states' => [
'visible' => [
':input.bynder-derivative' => ['value' => 'DAT'],
],
],
];
if (empty($breakpoint_group)) {
return $form;
}
$breakpoints = $this->breakpointManager->getBreakpointsByGroup($breakpoint_group);
$dat_query_mapping = $this->getSetting('dat_query_responsive') ?? [];
foreach ($breakpoints as $breakpoint_id => $breakpoint) {
foreach ($breakpoint->getMultipliers() as $multiplier) {
$sanitized_breakpoint_id = str_replace(['-', '.'], '_', $breakpoint_id);
$sanitized_multiplier = str_replace(['-', '.'], '_', $multiplier);
$item_mapping = $dat_query_mapping[$sanitized_breakpoint_id][$sanitized_multiplier] ?? [];
$item_mapping_dat_query = isset($item_mapping['dat_query']) && is_string($item_mapping['dat_query']) ? $item_mapping['dat_query'] : NULL;
$label = $multiplier . ' ' . $breakpoint->getLabel() . ' [' . $breakpoint->getMediaQuery() . ']';
$form['dat_query_responsive'][$sanitized_breakpoint_id][$sanitized_multiplier] = [
'#type' => 'details',
'#title' => $label,
'#open' => !empty($item_mapping_dat_query),
];
$form['dat_query_responsive'][$sanitized_breakpoint_id][$sanitized_multiplier]['dat_query'] = [
'#type' => 'textfield',
'#title' => $this->t('Query'),
'#default_value' => $item_mapping_dat_query,
];
}
}
return $form;
}
/**
* Ajax callback to refresh the breakpoint mapping form.
*/
public static function breakpointMappingFormAjax($form, FormStateInterface $form_state) {
$triggeringElement = $form_state->getTriggeringElement();
// Dynamically return the dependent ajax for elements based on the
// triggering element. This shouldn't be done statically because
// settings forms may be different, e.g. for layout builder, core, ...
if (!empty($triggeringElement['#array_parents'])) {
$subformKeys = $triggeringElement['#array_parents'];
// Remove the triggering element itself:
array_pop($subformKeys);
$subformKeys[] = 'dat_query_responsive';
// Return the subform:
return NestedArray::getValue($form, $subformKeys);
}
}
/**
* Get a setting value from form state or from our settings.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The FormState object.
* @param $setting
* The setting key.
*
* @return mixed|null
* The value.
*/
protected function getSettingFromFormState(FormStateInterface $form_state, $setting) {
$field_name = $this->fieldDefinition->getName();
if ($form_state->hasValue(['fields', $field_name, 'settings_edit_form', 'settings', $setting])) {
return $form_state->getValue(['fields', $field_name, 'settings_edit_form', 'settings', $setting]);
}
return $this->getSetting($setting);
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$settings = $this->getSettings();
if (!empty($settings['dat_query_responsive'])) {
$summary[] = $this->t('DAT configuration: Responsive, fallback: @dat', ['@dat' => $settings['dat_query']]);
}
else {
$summary[] = $this->t('Fallback DAT configuration: @dat', ['@dat' => $settings['dat_query']]);
}
$field_candidates = $this->getAttributesFieldsCandidates();
if (empty($settings['title_field'])) {
$summary[] = $this->t('Title attribute not displayed (not recommended).');
}
else {
$summary[] = $this->t('Title attribute field: @field', ['@field' => $field_candidates[$settings['title_field']]]);
}
if (empty($settings['alt_field'])) {
$summary[] = $this->t('Alt attribute not displayed (not recommended).');
}
else {
$summary[] = $this->t('Alt attribute field: @field', ['@field' => $field_candidates[$settings['alt_field']]]);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$settings = $this->getSettings();
$elements = [];
$is_entityreference = $this->fieldDefinition->getType() == 'entity_reference';
foreach ($items as $delta => $item) {
/** @var \Drupal\media\MediaInterface $media_entity */
if ($media_entity = $is_entityreference ? $item->entity : $items->getEntity()) {
/** @var \Drupal\media\MediaInterface $media_entity */
$media_entity = $this->entityRepository->getTranslationFromContext($media_entity, $langcode);
/** @var \Drupal\media\MediaSourceInterface $source_plugin */
$source_plugin = $media_entity->getSource();
if ($source_plugin instanceof Bynder && ($thumbnails = $source_plugin->getMetadata($media_entity, 'thumbnail_urls'))) {
$thumbnail_uri = NULL;
if (isset($thumbnails['transformBaseUrl']) && !empty($settings['dat_query'])) {
$thumbnail_uri = $thumbnails['transformBaseUrl'] . '?' . $this->getSetting('dat_query');
}
$attributes = [];
$attributes['loading'] = 'lazy';
if ($settings['title_field'] && $media_entity->hasField($settings['title_field']) && !$media_entity->get($settings['title_field'])->isEmpty()) {
$attributes['title'] = $media_entity->get($settings['title_field'])->value;
}
if ($settings['alt_field'] && $media_entity->hasField($settings['alt_field']) && !$media_entity->get($settings['alt_field'])->isEmpty()) {
$attributes['alt'] = $media_entity->get($settings['alt_field'])->value;
}
$elements[$delta] = [
'#theme' => 'bynder_responsive_image',
'#uri' => $thumbnail_uri,
'#attributes' => $attributes,
'#breakpoint_group' => $this->getSetting('breakpoint_group'),
'#dat_base_url' => $thumbnails['transformBaseUrl'],
'#dat_query_responsive' => $this->getSetting('dat_query_responsive'),
];
$this->renderer->addCacheableDependency($elements[$delta], $item);
}
}
}
return $elements;
}
}
Theme hook:
/**
* Implements hook_theme().
*/
function MODULE_theme($existing, $type, $theme, $path) {
return [
'bynder_responsive_image' => [
'variables' => [
'uri' => NULL,
'attributes' => [],
'breakpoint_group' => [],
'dat_base_url' => '',
'dat_query_responsive' => [],
'height' => NULL,
'width' => NULL,
],
],
];
}
A preprocess function for our template:
/**
* Implements hook_preprocess_hook().
*/
function template_preprocess_bynder_responsive_image(array &$variables) {
// Make sure that width and height are proper values
// If they exists we'll output them.
// @see http://www.w3.org/community/respimg/2012/06/18/florians-compromise/
if (isset($variables['width']) && empty($variables['width'])) {
unset($variables['width']);
unset($variables['height']);
}
elseif (isset($variables['height']) && empty($variables['height'])) {
unset($variables['width']);
unset($variables['height']);
}
// Retrieve all breakpoints and multipliers and reverse order of breakpoints.
// By default, breakpoints are ordered from smallest weight to largest:
// the smallest weight is expected to have the smallest breakpoint width,
// while the largest weight is expected to have the largest breakpoint
// width. For responsive images, we need largest breakpoint widths first, so
// we need to reverse the order of these breakpoints.
$dat_query_mapping = $variables['dat_query_responsive'];
$breakpoint_group = $variables['breakpoint_group'];
$breakpoints = array_reverse(\Drupal::service('breakpoint.manager')->getBreakpointsByGroup($breakpoint_group));
foreach ($breakpoints as $breakpoint_id => $breakpoint) {
$sanitized_breakpoint_id = str_replace(['-', '.'], '_', $breakpoint_id);
$multipliers = $dat_query_mapping[$sanitized_breakpoint_id];
if (isset($dat_query_mapping[$sanitized_breakpoint_id])) {
if ($sources = _MODULE_bynder_responsive_image_build_source_attributes($variables, $breakpoint, $multipliers)) {
$variables['sources'][] = $sources;
}
}
}
if (isset($variables['sources']) && count($variables['sources']) === 1 && !isset($variables['sources'][0]['media'])) {
// There is only one source tag with an empty media attribute. This means
// we can output an image tag with the srcset attribute instead of a
// picture tag.
$variables['output_image_tag'] = TRUE;
foreach ($variables['sources'][0] as $attribute => $value) {
if ($attribute != 'type') {
$variables['attributes'][$attribute] = $value;
}
}
$variables['img_element'] = [
'#theme' => 'image',
'#uri' => $variables['uri'],
'#attributes' => [],
];
}
else {
$variables['output_image_tag'] = FALSE;
// Prepare the fallback image. We use the src attribute, which might cause
// double downloads in browsers that don't support the picture tag.
$variables['img_element'] = [
'#theme' => 'image',
'#uri' => $variables['uri'],
'#attributes' => [],
];
}
if (isset($variables['attributes'])) {
if (isset($variables['attributes']['alt'])) {
$variables['img_element']['#alt'] = $variables['attributes']['alt'];
unset($variables['attributes']['alt']);
}
if (isset($variables['attributes']['title'])) {
$variables['img_element']['#title'] = $variables['attributes']['title'];
unset($variables['attributes']['title']);
}
$variables['img_element']['#attributes'] = $variables['attributes'];
}
}
The mentioned helper function:
function _MODULE_bynder_responsive_image_build_source_attributes(array $variables, BreakpointInterface $breakpoint, array $multipliers) {
$srcset = [];
$dat_base_url = $variables['dat_base_url'];
// Traverse the multipliers in reverse so the largest image is processed last.
// The last image's dimensions are used for img.srcset height and width.
foreach (array_reverse($multipliers) as $multiplier => $multiplier_settings) {
if (!empty($multiplier_settings['dat_query'])) {
$srcset[intval(mb_substr($multiplier, 0, -1) * 100)] = str_replace('io=', $dat_base_url . '?io=', $multiplier_settings['dat_query']);
}
}
if (empty($srcset)) {
return NULL;
}
// Sort the srcset from small to large image width or multiplier.
ksort($srcset);
$source_attributes = new Attribute([
'srcset' => implode(', ', array_unique($srcset)),
]);
$media_query = trim($breakpoint->getMediaQuery());
if (!empty($media_query)) {
$source_attributes->setAttribute('media', $media_query);
}
return $source_attributes;
}
Finally the Twig template in our module's "templates" dir:
{#
/**
* @file
* Default theme implementation of a responsive image.
*
* Available variables:
* - sources: The attributes of the <source> tags for this <picture> tag.
* - img_element: The controlling image, with the fallback image in srcset.
* - output_image_tag: Whether or not to output an <img> tag instead of a
* <picture> tag.
*
* @see template_preprocess()
* @see template_preprocess_responsive_image()
*
* @ingroup themeable
*/
#}
{% if output_image_tag %}
{{ img_element }}
{% else %}
<picture>
{% if sources %}
{% for source_attributes in sources %}
<source{{ source_attributes }}/>
{% endfor %}
{% endif %}
{# The controlling image, with the fallback image in srcset. #}
{{ img_element }}
</picture>
{% endif %}
I hope I can help someone looking for the same functionality.
The settings UI can be seen in the attached screenshots.
I'm reopening because:
This error appears when doing a blank (re)installation.
The path alias length module is enabled _before_ the pathauto module, which is one of its' dependencies but hasn't been added to the info.yml file...
I've added a patch to solve this.
apaderno β credited nightlife2008 β .
Thx Deciphered for updating the tests!
Hi everyone,
I stumbled upon this issue because of a problem one of our customers has when translating a webform.
When having an email handler in the source webform, the body of this email handler's email is displayed as a richtext field, which, by default, encapsulates the "_default" string in
tags, which later breaks the sending of the emails:
I recreated the patch on the latest dev codebase, and added another case where the body can also be an array.
Hey,
I came to the conclusion I could just reuse existing code from jsonapi_search_api with a little bit of search-api-view detection code (which could be improved!).
The fact that the patch alters more than required, is that I rebuilt the file first in a custom module, and while doing so, replaced/reordered some things:
- Jsonapi base path injection
- basepath variable removed in favor of just a string
- applied some code standard formatting
I tested it with my 160 views/displays and everything kept working afaik.
I welcome any feedback!
All credits go to Centarro's @jsacksick and @mglaman, the maintainers of the jsonapi_search_api module.
greets,
Kim
nightlife2008 β created an issue.