From 1e63bf7192a78eba5359004774b2e59c56e0c067 Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Mon, 17 Mar 2025 08:39:32 -0500 Subject: [PATCH 1/9] Capture changes from patch #7. --- js/patternkit.jsoneditor.js | 2 +- .../src/MediaLibraryJSONLibraryOpener.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/js/patternkit.jsoneditor.js b/js/patternkit.jsoneditor.js index b49fb07f..95e8d0e5 100644 --- a/js/patternkit.jsoneditor.js +++ b/js/patternkit.jsoneditor.js @@ -307,7 +307,7 @@ patternkitEditorArray(jQuery, Drupal, JSONEditor); } // Ajax command response to allow updating Editor field values. Drupal.AjaxCommands.prototype.patternkitEditorUpdate = function (ajax, response, status) { - window.patternkitEditor.getEditor(response.selector).setValue(response.value); + window.patternkitEditor.getEditor(response.selector)?.setValue(response.value); }; let $target = $('#patternkit-editor-target', context); diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index 2933503d..384d249b 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -98,6 +98,23 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { } else { $value = $this->getFileUrl($media); + // If we're populating the image src, also try to populate the image alt text. + if (str_ends_with($widget_id, '.image.src')) { + // Build the alt widget alt id. + $alt_id = str_replace('image.src', 'image.alt', $widget_id); + // Build the height/width widget ids. + $height_id = str_replace('image.src', 'image.height', $widget_id); + $width_id = str_replace('image.src', 'image.width', $widget_id); + // Grab the alt text from the media item. + $alt_text = $media->get('field_media_image')->first()->alt; + // Grab the width and height from the media item. + $img_width = $media->get('field_media_image')->first()->width; + $img_height = $media->get('field_media_image')->first()->height; + // Update the fields in json editor. + $response->addCommand(new PatternkitEditorUpdateCommand($alt_id, $alt_text)); + $response->addCommand(new PatternkitEditorUpdateCommand($width_id, $img_width)); + $response->addCommand(new PatternkitEditorUpdateCommand($height_id, $img_height)); + } } $response->addCommand(new PatternkitEditorUpdateCommand($widget_id, $value)); -- GitLab From e3a397caedf9a5ef26dd35f2a7a88968db346bb5 Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Mon, 17 Mar 2025 13:56:57 -0500 Subject: [PATCH 2/9] Generalize source file loading to not assume field name. --- .../src/MediaLibraryJSONLibraryOpener.php | 123 +++++++++--------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index 384d249b..edd72299 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -4,11 +4,11 @@ namespace Drupal\patternkit_media_library; use Drupal\Core\Access\AccessResult; use Drupal\Core\Ajax\AjaxResponse; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; +use Drupal\file\FileInterface; use Drupal\media\MediaInterface; use Drupal\media_library\MediaLibraryOpenerInterface; use Drupal\media_library\MediaLibraryState; @@ -19,46 +19,18 @@ use Drupal\patternkit\AJAX\PatternkitEditorUpdateCommand; */ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected EntityTypeManagerInterface $entityTypeManager; - - /** - * The file url generator service. - * - * @var \Drupal\Core\File\FileUrlGeneratorInterface - */ - protected FileUrlGeneratorInterface $fileUrlGenerator; - - /** - * The configuration factory service. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected ConfigFactoryInterface $configFactory; - - /** - * The loaded settings for the Pattern Media Library module. - * - * @var array - */ - protected array $settings; - /** * MediaLibraryFieldWidgetOpener constructor. * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager. - * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator + * @param \Drupal\Core\File\FileUrlGeneratorInterface $fileUrlGenerator * The file url generator service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, FileUrlGeneratorInterface $file_url_generator) { - $this->entityTypeManager = $entity_type_manager; - $this->fileUrlGenerator = $file_url_generator; - } + public function __construct( + protected EntityTypeManagerInterface $entityTypeManager, + protected FileUrlGeneratorInterface $fileUrlGenerator, + ) {} /** * {@inheritdoc} @@ -90,36 +62,52 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { return $response; } try { - /** @var \Drupal\media\Entity\Media $media */ + /** @var \Drupal\media\MediaInterface $media */ $media = $this->entityTypeManager->getStorage('media')->load($mid); + assert($media instanceof MediaInterface); if ($use_media_token) { - $value = $this->getMediaToken($media); + $media_token = $this->getMediaToken($media); + $response->addCommand(new PatternkitEditorUpdateCommand($widget_id, $media_token)); } else { - $value = $this->getFileUrl($media); - // If we're populating the image src, also try to populate the image alt text. + $image_src = $this->getFileUrl($media); + $response->addCommand(new PatternkitEditorUpdateCommand($widget_id, $image_src)); + + // If we're populating the image src, also try to populate the image + // alt text and dimensions. If a schema doesn't match this format for + // an image field, this additional processing will be ignored + // altogether. if (str_ends_with($widget_id, '.image.src')) { - // Build the alt widget alt id. - $alt_id = str_replace('image.src', 'image.alt', $widget_id); - // Build the height/width widget ids. - $height_id = str_replace('image.src', 'image.height', $widget_id); - $width_id = str_replace('image.src', 'image.width', $widget_id); - // Grab the alt text from the media item. - $alt_text = $media->get('field_media_image')->first()->alt; - // Grab the width and height from the media item. - $img_width = $media->get('field_media_image')->first()->width; - $img_height = $media->get('field_media_image')->first()->height; - // Update the fields in json editor. - $response->addCommand(new PatternkitEditorUpdateCommand($alt_id, $alt_text)); - $response->addCommand(new PatternkitEditorUpdateCommand($width_id, $img_width)); - $response->addCommand(new PatternkitEditorUpdateCommand($height_id, $img_height)); + // Attempt to load the source file to identify additional available + // attributes. + $image = $this->getSourceFile($media); + + // Stop here if the image was not able to be loaded. + if ($image !== NULL) { + // Remove 'src' from the end of the widget path to easily append the + // additional attributes. + $widget_base_path = substr($widget_id, 0, -3); + + // Images will typically produce the following attributes: 'alt', + // 'height', 'target_id', 'title', and 'width'. + $attributes = $media->get('field_media_image')->first()->getValue(); + foreach ($attributes as $key => $value) { + // Only assign attributes with non-empty values, and ignore the + // internal value for the target_id referencing the file entity. + if ($key != 'target_id' && $value) { + $target = $widget_base_path . $key; + + // If a field is not found in the schema client-side, the + // command should fail silently. + $response->addCommand(new PatternkitEditorUpdateCommand($target, $value)); + } + } + } } } - - $response->addCommand(new PatternkitEditorUpdateCommand($widget_id, $value)); } - catch (\Exception $exception) { + catch (\Exception) { return $response; } @@ -162,8 +150,7 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { * @throws \Drupal\Core\Entity\EntityMalformedException */ protected function getFileUrl(MediaInterface $media): string { - $fid = $media->getSource()->getSourceFieldValue($media); - $file = $this->entityTypeManager->getStorage('file')->load($fid); + $file = $this->getSourceFile($media); if ($file->hasLinkTemplate('canonical')) { $url = $file->toUrl()->setAbsolute(FALSE); @@ -178,4 +165,24 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { return $url; } + /** + * Load the source file entity from the given media. + * + * @param \Drupal\media\MediaInterface $media + * The media entity to load the source file from. + * + * @return \Drupal\file\FileInterface|null + * The loaded source file entity or NULL if the file could not be loaded. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function getSourceFile(MediaInterface $media): ?FileInterface { + $fid = $media->getSource()->getSourceFieldValue($media); + $file = $this->entityTypeManager->getStorage('file')->load($fid); + + assert($file === NULL || $file instanceof FileInterface); + return $file; + } + } -- GitLab From 24aa5a457fd1ec0fc4f9f34d976f5476a26d017b Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Thu, 20 Mar 2025 16:56:18 -0500 Subject: [PATCH 3/9] Add null safety to file URL processing. --- .../src/MediaLibraryJSONLibraryOpener.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index edd72299..6e31f0a8 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -152,17 +152,17 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { protected function getFileUrl(MediaInterface $media): string { $file = $this->getSourceFile($media); - if ($file->hasLinkTemplate('canonical')) { + if ($file?->hasLinkTemplate('canonical')) { $url = $file->toUrl()->setAbsolute(FALSE); } - elseif ($file->access('download')) { + elseif ($file?->access('download')) { $url = $this->fileUrlGenerator->generateString($file->getFileUri()); } else { - $url = $file->label(); + $url = $file?->label(); } - return $url; + return (string) $url; } /** -- GitLab From bcb18e8b3fe32376951deb2d682101f5e6cc80ac Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Fri, 21 Mar 2025 09:15:55 -0500 Subject: [PATCH 4/9] Add debug warning for missing editor value assignments. --- js/patternkit.jsoneditor.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/js/patternkit.jsoneditor.js b/js/patternkit.jsoneditor.js index 95e8d0e5..56645e2c 100644 --- a/js/patternkit.jsoneditor.js +++ b/js/patternkit.jsoneditor.js @@ -307,7 +307,15 @@ patternkitEditorArray(jQuery, Drupal, JSONEditor); } // Ajax command response to allow updating Editor field values. Drupal.AjaxCommands.prototype.patternkitEditorUpdate = function (ajax, response, status) { - window.patternkitEditor.getEditor(response.selector)?.setValue(response.value); + let editor = window.patternkitEditor.getEditor(response.selector); + + if (editor) { + editor.setValue(response.value); + } + else { + window.console?.debug('Unable to find an editor at "%s" to assign value "%s".', + response.selector, response.value); + } }; let $target = $('#patternkit-editor-target', context); -- GitLab From 7fd3e586cafd1027c508a83cef2721f8eb5973c3 Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Fri, 21 Mar 2025 10:09:48 -0500 Subject: [PATCH 5/9] Log media opener exceptions server-side. --- .../patternkit_media_library.services.yml | 7 +++++++ .../src/MediaLibraryJSONLibraryOpener.php | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/modules/patternkit_media_library/patternkit_media_library.services.yml b/modules/patternkit_media_library/patternkit_media_library.services.yml index d2fb1886..7e1df3ee 100644 --- a/modules/patternkit_media_library/patternkit_media_library.services.yml +++ b/modules/patternkit_media_library/patternkit_media_library.services.yml @@ -1,4 +1,11 @@ services: + _defaults: + autoconfigure: true + + logger.channel.patternkit_media_library: + parent: logger.channel_base + arguments: [ 'patternkit_media_library' ] + patternkit.opener.jsonlibrary: class: Drupal\patternkit_media_library\MediaLibraryJSONLibraryOpener arguments: diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index 6e31f0a8..75ab9f94 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -4,20 +4,28 @@ namespace Drupal\patternkit_media_library; use Drupal\Core\Access\AccessResult; use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\MessageCommand; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\File\FileUrlGeneratorInterface; +use Drupal\Core\Form\FormAjaxException; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; +use Drupal\Core\Utility\Error; use Drupal\file\FileInterface; use Drupal\media\MediaInterface; use Drupal\media_library\MediaLibraryOpenerInterface; use Drupal\media_library\MediaLibraryState; use Drupal\patternkit\AJAX\PatternkitEditorUpdateCommand; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Lock\Exception\InvalidArgumentException; /** * The media library opener for field widgets. */ -class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { +class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface, LoggerAwareInterface { + + use LoggerAwareTrait; /** * MediaLibraryFieldWidgetOpener constructor. @@ -107,7 +115,12 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface { } } } - catch (\Exception) { + catch (\Exception $exception) { + // Log the failure server-side for review and visibility. + Error::logException($this->logger, $exception); + + // @todo Add a client-side warning message to the form with MessageCommand + // and a new message list container on the form. return $response; } -- GitLab From bc13228c0309be5d28b708b91b358312b4780597 Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Fri, 21 Mar 2025 13:24:54 -0500 Subject: [PATCH 6/9] Add editor notification if the media opener encounters an exception. --- .../src/MediaLibraryJSONLibraryOpener.php | 20 ++++++++++++--- src/Plugin/Block/PatternkitBlock.php | 25 +++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index 75ab9f94..433f79a6 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -4,10 +4,11 @@ namespace Drupal\patternkit_media_library; use Drupal\Core\Access\AccessResult; use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\CloseDialogCommand; use Drupal\Core\Ajax\MessageCommand; +use Drupal\Core\Ajax\ScrollTopCommand; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\File\FileUrlGeneratorInterface; -use Drupal\Core\Form\FormAjaxException; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Utility\Error; @@ -18,7 +19,6 @@ use Drupal\media_library\MediaLibraryState; use Drupal\patternkit\AJAX\PatternkitEditorUpdateCommand; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; -use Symfony\Component\Lock\Exception\InvalidArgumentException; /** * The media library opener for field widgets. @@ -119,8 +119,20 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface, Logg // Log the failure server-side for review and visibility. Error::logException($this->logger, $exception); - // @todo Add a client-side warning message to the form with MessageCommand - // and a new message list container on the form. + // Display a message client-side for context when the value fails to be + // populated. + $response->addCommand(new MessageCommand( + 'There was a problem selecting the media asset. Save your work.', + '.patternkit-form-messages .messages-list', + ['type' => 'warning'] + )); + + // A redundant close dialog command is added by the + // MediaLibrarySelectForm, but we have to close the media dialog first for + // the scroll command to take effect. + $response->addCommand(new CloseDialogCommand('#drupal-modal')); + $response->addCommand(new ScrollTopCommand('.patternkit-form-messages')); + return $response; } diff --git a/src/Plugin/Block/PatternkitBlock.php b/src/Plugin/Block/PatternkitBlock.php index 465ea012..088c0ac7 100644 --- a/src/Plugin/Block/PatternkitBlock.php +++ b/src/Plugin/Block/PatternkitBlock.php @@ -266,14 +266,25 @@ class PatternkitBlock extends BlockBase implements ContainerFactoryPluginInterfa // in layout builder and elsewhere. $configuration['reusable'] = $patternkit_block->isNew() ? $is_block_form : $patternkit_block->isReusable(); + // Include a messages list wrapper to display AJAX warnings or messages. + // NB. The class must be added on a theme wrapper since the '#attributes' + // key is lost when the status_messages element renders as a placeholder. + $form['messages'] = [ + '#theme' => 'status_messages', + '#message_list' => [], + '#theme_wrappers' => [ + 'container' => [ + '#attributes' => [ + 'class' => ['patternkit-form-messages'], + ], + ], + ], + ]; + // Warn about accidental changes to reusable blocks. if (isset($configuration['reusable']) && $configuration['reusable']) { - $form['messages'] = [ - '#theme' => 'status_messages', - '#message_list' => [ - 'warning' => [$this->t('This block is reusable! Any changes made will be applied globally.')], - ], - ]; + $form['messages']['#message_list']['warning'][] = + $this->t('This block is reusable! Any changes made will be applied globally.'); } $form['reusable'] = [ @@ -560,7 +571,7 @@ class PatternkitBlock extends BlockBase implements ContainerFactoryPluginInterfa ['@version' => $pattern->getVersion()] ); } - $form['schema_desc'] = ['#markup' => $success_message]; + $form['messages']['#message_list']['status'][] = $success_message; } try { -- GitLab From a5fa83d9d8cafd0a8d46be34cee88400c6b238af Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Fri, 21 Mar 2025 13:40:00 -0500 Subject: [PATCH 7/9] Handle media load failures in the opener. --- .../src/MediaLibraryJSONLibraryOpener.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index 433f79a6..4122c530 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -70,9 +70,16 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface, Logg return $response; } try { - /** @var \Drupal\media\MediaInterface $media */ + /** @var \Drupal\media\MediaInterface|null $media */ $media = $this->entityTypeManager->getStorage('media')->load($mid); - assert($media instanceof MediaInterface); + + // Fail here if the Media entity couldn't be loaded. + if ($media === NULL) { + $this->logger->error('Unable to load media entity "@mid" for media opener.', [ + '@mid' => $mid, + ]); + return $response; + } if ($use_media_token) { $media_token = $this->getMediaToken($media); -- GitLab From 6539a007f5fce8a0b56e49e84f4160f376043528 Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Fri, 21 Mar 2025 13:43:17 -0500 Subject: [PATCH 8/9] More explicitly test for a media interface on loaded media result. --- .../src/MediaLibraryJSONLibraryOpener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index 4122c530..ea1d8c32 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -74,7 +74,7 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface, Logg $media = $this->entityTypeManager->getStorage('media')->load($mid); // Fail here if the Media entity couldn't be loaded. - if ($media === NULL) { + if (!($media instanceof MediaInterface)) { $this->logger->error('Unable to load media entity "@mid" for media opener.', [ '@mid' => $mid, ]); -- GitLab From 5e5712c1b2033be5ae094c9c0f4e74bb5b8465ac Mon Sep 17 00:00:00 2001 From: Stephen Lucero <slucero@redhat.com> Date: Wed, 26 Mar 2025 08:33:13 -0500 Subject: [PATCH 9/9] Replace remaining hardcoded image field name. --- .../src/MediaLibraryJSONLibraryOpener.php | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php index ea1d8c32..953717d6 100644 --- a/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php +++ b/modules/patternkit_media_library/src/MediaLibraryJSONLibraryOpener.php @@ -104,18 +104,23 @@ class MediaLibraryJSONLibraryOpener implements MediaLibraryOpenerInterface, Logg // additional attributes. $widget_base_path = substr($widget_id, 0, -3); - // Images will typically produce the following attributes: 'alt', - // 'height', 'target_id', 'title', and 'width'. - $attributes = $media->get('field_media_image')->first()->getValue(); - foreach ($attributes as $key => $value) { - // Only assign attributes with non-empty values, and ignore the - // internal value for the target_id referencing the file entity. - if ($key != 'target_id' && $value) { - $target = $widget_base_path . $key; - - // If a field is not found in the schema client-side, the - // command should fail silently. - $response->addCommand(new PatternkitEditorUpdateCommand($target, $value)); + $source_configuration = $media->getSource()?->getConfiguration(); + $source_field_name = is_array($source_configuration) ? $source_configuration['source_field'] : NULL; + + if ($source_field_name !== NULL) { + // Images will typically produce the following attributes: 'alt', + // 'height', 'target_id', 'title', and 'width'. + $attributes = $media->get($source_field_name)?->first()?->getValue() ?: []; + foreach ($attributes as $key => $value) { + // Only assign attributes with non-empty values, and ignore the + // internal value for the target_id referencing the file entity. + if ($key != 'target_id' && $value) { + $target = $widget_base_path . $key; + + // If a field is not found in the schema client-side, the + // command should fail silently. + $response->addCommand(new PatternkitEditorUpdateCommand($target, $value)); + } } } } -- GitLab