diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php index f465b18..5c9f078 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -345,7 +345,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st // Add the extension list to the page as JavaScript settings. if (isset($element['#upload_validators']['file_validate_extensions'][0])) { $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0]))); - $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list; + $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id'] . '-upload'] = $extension_list; } // Let #id point to the file element, so the field label's 'for' corresponds diff --git a/core/modules/file/tests/src/FunctionalJavascript/ClientValidationTest.php b/core/modules/file/tests/src/FunctionalJavascript/ClientValidationTest.php new file mode 100644 index 0000000..70ee8df --- /dev/null +++ b/core/modules/file/tests/src/FunctionalJavascript/ClientValidationTest.php @@ -0,0 +1,93 @@ +drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + + // Create a field with a basic filetype restriction. + $field_name = strtolower($this->randomMachineName()); + $field_settings = [ + 'file_extensions' => 'png', + ]; + $formatter_settings = [ + 'image_style' => 'large', + 'image_link' => '', + ]; + $this->createImageField($field_name, 'article', [], $field_settings, [], $formatter_settings); + + // Log in as a content author who can create Articles. + $user = $this->drupalCreateUser([ + 'access content', + 'create article content', + 'edit any article content', + 'delete any article content', + ]); + $this->drupalLogin($user); + + $uri = 'public://file.txt'; + file_unmanaged_save_data('Drupal rocks!', $uri, FILE_EXISTS_REPLACE); + $this->file_path = \Drupal::service('file_system')->realpath($uri); + } + + /** + * Tests the client side validation on file fields. + */ + public function testDisallowedExtensionErrorMessage() { + $this->drupalGet('node/add/article'); + $assert = $this->assertSession(); + + // Test uploading a file with a file extension not allowed. + // Try to upload a file with an unallowed extension + $input_selector = 'input.form-file'; + $input = $assert->elementExists('css', $input_selector); + $input->attachFile($this->file_path); + + // Trigger the upload logic with a mock "drop" event. + $script = 'var e = jQuery.Event("drop");' + . 'e.originalEvent = {dataTransfer: {files: jQuery("input.form-file").get(0).files}};' + . 'e.preventDefault = e.stopPropagation = function () {};' + . 'jQuery("input.form-file").trigger(e);'; + $this->getSession()->executeScript($script); + + // Verify that the error message is there. + $error_selector = '.file-upload-js-error'; + // Verify that there is one and only one error message on the page. + $condition = "jQuery('$error_selector').length == 1"; + $this->assertJsCondition($condition, 10000); + $assert->elementExists('css', $error_selector); + } + +} diff --git a/core/modules/image/image.install b/core/modules/image/image.install index fd14d03..c044912 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -61,3 +61,10 @@ function image_requirements($phase) { return $requirements; } + +/** + * Flush caches as we changed field formatter metadata. + */ +function image_update_8201() { + // Empty update to trigger a cache flush. +} diff --git a/core/modules/image/image.libraries.yml b/core/modules/image/image.libraries.yml index e9061a4..a47a2b5 100644 --- a/core/modules/image/image.libraries.yml +++ b/core/modules/image/image.libraries.yml @@ -3,3 +3,19 @@ admin: css: theme: css/image.admin.css: {} + +quickedit.inPlaceEditor.image: + version: VERSION + js: + js/editors/image.js: {} + js/theme.js: {} + css: + component: + css/editors/image.css: {} + theme: + css/editors/image.theme.css: {} + dependencies: + - core/jquery + - core/drupal + - core/underscore + - quickedit/quickedit diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml index ffeed86..4a0cb88 100644 --- a/core/modules/image/image.routing.yml +++ b/core/modules/image/image.routing.yml @@ -71,3 +71,29 @@ image.effect_edit_form: route_callbacks: - '\Drupal\image\Routing\ImageStyleRoutes::routes' + +image.upload: + path: '/quickedit/image/upload/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode_id}' + defaults: + _controller: '\Drupal\image\Controller\QuickEditImageController::upload' + options: + parameters: + entity: + type: entity:{entity_type} + requirements: + _permission: 'access in-place editing' + _access_quickedit_entity_field: 'TRUE' + _method: 'POST' + +image.info: + path: '/quickedit/image/info/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode_id}' + defaults: + _controller: '\Drupal\image\Controller\QuickEditImageController::getInfo' + options: + parameters: + entity: + type: entity:{entity_type} + requirements: + _permission: 'access in-place editing' + _access_quickedit_entity_field: 'TRUE' + _method: 'GET' diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index 4c3a27d..c1ee4df 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -22,6 +22,9 @@ * label = @Translation("Image"), * field_types = { * "image" + * }, + * quickedit = { + * "editor" = "image" * } * ) */ diff --git a/core/modules/image/src/Tests/ImageFieldTestBase.php b/core/modules/image/src/Tests/ImageFieldTestBase.php index 6081d32..04da7ae 100644 --- a/core/modules/image/src/Tests/ImageFieldTestBase.php +++ b/core/modules/image/src/Tests/ImageFieldTestBase.php @@ -2,9 +2,7 @@ namespace Drupal\image\Tests; -use Drupal\field\Entity\FieldConfig; use Drupal\simpletest\WebTestBase; -use Drupal\field\Entity\FieldStorageConfig; /** * TODO: Test the following functions. @@ -24,6 +22,8 @@ */ abstract class ImageFieldTestBase extends WebTestBase { + use ImageFieldCreationTrait; + /** * Modules to enable. * @@ -52,62 +52,6 @@ protected function setUp() { } /** - * Create a new image field. - * - * @param string $name - * The name of the new field (all lowercase), exclude the "field_" prefix. - * @param string $type_name - * The node type that this field will be added to. - * @param array $storage_settings - * A list of field storage settings that will be added to the defaults. - * @param array $field_settings - * A list of instance settings that will be added to the instance defaults. - * @param array $widget_settings - * Widget settings to be added to the widget defaults. - * @param array $formatter_settings - * Formatter settings to be added to the formatter defaults. - * @param string $description - * A description for the field. - */ - function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array(), $description = '') { - FieldStorageConfig::create(array( - 'field_name' => $name, - 'entity_type' => 'node', - 'type' => 'image', - 'settings' => $storage_settings, - 'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1, - ))->save(); - - $field_config = FieldConfig::create([ - 'field_name' => $name, - 'label' => $name, - 'entity_type' => 'node', - 'bundle' => $type_name, - 'required' => !empty($field_settings['required']), - 'settings' => $field_settings, - 'description' => $description, - ]); - $field_config->save(); - - entity_get_form_display('node', $type_name, 'default') - ->setComponent($name, array( - 'type' => 'image_image', - 'settings' => $widget_settings, - )) - ->save(); - - entity_get_display('node', $type_name, 'default') - ->setComponent($name, array( - 'type' => 'image', - 'settings' => $formatter_settings, - )) - ->save(); - - return $field_config; - - } - - /** * Preview an image in a node. * * @param \Drupal\Core\Image\ImageInterface $image diff --git a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php index ff91e68..3ec1a19 100644 --- a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php @@ -23,6 +23,9 @@ * label = @Translation("Responsive image"), * field_types = { * "image", + * }, + * quickedit = { + * "editor" = "image" * } * ) */ diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 2e03af9..b749899 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -22,7 +22,6 @@ use Drupal\Core\Session\AnonymousUserSession; use Drupal\Core\Session\UserSession; use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\Test\AssertMailTrait; use Drupal\Core\Url; use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; @@ -58,6 +57,9 @@ createRole as drupalCreateRole; createAdminRole as drupalCreateAdminRole; } + use TestFilesTrait { + getTestFiles as drupalGetTestFiles; + } /** * The profile to install as a basis for testing. @@ -169,11 +171,6 @@ protected $sessionId = NULL; /** - * Whether the files were copied to the test files directory. - */ - protected $generatedTestFiles = FALSE; - - /** * The maximum number of redirects to follow when handling responses. */ protected $maximumRedirects = 5; @@ -334,99 +331,6 @@ protected function findBlockInstance(Block $block) { } /** - * Gets a list of files that can be used in tests. - * - * The first time this method is called, it will call - * simpletest_generate_file() to generate binary and ASCII text files in the - * public:// directory. It will also copy all files in - * core/modules/simpletest/files to public://. These contain image, SQL, PHP, - * JavaScript, and HTML files. - * - * All filenames are prefixed with their type and have appropriate extensions: - * - text-*.txt - * - binary-*.txt - * - html-*.html and html-*.txt - * - image-*.png, image-*.jpg, and image-*.gif - * - javascript-*.txt and javascript-*.script - * - php-*.txt and php-*.php - * - sql-*.txt and sql-*.sql - * - * Any subsequent calls will not generate any new files, or copy the files - * over again. However, if a test class adds a new file to public:// that - * is prefixed with one of the above types, it will get returned as well, even - * on subsequent calls. - * - * @param $type - * File type, possible values: 'binary', 'html', 'image', 'javascript', - * 'php', 'sql', 'text'. - * @param $size - * (optional) File size in bytes to match. Defaults to NULL, which will not - * filter the returned list by size. - * - * @return - * List of files in public:// that match the filter(s). - */ - protected function drupalGetTestFiles($type, $size = NULL) { - if (empty($this->generatedTestFiles)) { - // Generate binary test files. - $lines = array(64, 1024); - $count = 0; - foreach ($lines as $line) { - simpletest_generate_file('binary-' . $count++, 64, $line, 'binary'); - } - - // Generate ASCII text test files. - $lines = array(16, 256, 1024, 2048, 20480); - $count = 0; - foreach ($lines as $line) { - simpletest_generate_file('text-' . $count++, 64, $line, 'text'); - } - - // Copy other test files from simpletest. - $original = drupal_get_path('module', 'simpletest') . '/files'; - $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/'); - foreach ($files as $file) { - file_unmanaged_copy($file->uri, PublicStream::basePath()); - } - - $this->generatedTestFiles = TRUE; - } - - $files = array(); - // Make sure type is valid. - if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) { - $files = file_scan_directory('public://', '/' . $type . '\-.*/'); - - // If size is set then remove any files that are not of that size. - if ($size !== NULL) { - foreach ($files as $file) { - $stats = stat($file->uri); - if ($stats['size'] != $size) { - unset($files[$file->uri]); - } - } - } - } - usort($files, array($this, 'drupalCompareFiles')); - return $files; - } - - /** - * Compare two files based on size and file name. - */ - protected function drupalCompareFiles($file1, $file2) { - $compare_size = filesize($file1->uri) - filesize($file2->uri); - if ($compare_size) { - // Sort by file size. - return $compare_size; - } - else { - // The files were the same size, so sort alphabetically. - return strnatcmp($file1->name, $file2->name); - } - } - - /** * Log in a user with the internal browser. * * If a user is already logged in, then the current user is logged out before diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml index 3476833..7e585b1 100644 --- a/core/themes/stable/stable.info.yml +++ b/core/themes/stable/stable.info.yml @@ -98,6 +98,12 @@ libraries-override: css: theme: css/image.admin.css: css/image/image.admin.css + image/quickedit.inPlaceEditor.image: + css: + component: + css/editors/image.css: css/image/editors/image.css + theme: + css/editors/image.theme.css: css/image/editors/image.theme.css language/drupal.language.admin: css: