diff --git a/composer.lock b/composer.lock index b645632..3b54d14 100644 --- a/composer.lock +++ b/composer.lock @@ -3078,6 +3078,42 @@ "time": "2016-07-05T20:48:03+00:00" }, { + "name": "drupal/coder", + "version": "8.2.8", + "source": { + "type": "git", + "url": "https://github.com/klausi/coder.git", + "reference": "6d717e1a5a5dd592ebbeaafad11746849fb52532" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/klausi/coder/zipball/6d717e1a5a5dd592ebbeaafad11746849fb52532", + "reference": "6d717e1a5a5dd592ebbeaafad11746849fb52532", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "squizlabs/php_codesniffer": ">=2.5.1", + "symfony/yaml": ">=2.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=3.7" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "description": "Coder is a library to review Drupal code.", + "homepage": "https://www.drupal.org/project/coder", + "keywords": [ + "code review", + "phpcs", + "standards" + ], + "time": "2016-07-05T20:48:03+00:00" + }, + { "name": "fabpot/goutte", "version": "v3.1.2", "source": { @@ -4217,6 +4253,84 @@ "time": "2016-11-30T04:02:31+00:00" }, { + "name": "squizlabs/php_codesniffer", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "9b324f3a1132459a7274a0ace2e1b766ba80930f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9b324f3a1132459a7274a0ace2e1b766ba80930f", + "reference": "9b324f3a1132459a7274a0ace2e1b766ba80930f", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2016-11-30T04:02:31+00:00" + }, + { "name": "symfony/browser-kit", "version": "v2.8.16", "source": { diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 687a62c..5832f12 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -13,6 +13,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\file\Entity\File; +use Drupal\file\FileFormTrait; use Drupal\file\FileInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\Unicode; @@ -710,206 +711,20 @@ function file_cron() { * An array of file entities or a single file entity if $delta != NULL. Each * array element contains the file entity if the upload succeeded or FALSE if * there was an error. Function returns NULL if no file was uploaded. + * + * @deprecated in Drupal 8.3.x, will be removed before Drupal 9.0.0. + * This function should not be used for form validation, use + * \Drupal\file\FileFormTrait::fileSaveUpload() instead. + * + * @see \Drupal\file\FileFormTrait::fileSaveUpload() */ function file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) { - $user = \Drupal::currentUser(); - static $upload_cache; - - $all_files = \Drupal::request()->files->get('files', array()); - // Make sure there's an upload to process. - if (empty($all_files[$form_field_name])) { - return NULL; - } - $file_upload = $all_files[$form_field_name]; - - // Return cached objects without processing since the file will have - // already been processed and the paths in $_FILES will be invalid. - if (isset($upload_cache[$form_field_name])) { - if (isset($delta)) { - return $upload_cache[$form_field_name][$delta]; - } - return $upload_cache[$form_field_name]; - } - - // Prepare uploaded files info. Representation is slightly different - // for multiple uploads and we fix that here. - $uploaded_files = $file_upload; - if (!is_array($file_upload)) { - $uploaded_files = array($file_upload); - } - - $files = array(); - foreach ($uploaded_files as $i => $file_info) { - // Check for file upload errors and return FALSE for this file if a lower - // level system error occurred. For a complete list of errors: - // See http://php.net/manual/features.file-upload.errors.php. - switch ($file_info->getError()) { - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: - drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size()))), 'error'); - $files[$i] = FALSE; - continue; - - case UPLOAD_ERR_PARTIAL: - case UPLOAD_ERR_NO_FILE: - drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $file_info->getFilename())), 'error'); - $files[$i] = FALSE; - continue; - - case UPLOAD_ERR_OK: - // Final check that this is a valid upload, if it isn't, use the - // default error handler. - if (is_uploaded_file($file_info->getRealPath())) { - break; - } - - // Unknown error - default: - drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $file_info->getFilename())), 'error'); - $files[$i] = FALSE; - continue; - - } - // Begin building file entity. - $values = array( - 'uid' => $user->id(), - 'status' => 0, - 'filename' => $file_info->getClientOriginalName(), - 'uri' => $file_info->getRealPath(), - 'filesize' => $file_info->getSize(), - ); - $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']); - $file = File::create($values); - - $extensions = ''; - if (isset($validators['file_validate_extensions'])) { - if (isset($validators['file_validate_extensions'][0])) { - // Build the list of non-munged extensions if the caller provided them. - $extensions = $validators['file_validate_extensions'][0]; - } - else { - // If 'file_validate_extensions' is set and the list is empty then the - // caller wants to allow any extension. In this case we have to remove the - // validator or else it will reject all extensions. - unset($validators['file_validate_extensions']); - } - } - else { - // No validator was provided, so add one using the default list. - // Build a default non-munged safe list for file_munge_filename(). - $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; - $validators['file_validate_extensions'] = array(); - $validators['file_validate_extensions'][0] = $extensions; - } - - if (!empty($extensions)) { - // Munge the filename to protect against possible malicious extension - // hiding within an unknown file type (ie: filename.html.foo). - $file->setFilename(file_munge_filename($file->getFilename(), $extensions)); - } - - // Rename potentially executable files, to help prevent exploits (i.e. will - // rename filename.php.foo and filename.php to filename.php.foo.txt and - // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' - // evaluates to TRUE. - if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { - $file->setMimeType('text/plain'); - // The destination filename will also later be used to create the URI. - $file->setFilename($file->getFilename() . '.txt'); - // The .txt extension may not be in the allowed list of extensions. We have - // to add it here or else the file upload will fail. - if (!empty($extensions)) { - $validators['file_validate_extensions'][0] .= ' txt'; - drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->getFilename()))); - } - } - - // If the destination is not provided, use the temporary directory. - if (empty($destination)) { - $destination = 'temporary://'; - } - - // Assert that the destination contains a valid stream. - $destination_scheme = file_uri_scheme($destination); - if (!file_stream_wrapper_valid_scheme($destination_scheme)) { - drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination)), 'error'); - $files[$i] = FALSE; - continue; - } - - $file->source = $form_field_name; - // A file URI may already have a trailing slash or look like "public://". - if (substr($destination, -1) != '/') { - $destination .= '/'; - } - $file->destination = file_destination($destination . $file->getFilename(), $replace); - // If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and - // there's an existing file so we need to bail. - if ($file->destination === FALSE) { - drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination)), 'error'); - $files[$i] = FALSE; - continue; - } - - // Add in our check of the file name length. - $validators['file_validate_name_length'] = array(); - - // Call the validation functions specified by this function's caller. - $errors = file_validate($file, $validators); - - // Check for errors. - if (!empty($errors)) { - $message = array( - 'error' => array( - '#markup' => t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename())), - ), - 'item_list' => array( - '#theme' => 'item_list', - '#items' => $errors, - ), - ); - // @todo Add support for render arrays in drupal_set_message()? See - // https://www.drupal.org/node/2505497. - drupal_set_message(\Drupal::service('renderer')->renderPlain($message), 'error'); - $files[$i] = FALSE; - continue; - } - - // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary - // directory. This overcomes open_basedir restrictions for future file - // operations. - $file->setFileUri($file->destination); - if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) { - drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error'); - \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->getFilename(), '%destination' => $file->getFileUri())); - $files[$i] = FALSE; - continue; - } - - // Set the permissions on the new file. - drupal_chmod($file->getFileUri()); - - // If we are replacing an existing file re-use its database record. - // @todo Do not create a new entity in order to update it. See - // https://www.drupal.org/node/2241865. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->getFileUri())); - if (count($existing_files)) { - $existing = reset($existing_files); - $file->fid = $existing->id(); - $file->setOriginalId($existing->id()); - } - } - - // If we made it this far it's safe to record this file in the database. - $file->save(); - $files[$i] = $file; + $errors = []; + $files = FileFormTrait::_fileSaveUpload($form_field_name, $validators, $destination, $delta, $replace, $errors); + foreach ($errors as $error) { + drupal_set_message($error, 'error'); } - - // Add files to the cache. - $upload_cache[$form_field_name] = $files; - - return isset($delta) ? $files[$delta] : $files; + return $files; } /** @@ -1192,9 +1007,8 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state) $files_uploaded = $element['#multiple'] && count(array_filter($file_upload)) > 0; $files_uploaded |= !$element['#multiple'] && !empty($file_upload); if ($files_uploaded) { - if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) { + if (!$files = FileFormTrait::fileSaveUpload($element, $form_state)) { \Drupal::logger('file')->notice('The file upload failed. %upload', array('%upload' => $upload_name)); - $form_state->setError($element, t('Files in the @name field were unable to be uploaded.', array('@name' => $element['#title']))); return array(); } diff --git a/core/modules/file/src/FileFormTrait.php b/core/modules/file/src/FileFormTrait.php new file mode 100644 index 0000000..1554e84 --- /dev/null +++ b/core/modules/file/src/FileFormTrait.php @@ -0,0 +1,317 @@ + 1) { + // Render multiple errors into a single message. + $render_array = [ + 'error' => [ + '#markup' => t('One or more files could not be uploaded.'), + ], + 'item_list' => [ + '#theme' => 'item_list', + '#items' => $errors, + ], + ]; + $error_message = \Drupal::service('renderer')->renderPlain($render_array); + } + else { + $error_message = reset($errors); + } + $form_state->setError($element, $error_message); + } + + return $files; + } + + /** + * Saves file uploads to a new location. + * + * The files will be added to the {file_managed} table as temporary files. + * Temporary files are periodically cleaned. Use the 'file.usage' service to + * register the usage of the file which will automatically mark it as permanent. + * + * @param string $form_field_name + * A string that is the associative array key of the upload form element in + * the form array. + * @param array $validators + * (optional) An associative array of callback functions used to validate the + * file. See file_validate() for a full discussion of the array format. + * If the array is empty, it will be set up to call file_validate_extensions() + * with a safe list of extensions, as follows: "jpg jpeg gif png txt doc + * xls pdf ppt pps odt ods odp". To allow all extensions, you must explicitly + * set this array to ['file_validate_extensions' => '']. (Beware: this is not + * safe and should only be allowed for trusted users, if at all.) + * @param string|false $destination + * (optional) A string containing the URI that the file should be copied to. + * This must be a stream wrapper URI. If this value is omitted or set to + * FALSE, Drupal's temporary files scheme will be used ("temporary://"). + * @param null|int $delta + * (optional) The delta of the file to return the file entity. + * Defaults to NULL. + * @param int $replace + * (optional) The replace behavior when the destination file already exists. + * Possible values include: + * - FILE_EXISTS_REPLACE: Replace the existing file. + * - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the + * filename is unique. + * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * @param array $errors_to_return + * (optional) Any errors that have occurred whilst uploading the file. + * + * @return array|\Drupal\file\FileInterface|null|false + * An array of file entities or a single file entity if $delta != NULL. Each + * array element contains the file entity if the upload succeeded or FALSE if + * there was an error. Function returns NULL if no file was uploaded. + * + * @see file_save_upload() + * @see file_save_upload() + * + * @internal + */ + public static function _fileSaveUpload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME, &$errors_to_return = []) { + $user = \Drupal::currentUser(); + static $upload_cache; + + $all_files = \Drupal::request()->files->get('files', []); + // Make sure there's an upload to process. + if (empty($all_files[$form_field_name])) { + return NULL; + } + $file_upload = $all_files[$form_field_name]; + + // Return cached objects without processing since the file will have + // already been processed and the paths in $_FILES will be invalid. + if (isset($upload_cache[$form_field_name])) { + if (isset($delta)) { + return $upload_cache[$form_field_name][$delta]; + } + return $upload_cache[$form_field_name]; + } + + // Prepare uploaded files info. Representation is slightly different + // for multiple uploads and we fix that here. + $uploaded_files = $file_upload; + if (!is_array($file_upload)) { + $uploaded_files = array($file_upload); + } + + $files = array(); + foreach ($uploaded_files as $i => $file_info) { + // Check for file upload errors and return FALSE for this file if a lower + // level system error occurred. For a complete list of errors: + // See http://php.net/manual/features.file-upload.errors.php. + switch ($file_info->getError()) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + $errors_to_return[] = new TranslatableMarkup('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())]); + $files[$i] = FALSE; + break; + + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + $errors_to_return[] = new TranslatableMarkup('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()]); + $files[$i] = FALSE; + break; + + case UPLOAD_ERR_OK: + // Final check that this is a valid upload, if it isn't, use the + // default error handler. + if (is_uploaded_file($file_info->getRealPath())) { + break; + } + + default: + // Unknown error. + $errors_to_return[] = new TranslatableMarkup('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]); + $files[$i] = FALSE; + break; + + } + // Begin building file entity. + $values = [ + 'uid' => $user->id(), + 'status' => 0, + 'filename' => $file_info->getClientOriginalName(), + 'uri' => $file_info->getRealPath(), + 'filesize' => $file_info->getSize(), + ]; + $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']); + $file = File::create($values); + + $extensions = ''; + if (isset($validators['file_validate_extensions'])) { + if (isset($validators['file_validate_extensions'][0])) { + // Build the list of non-munged extensions if the caller provided them. + $extensions = $validators['file_validate_extensions'][0]; + } + else { + // If 'file_validate_extensions' is set and the list is empty then the + // caller wants to allow any extension. In this case we have to remove the + // validator or else it will reject all extensions. + unset($validators['file_validate_extensions']); + } + } + else { + // No validator was provided, so add one using the default list. + // Build a default non-munged safe list for file_munge_filename(). + $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; + $validators['file_validate_extensions'] = array(); + $validators['file_validate_extensions'][0] = $extensions; + } + + if (!empty($extensions)) { + // Munge the filename to protect against possible malicious extension + // hiding within an unknown file type (ie: filename.html.foo). + $file->setFilename(file_munge_filename($file->getFilename(), $extensions)); + } + + // Rename potentially executable files, to help prevent exploits (i.e. will + // rename filename.php.foo and filename.php to filename.php.foo.txt and + // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' + // evaluates to TRUE. + if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { + $file->setMimeType('text/plain'); + // The destination filename will also later be used to create the URI. + $file->setFilename($file->getFilename() . '.txt'); + // The .txt extension may not be in the allowed list of extensions. We have + // to add it here or else the file upload will fail. + if (!empty($extensions)) { + $validators['file_validate_extensions'][0] .= ' txt'; + drupal_set_message(new TranslatableMarkup('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()])); + } + } + + // If the destination is not provided, use the temporary directory. + if (empty($destination)) { + $destination = 'temporary://'; + } + + // Assert that the destination contains a valid stream. + $destination_scheme = file_uri_scheme($destination); + if (!file_stream_wrapper_valid_scheme($destination_scheme)) { + $errors_to_return[] = new TranslatableMarkup('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination]); + $files[$i] = FALSE; + continue; + } + + $file->source = $form_field_name; + // A file URI may already have a trailing slash or look like "public://". + if (substr($destination, -1) != '/') { + $destination .= '/'; + } + $file->destination = file_destination($destination . $file->getFilename(), $replace); + // If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and + // there's an existing file so we need to bail. + if ($file->destination === FALSE) { + $errors_to_return[] = new TranslatableMarkup('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination]); + $files[$i] = FALSE; + continue; + } + + // Add in our check of the file name length. + $validators['file_validate_name_length'] = array(); + + // Call the validation functions specified by this function's caller. + $errors = file_validate($file, $validators); + + // Check for errors. + if (!empty($errors)) { + $message = array( + 'error' => array( + '#markup' => t('The specified file %name could not be uploaded.', array('%name' => $file->getFilename())), + ), + 'item_list' => array( + '#theme' => 'item_list', + '#items' => $errors, + ), + ); + // @todo Add support for render arrays in drupal_set_message()? See + // https://www.drupal.org/node/2505497. + $errors_to_return[] = \Drupal::service('renderer')->renderPlain($message); + $files[$i] = FALSE; + continue; + } + + // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary + // directory. This overcomes open_basedir restrictions for future file + // operations. + $file->setFileUri($file->destination); + if (!\Drupal::service('file_system')->moveUploadedFile($file_info->getRealPath(), $file->getFileUri())) { + $errors_to_return[] = new TranslatableMarkup('File upload error. Could not move uploaded file.'); + \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->getFilename(), '%destination' => $file->getFileUri())); + $files[$i] = FALSE; + continue; + } + + // Set the permissions on the new file. + drupal_chmod($file->getFileUri()); + + // If we are replacing an existing file re-use its database record. + // @todo Do not create a new entity in order to update it. See + // https://www.drupal.org/node/2241865. + if ($replace == FILE_EXISTS_REPLACE) { + $existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->getFileUri())); + if (count($existing_files)) { + $existing = reset($existing_files); + $file->fid = $existing->id(); + $file->setOriginalId($existing->id()); + } + } + + // If we made it this far it's safe to record this file in the database. + $file->save(); + $files[$i] = $file; + } + + // Add files to the cache. + $upload_cache[$form_field_name] = $files; + + return isset($delta) ? $files[$delta] : $files; + } + +} diff --git a/core/modules/file/src/Tests/SaveUploadFormTest.php b/core/modules/file/src/Tests/SaveUploadFormTest.php new file mode 100644 index 0000000..3714b97 --- /dev/null +++ b/core/modules/file/src/Tests/SaveUploadFormTest.php @@ -0,0 +1,460 @@ +drupalCreateUser(['access site reports']); + $this->drupalLogin($account); + + $image_files = $this->drupalGetTestFiles('image'); + $this->image = File::create((array) current($image_files)); + + list(, $this->imageExtension) = explode('.', $this->image->getFilename()); + $this->assertTrue(is_file($this->image->getFileUri()), "The image file we're going to upload exists."); + + $this->phpfile = current($this->drupalGetTestFiles('php')); + $this->assertTrue(is_file($this->phpfile->uri), 'The PHP file we are going to upload exists.'); + + $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); + + // Upload with replace to guarantee there's something there. + $edit = [ + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called then clean out the hook + // counters. + $this->assertFileHooksCalled(['validate', 'insert']); + file_test_reset(); + } + + /** + * Tests the file_save_upload_from_form() function. + */ + public function testNormal() { + $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); + $this->assertTrue($max_fid_after > $this->maxFidBefore, 'A new file was created.'); + $file1 = File::load($max_fid_after); + $this->assertTrue($file1, 'Loaded the file.'); + // MIME type of the uploaded image may be either image/jpeg or image/png. + $this->assertEqual(substr($file1->getMimeType(), 0, 5), 'image', 'A MIME type was set.'); + + // Reset the hook counters to get rid of the 'load' we just called. + file_test_reset(); + + // Upload a second file. + $image2 = current($this->drupalGetTestFiles('image')); + $edit = ['files[file_test_upload][]' => drupal_realpath($image2->uri)]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!')); + $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'insert']); + + $file2 = File::load($max_fid_after); + $this->assertTrue($file2, 'Loaded the file'); + // MIME type of the uploaded image may be either image/jpeg or image/png. + $this->assertEqual(substr($file2->getMimeType(), 0, 5), 'image', 'A MIME type was set.'); + + // Load both files using File::loadMultiple(). + $files = File::loadMultiple([$file1->id(), $file2->id()]); + $this->assertTrue(isset($files[$file1->id()]), 'File was loaded successfully'); + $this->assertTrue(isset($files[$file2->id()]), 'File was loaded successfully'); + + // Upload a third file to a subdirectory. + $image3 = current($this->drupalGetTestFiles('image')); + $image3_realpath = drupal_realpath($image3->uri); + $dir = $this->randomMachineName(); + $edit = [ + 'files[file_test_upload][]' => $image3_realpath, + 'file_subdir' => $dir, + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!')); + $this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath)))); + } + + /** + * Tests extension handling. + */ + public function testHandleExtension() { + // The file being tested is a .gif which is in the default safe list + // of extensions to allow when the extension validator isn't used. This is + // implicitly tested at the testNormal() test. Here we tell + // file_save_upload_from_form() to only allow ".foo". + $extensions = 'foo'; + $edit = [ + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'extensions' => $extensions, + ]; + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $message = t('Only files with the following extensions are allowed:') . ' ' . $extensions . ''; + $this->assertRaw($message, 'Cannot upload a disallowed extension'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate']); + + // Reset the hook counters. + file_test_reset(); + + $extensions = 'foo ' . $this->imageExtension; + // Now tell file_save_upload_from_form() to allow the extension of our test image. + $edit = [ + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'extensions' => $extensions, + ]; + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertNoRaw(t('Only files with the following extensions are allowed:'), 'Can upload an allowed extension.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'load', 'update']); + + // Reset the hook counters. + file_test_reset(); + + // Now tell file_save_upload_from_form() to allow any extension. + $edit = [ + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'allow_all_extensions' => TRUE, + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertNoRaw(t('Only files with the following extensions are allowed:'), 'Can upload any extension.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'load', 'update']); + } + + /** + * Tests dangerous file handling. + */ + public function testHandleDangerousFile() { + $config = $this->config('system.file'); + // Allow the .php extension and make sure it gets renamed to .txt for + // safety. Also check to make sure its MIME type was changed. + $edit = [ + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload][]' => drupal_realpath($this->phpfile->uri), + 'is_image_file' => FALSE, + 'extensions' => 'php', + ]; + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $message = t('For security reasons, your upload has been renamed to') . ' ' . $this->phpfile->filename . '.txt' . ''; + $this->assertRaw($message, 'Dangerous file was renamed.'); + $this->assertRaw(t('File MIME type is text/plain.'), "Dangerous file's MIME type was changed."); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'insert']); + + // Ensure dangerous files are not renamed when insecure uploads is TRUE. + // Turn on insecure uploads. + $config->set('allow_insecure_uploads', 1)->save(); + // Reset the hook counters. + file_test_reset(); + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.'); + $this->assertRaw(t('File name is @filename', ['@filename' => $this->phpfile->filename]), 'Dangerous file was not renamed when insecure uploads is TRUE.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'insert']); + + // Turn off insecure uploads. + $config->set('allow_insecure_uploads', 0)->save(); + } + + /** + * Tests file munge handling. + */ + public function testHandleFileMunge() { + // Ensure insecure uploads are disabled for this test. + $this->config('system.file')->set('allow_insecure_uploads', 0)->save(); + $this->image = file_move($this->image, $this->image->getFileUri() . '.foo.' . $this->imageExtension); + + // Reset the hook counters to get rid of the 'move' we just called. + file_test_reset(); + + $extensions = $this->imageExtension; + $edit = [ + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'extensions' => $extensions, + ]; + + $munged_filename = $this->image->getFilename(); + $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.')); + $munged_filename .= '_.' . $this->imageExtension; + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('For security reasons, your upload has been renamed'), 'Found security message.'); + $this->assertRaw(t('File name is @filename', ['@filename' => $munged_filename]), 'File was successfully munged.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'insert']); + + // Ensure we don't munge files if we're allowing any extension. + // Reset the hook counters. + file_test_reset(); + + $edit = [ + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'allow_all_extensions' => TRUE, + ]; + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertNoRaw(t('For security reasons, your upload has been renamed'), 'Found no security message.'); + $this->assertRaw(t('File name is @filename', ['@filename' => $this->image->getFilename()]), 'File was not munged when allowing any extension.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'insert']); + } + + /** + * Tests renaming when uploading over a file that already exists. + */ + public function testExistingRename() { + $edit = [ + 'file_test_replace' => FILE_EXISTS_RENAME, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()) + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'insert']); + } + + /** + * Tests replacement when uploading over a file that already exists. + */ + public function testExistingReplace() { + $edit = [ + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()) + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(['validate', 'load', 'update']); + } + + /** + * Tests for failure when uploading over a file that already exists. + */ + public function testExistingError() { + $edit = [ + 'file_test_replace' => FILE_EXISTS_ERROR, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()) + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Check that the no hooks were called while failing. + $this->assertFileHooksCalled([]); + } + + /** + * Tests for no failures when not uploading a file. + */ + public function testNoUpload() { + $this->drupalPostForm('file-test/save_upload_from_form_test', [], t('Submit')); + $this->assertNoRaw(t('Epic upload FAIL!'), 'Failure message not found.'); + } + + /** + * Tests for log entry on failing destination. + */ + public function testDrupalMovingUploadedFileError() { + // Create a directory and make it not writable. + $test_directory = 'test_drupal_move_uploaded_file_fail'; + drupal_mkdir('temporary://' . $test_directory, 0000); + $this->assertTrue(is_dir('temporary://' . $test_directory)); + + $edit = [ + 'file_subdir' => $test_directory, + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()) + ]; + + \Drupal::state()->set('file_test.disable_error_collection', TRUE); + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('File upload error. Could not move uploaded file.'), 'Found the failure message.'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Uploading failed. Now check the log. + $this->drupalGet('admin/reports/dblog'); + $this->assertResponse(200); + $this->assertRaw(t('Upload error. Could not move uploaded file @file to destination @destination.', [ + '@file' => $this->image->getFilename(), + '@destination' => 'temporary://' . $test_directory . '/' . $this->image->getFilename() + ]), 'Found upload error log entry.'); + } + + /** + * Tests that form validation does not change error messages. + */ + public function testErrorMessagesAreNotChanged() { + $error = 'An error message set before file_save_upload_from_form()'; + + $edit = [ + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'error_message' => $error, + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Ensure the expected error message is present and the counts before and + // after calling file_save_upload_from_form() are correct. + $this->assertText($error); + $this->assertRaw('Number of error messages before file_save_upload_from_form(): 1'); + $this->assertRaw('Number of error messages after file_save_upload_from_form(): 1'); + + // Test that error messages are preserved when an error occurs. + $edit = [ + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'error_message' => $error, + 'extensions' => 'foo' + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Ensure the expected error message is present and the counts before and + // after calling file_save_upload_from_form() are correct. + $this->assertText($error); + $this->assertRaw('Number of error messages before file_save_upload_from_form(): 1'); + $this->assertRaw('Number of error messages after file_save_upload_from_form(): 1'); + + // Test a successful upload with no messages. + $edit = [ + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('You WIN!'), 'Found the success message.'); + + // Ensure the error message is not present and the counts before and after + // calling file_save_upload_from_form() are correct. + $this->assertNoText($error); + $this->assertRaw('Number of error messages before file_save_upload_from_form(): 0'); + $this->assertRaw('Number of error messages after file_save_upload_from_form(): 0'); + } + + /** + * Tests that multiple validation errors are combined in one message. + */ + public function testCombinedErrorMessages() { + $textfile = current($this->drupalGetTestFiles('text')); + $this->assertTrue(is_file($textfile->uri), 'The text file we are going to upload exists.'); + + $edit = [ + 'files[file_test_upload][]' => [ + drupal_realpath($this->phpfile->uri), + drupal_realpath($textfile->uri), + ], + 'allow_all_extensions' => FALSE, + 'is_image_file' => TRUE, + 'extensions' => 'jpeg', + ]; + + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + + // Search for combined error message followed by a formatted list of messages. + $this->assertRaw(t('One or more files could not be uploaded.') . '
', 'Error message contains combined list of validation errors.'); + } + + /** + * Tests highlighting of file upload field when it has an error. + */ + public function testUploadFieldIsHighlighted() { + $this->assertEqual(0, count($this->cssSelect('input[name="files[file_test_upload][]"].error')), 'Successful file upload has no error.'); + + $edit = [ + 'files[file_test_upload][]' => drupal_realpath($this->image->getFileUri()), + 'extensions' => 'foo' + ]; + $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit')); + $this->assertResponse(200, 'Received a 200 response for posted test file.'); + $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.'); + $this->assertEqual(1, count($this->cssSelect('input[name="files[file_test_upload][]"].error')), 'File upload field has error.'); + } + +} diff --git a/core/modules/file/tests/file_test/file_test.routing.yml b/core/modules/file/tests/file_test/file_test.routing.yml index cdbf08e..1c5a763 100644 --- a/core/modules/file/tests/file_test/file_test.routing.yml +++ b/core/modules/file/tests/file_test/file_test.routing.yml @@ -4,3 +4,9 @@ file.test: _form: 'Drupal\file_test\Form\FileTestForm' requirements: _access: 'TRUE' +file.save_upload_from_form_test: + path: '/file-test/save_upload_from_form_test' + defaults: + _form: 'Drupal\file_test\Form\FileTestSaveUploadFromForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/file/tests/file_test/src/Form/FileTestSaveUploadFromForm.php b/core/modules/file/tests/file_test/src/Form/FileTestSaveUploadFromForm.php new file mode 100644 index 0000000..0edf5a2 --- /dev/null +++ b/core/modules/file/tests/file_test/src/Form/FileTestSaveUploadFromForm.php @@ -0,0 +1,169 @@ +state = $state; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('state') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return '_file_test_save_upload_from_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['file_test_upload'] = [ + '#type' => 'file', + '#multiple' => TRUE, + '#title' => $this->t('Upload a file'), + ]; + $form['file_test_replace'] = [ + '#type' => 'select', + '#title' => $this->t('Replace existing image'), + '#options' => [ + FILE_EXISTS_RENAME => $this->t('Appends number until name is unique'), + FILE_EXISTS_REPLACE => $this->t('Replace the existing file'), + FILE_EXISTS_ERROR => $this->t('Fail with an error'), + ], + '#default_value' => FILE_EXISTS_RENAME, + ]; + $form['file_subdir'] = [ + '#type' => 'textfield', + '#title' => $this->t('Subdirectory for test file'), + '#default_value' => '', + ]; + + $form['extensions'] = [ + '#type' => 'textfield', + '#title' => $this->t('Allowed extensions.'), + '#default_value' => '', + ]; + + $form['allow_all_extensions'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow all extensions?'), + '#default_value' => FALSE, + ]; + + $form['is_image_file'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Is this an image file?'), + '#default_value' => TRUE, + ]; + + $form['error_message'] = [ + '#type' => 'textfield', + '#title' => $this->t('Custom error message.'), + '#default_value' => '', + ]; + + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Submit'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // Process the upload and perform validation. Note: we're using the + // form value for the $replace parameter. + if (!$form_state->isValueEmpty('file_subdir')) { + $destination = 'temporary://' . $form_state->getValue('file_subdir'); + file_prepare_directory($destination, FILE_CREATE_DIRECTORY); + } + else { + $destination = FALSE; + } + + // Preset custom error message if requested. + if ($form_state->getValue('error_message')) { + drupal_set_message($form_state->getValue('error_message'), 'error'); + } + + // Setup validators. + $validators = []; + if ($form_state->getValue('is_image_file')) { + $validators['file_validate_is_image'] = []; + } + + if ($form_state->getValue('allow_all_extensions')) { + $validators['file_validate_extensions'] = []; + } + elseif (!$form_state->isValueEmpty('extensions')) { + $validators['file_validate_extensions'] = [$form_state->getValue('extensions')]; + } + + // The test for drupal_move_uploaded_file() triggering a warning is + // unavoidable. We're interested in what happens afterwards in + // file_save_upload_from_form(). + if ($this->state->get('file_test.disable_error_collection')) { + define('SIMPLETEST_COLLECT_ERRORS', FALSE); + } + + $form['file_test_upload']['#upload_validators'] = $validators; + $form['file_test_upload']['#upload_location'] = $destination; + + drupal_set_message($this->t('Number of error messages before file_save_upload_from_form(): @count.', ['@count' => count(drupal_get_messages('error', FALSE))])); + $file = $this->fileSaveUpload($form['file_test_upload'], $form_state, 0, $form_state->getValue('file_test_replace')); + drupal_set_message($this->t('Number of error messages after file_save_upload_from_form(): @count.', ['@count' => count(drupal_get_messages('error', FALSE))])); + + if ($file) { + $form_state->setValue('file_test_upload', $file); + drupal_set_message($this->t('File @filepath was uploaded.', ['@filepath' => $file->getFileUri()])); + drupal_set_message($this->t('File name is @filename.', ['@filename' => $file->getFilename()])); + drupal_set_message($this->t('File MIME type is @mimetype.', ['@mimetype' => $file->getMimeType()])); + drupal_set_message($this->t('You WIN!')); + } + elseif ($file === FALSE) { + drupal_set_message($this->t('Epic upload FAIL!'), 'error'); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { } + +} diff --git a/core/modules/locale/src/Form/ImportForm.php b/core/modules/locale/src/Form/ImportForm.php index 0ea7180..f344873 100644 --- a/core/modules/locale/src/Form/ImportForm.php +++ b/core/modules/locale/src/Form/ImportForm.php @@ -5,6 +5,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\file\FileFormTrait; use Drupal\language\ConfigurableLanguageManagerInterface; use Drupal\language\Entity\ConfigurableLanguage; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -13,6 +14,7 @@ * Form constructor for the translation import screen. */ class ImportForm extends FormBase { + use FileFormTrait; /** * Uploaded file entity. @@ -108,6 +110,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ), '#size' => 50, '#upload_validators' => $validators, + '#upload_location' => 'translations://', '#attributes' => array('class' => array('file-import-input')), ); $form['langcode'] = array( @@ -154,7 +157,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { - $this->file = file_save_upload('file', $form['file']['#upload_validators'], 'translations://', 0); + $this->file = $this->fileSaveUpload($form['file'], $form_state, 0); // Ensure we have the file uploaded. if (!$this->file) { diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php index 37b5ea1..43cbc8d 100644 --- a/core/modules/system/src/Form/ThemeSettingsForm.php +++ b/core/modules/system/src/Form/ThemeSettingsForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\StreamWrapper\PublicStream; +use Drupal\file\FileFormTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -212,7 +213,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = '#type' => 'file', '#title' => t('Upload logo image'), '#maxlength' => 40, - '#description' => t("If you don't have direct file access to the server, use this field to upload your logo.") + '#description' => t("If you don't have direct file access to the server, use this field to upload your logo."), + '#upload_validators' => array( + 'file_validate_is_image' => array(), + ), ); } @@ -252,7 +256,12 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = $form['favicon']['settings']['favicon_upload'] = array( '#type' => 'file', '#title' => t('Upload favicon image'), - '#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon.") + '#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon."), + '#upload_validators' => array( + 'file_validate_extensions' => array( + 'ico png gif jpg jpeg apng svg', + ), + ), ); } @@ -355,37 +364,18 @@ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); if ($this->moduleHandler->moduleExists('file')) { - // Handle file uploads. - $validators = array('file_validate_is_image' => array()); - // Check for a new uploaded logo. - $file = file_save_upload('logo_upload', $validators, FALSE, 0); - if (isset($file)) { - // File upload was attempted. - if ($file) { - // Put the temporary file in form_values so we can save it on submit. - $form_state->setValue('logo_upload', $file); - } - else { - // File upload failed. - $form_state->setErrorByName('logo_upload', $this->t('The logo could not be uploaded.')); - } + $file = FileFormTrait::fileSaveUpload($form['logo']['settings']['logo_upload'], $form_state, 0); + if ($file) { + // Put the temporary file in form_values so we can save it on submit. + $form_state->setValue('logo_upload', $file); } - $validators = array('file_validate_extensions' => array('ico png gif jpg jpeg apng svg')); - // Check for a new uploaded favicon. - $file = file_save_upload('favicon_upload', $validators, FALSE, 0); - if (isset($file)) { - // File upload was attempted. - if ($file) { - // Put the temporary file in form_values so we can save it on submit. - $form_state->setValue('favicon_upload', $file); - } - else { - // File upload failed. - $form_state->setErrorByName('favicon_upload', $this->t('The favicon could not be uploaded.')); - } + $file = FileFormTrait::fileSaveUpload($form['favicon']['settings']['favicon_upload'], $form_state, 0); + if ($file) { + // Put the temporary file in form_values so we can save it on submit. + $form_state->setValue('favicon_upload', $file); } // When intending to use the default logo, unset the logo_path.