diff --git a/core/modules/media_library/config/install/media_library.settings.yml b/core/modules/media_library/config/install/media_library.settings.yml new file mode 100644 index 00000000000..e544e183981 --- /dev/null +++ b/core/modules/media_library/config/install/media_library.settings.yml @@ -0,0 +1 @@ +advanced_ui: false diff --git a/core/modules/media_library/config/schema/media_library.schema.yml b/core/modules/media_library/config/schema/media_library.schema.yml index 544c18974b9..312f4ab6071 100644 --- a/core/modules/media_library/config/schema/media_library.schema.yml +++ b/core/modules/media_library/config/schema/media_library.schema.yml @@ -8,3 +8,11 @@ field.widget.settings.media_library_widget: sequence: type: string label: 'Media type ID' + +media_library.settings: + type: config_object + label: 'Media library settings' + mapping: + advanced_ui: + type: boolean + label: 'Enable advanced UI' diff --git a/core/modules/media_library/media_library.install b/core/modules/media_library/media_library.install index c9f4e8afbee..05ba1cb1968 100644 --- a/core/modules/media_library/media_library.install +++ b/core/modules/media_library/media_library.install @@ -191,3 +191,14 @@ function media_library_update_8703() { // items in the 'media' view. It has been converted to a post-update hook. // @see media_library_post_update_add_buttons_to_page_view() } + +/** + * Creates the media_library.settings config object. + */ +function media_library_update_8704() { + \Drupal::configFactory() + ->getEditable('media_library.settings') + // Enable the advanced UI by default, to preserve existing behavior. + ->set('advanced_ui', TRUE) + ->save(); +} diff --git a/core/modules/media_library/media_library.links.menu.yml b/core/modules/media_library/media_library.links.menu.yml new file mode 100644 index 00000000000..69d7a6d48fb --- /dev/null +++ b/core/modules/media_library/media_library.links.menu.yml @@ -0,0 +1,5 @@ +media_library.settings: + title: 'Media Library settings' + parent: system.admin_config_media + description: 'Manage Media Library settings.' + route_name: media_library.settings diff --git a/core/modules/media_library/media_library.routing.yml b/core/modules/media_library/media_library.routing.yml index efc9836e470..3a15fb659c6 100644 --- a/core/modules/media_library/media_library.routing.yml +++ b/core/modules/media_library/media_library.routing.yml @@ -4,3 +4,11 @@ media_library.ui: _controller: 'media_library.ui_builder:buildUi' requirements: _custom_access: 'media_library.ui_builder:checkAccess' + +media_library.settings: + path: '/admin/config/media/media-library' + defaults: + _form: '\Drupal\media_library\Form\SettingsForm' + _title: 'Media Library settings' + requirements: + _permission: 'administer media' diff --git a/core/modules/media_library/src/Form/AddFormBase.php b/core/modules/media_library/src/Form/AddFormBase.php index b506928db3a..2a3f526571d 100644 --- a/core/modules/media_library/src/Form/AddFormBase.php +++ b/core/modules/media_library/src/Form/AddFormBase.php @@ -379,7 +379,7 @@ abstract class AddFormBase extends FormBase { protected function buildCurrentSelectionArea(array $form, FormStateInterface $form_state) { $pre_selected_items = $this->getPreSelectedMediaItems($form_state); - if (!$pre_selected_items) { + if (!$pre_selected_items || !$this->isAdvancedUi()) { return []; } @@ -461,26 +461,32 @@ abstract class AddFormBase extends FormBase { * An actions element containing the actions of the form. */ protected function buildActions(array $form, FormStateInterface $form_state) { - return [ + $actions = [ '#type' => 'actions', 'save_select' => [ '#type' => 'submit', '#button_type' => 'primary', - '#value' => $this->t('Save and select'), + '#value' => $this->t('Save'), '#ajax' => [ 'callback' => '::updateLibrary', 'wrapper' => 'media-library-add-form-wrapper', ], ], - 'save_insert' => [ - '#type' => 'submit', - '#value' => $this->t('Save and insert'), - '#ajax' => [ - 'callback' => '::updateWidget', - 'wrapper' => 'media-library-add-form-wrapper', - ], - ], ]; + if ($this->isAdvancedUi()) { + $actions['save_select']['#value'] = $this->t('Save and select'); + $actions['save_insert'] = [ + 'save_insert' => [ + '#type' => 'submit', + '#value' => $this->t('Save and insert'), + '#ajax' => [ + 'callback' => '::updateWidget', + 'wrapper' => 'media-library-add-form-wrapper', + ], + ], + ]; + } + return $actions; } /** @@ -840,4 +846,19 @@ abstract class AddFormBase extends FormBase { return array_merge($pre_selected_media, $added_media); } + /** + * Determines if the "advanced UI" of the Media Library is enabled. + * + * This exposes additional features that are useful to power users. + * + * @return bool + * TRUE if the advanced UI is enabled, FALSE otherwise. + * + * @see ::buildActions() + * @see ::buildCurrentSelectionArea() + */ + protected function isAdvancedUi() { + return (bool) $this->config('media_library.settings')->get('advanced_ui'); + } + } diff --git a/core/modules/media_library/src/Form/SettingsForm.php b/core/modules/media_library/src/Form/SettingsForm.php new file mode 100644 index 00000000000..c171ece8088 --- /dev/null +++ b/core/modules/media_library/src/Form/SettingsForm.php @@ -0,0 +1,51 @@ + 'checkbox', + '#title' => $this->t('Enable advanced UI'), + '#default_value' => $this->config('media_library.settings')->get('advanced_ui'), + '#description' => $this->t('If checked, users creating new media items in the media library will see a summary of their selected media items, and they will be able insert their selection directly into the media field or text editor.'), + ]; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->config('media_library.settings') + ->set('advanced_ui', (bool) $form_state->getValue('advanced_ui')) + ->save(); + + parent::submitForm($form, $form_state); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/SettingsFormTest.php b/core/modules/media_library/tests/src/Functional/SettingsFormTest.php new file mode 100644 index 00000000000..35166eade66 --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/SettingsFormTest.php @@ -0,0 +1,46 @@ +drupalCreateUser([ + 'access administration pages', + 'administer media', + ]); + $this->drupalLogin($account); + + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + $this->drupalGet('/admin/config'); + $page->clickLink('Media Library settings'); + $page->checkField('Enable advanced UI'); + $page->pressButton('Save configuration'); + $assert_session->checkboxChecked('Enable advanced UI'); + $page->uncheckField('Enable advanced UI'); + $page->pressButton('Save configuration'); + $assert_session->checkboxNotChecked('Enable advanced UI'); + } + +} diff --git a/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdate8704Test.php b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdate8704Test.php new file mode 100644 index 00000000000..819f39cf1dc --- /dev/null +++ b/core/modules/media_library/tests/src/Functional/Update/MediaLibraryUpdate8704Test.php @@ -0,0 +1,37 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../../../media/tests/fixtures/update/drupal-8.4.0-media_installed.php', + __DIR__ . '/../../../fixtures/update/drupal-8.7.2-media_library_installed.php', + ]; + } + + /** + * Tests that the update creates the media_library.settings config object. + */ + public function testUpdate() { + $this->assertNull($this->config('media_library.settings')->get('advanced_ui')); + $this->runUpdates(); + $this->assertTrue($this->config('media_library.settings')->get('advanced_ui')); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php index 36f30043fdf..b00a24b3c92 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/EmbeddedFormWidgetTest.php @@ -69,6 +69,10 @@ class EmbeddedFormWidgetTest extends WebDriverTestBase { ]) ->save(); + $this->config('media_library.settings') + ->set('advanced_ui', TRUE) + ->save(); + $user = $this->drupalCreateUser([ 'access content', 'access media overview', diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php index 5da6ddc42ce..a8f855fb040 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php @@ -1025,6 +1025,374 @@ class MediaLibraryTest extends WebDriverTestBase { $assert_session->elementExists('css', '.media-library-add-form'); $assert_session->fieldExists('Add files'); + // Assert we can upload a file to the default tab type_three. + $assert_session->elementExists('css', '.media-library-add-form--without-input'); + $assert_session->elementNotExists('css', '.media-library-add-form--with-input'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + $this->assertJsCondition('jQuery(".media-library-add-form__added-media").is(":focus")'); + $assert_session->pageTextContains('The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.'); + $assert_session->elementAttributeContains('css', '.media-library-add-form__added-media', 'aria-label', 'Added media items'); + $assert_session->elementExists('css', '.media-library-add-form--with-input'); + $assert_session->elementNotExists('css', '.media-library-add-form--without-input'); + // We do not have a pre-selected items, so the container should not be added + // to the form. + $assert_session->elementNotExists('css', '.media-library-add-form__selected-media'); + // Files are temporary until the form is saved. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri())); + $this->assertTrue($file->isTemporary()); + // Assert the revision_log_message field is not shown. + $upload_form = $assert_session->elementExists('css', '.media-library-add-form'); + $assert_session->fieldNotExists('Revision log message', $upload_form); + // Assert the name field contains the filename and the alt text is required. + $assert_session->fieldValueEquals('Name', $png_image->filename); + $this->pressSaveButton(TRUE); + $this->waitForText('Alternative text field is required'); + $page->fillField('Alternative text', $this->randomString()); + $this->pressSaveButton(); + $this->assertJsCondition('jQuery("input[name=\'media_library_select_form[0]\']").is(":focus")'); + // The file should be permanent now. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertFalse($file->isTemporary()); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains($png_image->filename); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + $assert_session->pageTextContains('1 of 2 items selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + // Ensure the created item is added in the widget. + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); + $this->waitForNoText('Add or select media'); + $this->waitForText($png_image->filename); + + // Remove the item. + $assert_session->elementExists('css', '.media-library-item__remove')->click(); + $this->waitForNoText($png_image->filename); + + $this->openMediaLibraryForField('field_twin_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Three'); + $png_uri_2 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2)); + $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + $this->pressSaveButton(); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); + $this->waitForNoText('Add or select media'); + $this->waitForText($file_system->basename($png_uri_2)); + + // Also make sure that we can upload to the unlimited cardinality field. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Three'); + + // Select a media item to check if the selection is persisted when adding + // new items. + $existing_media_name = $file_system->basename($png_uri_2); + $checkbox = $page->findField("Select $existing_media_name"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $png_uri_3 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3)); + $this->waitForText('The media item has been created but has not yet been saved.'); + $page->fillField('Name', 'Unlimited Cardinality Image'); + $page->fillField('Alternative text', $this->randomString()); + $this->pressSaveButton(); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $added_media_name = $added_media->label(); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains('Unlimited Cardinality Image'); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + // Assert the item that was selected before uploading the file is still + // selected. + $assert_session->pageTextContains('2 items selected'); + $assert_session->checkboxChecked("Select $added_media_name"); + $assert_session->checkboxChecked("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); + $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); + $selected_checkboxes = []; + foreach ($checkboxes as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getAttribute('value'); + } + } + $this->assertCount(2, $selected_checkboxes); + // Ensure the created item is added in the widget. + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->waitForText('Added 2 media items.'); + $this->waitForNoText('Add or select media'); + $this->waitForText('Unlimited Cardinality Image'); + + // Assert we can now only upload one more media item. + $this->openMediaLibraryForField('field_twin_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Four'); + $this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple')); + $assert_session->pageTextContains('One file only.'); + + // Assert media type four should only allow jpg files by trying a png file + // first. + $png_uri_4 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add file', $file_system->realpath($png_uri_4), FALSE); + $this->waitForText('Only files with the following extensions are allowed'); + // Assert that jpg files are accepted by type four. + $jpg_uri_2 = $file_system->copy($jpg_image->uri, 'public://'); + $this->addMediaFileToField('Add file', $file_system->realpath($jpg_uri_2)); + $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + // The type_four media type has another optional image field. + $assert_session->pageTextContains('Extra Image'); + $jpg_uri_3 = $file_system->copy($jpg_image->uri, 'public://'); + $this->addMediaFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_uri_3)); + $this->waitForText($file_system->basename($jpg_uri_3)); + // Ensure that the extra image was uploaded to the correct directory. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri())); + $this->pressSaveButton(); + // Ensure the media item was saved to the library and automatically + // selected. + $this->waitForText('Add or select media'); + $this->waitForText($file_system->basename($jpg_uri_2)); + // Ensure the created item is added in the widget. + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); + $this->waitForNoText('Add or select media'); + $assert_session->pageTextContains($file_system->basename($jpg_uri_2)); + + // Assert we can also remove selected items from the selection area in the + // upload form. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Three'); + $checkbox = $page->findField("Select $existing_media_name"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); + $png_uri_5 = $file_system->copy($png_image->uri, 'public://'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_5)); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $page->fillField('Alternative text', $this->randomString()); + $this->pressSaveButton(); + $page->uncheckField('media_library_select_form[2]'); + $this->waitForText('1 item selected'); + $this->waitForText("Select $existing_media_name"); + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + $added_media_name = $added_media->label(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->checkboxChecked("Select $added_media_name"); + $assert_session->checkboxNotChecked("Select $existing_media_name"); + $assert_session->hiddenFieldValueEquals('current_selection', $added_media->id()); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); + $this->waitForNoText('Add or select media'); + $this->waitForText($file_system->basename($png_uri_5)); + + // Assert removing an uploaded media item before save works as expected. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Three'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + // Assert the focus is shifted to the added media items. + $this->assertJsCondition('jQuery(".media-library-add-form__added-media").is(":focus")'); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $assert_session->elementExists('css', '.media-library-add-form__fields'); + $assert_session->elementNotExists('css', '.media-library-menu'); + // Press the 'Remove button' and assert the user is sent back to the media + // library. + $assert_session->elementExists('css', '.media-library-add-form__remove-button')->click(); + // Assert the remove message is shown. + $this->waitForText("The media item $png_image->filename has been removed."); + // Assert the focus is shifted to the first tabbable element of the add + // form, which should be the source field. + $this->assertJsCondition('jQuery("#media-library-add-form-wrapper :tabbable").is(":focus")'); + $assert_session->elementNotExists('css', '.media-library-add-form__fields'); + $assert_session->elementExists('css', '.media-library-menu'); + $assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click(); + + // Assert uploading multiple files. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Three'); + // Assert the existing items are remembered when adding and removing media. + $checkbox = $page->findField("Select $existing_media_name"); + $checkbox->click(); + // Assert we can add multiple files. + $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple')); + // Create a list of new files to upload. + $filenames = []; + $remote_paths = []; + foreach (range(1, 4) as $i) { + $path = $file_system->copy($png_image->uri, 'public://'); + $filenames[] = $file_system->basename($path); + $remote_paths[] = $driver->uploadFileAndGetRemoteFilePath($file_system->realpath($path)); + } + $page->findField('Add files')->setValue(implode("\n", $remote_paths)); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $this->assertElementExistsAfterWait('css', '.media-library-add-form__fields'); + $assert_session->elementNotExists('css', '.media-library-menu'); + // Assert all files have been added. + $assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]); + $assert_session->fieldValueEquals('media[1][fields][name][0][value]', $filenames[1]); + $assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]); + $assert_session->fieldValueEquals('media[3][fields][name][0][value]', $filenames[3]); + // Set alt texts for items 1 and 2, leave the alt text empty for items 3 + // and 4 to assert the field validation does not stop users from removing + // items. + $page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]); + $page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]); + // Assert the file is available in the file storage. + $files = $file_storage->loadByProperties(['filename' => $filenames[1]]); + $this->assertCount(1, $files); + $file_1_uri = reset($files)->getFileUri(); + // Remove the second file and assert the focus is shifted to the container + // of the next media item and field values are still correct. + $page->pressButton('media-1-remove-button'); + $this->assertJsCondition('jQuery(".media-library-add-form__media[data-media-library-added-delta=2]").is(":focus")'); + $assert_session->pageTextContains('The media item ' . $filenames[1] . ' has been removed.'); + // Assert the file was deleted. + $this->assertEmpty($file_storage->loadByProperties(['filename' => $filenames[1]])); + $this->assertFileNotExists($file_1_uri); + + // When a file is already in usage, it should not be deleted. To test, + // let's add a usage for $filenames[3] (now in the third position). + $files = $file_storage->loadByProperties(['filename' => $filenames[3]]); + $this->assertCount(1, $files); + $target_file = reset($files); + Media::create([ + 'bundle' => 'type_three', + 'name' => 'Disturbing', + 'field_media_test_image' => [ + ['target_id' => $target_file->id()], + ], + ])->save(); + // Remove $filenames[3] (now in the third position) and assert the focus is + // shifted to the container of the previous media item and field values are + // still correct. + $page->pressButton('media-3-remove-button'); + $this->assertTrue($assert_session->waitForText('The media item ' . $filenames[3] . ' has been removed.')); + // Assert the file was not deleted, due to being in use elsewhere. + $this->assertNotEmpty($file_storage->loadByProperties(['filename' => $filenames[3]])); + $this->assertFileExists($target_file->getFileUri()); + + // The second media item should be removed (this has the delta 1 since we + // start counting from 0). + $assert_session->elementNotExists('css', '.media-library-add-form__media[data-media-library-added-delta=1]'); + $media_item_one = $assert_session->elementExists('css', '.media-library-add-form__media[data-media-library-added-delta=0]'); + $assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one); + $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); + $media_item_three = $assert_session->elementExists('css', '.media-library-add-form__media[data-media-library-added-delta=2]'); + $assert_session->fieldValueEquals('Name', $filenames[2], $media_item_three); + $assert_session->fieldValueEquals('Alternative text', '', $media_item_three); + } + + /** + * Tests that uploads in the widget's advanced UI works as expected. + * + * Note that this test will occasionally fail with SQLite until + * https://www.drupal.org/node/3066447 is addressed. + * + * @todo Merge this with testWidgetUpload() in + * https://www.drupal.org/project/drupal/issues/3087227 + */ + public function testWidgetUploadAdvancedUi() { + $this->config('media_library.settings')->set('advanced_ui', TRUE)->save(); + + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + $driver = $this->getSession()->getDriver(); + + foreach ($this->getTestFiles('image') as $image) { + $extension = pathinfo($image->filename, PATHINFO_EXTENSION); + if ($extension === 'png') { + $png_image = $image; + } + elseif ($extension === 'jpg') { + $jpg_image = $image; + } + } + + if (!isset($png_image) || !isset($jpg_image)) { + $this->fail('Expected test files not present.'); + } + + // Create a user that can only add media of type four. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create type_one media', + 'create type_four media', + 'view media', + ]); + $this->drupalLogin($user); + + // Visit a node create page and open the media library. + $this->drupalGet('node/add/basic_page'); + $this->openMediaLibraryForField('field_twin_media'); + $assert_session->pageTextContains('Add or select media'); + + // Assert the upload form is not visible for default tab type_three without + // the proper permissions. + $assert_session->elementNotExists('css', '.media-library-add-form'); + + // Assert the upload form is not visible for the non-file based media type + // type_one. + $this->switchToMediaType('One'); + $assert_session->elementNotExists('css', '.media-library-add-form'); + + // Assert the upload form is visible for type_four. + $this->switchToMediaType('Four'); + $assert_session->fieldExists('Add files'); + $assert_session->pageTextContains('Maximum 2 files.'); + + // Create a user that can create media for all media types. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create media', + 'view media', + ]); + $this->drupalLogin($user); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $this->container->get('file_system'); + + // Add to the twin media field. + $this->openMediaLibraryForField('field_twin_media'); + $assert_session->pageTextContains('Add or select media'); + + // Assert the upload form is now visible for default tab type_three. + $assert_session->elementExists('css', '.media-library-add-form'); + $assert_session->fieldExists('Add files'); + // Assert we can upload a file to the default tab type_three. $assert_session->elementExists('css', '.media-library-add-form--without-input'); $assert_session->elementNotExists('css', '.media-library-add-form--with-input'); @@ -1078,13 +1446,13 @@ class MediaLibraryTest extends WebDriverTestBase { $assert_session->elementExists('css', '.media-library-item__remove')->click(); $this->waitForNoText($png_image->filename); - // Assert we can also directly insert uploaded files in the widget. $this->openMediaLibraryForField('field_twin_media'); $assert_session->pageTextContains('Add or select media'); $this->switchToMediaType('Three'); $png_uri_2 = $file_system->copy($png_image->uri, 'public://'); $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2)); $this->waitForFieldExists('Alternative text')->setValue($this->randomString()); + // Assert we can also directly insert uploaded files in the widget. $this->saveAnd('insert'); $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); $this->waitForNoText('Add or select media'); @@ -1193,12 +1561,12 @@ class MediaLibraryTest extends WebDriverTestBase { // assertWaitOnAjaxRequest() required for input "id" attributes to // consistently match their label's "for" attribute. $assert_session->assertWaitOnAjaxRequest(); + $page->fillField('Alternative text', $this->randomString()); // Assert the pre-selected items are shown. $selection_area = $this->assertElementExistsAfterWait('css', '.media-library-add-form__selected-media'); $assert_session->elementExists('css', 'summary', $selection_area)->click(); $assert_session->checkboxChecked("Select $existing_media_name", $selection_area); $selection_area->uncheckField("Select $existing_media_name"); - $page->fillField('Alternative text', $this->randomString()); $assert_session->hiddenFieldValueEquals('current_selection', ''); // Close the details element so that clicking the Save and select works. // @todo Fix dialog or test so this is not necessary to prevent random @@ -1324,6 +1692,7 @@ class MediaLibraryTest extends WebDriverTestBase { $selection_area = $assert_session->elementExists('css', '.media-library-add-form__selected-media'); $assert_session->elementExists('css', 'summary', $selection_area)->click(); $assert_session->checkboxChecked("Select $existing_media_name", $selection_area); + // Remove the last file and assert the focus is shifted to the container // of the first media item and field values are still correct. $page->pressButton('media-2-remove-button'); @@ -1366,6 +1735,207 @@ class MediaLibraryTest extends WebDriverTestBase { $this->switchToMediaType('Three'); $assert_session->fieldNotExists('Add Type Five via URL'); + // Assert we can add an oEmbed video to media type five. + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $youtube_url); + $assert_session->pageTextContains('Allowed providers: YouTube, Vimeo.'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved.'); + // Assert the name field contains the remote video title. + $assert_session->fieldValueEquals('Name', $youtube_title); + $this->pressSaveButton(); + $this->waitForText('Add Type Five via URL'); + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains($youtube_title); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + + // Assert the created oEmbed video is correctly added to the widget. + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); + $this->waitForNoText('Add or select media'); + $this->waitForText($youtube_title); + + // Open the media library again for the unlimited field and go to the tab + // for media type five. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Five'); + // Assert the video is available on the tab. + $assert_session->pageTextContains($youtube_title); + + // Assert we can only add supported URLs. + $page->fillField('Add Type Five via URL', 'https://www.youtube.com/'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('No matching provider found.'); + // Assert we can not add a video ID that doesn't exist. We need to use a + // video ID that will not be filtered by the regex, because otherwise the + // message 'No matching provider found.' will be returned. + $page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1'); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('Could not retrieve the oEmbed resource.'); + + // Select a media item to check if the selection is persisted when adding + // new items. + $checkbox = $page->findField("Select $youtube_title"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->pageTextContains('1 item selected'); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + + // Assert we can add a oEmbed video with a custom name. + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved.'); + $page->fillField('Name', 'Custom video title'); + $assert_session->elementNotExists('css', '.media-library-add-form__selected-media'); + $this->pressSaveButton(); + + // Load the created media item. + $media_items = Media::loadMultiple(); + $added_media = array_pop($media_items); + // Ensure the media item was saved to the library and automatically + // selected. The added media items should be in the first position of the + // add form. + $assert_session->pageTextContains('Add or select media'); + $assert_session->pageTextContains('Custom video title'); + $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id()); + $assert_session->checkboxChecked('media_library_select_form[0]'); + // Assert the item that was selected before uploading the file is still + // selected. + $assert_session->pageTextContains('2 items selected'); + $assert_session->checkboxChecked("Select Custom video title"); + $assert_session->checkboxChecked("Select $youtube_title"); + $assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media->id()])); + $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input'); + $selected_checkboxes = []; + foreach ($checkboxes as $checkbox) { + if ($checkbox->isChecked()) { + $selected_checkboxes[] = $checkbox->getAttribute('value'); + } + } + $this->assertCount(2, $selected_checkboxes); + // Ensure the created item is added in the widget. + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->waitForText('Added 2 media items.'); + $this->waitForNoText('Add or select media'); + $this->waitForText('Custom video title'); + + // Assert we can directly insert added oEmbed media in the widget. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $vimeo_url); + $page->pressButton('Add'); + $this->waitForText('The media item has been created but has not yet been saved.'); + $this->pressSaveButton(); + $this->waitForText('Add or select media'); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->waitForNoText('Add or select media'); + $this->waitForText($vimeo_title); + + // Assert we can remove selected items from the selection area in the oEmbed + // form. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Five'); + $checkbox = $page->findField("Select $vimeo_title"); + $selected_item_id = $checkbox->getAttribute('value'); + $checkbox->click(); + $assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id); + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $assert_session->assertWaitOnAjaxRequest(); + $this->waitForText('The media item has been created but has not yet been saved'); + $page->fillField('Name', 'Another video'); + $this->pressSaveButton(); + $page->uncheckField('media_library_select_form[1]'); + $this->waitForText('1 item selected'); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); + $this->waitForNoText('Add or select media'); + $this->waitForText('Another video'); + + // Assert removing an added oEmbed media item before save works as expected. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + $this->switchToMediaType('Five'); + $page->fillField('Add Type Five via URL', $youtube_url); + $page->pressButton('Add'); + // Assert the focus is shifted to the added media items. + $this->assertJsCondition('jQuery(".media-library-add-form__added-media").is(":focus")'); + // Assert the media item fields are shown and the vertical tabs are no + // longer shown. + $assert_session->elementExists('css', '.media-library-add-form__fields'); + $assert_session->elementNotExists('css', '.media-library-menu'); + // Press the 'Remove button' and assert the user is sent back to the media + // library. + $assert_session->elementExists('css', '.media-library-add-form__remove-button')->click(); + // Assert the remove message is shown. + $this->waitForText("The media item $youtube_title has been removed."); + // Assert the focus is shifted to the first tabbable element of the add + // form, which should be the source field. + $this->assertJsCondition('jQuery("#media-library-add-form-wrapper :tabbable").is(":focus")'); + $assert_session->elementNotExists('css', '.media-library-add-form__fields'); + $assert_session->elementExists('css', '.media-library-menu'); + } + + /** + * Tests that oEmbed media can be added in the widget's advanced UI. + * + * @todo Merge this with testWidgetOEmbed() in + * https://www.drupal.org/project/drupal/issues/3087227 + */ + public function testWidgetOEmbedAdvancedUi() { + $this->config('media_library.settings')->set('advanced_ui', TRUE)->save(); + + $this->hijackProviderEndpoints(); + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $youtube_title = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)"; + $youtube_url = 'https://www.youtube.com/watch?v=PWjcqE3QKBg'; + $vimeo_title = "Drupal Rap Video - Schipulcon09"; + $vimeo_url = 'https://vimeo.com/7073899'; + ResourceController::setResourceUrl($youtube_url, $this->getFixturesDirectory() . '/video_youtube.json'); + ResourceController::setResourceUrl($vimeo_url, $this->getFixturesDirectory() . '/video_vimeo.json'); + ResourceController::setResource404('https://www.youtube.com/watch?v=PWjcqE3QKBg1'); + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + // Add to the unlimited media field. + $this->openMediaLibraryForField('field_unlimited_media'); + $assert_session->pageTextContains('Add or select media'); + + // Assert the default tab for media type one does not have an oEmbed form. + $assert_session->fieldNotExists('Add Type Five via URL'); + + // Assert other media types don't have the oEmbed form fields. + $this->switchToMediaType('Three'); + $assert_session->fieldNotExists('Add Type Five via URL'); + // Assert we can add an oEmbed video to media type five. $this->switchToMediaType('Five'); $page->fillField('Add Type Five via URL', $youtube_url); @@ -1480,6 +2050,7 @@ class MediaLibraryTest extends WebDriverTestBase { $page->fillField('Add Type Five via URL', $vimeo_url); $page->pressButton('Add'); $this->waitForText('The media item has been created but has not yet been saved.'); + $this->saveAnd('insert'); $this->assertNotEmpty($assert_session->waitForText('Added one media item.')); $this->waitForNoText('Add or select media'); @@ -1511,6 +2082,7 @@ class MediaLibraryTest extends WebDriverTestBase { // fails. https://www.drupal.org/project/drupal/issues/3055648 $this->click('details.media-library-add-form__selected-media summary'); $this->saveAnd('select'); + $media_items = Media::loadMultiple(); $added_media = array_pop($media_items); $this->waitForText('1 item selected'); @@ -1750,6 +2322,32 @@ class MediaLibraryTest extends WebDriverTestBase { $this->assertSession()->assertWaitOnAjaxRequest(); } + /** + * Clicks "Save" button and waits for AJAX completion. + * + * @param bool $expect_errors + * Whether validation errors are expected after the "Save" button is + * pressed. Defaults to FALSE. + */ + protected function pressSaveButton($expect_errors = FALSE) { + $buttons = $this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Save'); + + // If no validation errors are expected, wait for the "Insert selected" + // button to return. + if (!$expect_errors) { + $result = $buttons->waitFor(10, function ($buttons) { + /** @var \Behat\Mink\Element\NodeElement $buttons */ + return $buttons->findButton('Insert selected'); + }); + $this->assertNotEmpty($result); + } + + // assertWaitOnAjaxRequest() required for input "id" attributes to + // consistently match their label's "for" attribute. + $this->assertSession()->assertWaitOnAjaxRequest(); + } + /** * Clicks a button that opens a media widget and confirms it is open. *