class ManagedFileElementHelper

Helper class for ManagedFile element form file uploads.

This class handles the Form API and user-facing messages.

For API-level form file uploading, use \Drupal\file\Upload\FormFileUploader instead.

Hierarchy

Expanded class hierarchy of ManagedFileElementHelper

See also

\Drupal\file\Upload\FormFileUploader

\Drupal\file\Element\ManagedFile::valueCallback()

5 files declare their use of ManagedFileElementHelper
file.module in core/modules/file/file.module
FileTestSaveUploadFromForm.php in core/modules/file/tests/file_test/src/Form/FileTestSaveUploadFromForm.php
ImportForm.php in core/modules/locale/src/Form/ImportForm.php
ManagedFile.php in core/modules/file/src/Element/ManagedFile.php
SettingsForm.php in core/modules/navigation/src/Form/SettingsForm.php

File

core/modules/file/src/Upload/ManagedFileElementHelper.php, line 28

Namespace

Drupal\file\Upload
View source
class ManagedFileElementHelper {
  use StringTranslationTrait;
  public function __construct(protected readonly MessengerInterface $messenger, protected readonly RendererInterface $renderer, protected readonly FormFileUploader $formFileUploader, protected readonly UploadedFilesExtractor $uploadedFilesExtractor, protected readonly FileSystemInterface $fileSystem, #[AutowireServiceClosure('logger.channel.file')] protected readonly \Closure $logger) {
  }
  
  /**
   * Saves form file uploads.
   *
   * 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 array $element
   *   The FAPI element whose values are being saved.
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The current state of the form.
   * @param null|int $delta
   *   (optional) The delta of the file to return the file entity.
   *   Defaults to NULL.
   * @param \Drupal\Core\File\FileExists $fileExists
   *   (optional) The replace behavior when the destination file already exists.
   *
   * @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 https://www.drupal.org/project/drupal/issues/3069020
   * @see https://www.drupal.org/project/drupal/issues/2482783
   */
  public function saveFileUploads(array $element, FormStateInterface $formState, ?int $delta = NULL, FileExists $fileExists = FileExists::Rename) : array|FileInterface|null|false {
    // Get all errors set before calling this method. This will also clear them
    // from the messenger service.
    $errorsBefore = $this->messenger
      ->deleteByType(MessengerInterface::TYPE_ERROR);
    $uploadLocation = $element['#upload_location'] ?? 'temporary://';
    $uploadName = implode('_', $element['#parents']);
    $uploadValidators = $element['#upload_validators'] ?? [];
    if ($uploadLocation === FALSE) {
      $uploadLocation = 'temporary://';
    }
    $result = $this->formFileUploader
      ->saveFormUploadedFiles($uploadName, $uploadValidators, $uploadLocation, $delta, $fileExists);
    // Get new errors that are generated while trying to save the upload. This
    // will also clear them from the messenger service.
    $errorsNew = $this->messenger
      ->deleteByType(MessengerInterface::TYPE_ERROR);
    if (!empty($errorsNew)) {
      if (count($errorsNew) > 1) {
        // Render multiple errors into a single message.
        // This is needed because only one error per element is supported.
        $render_array = [
          'error' => [
            '#markup' => $this->t('One or more files could not be uploaded.'),
          ],
          'item_list' => [
            '#theme' => 'item_list',
            '#items' => $errorsNew,
          ],
        ];
        $errorMessage = $this->renderer
          ->renderInIsolation($render_array);
      }
      else {
        $errorMessage = reset($errorsNew);
      }
      $formState->setError($element, $errorMessage);
    }
    // Ensure that errors set prior to calling this method are still shown to
    // the user.
    if (!empty($errorsBefore)) {
      foreach ($errorsBefore as $error) {
        $this->messenger
          ->addError($error);
      }
    }
    return $result;
  }
  
  /**
   * Saves any files that have been uploaded into a managed_file element.
   *
   * @param array $element
   *   The FAPI element whose values are being saved.
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The current state of the form.
   *
   * @return array<int, FileInterface>|false
   *   An array of file entities for each file that was saved, keyed by its file
   *   ID. Each array element contains a file entity. Function returns FALSE if
   *   upload directory could not be created or no files were uploaded.
   */
  public function managedFileSaveUpload(array $element, FormStateInterface $formState) : array|false {
    $uploadName = implode('_', $element['#parents']);
    $fileUpload = $this->uploadedFilesExtractor
      ->extractUploadedFiles($uploadName);
    $destination = $element['#upload_location'] ?? NULL;
    if (isset($destination) && !$this->fileSystem
      ->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY)) {
      ($this->logger)()
        ->notice('The upload directory %directory for the file field %name could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', [
        '%directory' => $destination,
        '%name' => $element['#field_name'],
      ]);
      $formState->setError($element, $this->t('The file could not be uploaded.'));
      return FALSE;
    }
    // Save attached files to the database.
    $filesUploaded = $element['#multiple'] && count(array_filter($fileUpload)) > 0;
    $filesUploaded |= !$element['#multiple'] && !empty($fileUpload);
    if ($filesUploaded) {
      if (!$files = $this->saveFileUploads($element, $formState)) {
        ($this->logger)()
          ->notice('The file upload failed. %upload', [
          '%upload' => $uploadName,
        ]);
        return [];
      }
      // Value callback expects FIDs to be keys.
      $files = array_filter($files);
      $fids = array_map(function ($file) {
        return $file->id();
      }, $files);
      return empty($files) ? [] : array_combine($fids, $files);
    }
    return [];
  }

}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.