diff --git a/src/Element/EntityBrowserElement.php b/src/Element/EntityBrowserElement.php index 00840aa..2b1c572 100644 --- a/src/Element/EntityBrowserElement.php +++ b/src/Element/EntityBrowserElement.php @@ -24,6 +24,9 @@ use Drupal\Core\Entity\EntityInterface; * - #selection_mode: (optional) Determines how selection in entity browser will * be handled. Will selection be appended/prepended or it will be replaced * in case of editing. Defaults to append. + * - #widget_context: (optional) Widget configuration overrides which enable + * use cases where the instance of a widget needs awareness of contextual + * configuration like field settings. * * Return value will be an array of selected entities, which will appear under * 'entities' key on the root level of the element's values in the form state. @@ -86,6 +89,7 @@ class EntityBrowserElement extends FormElement { '#process' => [[$class, 'processEntityBrowser']], '#default_value' => [], '#entity_browser_validators' => [], + '#widget_context' => [], '#attached' => ['library' => ['entity_browser/common']], ]; } @@ -172,6 +176,7 @@ class EntityBrowserElement extends FormElement { [ 'validators' => $validators, 'selected_entities' => $entity_browser_preselected_entities, + 'widget_context' => $element['#widget_context'], ] ); diff --git a/src/Form/EntityBrowserForm.php b/src/Form/EntityBrowserForm.php index 8f3c1e2..340e425 100644 --- a/src/Form/EntityBrowserForm.php +++ b/src/Form/EntityBrowserForm.php @@ -92,6 +92,7 @@ class EntityBrowserForm extends FormBase implements EntityBrowserFormInterface { } $form_state->set(['entity_browser', 'selected_entities'], []); $form_state->set(['entity_browser', 'validators'], []); + $form_state->set(['entity_browser', 'widget_context'], []); $form_state->set(['entity_browser', 'selection_completed'], FALSE); // Initialize form state with persistent data, if present. diff --git a/src/Plugin/EntityBrowser/Widget/Upload.php b/src/Plugin/EntityBrowser/Widget/Upload.php index 9b5493c..c6b2eb6 100644 --- a/src/Plugin/EntityBrowser/Widget/Upload.php +++ b/src/Plugin/EntityBrowser/Widget/Upload.php @@ -99,6 +99,7 @@ class Upload extends WidgetBase { public function getForm(array &$original_form, FormStateInterface $form_state, array $additional_widget_parameters) { $form = parent::getForm($original_form, $form_state, $additional_widget_parameters); $field_cardinality = $form_state->get(['entity_browser', 'validators', 'cardinality', 'cardinality']); + $upload_validators = $form_state->has(['entity_browser', 'widget_context', 'upload_validators']) ? $form_state->get(['entity_browser', 'widget_context', 'upload_validators']) : []; $form['upload'] = [ '#type' => 'managed_file', '#title' => $this->t('Choose a file'), @@ -107,9 +108,9 @@ class Upload extends WidgetBase { // Multiple uploads will only be accepted if the source field allows // more than one value. '#multiple' => $field_cardinality != 1 && $this->configuration['multiple'], - '#upload_validators' => [ + '#upload_validators' => array_merge([ 'file_validate_extensions' => [$this->configuration['extensions']], - ], + ], $upload_validators), ]; return $form; diff --git a/src/Plugin/Field/FieldWidget/EntityReferenceBrowserWidget.php b/src/Plugin/Field/FieldWidget/EntityReferenceBrowserWidget.php index 0ac85bc..b4176e5 100644 --- a/src/Plugin/Field/FieldWidget/EntityReferenceBrowserWidget.php +++ b/src/Plugin/Field/FieldWidget/EntityReferenceBrowserWidget.php @@ -354,21 +354,24 @@ class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactor // Enable entity browser if requirements for that are fulfilled. if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) { + $persistentData = $this->getPersistentData(); + $element['entity_browser'] = [ '#type' => 'entity_browser', '#entity_browser' => $this->getSetting('entity_browser'), '#cardinality' => $cardinality, '#selection_mode' => $selection_mode, '#default_value' => $entities, - '#entity_browser_validators' => ['entity_type' => ['type' => $entity_type]], + '#entity_browser_validators' => $persistentData['validators'], + '#widget_context' => $persistentData['widget_context'], '#custom_hidden_id' => $hidden_id, '#process' => [ ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'], [get_called_class(), 'processEntityBrowser'], ], ]; - } + $element['#attached']['library'][] = 'entity_browser/entity_reference'; $field_parents = $element['#field_parents']; @@ -561,6 +564,7 @@ class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactor 'validators' => [ 'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')], ], + 'widget_context' => [], ]; } diff --git a/src/Plugin/Field/FieldWidget/FileBrowserWidget.php b/src/Plugin/Field/FieldWidget/FileBrowserWidget.php index 7d87645..ecb07fa 100644 --- a/src/Plugin/Field/FieldWidget/FileBrowserWidget.php +++ b/src/Plugin/Field/FieldWidget/FileBrowserWidget.php @@ -433,23 +433,27 @@ class FileBrowserWidget extends EntityReferenceBrowserWidget { * * This is a combination of logic shared between the File and Image widgets. * + * @param bool $upload + * Whether or not upload-specific validators should be returned. + * * @return array * An array suitable for passing to file_save_upload() or the file field * element's '#upload_validators' property. */ - public function getFileValidators() { + public function getFileValidators($upload = FALSE) { $validators = []; $settings = $this->fieldDefinition->getSettings(); - // Cap the upload size according to the PHP limit. - $max_filesize = Bytes::toInt(file_upload_max_size()); - if (!empty($settings['max_filesize'])) { - $max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize'])); + if ($upload) { + // Cap the upload size according to the PHP limit. + $max_filesize = Bytes::toInt(file_upload_max_size()); + if (!empty($settings['max_filesize'])) { + $max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize'])); + } + // There is always a file size limit due to the PHP server limit. + $validators['file_validate_size'] = [$max_filesize]; } - // There is always a file size limit due to the PHP server limit. - $validators['file_validate_size'] = [$max_filesize]; - // Images have expected defaults for file extensions. // See \Drupal\image\Plugin\Field\FieldWidget::formElement() for details. if ($this->fieldDefinition->getType() == 'image') { @@ -463,7 +467,7 @@ class FileBrowserWidget extends EntityReferenceBrowserWidget { $validators['file_validate_extensions'] = [$settings['file_extensions']]; } - // Add upload resolution validation. + // Add resolution validation. if ($settings['max_resolution'] || $settings['min_resolution']) { $validators['entity_browser_file_validate_image_resolution'] = [$settings['max_resolution'], $settings['min_resolution']]; } @@ -479,9 +483,9 @@ class FileBrowserWidget extends EntityReferenceBrowserWidget { $settings = $this->fieldDefinition->getSettings(); // Add validators based on our current settings. $data['validators']['file'] = ['validators' => $this->getFileValidators()]; - // Provide context for widgets to enhance their configuration. Currently - // we only know that "upload_location" is used. + // Provide context for widgets to enhance their configuration. $data['widget_context']['upload_location'] = $settings['uri_scheme'] . '://' . $settings['file_directory']; + $data['widget_context']['upload_validators'] = $this->getFileValidators(TRUE); return $data; } diff --git a/src/WidgetBase.php b/src/WidgetBase.php index e2479df..da094e4 100644 --- a/src/WidgetBase.php +++ b/src/WidgetBase.php @@ -112,15 +112,8 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain public function getForm(array &$original_form, FormStateInterface $form_state, array $additional_widget_parameters) { $form = []; - // Allow configuration overrides at runtime based on form state to enable - // use cases where the instance of a widget may have contextual - // configuration like field settings. "widget_context" doesn't have to be - // used in this way, if a widget doesn't want its default configuration - // overwritten it can not call this method and implement its own logic. - foreach ($this->defaultConfiguration() as $key => $value) { - if ($form_state->has(['entity_browser', 'widget_context', $key]) && isset($this->configuration[$key])) { - $this->configuration[$key] = $form_state->get(['entity_browser', 'widget_context', $key]); - } + if ($form_state->has(['entity_browser', 'widget_context'])) { + $this->handleWidgetContext($form_state->get(['entity_browser', 'widget_context'])); } // Check if widget supports auto select functionality and expose config to @@ -361,4 +354,22 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain return $this->getPluginDefinition()['auto_select'] && $this->getConfiguration()['settings']['auto_select']; } + /** + * Allow configuration overrides at runtime based on widget context passed to + * this widget from the Entity Browser element. + * + * Widgets can override this method to replace the default behavior of + * replacing configuration with widget context if array keys match. + * + * @param array $widget_context + * The widget context. + */ + protected function handleWidgetContext($widget_context) { + foreach ($this->defaultConfiguration() as $key => $value) { + if (isset($widget_context[$key]) && isset($this->configuration[$key])) { + $this->configuration[$key] = $widget_context[$key]; + } + } + } + } diff --git a/tests/src/FunctionalJavascript/ImageFieldTest.php b/tests/src/FunctionalJavascript/ImageFieldTest.php index 07dbe07..a08781a 100644 --- a/tests/src/FunctionalJavascript/ImageFieldTest.php +++ b/tests/src/FunctionalJavascript/ImageFieldTest.php @@ -43,11 +43,13 @@ class ImageFieldTest extends EntityBrowserJavascriptTestBase { 'label' => 'Images', 'settings' => [ 'file_extensions' => 'jpg', + 'file_directory' => 'entity-browser-test', + 'max_resolution' => '40x40', 'title_field' => TRUE, ], ])->save(); - file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg'); + file_unmanaged_copy(\Drupal::root() . '/core/modules/simpletest/files/image-test.jpg', 'public://example.jpg'); $this->image = File::create([ 'uri' => 'public://example.jpg', ]); @@ -85,6 +87,17 @@ class ImageFieldTest extends EntityBrowserJavascriptTestBase { ->load('test_entity_browser_iframe_view'); $browser->setDisplay('iframe'); $browser->getDisplay()->setConfiguration($display_config); + $browser->addWidget([ + // These settings should get overridden by our field settings. + 'settings' => [ + 'upload_location' => 'public://', + 'extensions' => 'png', + ], + 'weight' => 1, + 'label' => 'Upload images', + 'id' => 'upload', + ]); + $browser->setWidgetSelector('tabs'); $browser->save(); $account = $this->drupalCreateUser([ @@ -100,7 +113,6 @@ class ImageFieldTest extends EntityBrowserJavascriptTestBase { * Tests basic usage for an image field. */ public function testImageFieldUsage() { - $this->drupalGet('node/add/article'); $this->assertSession()->linkExists('Select images'); $this->getSession()->getPage()->clickLink('Select images'); @@ -139,4 +151,46 @@ class ImageFieldTest extends EntityBrowserJavascriptTestBase { $this->assertSession()->linkExists('Select entities'); } + /** + * Tests that settings are passed from the image field to the upload widget. + */ + public function testImageFieldSettings() { + $root = \Drupal::root(); + $file_wrong_type = $root . '/core/misc/druplicon.png'; + $file_too_big = $root . '/core/modules/simpletest/files/image-2.jpg'; + $file_just_right = $root . '/core/modules/simpletest/files/image-test.jpg'; + $this->drupalGet('node/add/article'); + $this->assertSession()->linkExists('Select images'); + $this->getSession()->getPage()->clickLink('Select images'); + $this->getSession()->switchToIFrame('entity_browser_iframe_test_entity_browser_iframe_view'); + // Switch to the image tab. + $this->clickLink('Upload images'); + // Attempt to upload an invalid image type. The upload widget is configured + // to allow png but the field widget is configured to allow jpg, so we + // expect the field to override the widget. + $this->getSession()->getPage()->attachFileToField('files[upload][]', $file_wrong_type); + $this->waitForAjaxToFinish(); + $this->assertSession()->pageTextContains('Only files with the following extensions are allowed: jpg'); + $this->assertSession()->pageTextContains('The specified file druplicon.png could not be uploaded'); + // Upload an image bigger than the field widget's configured max size. + $this->getSession()->getPage()->attachFileToField('files[upload][]', $file_too_big); + $this->waitForAjaxToFinish(); + $this->assertSession()->pageTextContains('The image was resized to fit within the maximum allowed dimensions of 40x40 pixels.'); + // Upload an image that passes validation and finish the upload. + $this->getSession()->getPage()->attachFileToField('files[upload][]', $file_just_right); + $this->waitForAjaxToFinish(); + $this->getSession()->getPage()->pressButton('Select files'); + $this->getSession()->getPage()->pressButton('Use selected'); + $this->assertSession()->pageTextContains('image-test.jpg'); + // Check that the file has uploaded to the correct sub-directory. + $this->getSession()->switchToIFrame(); + $this->waitForAjaxToFinish(); + $entity_id = $this->getSession()->evaluateScript('jQuery("#edit-field-image-wrapper [data-entity-id]").data("entity-id")'); + $this->assertStringStartsWith('file:', $entity_id); + /** @var \Drupal\file\Entity\File $file */ + $fid = explode(':', $entity_id)[1]; + $file = File::load($fid); + $this->assertContains('entity-browser-test', $file->getFileUri()); + } + }