/**
  * Return a string as a safe filename
  *
  * @param string $string
  *   The candidtate filename.
  * @param array $options
  *   - array extensions: allowable extensions no periods
  *   - string ext: default extension if non found; blank for none no period
  *
  * @return string
  * - lowercased, with only letters, numbers, dots and hyphens
  *
  * @see file_munge_filename().
  */
 protected function filenameSafe($string, $options = array())
 {
     $options += array('extensions' => array('txt', 'md'), 'ext' => 'txt');
     $string = preg_replace('/[^a-z0-9\\-\\.]/', '-', strtolower($string));
     $string = preg_replace('/-{2,}/', '-', $string);
     // Add an extension if not found
     if ($options['ext'] && !preg_match('/\\.[a-z]{1,5}$/', $string)) {
         $string .= '.' . trim($options['ext'], '.');
     }
     if ($string && function_exists('file_munge_filename')) {
         $string = file_munge_filename($string, implode(' ', $options['extensions']), FALSE);
     }
     //@todo Add in the module that cleans name if it's installed
     return $string;
 }
示例#2
0
 /**
  * {@inheritdoc}
  */
 public static function valueCallback(&$element, $input, FormStateInterface $form_state)
 {
     $file_names = [];
     $return['uploaded_files'] = NULL;
     if ($input !== FALSE) {
         $user_input = NestedArray::getValue($form_state->getUserInput(), $element['#parents'] + ['uploaded_files']);
         if (!empty($user_input['uploaded_files'])) {
             $file_names = array_filter(explode(';', $user_input['uploaded_files']));
             $tmp_override = \Drupal::config('dropzonejs.settings')->get('tmp_dir');
             $temp_path = $tmp_override ? $tmp_override : \Drupal::config('system.file')->get('path.temporary');
             foreach ($file_names as $name) {
                 // The upload handler appended the txt extension to the file for
                 // security reasons. We will remove it in this callback.
                 $old_filepath = "{$temp_path}/{$name}";
                 // The upload handler appended the txt extension to the file for
                 // security reasons. Because here we know the acceptable extensions
                 // we can remove that extension and sanitize the filename.
                 $name = self::fixTmpFilename($name);
                 $name = file_munge_filename($name, self::getValidExtensions($element));
                 // Finaly rename the file and add it to results.
                 $new_filepath = "{$temp_path}/{$name}";
                 $move_result = file_unmanaged_move($old_filepath, $new_filepath);
                 if ($move_result) {
                     $return['uploaded_files'][] = ['path' => $move_result, 'filename' => $name];
                 } else {
                     drupal_set_message(t('There was a problem while processing the file named @name', ['@name' => $name]), 'error');
                 }
             }
         }
         $form_state->setValueForElement($element, $return);
         return $return;
     }
 }
示例#3
0
 /**
  * Ensure that unmunge gets your name back.
  */
 function testUnMunge()
 {
     $munged_name = file_munge_filename($this->name, '', FALSE);
     $unmunged_name = file_unmunge_filename($munged_name);
     $this->assertIdentical($unmunged_name, $this->name, format_string('The unmunged (%unmunged) filename matches the original (%original)', array('%unmunged' => $unmunged_name, '%original' => $this->name)));
 }
  /**
   * An adaptation of file_save_upload() that includes more verbose errors.
   *
   * @param string $source
   *   A string specifying the filepath or URI of the uploaded file to save.
   *
   * @return stdClass
   *   The saved file object.
   *
   * @throws \RestfulBadRequestException
   * @throws \RestfulServiceUnavailable
   *
   * @see file_save_upload()
   */
  protected function fileSaveUpload($source) {
    static $upload_cache;

    $account = $this->getAccount();
    $options = $this->getPluginKey('options');

    $validators = $options['validators'];
    $destination = $options['scheme'] . "://";
    $replace = $options['replace'];

    // 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[$source])) {
      return $upload_cache[$source];
    }

    // Make sure there's an upload to process.
    if (empty($_FILES['files']['name'][$source])) {
      return NULL;
    }

    // Check for file upload errors and return FALSE if a lower level system
    // error occurred. For a complete list of errors:
    // See http://php.net/manual/features.file-upload.errors.php.
    switch ($_FILES['files']['error'][$source]) {
      case UPLOAD_ERR_INI_SIZE:
      case UPLOAD_ERR_FORM_SIZE:
        $message = format_string('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$source], '%maxsize' => format_size(file_upload_max_size())));
        throw new \RestfulBadRequestException($message);

      case UPLOAD_ERR_PARTIAL:
      case UPLOAD_ERR_NO_FILE:
        $message = format_string('The file %file could not be saved, because the upload did not complete.', array('%file' => $_FILES['files']['name'][$source]));
        throw new \RestfulBadRequestException($message);

      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($_FILES['files']['tmp_name'][$source])) {
          break;
        }

      // Unknown error
      default:
        $message = format_string('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source]));
        throw new \RestfulServiceUnavailable($message);
    }

    // Begin building file object.
    $file = new stdClass();
    $file->uid      = $account->uid;
    $file->status   = 0;
    $file->filename = trim(drupal_basename($_FILES['files']['name'][$source]), '.');
    $file->uri      = $_FILES['files']['tmp_name'][$source];
    $file->filemime = file_get_mimetype($file->filename);
    $file->filesize = $_FILES['files']['size'][$source];

    $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->filename = file_munge_filename($file->filename, $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 (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
      $file->filemime = 'text/plain';
      $file->uri .= '.txt';
      $file->filename .= '.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';

        // Unlike file_save_upload() we don't need to let the user know that
        // for security reasons, your upload has been renamed, since RESTful
        // will return the file name in the response.
      }
    }

    // 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 (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
      $message = format_string('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination));
      throw new \RestfulServiceUnavailable($message);
    }

    $file->source = $source;
    // A URI may already have a trailing slash or look like "public://".
    if (substr($destination, -1) != '/') {
      $destination .= '/';
    }
    $file->destination = file_destination($destination . $file->filename, $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) {
      $message = format_string('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $source, '%directory' => $destination));
      throw new \RestfulServiceUnavailable($message);
    }

    // Add in our check of the 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 = format_string('The specified file %name could not be uploaded.', array('%name' => $file->filename));
      if (count($errors) > 1) {
        $message .= theme('item_list', array('items' => $errors));
      }
      else {
        $message .= ' ' . array_pop($errors);
      }

      throw new \RestfulServiceUnavailable($message);
    }

    // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
    // directory. This overcomes open_basedir restrictions for future file
    // operations.
    $file->uri = $file->destination;
    if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) {
      watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri));
      $message = 'File upload error. Could not move uploaded file.';
      throw new \RestfulServiceUnavailable($message);
    }

    // Set the permissions on the new file.
    drupal_chmod($file->uri);

    // If we are replacing an existing file re-use its database record.
    if ($replace == FILE_EXISTS_REPLACE) {
      $existing_files = file_load_multiple(array(), array('uri' => $file->uri));
      if (count($existing_files)) {
        $existing = reset($existing_files);
        $file->fid = $existing->fid;
      }
    }

    // If we made it this far it's safe to record this file in the database.
    if ($file = file_save($file)) {
      // Add file to the cache.
      $upload_cache[$source] = $file;
      return $file;
    }

    // Something went wrong, so throw a general exception.
    throw new \RestfulServiceUnavailable('Unknown error has occurred.');
  }
 /**
  * Submit handler for reading a stream wrapper.
  *
  * Drupal now has full support for PHP's stream wrappers, which means that
  * instead of the traditional use of all the file functions
  * ($fp = fopen("/tmp/some_file.txt");) far more sophisticated and generalized
  * (and extensible) things can be opened as if they were files. Drupal itself
  * provides the public:// and private:// schemes for handling public and
  * private files. PHP provides file:// (the default) and http://, so that a
  * URL can be read or written (as in a POST) as if it were a file. In
  * addition, new schemes can be provided for custom applications. The Stream
  * Wrapper Example, if installed, impleents a custom 'session' scheme that
  * you can test with this example.
  *
  * Here we take the stream wrapper provided in the form. We grab the
  * contents with file_get_contents(). Notice that's it's as simple as that:
  * file_get_contents("http://example.com") or
  * file_get_contents("public://somefile.txt") just works. Although it's
  * not necessary, we use file_unmanaged_save_data() to save this file locally
  * and then find a local URL for it by using file_create_url().
  *
  * @param array $form
  *   An associative array containing the structure of the form.
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form.
  */
 public function handleFileRead(array &$form, FormStateInterface $form_state)
 {
     $form_values = $form_state->getValues();
     $uri = $form_values['fileops_file'];
     if (empty($uri) or !is_file($uri)) {
         drupal_set_message(t('The file "%uri" does not exist', array('%uri' => $uri)), 'error');
         return;
     }
     // Make a working filename to save this by stripping off the (possible)
     // file portion of the streamwrapper. If it's an evil file extension,
     // file_munge_filename() will neuter it.
     $filename = file_munge_filename(preg_replace('@^.*/@', '', $uri), '', TRUE);
     $buffer = file_get_contents($uri);
     if ($buffer) {
         $sourcename = file_unmanaged_save_data($buffer, 'public://' . $filename);
         if ($sourcename) {
             $url = $this->getExternalUrl($sourcename);
             $this->setDefaultFile($sourcename);
             if ($url) {
                 drupal_set_message($this->t('The file was read and copied to %filename which is accessible at <a href=":url">this URL</a>', array('%filename' => $sourcename, ':url' => $url->toString())));
             } else {
                 drupal_set_message($this->t('The file was read and copied to %filename (not accessible externally)', array('%filename' => $sourcename)));
             }
         } else {
             drupal_set_message(t('Failed to save the file'));
         }
     } else {
         // We failed to get the contents of the requested file.
         drupal_set_message(t('Failed to retrieve the file %file', array('%file' => $uri)));
     }
 }
 /**
  * {@inheritdoc}
  *
  * @see ManagedFile::valueCallback
  * @see file_managed_file_save_upload()
  */
 public static function valueCallback(&$element, $input, FormStateInterface $form_state)
 {
     $id = $element['#id'];
     // If a unique identifier added with '--', we need to exclude it
     if (preg_match('/(.*)(--[0-9]+)$/', $id, $reg)) {
         $id = $reg[1];
     }
     // Seems cleaner to use something like this, but it's empty.
     // $request_files = \Drupal::request()->files;
     $input = $form_state->getUserInput();
     $files = array();
     foreach ($input as $key => $value) {
         if (preg_match('/' . $id . '_([0-9]+)_(.*)/', $key, $reg)) {
             $i = $reg[1];
             $key = $reg[2];
             // Only add the keys we expect.
             if (!in_array($key, array('tmpname', 'name', 'status'))) {
                 continue;
             }
             // Munge the submitted file names for security.
             //
             // Similar munging is normally done by file_save_upload(), but submit
             // handlers for forms containing plupload elements can't use
             // file_save_upload(), for reasons discussed in plupload_test_submit().
             // So we have to do this for them.
             //
             // Note that we do the munging here in the value callback function
             // (rather than during form validation or elsewhere) because we want to
             // actually modify the submitted values rather than reject them outright;
             // file names that require munging can be innocent and do not necessarily
             // indicate an attempted exploit. Actual validation of the file names is
             // performed later, in plupload_element_validate().
             if (in_array($key, array('tmpname', 'name'))) {
                 // Find the whitelist of extensions to use when munging. If there are
                 // none, we'll be adding default ones in plupload_element_process(), so
                 // use those here.
                 if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
                     $extensions = $element['#upload_validators']['file_validate_extensions'][0];
                 } else {
                     $validators = _plupload_default_upload_validators();
                     $extensions = $validators['file_validate_extensions'][0];
                 }
                 $value = file_munge_filename($value, $extensions, FALSE);
                 // To prevent directory traversal issues, make sure the file name does
                 // not contain any directory components in it. (This more properly
                 // belongs in the form validation step, but it's simpler to do here so
                 // that we don't have to deal with the temporary file names during form
                 // validation and can just focus on the final file name.)
                 //
                 // This step is necessary since this module allows a large amount of
                 // flexibility in where its files are placed (for example, they could
                 // be intended for public://subdirectory rather than public://, and we
                 // don't want an attacker to be able to get them back into the top
                 // level of public:// in that case).
                 $value = rtrim(drupal_basename($value), '.');
                 // Based on the same feture from file_save_upload().
                 if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\\.(php|pl|py|cgi|asp|js)(\\.|$)/i', $value) && substr($value, -4) != '.txt') {
                     $value .= '.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)) {
                         $element['#upload_validators']['file_validate_extensions'][0] .= ' txt';
                         drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $value)));
                     }
                 }
             }
             // The temporary file name has to be processed further so it matches what
             // was used when the file was written; see plupload_handle_uploads().
             if ($key == 'tmpname') {
                 $value = _plupload_fix_temporary_filename($value);
                 // We also define an extra key 'tmppath' which is useful so that submit
                 // handlers do not need to know which directory plupload stored the
                 // temporary files in before trying to copy them.
                 $files[$i]['tmppath'] = \Drupal::config('plupload.settings')->get('temporary_uri') . $value;
             } elseif ($key == 'name') {
                 $value = \Drupal::service('transliteration')->transliterate($value);
             }
             // Store the final value in the array we will return.
             $files[$i][$key] = $value;
         }
     }
     return $files;
 }
示例#7
0
 /**
  * Uploads the file to EMDB.
  *
  * @param string $form_field_name
  *   A string that is the associative array key of the upload form element in
  *   the form array.
  * @param string $catalog_id
  *   The catalog id for the catalog we are uploading to.
  * @param array $metadata
  *   Additional properties that can be passed into EMDB and stored as metadata
  *   on the file. These values are not stored locally in Drupal.
  * @param array $validators
  *   An optional, associative array of callback functions used to validate the
  *   file.
  * @param string|bool $destination_dir
  *   A string containing the URI that the file should be copied to. This must
  *   be a stream wrapper URI. If this value is omitted, Drupal's temporary
  *   files scheme will be used ("temporary://").
  * @param int $delta
  *   Delta of the file to save or NULL to save all files. Defaults to NULL.
  * @param int $replace
  *   Replace behavior when the destination file already exists:
  *   - FILE_EXISTS_REPLACE: Replace the existing file.
  *   - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
  *     unique.
  *   - FILE_EXISTS_ERROR: Do nothing and return FALSE.
  *
  * @return \Drupal\embridge\EmbridgeAssetEntityInterface[]
  *   Function returns array of files or a single file object if $delta
  *   != NULL. Each file object contains the file information if the
  *   upload succeeded or FALSE in the event of an error. Function
  *   returns NULL if no file was uploaded.
  *
  *   The docs for the "File interface" group, which you can find under
  *   Related topics, or the header at the top of this file, documents the
  *   components of a file entity. In addition to the standard components,
  *   this function adds:
  *   - source: Path to the file before it is moved.
  *   - destination: Path to the file after it is moved (same as 'uri').
  */
 public static function saveUpload($form_field_name, $catalog_id, $metadata = array(), $validators = array(), $destination_dir = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME)
 {
     $user = \Drupal::currentUser();
     static $upload_cache;
     $file_upload = \Drupal::request()->files->get("files[{$form_field_name}]", NULL, TRUE);
     // Make sure there's an upload to process.
     if (empty($file_upload)) {
         return NULL;
     }
     // 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.
     /** @var \Symfony\Component\HttpFoundation\File\UploadedFile[] $uploaded_files */
     $uploaded_files = $file_upload;
     if (!is_array($file_upload)) {
         $uploaded_files = array($file_upload);
     }
     $assets = 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');
                 $assets[$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');
                 $assets[$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;
                 }
             default:
                 // Unknown error.
                 drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $file_info->getFilename())), 'error');
                 $assets[$i] = FALSE;
                 continue;
         }
         // Begin building file entity.
         $values = array('uid' => $user->id(), 'filename' => $file_info->getClientOriginalName(), 'filesize' => $file_info->getSize(), 'catalog_id' => $catalog_id);
         $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']);
         // Create our Embridge Entity.
         /** @var \Drupal\embridge\EmbridgeAssetEntityInterface $asset */
         $asset = EmbridgeAssetEntity::create($values);
         $extensions = '';
         if (isset($validators['embridge_asset_validate_file_extensions'])) {
             if (isset($validators['embridge_asset_validate_file_extensions'][0])) {
                 // Build the list of non-munged exts if the caller provided them.
                 $extensions = $validators['embridge_asset_validate_file_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['embridge_asset_validate_file_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['embridge_asset_validate_file_extensions'] = array();
             $validators['embridge_asset_validate_file_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).
             $asset->setFilename(file_munge_filename($asset->getFilename(), $extensions));
         }
         // Rename potentially executable files, to help prevent exploits.
         if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\\.(php|pl|py|cgi|asp|js)(\\.|$)/i', $asset->getFilename()) && substr($asset->getFilename(), -4) != '.txt') {
             $asset->setMimeType('text/plain');
             // The destination filename will also later be used to create the URI.
             $asset->setFilename($asset->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['embridge_asset_validate_file_extensions'][0] .= ' txt';
                 drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $asset->getFilename())));
             }
         }
         // If the destination is not provided, use the temporary directory.
         if (empty($destination_dir)) {
             $destination_dir = 'temporary://';
         }
         // Assert that the destination contains a valid stream.
         $destination_scheme = file_uri_scheme($destination_dir);
         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_dir)), 'error');
             $assets[$i] = FALSE;
             continue;
         }
         // A file URI may already have a trailing slash or look like "public://".
         if (substr($destination_dir, -1) != '/') {
             $destination_dir .= '/';
         }
         $asset_destination = file_destination($destination_dir . $asset->getFilename(), $replace);
         // If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR
         // and there's an existing file so we need to bail.
         if ($asset_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_dir)), 'error');
             $assets[$i] = FALSE;
             continue;
         }
         // Add in our check of the file name length.
         // TODO: Do we need this?
         // $validators['file_validate_name_length'] = array();
         // Call the validation functions specified by this function's caller.
         $errors = embridge_asset_validate($asset, $validators);
         // Check for errors.
         if (!empty($errors)) {
             $message = array('error' => array('#markup' => t('The specified file %name could not be uploaded.', array('%name' => $asset->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');
             $assets[$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.
         $asset->setSourcePath($asset_destination);
         if (!drupal_move_uploaded_file($file_info->getRealPath(), $asset->getSourcePath())) {
             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' => $asset->getFilename(), '%destination' => $asset->getSourcePath()));
             $assets[$i] = FALSE;
             continue;
         }
         // Set the permissions on the new file.
         drupal_chmod($asset->getSourcePath());
         // 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('embridge_asset_entity', array('uri' => $asset->getSourcePath()));
             if (count($existing_files)) {
                 $existing = reset($existing_files);
                 $asset->setOriginalId($existing->id());
             }
         }
         /** @var \Drupal\embridge\EnterMediaDbClientInterface $embridge_client */
         $embridge_client = \Drupal::getContainer()->get('embridge.client');
         try {
             $embridge_client->upload($asset, $metadata);
         } catch (\Exception $e) {
             $message = $e->getMessage();
             drupal_set_message(t('Uploading the file "%file" to EnterMedia failed with the message "%message".', array('%file' => $asset->getFilename(), '%message' => $message)), 'error');
             $assets[$i] = FALSE;
             continue;
         }
         // If we made it this far it's safe to record this file in the database.
         $asset->save();
         $assets[$i] = $asset;
     }
     // Add files to the cache.
     $upload_cache[$form_field_name] = $assets;
     return isset($delta) ? $assets[$delta] : $assets;
 }