You are here

function filefield_sources_save_file in FileField Sources 8

Same name and namespace in other branches
  1. 7 filefield_sources.module \filefield_sources_save_file()

Save a file into the database after validating it.

This function is identical to the core function file_save_upload() except that it accepts an input file path instead of an input file source name.

See also

file_save_upload()

3 calls to filefield_sources_save_file()
Attach::value in src/Plugin/FilefieldSource/Attach.php
Value callback for file field source plugin.
Clipboard::value in src/Plugin/FilefieldSource/Clipboard.php
Value callback for file field source plugin.
Remote::value in src/Plugin/FilefieldSource/Remote.php
Value callback for file field source plugin.

File

./filefield_sources.module, line 438
Extend FileField to allow files from multiple sources.

Code

function filefield_sources_save_file($filepath, $validators = [], $destination = FALSE, $replace = FileSystemInterface::EXISTS_RENAME) {
  $user = \Drupal::currentUser();

  // Begin building file object.
  $file = File::create([
    'uri' => $filepath,
    'uid' => $user
      ->id(),
    'status' => FileSystemInterface::EXISTS_RENAME,
  ]);
  $file
    ->setFilename(trim(basename($filepath), '.'));
  $file
    ->setMimeType(\Drupal::service('file.mime_type.guesser')
    ->guess($file
    ->getFilename()));
  $file
    ->setSize(filesize($filepath));
  $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'] = [];
    $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');
    $file
      ->setFileUri($file
      ->getFileUri() . '.txt');
    $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::messenger()
        ->addStatus(t('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 = \Drupal::service('stream_wrapper_manager')
    ->getScheme($destination);
  if (!$destination_scheme || !\Drupal::service('stream_wrapper_manager')
    ->isValidScheme($destination_scheme)) {
    \Drupal::messenger()
      ->addError(t('The file could not be uploaded, because the destination %destination is invalid.', [
      '%destination' => $destination,
    ]), 'error');
    return FALSE;
  }

  // A URI may already have a trailing slash or look like "public://".
  if (substr($destination, -1) != '/') {
    $destination .= '/';
  }

  // Ensure the destination is writable.
  \Drupal::service('file_system')
    ->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY);

  // Check if this is actually the same file being "attached" to a file record.
  // If so, it acts as a file replace, except no file is actually moved.
  $reuse_file = $destination . $file
    ->getFilename() === $file
    ->getFileUri();
  if ($reuse_file) {
    $replace = FileSystemInterface::EXISTS_REPLACE;
  }
  $file->destination = \Drupal::service('file_system')
    ->getDestinationFilename($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::messenger()
      ->addError(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', [
      '%source' => $file
        ->getFilename(),
      '%directory' => $destination,
    ]), 'error');
    return FALSE;
  }

  // Add in our check of the the file name length.
  $validators['file_validate_name_length'] = [];

  // Call the validation functions specified by this function's caller.
  $errors = file_validate($file, $validators);

  // Check for errors.
  if (!empty($errors)) {
    $message = t('The specified file %name could not be uploaded.', [
      '%name' => $file
        ->getFilename(),
    ]);
    if (count($errors) > 1) {
      $message .= theme('item_list', [
        'items' => $errors,
      ]);
    }
    else {
      $message .= ' ' . array_pop($errors);
    }
    \Drupal::messenger()
      ->addError($message, 'error');
    return FALSE;
  }

  // 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 (!$reuse_file && !\Drupal::service('file_system')
    ->copy($filepath, $file
    ->getFileUri(), $replace)) {
    \Drupal::messenger()
      ->addError(t('File upload error. Could not move uploaded file.'), 'error');
    \Drupal::logger('filefield_sources')
      ->log(E_NOTICE, 'Upload error. Could not move uploaded file %file to destination %destination.', [
      '%file' => $file
        ->getFilename(),
      '%destination' => $file
        ->getFileUri(),
    ]);
    return FALSE;
  }

  // Set the permissions on the new file.
  \Drupal::service('file_system')
    ->chmod($file
    ->getFileUri());

  // If we are replacing an existing file re-use its database record.
  if ($replace == FileSystemInterface::EXISTS_REPLACE) {
    $existing_files = File::loadMultiple([], [
      'uri' => $file
        ->getFileUri(),
    ]);
    if (count($existing_files)) {
      $existing = reset($existing_files);
      $file
        ->setOriginalId($existing
        ->id());
    }
  }

  // If we made it this far it's safe to record this file in the database.
  $file
    ->save();
  return $file;
}