You are here

public static function Remote::value in FileField Sources 8

Value callback for file field source plugin.

Parameters

array $element: An associative array containing the properties of the element.

mixed $input: The incoming input to populate the form element. If this is FALSE, the element's default value should be returned.

\Drupal\Core\Form\FormStateInterface $form_state: The current state of the form.

Return value

mixed The value to assign to the element.

Overrides FilefieldSourceInterface::value

File

src/Plugin/FilefieldSource/Remote.php, line 31

Class

Remote
A FileField source plugin to allow downloading a file from a remote server.

Namespace

Drupal\filefield_sources\Plugin\FilefieldSource

Code

public static function value(array &$element, &$input, FormStateInterface $form_state) {
  if (isset($input['filefield_remote']['url']) && strlen($input['filefield_remote']['url']) > 0 && UrlHelper::isValid($input['filefield_remote']['url']) && $input['filefield_remote']['url'] != FILEFIELD_SOURCE_REMOTE_HINT_TEXT) {
    $field = \Drupal::entityTypeManager()
      ->getStorage('field_config')
      ->load($element['#entity_type'] . '.' . $element['#bundle'] . '.' . $element['#field_name']);
    $url = $input['filefield_remote']['url'];

    // Check that the destination is writable.
    $temporary_directory = 'temporary://';
    if (!\Drupal::service('file_system')
      ->prepareDirectory($temporary_directory, FileSystemInterface::MODIFY_PERMISSIONS)) {
      \Drupal::logger('filefield_sources')
        ->log(E_NOTICE, 'The directory %directory is not writable, because it does not have the correct permissions set.', [
        '%directory' => \Drupal::service('file_system')
          ->realpath($temporary_directory),
      ]);
      \Drupal::messenger()
        ->addError(t('The file could not be transferred because the temporary directory is not writable.'), 'error');
      return;
    }

    // Check that the destination is writable.
    $directory = $element['#upload_location'];
    $mode = Settings::get('file_chmod_directory', FileSystem::CHMOD_DIRECTORY);

    // This first chmod check is for other systems such as S3, which don't
    // work with file_prepare_directory().
    if (!\Drupal::service('file_system')
      ->chmod($directory, $mode) && !\Drupal::service('file_system')
      ->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY)) {
      \Drupal::logger('filefield_sources')
        ->log(E_NOTICE, 'File %file could not be copied, because the destination directory %destination is not configured correctly.', [
        '%file' => $url,
        '%destination' => \Drupal::service('file_system')
          ->realpath($directory),
      ]);
      \Drupal::messenger()
        ->addError(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', [
        '%file' => $url,
      ]), 'error');
      return;
    }

    // Check the headers to make sure it exists and is within the allowed
    // size.
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, TRUE);
    curl_setopt($ch, CURLOPT_NOBODY, TRUE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_HEADERFUNCTION, [
      get_called_class(),
      'parseHeader',
    ]);

    // Causes a warning if PHP safe mode is on.
    @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_exec($ch);
    $info = curl_getinfo($ch);
    if ($info['http_code'] != 200) {
      curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
      $file_contents = curl_exec($ch);
      $info = curl_getinfo($ch);
    }
    curl_close($ch);
    if ($info['http_code'] != 200) {
      switch ($info['http_code']) {
        case 403:
          $form_state
            ->setError($element, t('The remote file could not be transferred because access to the file was denied.'));
          break;
        case 404:
          $form_state
            ->setError($element, t('The remote file could not be transferred because it was not found.'));
          break;
        default:
          $form_state
            ->setError($element, t('The remote file could not be transferred due to an HTTP error (@code).', [
            '@code' => $info['http_code'],
          ]));
      }
      return;
    }

    // Update the $url variable to reflect any redirects.
    $url = $info['url'];
    $url_info = parse_url($url);

    // Determine the proper filename by reading the filename given in the
    // Content-Disposition header. If the server fails to send this header,
    // fall back on the basename of the URL.
    //
    // We prefer to use the Content-Disposition header, because we can then
    // use URLs like http://example.com/get_file/23 which would otherwise be
    // rejected because the URL basename lacks an extension.

    /** @var \Drupal\Core\File\FileSystem $filesystem */
    $filesystem = \Drupal::service('file_system');
    $filename = static::filename();
    if (empty($filename)) {
      $filename = rawurldecode($filesystem
        ->basename($url_info['path']));
    }
    $filename = \Drupal::transliteration()
      ->transliterate($filename);
    $pathinfo = pathinfo($filename);

    // Create the file extension from the MIME header if all else has failed.
    if (empty($pathinfo['extension']) && ($extension = static::mimeExtension())) {
      $filename = $filename . '.' . $extension;
      $pathinfo = pathinfo($filename);
    }
    $filename = filefield_sources_clean_filename($filename, $field
      ->getSetting('file_extensions'));
    $filepath = \Drupal::service('file_system')
      ->createFilename($filename, $temporary_directory);
    if (empty($pathinfo['extension'])) {
      $form_state
        ->setError($element, t('The remote URL must be a file and have an extension.'));
      return;
    }

    // Perform basic extension check on the file before trying to transfer.
    $extensions = $field
      ->getSetting('file_extensions');
    $regex = '/\\.(' . preg_replace('/[ +]/', '|', preg_quote($extensions)) . ')$/i';
    if (!empty($extensions) && !preg_match($regex, $filename)) {
      $form_state
        ->setError($element, t('Only files with the following extensions are allowed: %files-allowed.', [
        '%files-allowed' => $extensions,
      ]));
      return;
    }

    // Check file size based off of header information.
    if (!empty($element['#upload_validators']['file_validate_size'][0])) {
      $max_size = $element['#upload_validators']['file_validate_size'][0];
      $file_size = $info['download_content_length'];
      if ($file_size > $max_size) {
        $form_state
          ->setError($element, t('The remote file is %filesize exceeding the maximum file size of %maxsize.', [
          '%filesize' => format_size($file_size),
          '%maxsize' => format_size($max_size),
        ]));
        return;
      }
    }

    // Set progress bar information.
    $options = [
      'key' => $element['#entity_type'] . '_' . $element['#bundle'] . '_' . $element['#field_name'] . '_' . $element['#delta'],
      'filepath' => $filepath,
    ];
    static::setTransferOptions($options);
    $transfer_success = FALSE;

    // If we've already downloaded the entire file because the
    // header-retrieval failed, just ave the contents we have.
    if (isset($file_contents)) {
      if ($fp = @fopen($filepath, 'w')) {
        fwrite($fp, $file_contents);
        fclose($fp);
        $transfer_success = TRUE;
      }
    }
    else {
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_HEADER, FALSE);
      curl_setopt($ch, CURLOPT_WRITEFUNCTION, [
        get_called_class(),
        'curlWrite',
      ]);

      // Causes a warning if PHP safe mode is on.
      @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
      $transfer_success = curl_exec($ch);
      curl_close($ch);
    }
    if ($transfer_success && ($file = filefield_sources_save_file($filepath, $element['#upload_validators'], $element['#upload_location']))) {
      if (!in_array($file
        ->id(), $input['fids'])) {
        $input['fids'][] = $file
          ->id();
      }
    }

    // Delete the temporary file.
    @unlink($filepath);
  }
}