diff --git a/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php index a9c3fe702e..6dd319c75b 100644 --- a/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php @@ -2,6 +2,8 @@ namespace Drupal\Core\Ajax; +use Drupal\Component\Utility\Html; + /** * Defines an AJAX command that closes the currently visible modal dialog. * @@ -16,7 +18,7 @@ class CloseModalDialogCommand extends CloseDialogCommand { * (optional) Whether to persist the dialog in the DOM or not. */ public function __construct($persist = FALSE) { - $this->selector = '#drupal-modal'; + $this->selector = '#drupal-modal-' . \Drupal::state()->get('current_modal'); $this->persist = $persist; } diff --git a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php index a55b0eaf60..44ec1cbaf7 100644 --- a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php @@ -2,6 +2,9 @@ namespace Drupal\Core\Ajax; +use Drupal\Component\Utility\Crypt; +use Drupal\Component\Utility\Html; + /** * Defines an AJAX command to open certain content in a dialog in a modal dialog. * @@ -31,8 +34,10 @@ class OpenModalDialogCommand extends OpenDialogCommand { * populated automatically from the current request. */ public function __construct($title, $content, array $dialog_options = [], $settings = NULL) { + $id = Crypt::randomBytesBase64(); + \Drupal::state()->set('current_modal', $id); $dialog_options['modal'] = TRUE; - parent::__construct('#drupal-modal', $title, $content, $dialog_options, $settings); + parent::__construct('#drupal-modal-' . $id, $title, $content, $dialog_options, $settings); } } diff --git a/core/modules/media_library/src/Form/AddFormBase.php b/core/modules/media_library/src/Form/AddFormBase.php index 9a1b2a1544..7594933a71 100644 --- a/core/modules/media_library/src/Form/AddFormBase.php +++ b/core/modules/media_library/src/Form/AddFormBase.php @@ -3,7 +3,7 @@ namespace Drupal\media_library\Form; use Drupal\Core\Ajax\AjaxResponse; -use Drupal\Core\Ajax\CloseDialogCommand; +use Drupal\Core\Ajax\CloseModalDialogCommand; use Drupal\Core\Ajax\InvokeCommand; use Drupal\Core\Ajax\ReplaceCommand; use Drupal\Core\Entity\Entity\EntityFormDisplay; @@ -763,7 +763,7 @@ public function updateWidget(array &$form, FormStateInterface $form_state) { $state = $this->getMediaLibraryState($form_state); return $this->openerResolver->get($state) ->getSelectionResponse($state, $current_media_ids) - ->addCommand(new CloseDialogCommand()); + ->addCommand(new CloseModalDialogCommand()); } /** diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php index 2d0702bd4d..595acbb321 100644 --- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php +++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php @@ -2,7 +2,7 @@ namespace Drupal\media_library\Plugin\views\field; -use Drupal\Core\Ajax\CloseDialogCommand; +use Drupal\Core\Ajax\CloseModalDialogCommand; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -130,7 +130,7 @@ public static function updateWidget(array &$form, FormStateInterface $form_state return \Drupal::service('media_library.opener_resolver') ->get($state) ->getSelectionResponse($state, $selected_ids) - ->addCommand(new CloseDialogCommand()); + ->addCommand(new CloseModalDialogCommand()); } /** diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.routing.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.routing.yml new file mode 100644 index 0000000000..625fa2f22d --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.routing.yml @@ -0,0 +1,7 @@ +media_library_test.modal_link: + path: '/media-library-test-modal' + defaults: + _title: 'Test Modal' + _controller: '\Drupal\media_library_test\Controller\MediaLibraryTestController::build' + requirements: + _permission: 'access content' diff --git a/core/modules/media_library/tests/modules/media_library_test/src/Controller/MediaLibraryTestController.php b/core/modules/media_library/tests/modules/media_library_test/src/Controller/MediaLibraryTestController.php new file mode 100644 index 0000000000..f93a39a4cf --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/src/Controller/MediaLibraryTestController.php @@ -0,0 +1,47 @@ + [ + 'library' => ['core/drupal.ajax'], + ], + '#type' => 'link', + '#title' => $type, + '#url' => Url::fromRoute('node.add', ['node_type' => $type], [ + 'attributes' => [ + 'class' => [ + 'use-ajax', + 'modal-' . $type, + ], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => json_encode([ + 'width' => '80%', + ]), + ], + ]), + ]; + } + + return $build; + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/ModalTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/ModalTest.php new file mode 100644 index 0000000000..688faf2be0 --- /dev/null +++ b/core/modules/media_library/tests/src/FunctionalJavascript/ModalTest.php @@ -0,0 +1,166 @@ + 'test_format', + 'name' => 'Test format', + 'filters' => [ + 'media_embed' => ['status' => TRUE], + ], + ])->save(); + Editor::create([ + 'editor' => 'ckeditor', + 'format' => 'test_format', + 'settings' => [ + 'toolbar' => [ + 'rows' => [ + [ + [ + 'name' => 'Main', + 'items' => [ + 'Source', + 'Undo', + 'Redo', + ], + ], + ], + [ + [ + 'name' => 'Embeds', + 'items' => [ + 'DrupalMediaLibrary', + ], + ], + ], + ], + ], + ], + ])->save(); + + $this->drupalCreateContentType(['type' => 'blog']); + + // Note that media_install() grants 'view media' to all users by default. + $this->user = $this->drupalCreateUser([ + 'use text format test_format', + 'access media overview', + 'create blog content', + ]); + + // Create a media type that starts with the letter a, to test tab order. + $this->createMediaType('image', ['id' => 'arrakis', 'label' => 'Arrakis']); + + // Create a sample media entity to be embedded. + $this->createMediaType('image', ['id' => 'image', 'label' => 'Image']); + File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ])->save(); + $this->media = Media::create([ + 'bundle' => 'image', + 'name' => 'Fear is the mind-killer', + 'field_media_image' => [ + [ + 'target_id' => 1, + 'alt' => 'default alt', + 'title' => 'default title', + ], + ], + ]); + $this->media->save(); + + $arrakis_media = Media::create([ + 'bundle' => 'arrakis', + 'name' => 'Le baron Vladimir Harkonnen', + 'field_media_image' => [ + [ + 'target_id' => 1, + 'alt' => 'Il complote pour détruire le duc Leto', + 'title' => 'Il complote pour détruire le duc Leto', + ], + ], + ]); + $arrakis_media->save(); + + $this->drupalLogin($this->user); + } + + /** + * Tests that the Media library insert button in CKEDITOR also works in modal. + */ + public function testCKeditorInModal(){ + $this->drupalGet('/media-library-test-modal'); + $assert_session = $this->assertSession(); + $this->click('.modal-blog'); + $assert_session->waitForElement('css', '.ui-dialog-content'); + $instance_id = $assert_session->elementExists('css', '[data-drupal-selector="edit-body-0-value"]')->getAttribute('id'); + $this->waitForEditor($instance_id); + $this->pressEditorButton('drupalmedialibrary', $instance_id); + $this->assertNotEmpty($assert_session->waitForId('media-library-content')); + $assert_session->pageTextContains('0 of 1 item selected'); + $assert_session->elementExists('css', '.js-media-library-item')->click(); + $assert_session->pageTextContains('1 of 1 item selected'); + $assert_session->elementExists('css', '.media-library-widget-modal .ui-dialog-buttonpane')->pressButton('Insert selected'); + $this->assignNameToCkeditorIframe('ckeditor', $instance_id); + $this->getSession()->switchToIFrame('ckeditor'); + $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia drupal-media .media', 2000)); + } + +} diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php index 353629fc14..9516ab7806 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php @@ -732,4 +732,77 @@ public function testWidgetUploadAdvancedUi() { $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); } + /** + * Tests if the widget is working correctly when opened from a modal. + */ + public function testWidgetInModalContext(){ + $page = $this->getSession()->getPage(); + + 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 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); + $this->drupalGet('/media-library-test-modal'); + $assert_session = $this->assertSession(); + $this->click('.modal-basic_page'); + $assert_session->waitForElement('css', '.ui-dialog-content'); + + $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. + // Add to the twin media field. + $this->openMediaLibraryForField('field_twin_media'); + + // Assert the upload form is now visible for default tab type_three. + $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldExists('Add files'); + + // Assert we can upload a file to the default tab type_three. + $assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + $this->assertMediaAdded(); + $assert_session->elementExists('css', '.js-media-library-add-form[data-input]'); + // We do not have pre-selected items, so the container should not be added + // to the form. + $assert_session->pageTextNotContains('Additional 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', '.js-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); + $page->fillField('Alternative text', $this->randomString()); + $buttons = $this->assertElementExistsAfterWait('css', '.media-library-widget-modal .ui-dialog-buttonpane'); + + $buttons->pressButton('Save'); + $this->waitForText('Insert selected'); + $buttons->pressButton('Insert selected'); + $assert_session->waitForElementVisible('css', '#field_twin_media-media-library-wrapper .js-media-library-selection .js-media-library-item'); + + } } diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php index e6f7c666e1..c9d76b3594 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php @@ -14,7 +14,7 @@ class DialogTest extends WebDriverTestBase { /** - * {@inheritdoc} + * {@inheritdoc}./ */ protected static $modules = ['ajax_test', 'ajax_forms_test', 'contact']; @@ -51,7 +51,7 @@ public function testDialog() { $link1_dialog_div = $this->assertSession()->waitForElementVisible('css', 'div.ui-dialog'); $this->assertNotNull($link1_dialog_div, 'Link was used to open a dialog ( modal )'); - $link1_modal = $link1_dialog_div->find('css', '#drupal-modal'); + $link1_modal = $link1_dialog_div->find('css', '[id*="drupal-modal-"]'); $this->assertNotNull($link1_modal, 'Link was used to open a dialog ( non-modal )'); $this->assertSession()->responseContains($dialog_contents); diff --git a/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php b/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php index 2a5cc4faf3..07a4980dab 100644 --- a/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php +++ b/core/tests/Drupal/Tests/Core/Ajax/AjaxCommandsTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Ajax\AnnounceCommand; use Drupal\Core\Asset\AttachedAssets; +use Drupal\Core\State\StateInterface; use Drupal\Tests\UnitTestCase; use Drupal\Core\Ajax\AddCssCommand; use Drupal\Core\Ajax\AfterCommand; @@ -28,6 +29,7 @@ use Drupal\Core\Ajax\RedirectCommand; use Drupal\Core\Ajax\UpdateBuildIdCommand; use Drupal\Core\Ajax\OpenDialogCommand; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Test coverage for various classes in the \Drupal\Core\Ajax namespace. @@ -379,6 +381,10 @@ public function testOpenDialogCommand() { * @covers \Drupal\Core\Ajax\OpenModalDialogCommand */ public function testOpenModalDialogCommand() { + $container = $this->prophesize(ContainerInterface::class); + $state = $this->prophesize(StateInterface::class); + $container->get('state')->willReturn($state->reveal()); + \Drupal::setContainer($container->reveal()); $command = $this->getMockBuilder('Drupal\Core\Ajax\OpenModalDialogCommand') ->setConstructorArgs([ 'Title', '

Text!

', [ @@ -395,6 +401,8 @@ public function testOpenModalDialogCommand() { ->method('getRenderedContent') ->willReturn('rendered content'); + $command_result = $command->render(); + $expected = [ 'command' => 'openDialog', 'selector' => '#drupal-modal', @@ -407,20 +415,28 @@ public function testOpenModalDialogCommand() { 'modal' => TRUE, ], ]; - $this->assertEquals($expected, $command->render()); + // The ID is based on a random hash so it cannot be predicted. + // We take it out of the comparison. + unset($expected['selector']); + unset($command_result['selector']); + $this->assertEquals($expected, $command_result); } /** * @covers \Drupal\Core\Ajax\CloseModalDialogCommand */ public function testCloseModalDialogCommand() { + $container = $this->prophesize(ContainerInterface::class); + $state = $this->prophesize(StateInterface::class); + $state->get('current_modal')->willReturn('foo'); + $container->get('state')->willReturn($state->reveal()); + \Drupal::setContainer($container->reveal()); $command = new CloseModalDialogCommand(); $expected = [ 'command' => 'closeDialog', - 'selector' => '#drupal-modal', + 'selector' => '#drupal-modal-foo', 'persist' => FALSE, ]; - $this->assertEquals($expected, $command->render()); }