You are here

abstract class WebformManagedFileBase in Webform 8.5

Same name and namespace in other branches
  1. 6.x src/Plugin/WebformElement/WebformManagedFileBase.php \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase

Provides a base class webform 'managed_file' elements.

Hierarchy

Expanded class hierarchy of WebformManagedFileBase

6 files declare their use of WebformManagedFileBase
RemotePostWebformHandler.php in src/Plugin/WebformHandler/RemotePostWebformHandler.php
WebformDevelSchema.php in modules/webform_devel/src/WebformDevelSchema.php
WebformSubmissionExportImportImporter.php in modules/webform_submission_export_import/src/WebformSubmissionExportImportImporter.php
WebformSubmissionStorage.php in src/WebformSubmissionStorage.php
WebformUiElementTypeFormBase.php in modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php

... See full list

File

src/Plugin/WebformElement/WebformManagedFileBase.php, line 43

Namespace

Drupal\webform\Plugin\WebformElement
View source
abstract class WebformManagedFileBase extends WebformElementBase implements WebformElementAttachmentInterface, WebformElementEntityReferenceInterface {

  /**
   * List of blacklisted mime types that must be downloaded.
   *
   * @var array
   */
  protected static $blacklistedMimeTypes = [
    'application/pdf',
    'application/xml',
    'image/svg+xml',
    'text/html',
  ];

  /**
   * The 'file_system' service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * The 'file.usage' service.
   *
   * @var \Drupal\file\FileUsage\FileUsageInterface
   */
  protected $fileUsage;

  /**
   * The 'transliteration' service.
   *
   * @var \Drupal\Component\Transliteration\TransliterationInterface
   */
  protected $transliteration;

  /**
   * The 'language_manager' service.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * WebformManagedFileBase constructor.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
   *   The element info manager.
   * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager
   *   The webform element manager.
   * @param \Drupal\webform\WebformTokenManagerInterface $token_manager
   *   The webform token manager.
   * @param \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager
   *   The webform libraries manager.
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   The file system service.
   * @param \Drupal\file\FileUsage\FileUsageInterface|null $file_usage
   *   The file usage service.
   * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
   *   The transliteration service.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, ConfigFactoryInterface $config_factory, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info, WebformElementManagerInterface $element_manager, WebformTokenManagerInterface $token_manager, WebformLibrariesManagerInterface $libraries_manager, FileSystemInterface $file_system, $file_usage, TransliterationInterface $transliteration, LanguageManagerInterface $language_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $logger, $config_factory, $current_user, $entity_type_manager, $element_info, $element_manager, $token_manager, $libraries_manager);
    $this->fileSystem = $file_system;
    $this->fileUsage = $file_usage;
    $this->transliteration = $transliteration;
    $this->languageManager = $language_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('logger.factory')
      ->get('webform'), $container
      ->get('config.factory'), $container
      ->get('current_user'), $container
      ->get('entity_type.manager'), $container
      ->get('plugin.manager.element_info'), $container
      ->get('plugin.manager.webform.element'), $container
      ->get('webform.token_manager'), $container
      ->get('webform.libraries_manager'), $container
      ->get('file_system'), $container
      ->has('file.usage') ? $container
      ->get('file.usage') : NULL, $container
      ->get('transliteration'), $container
      ->get('language_manager'));
  }

  /**
   * {@inheritdoc}
   */
  protected function defineDefaultProperties() {
    $file_extensions = $this
      ->getFileExtensions();
    $properties = parent::defineDefaultProperties() + [
      'multiple' => FALSE,
      'max_filesize' => '',
      'file_extensions' => $file_extensions,
      'file_name' => '',
      'file_help' => '',
      'file_preview' => '',
      'file_placeholder' => '',
      'uri_scheme' => 'private',
      'sanitize' => FALSE,
      'button' => FALSE,
      'button__title' => '',
      'button__attributes' => [],
    ];

    // File uploads can't be prepopulated.
    unset($properties['prepopulate']);
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  protected function defineTranslatableProperties() {
    return array_merge(parent::defineTranslatableProperties(), [
      'file_placeholder',
    ]);
  }

  /****************************************************************************/

  /**
   * {@inheritdoc}
   */
  public function supportsMultipleValues() {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function hasMultipleWrapper() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isMultiline(array $element) {
    if ($this
      ->hasMultipleValues($element)) {
      return TRUE;
    }
    else {
      return parent::isMultiline($element);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function isEnabled() {
    if (!parent::isEnabled()) {
      return FALSE;
    }

    // Disable File element is there are no visible stream wrappers.
    $scheme_options = static::getVisibleStreamWrappers();
    return empty($scheme_options) ? FALSE : TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function hasManagedFiles(array $element) {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function displayDisabledWarning(array $element) {

    // Display standard disabled element warning.
    if (!parent::isEnabled()) {
      parent::displayDisabledWarning($element);
    }
    else {

      // Display 'managed_file' stream wrappers warning.
      $scheme_options = static::getVisibleStreamWrappers();
      $uri_scheme = $this
        ->getUriScheme($element);
      if (!isset($scheme_options[$uri_scheme]) && $this->currentUser
        ->hasPermission('administer webform')) {
        $this
          ->messenger()
          ->addWarning($this
          ->t('The \'File\' element is unavailable because a <a href="https://www.ostraining.com/blog/drupal/creating-drupal-8-private-file-system/">private files directory</a> has not been configured and public file uploads have not been enabled. For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'));
        $context = [
          'link' => Link::fromTextAndUrl($this
            ->t('Edit'), UrlGenerator::fromRoute('<current>'))
            ->toString(),
        ];
        $this->logger
          ->notice("The 'File' element is unavailable because no stream wrappers are available", $context);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) {

    // Track if this element has been processed because the work-around below
    // for 'Issue #2705471: Webform states File fields' which nests the
    // 'managed_file' element in a basic container, which triggers this element
    // to processed a second time.
    if (!empty($element['#webform_managed_file_processed'])) {
      return;
    }
    $element['#webform_managed_file_processed'] = TRUE;

    // Must come after #element_validate hook is defined.
    parent::prepare($element, $webform_submission);

    // Check if the URI scheme exists and can be used the upload location.
    $scheme_options = static::getVisibleStreamWrappers();
    $uri_scheme = $this
      ->getUriScheme($element);
    if (!isset($scheme_options[$uri_scheme])) {
      $element['#access'] = FALSE;
      $this
        ->displayDisabledWarning($element);
    }
    elseif ($webform_submission) {
      $element['#upload_location'] = $this
        ->getUploadLocation($element, $webform_submission
        ->getWebform());
    }

    // Get file limit.
    if ($webform_submission) {
      $file_limit = $webform_submission
        ->getWebform()
        ->getSetting('form_file_limit') ?: \Drupal::config('webform.settings')
        ->get('settings.default_form_file_limit') ?: '';
    }
    else {
      $file_limit = '';
    }

    // Validate callbacks.
    $element_validate = [];

    // Convert File entities into file ids (akk fids).
    $element_validate[] = [
      get_class($this),
      'validateManagedFile',
    ];

    // Check file upload limit.
    if ($file_limit) {
      $element_validate[] = [
        get_class($this),
        'validateManagedFileLimit',
      ];
    }

    // NOTE: Using array_splice() to make sure that static::validateManagedFile
    // is executed before all other validation hooks are executed but after
    // \Drupal\file\Element\ManagedFile::validateManagedFile.
    array_splice($element['#element_validate'], 1, 0, $element_validate);

    // Upload validators.
    // @see webform_preprocess_file_upload_help
    $element['#upload_validators']['file_validate_size'] = [
      $this
        ->getMaxFileSize($element),
    ];
    $element['#upload_validators']['file_validate_extensions'] = [
      $this
        ->getFileExtensions($element),
    ];

    // Define 'webform_file_validate_extensions' which allows file
    // extensions within webforms to be comma-delimited. The
    // 'webform_file_validate_extensions' will be ignored by file_validate().
    // @see file_validate()
    // Issue #3136578: Comma-separate the list of allowed file extensions.
    // @see https://www.drupal.org/project/drupal/issues/3136578
    $element['#upload_validators']['webform_file_validate_extensions'] = [];
    $element['#upload_validators']['webform_file_validate_name_length'] = [];

    // Add file upload help to the element as #description, #help, or #more.
    // Copy upload validator so that we can add webform's file limit to
    // file upload help only.
    $upload_validators = $element['#upload_validators'];
    if ($file_limit) {
      $upload_validators['webform_file_limit'] = [
        Bytes::toInt($file_limit),
      ];
    }
    $file_upload_help = [
      '#theme' => 'file_upload_help',
      '#upload_validators' => $upload_validators,
      '#cardinality' => empty($element['#multiple']) ? 1 : $element['#multiple'],
    ];
    $file_help = isset($element['#file_help']) ? $element['#file_help'] : 'description';
    if ($file_help !== 'none') {
      if (isset($element["#{$file_help}"])) {
        if (is_array($element["#{$file_help}"])) {
          $file_help_content = $element["#{$file_help}"];
        }
        else {
          $file_help_content = [
            '#markup' => $element["#{$file_help}"],
          ];
        }
        $file_help_content += [
          '#suffix' => '<br/>',
        ];
        $element["#{$file_help}"] = [
          'content' => $file_help_content,
        ];
      }
      else {
        $element["#{$file_help}"] = [];
      }
      $element["#{$file_help}"]['file_upload_help'] = $file_upload_help;
    }

    // Issue #2705471: Webform states File fields.
    // Workaround: Wrap the 'managed_file' element in a basic container.
    if (!empty($element['#prefix'])) {
      $container = [
        '#prefix' => $element['#prefix'],
        '#suffix' => $element['#suffix'],
      ];
      unset($element['#prefix'], $element['#suffix']);
      $container[$element['#webform_key']] = $element + [
        '#webform_managed_file_processed' => TRUE,
      ];
      $element = $container;
    }

    // Add process callback.
    // Set element's #process callback so that is not replaced by
    // additional #process callbacks.
    $this
      ->setElementDefaultCallback($element, 'process');
    $element['#process'][] = [
      get_class($this),
      'processManagedFile',
    ];

    // Add managed file upload tracking.
    if (\Drupal::moduleHandler()
      ->moduleExists('file')) {
      $element['#attached']['library'][] = 'webform/webform.element.managed_file';
    }
  }

  /**
   * {@inheritdoc}
   */
  public function setDefaultValue(array &$element) {
    if (!empty($element['#default_value'])) {
      $element['#default_value'] = (array) $element['#default_value'];
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) {
    $value = $this
      ->getValue($element, $webform_submission, $options);
    $file = $this
      ->getFile($element, $value, $options);
    if (empty($file)) {
      return '';
    }
    $format = $this
      ->getItemFormat($element);
    switch ($format) {
      case 'basename':
      case 'extension':
      case 'data':
      case 'id':
      case 'mime':
      case 'name':
      case 'raw':
      case 'size':
      case 'url':
      case 'value':
        return $this
          ->formatTextItem($element, $webform_submission, $options);
      case 'link':
        return [
          '#theme' => 'file_link',
          '#file' => $file,
        ];
      default:
        $theme = str_replace('webform_', 'webform_element_', $this
          ->getPluginId());
        if (strpos($theme, 'webform_') !== 0) {
          $theme = 'webform_element_' . $theme;
        }
        return [
          '#theme' => $theme,
          '#element' => $element,
          '#value' => $value,
          '#options' => $options,
          '#file' => $file,
        ];
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function formatTextItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) {
    $value = $this
      ->getValue($element, $webform_submission, $options);
    $file = $this
      ->getFile($element, $value, $options);
    if (empty($file)) {
      return '';
    }
    $format = $this
      ->getItemFormat($element);
    switch ($format) {
      case 'data':
        return base64_encode(file_get_contents($file
          ->getFileUri()));
      case 'id':
        return $file
          ->id();
      case 'mime':
        return $file
          ->getMimeType();
      case 'name':
        return $file
          ->getFilename();
      case 'basename':
        $filename = $file
          ->getFilename();
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
        return substr(pathinfo($filename, PATHINFO_BASENAME), 0, -strlen(".{$extension}"));
      case 'size':
        return $file
          ->getSize();
      case 'extension':
        return pathinfo($file
          ->getFileUri(), PATHINFO_EXTENSION);
      case 'url':
      case 'value':
      case 'raw':
      default:
        return file_create_url($file
          ->getFileUri());
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getItemDefaultFormat() {
    return 'file';
  }

  /**
   * {@inheritdoc}
   */
  public function getItemFormats() {
    return parent::getItemFormats() + [
      'file' => $this
        ->t('File'),
      'link' => $this
        ->t('Link'),
      'url' => $this
        ->t('URL'),
      'name' => $this
        ->t('File name'),
      'basename' => $this
        ->t('File base name (no extension)'),
      'id' => $this
        ->t('File ID'),
      'mime' => $this
        ->t('File mime type'),
      'size' => $this
        ->t('File size (Bytes)'),
      'data' => $this
        ->t('File content (Base64)'),
      'extension' => $this
        ->t('File extension'),
    ];
  }

  /**
   * Get file.
   *
   * @param array $element
   *   An element.
   * @param array|mixed $value
   *   A value.
   * @param array $options
   *   An array of options.
   *
   * @return \Drupal\file\FileInterface
   *   A file.
   */
  protected function getFile(array $element, $value, array $options) {

    // The value is an array when the posted back file has not been processed
    // and it should ignored.
    if (empty($value) || is_array($value)) {
      return NULL;
    }
    if ($value instanceof FileInterface) {
      return $value;
    }
    return $this->entityTypeManager
      ->getStorage('file')
      ->loadUnchanged($value);
  }

  /**
   * Get files.
   *
   * @param array $element
   *   An element.
   * @param array|mixed $value
   *   A value.
   * @param array $options
   *   An array of options.
   *
   * @return array
   *   An associative array containing files.
   */
  protected function getFiles(array $element, $value, array $options = []) {
    if (empty($value)) {
      return [];
    }
    $fids = (array) $value;
    $this->entityTypeManager
      ->getStorage('file')
      ->resetCache($fids);
    return $this->entityTypeManager
      ->getStorage('file')
      ->loadMultiple($fids);
  }

  /**
   * {@inheritdoc}
   */
  public function getElementSelectorOptions(array $element) {
    $title = $this
      ->getAdminLabel($element);
    $name = $element['#webform_key'];
    $input = $this
      ->hasMultipleValues($element) ? ":input[name=\"files[{$name}][]\"]" : ":input[name=\"files[{$name}]\"]";
    return [
      $input => $title . '  [' . $this
        ->getPluginLabel() . ']',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function postSave(array &$element, WebformSubmissionInterface $webform_submission, $update = TRUE) {

    // Get current value and original value for this element.
    $key = $element['#webform_key'];
    $webform = $webform_submission
      ->getWebform();
    if ($webform
      ->isResultsDisabled()) {
      return;
    }
    $original_data = $webform_submission
      ->getOriginalData();
    $data = $webform_submission
      ->getData();
    $value = isset($data[$key]) ? $data[$key] : [];
    $fids = is_array($value) ? $value : [
      $value,
    ];
    $original_value = isset($original_data[$key]) ? $original_data[$key] : [];
    $original_fids = is_array($original_value) ? $original_value : [
      $original_value,
    ];

    // Delete the old file uploads.
    $delete_fids = array_diff($original_fids, $fids);
    static::deleteFiles($webform_submission, $delete_fids);

    // Add new files.
    $this
      ->addFiles($element, $webform_submission, $fids);
  }

  /**
   * {@inheritdoc}
   */
  public function postDelete(array &$element, WebformSubmissionInterface $webform_submission) {

    // Uploaded files are deleted via the webform submission.
    // This ensures that all files associated with a submission are deleted.
    // @see \Drupal\webform\WebformSubmissionStorage::delete
  }

  /**
   * {@inheritdoc}
   */
  public function getTestValues(array $element, WebformInterface $webform, array $options = []) {
    if ($this
      ->isDisabled()) {
      return NULL;
    }

    // Get element or composite key.
    if (isset($element['#webform_key'])) {
      $key = $element['#webform_key'];
    }
    elseif (isset($element['#webform_composite_key'])) {
      $key = $element['#webform_composite_key'];
    }
    else {
      return NULL;
    }

    // Append delta to key.
    // @see \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::getTestValues
    if (isset($options['delta'])) {
      $key .= '_' . $options['delta'];
    }
    $file_extensions = explode(' ', $this
      ->getFileExtensions($element));
    $file_extension = $file_extensions[array_rand($file_extensions)];
    $upload_location = $this
      ->getUploadLocation($element, $webform);
    $file_destination = $upload_location . '/' . $key . '.' . $file_extension;

    // Look for an existing temp files that have not been uploaded.
    $fids = $this->entityTypeManager
      ->getStorage('file')
      ->getQuery()
      ->condition('status', 0)
      ->condition('uid', $this->currentUser
      ->id())
      ->condition('uri', $upload_location . '/' . $key . '.%', 'LIKE')
      ->execute();
    if ($fids) {
      return reset($fids);
    }

    // Copy sample file or generate a new temp file that can be uploaded.
    $sample_file = drupal_get_path('module', 'webform') . '/tests/files/sample.' . $file_extension;
    if (file_exists($sample_file)) {
      $file_uri = $this->fileSystem
        ->copy($sample_file, $file_destination);
    }
    else {
      $file_uri = $this->fileSystem
        ->saveData('{empty}', $file_destination);
    }
    $file = $this->entityTypeManager
      ->getStorage('file')
      ->create([
      'uri' => $file_uri,
      'uid' => $this->currentUser
        ->id(),
    ]);
    $file
      ->save();
    $fid = $file
      ->id();
    return [
      $fid,
    ];
  }

  /**
   * Get max file size for an element.
   *
   * @param array $element
   *   An element.
   *
   * @return int
   *   Max file size.
   */
  protected function getMaxFileSize(array $element) {
    $max_filesize = $this->configFactory
      ->get('webform.settings')
      ->get('file.default_max_filesize') ?: Environment::getUploadMaxSize();
    $max_filesize = Bytes::toInt($max_filesize);
    if (!empty($element['#max_filesize'])) {
      $max_filesize = min($max_filesize, Bytes::toInt($element['#max_filesize'] . 'MB'));
    }
    return $max_filesize;
  }

  /**
   * Get the allowed file extensions for an element.
   *
   * @param array $element
   *   An element.
   *
   * @return int
   *   File extensions.
   */
  protected function getFileExtensions(array $element = NULL) {
    $extensions = !empty($element['#file_extensions']) ? $element['#file_extensions'] : $this
      ->getDefaultFileExtensions();
    $extensions = str_replace(',', ' ', $extensions);
    return $extensions;
  }

  /**
   * Get the default allowed file extensions.
   *
   * @return int
   *   File extensions.
   */
  protected function getDefaultFileExtensions() {
    $file_type = str_replace('webform_', '', $this
      ->getPluginId());
    return $this->configFactory
      ->get('webform.settings')
      ->get("file.default_{$file_type}_extensions");
  }

  /**
   * Get file upload location.
   *
   * @param array $element
   *   An element.
   * @param \Drupal\webform\WebformInterface $webform
   *   A webform.
   *
   * @return string
   *   Upload location.
   */
  protected function getUploadLocation(array $element, WebformInterface $webform) {
    if (empty($element['#upload_location'])) {
      $upload_location = $this
        ->getUriScheme($element) . '://webform/' . $webform
        ->id() . '/_sid_';
    }
    else {
      $upload_location = $element['#upload_location'];
    }

    // Make sure the upload location exists and is writable.
    $this->fileSystem
      ->prepareDirectory($upload_location, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
    return $upload_location;
  }

  /**
   * Get file upload URI scheme.
   *
   * Defaults to private file uploads.
   *
   * Drupal file upload by anonymous or untrusted users into public file systems
   * -- PSA-2016-003.
   *
   * @param array $element
   *   An element.
   *
   * @return string
   *   File upload URI scheme.
   *
   * @see https://www.drupal.org/psa-2016-003
   */
  protected function getUriScheme(array $element) {
    if (isset($element['#uri_scheme'])) {
      return $element['#uri_scheme'];
    }
    $scheme_options = static::getVisibleStreamWrappers();
    if (isset($scheme_options['private'])) {
      return 'private';
    }
    elseif (isset($scheme_options['public'])) {
      return 'public';
    }
    else {
      return 'private';
    }
  }

  /**
   * Process callback  for managed file elements.
   */
  public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {

    // Disable inline form errors for multiple file upload checkboxes.
    if (!empty($element['#multiple'])) {
      foreach (Element::children($element) as $key) {
        if (isset($element[$key]['selected'])) {
          $element[$key]['selected']['#error_no_message'] = TRUE;
        }
      }
    }

    // Truncate multiple files.
    // Checks if user has uploaded more files than allowed.
    // @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::validateMultipleCount
    // @see \Drupal\file\Element\ManagedFile::processManagedFile.
    if (!empty($element['#multiple']) && $element['#multiple'] > 1 && !empty($element['#files']) && count($element['#files']) > $element['#multiple']) {
      $total_files = count($element['#files']);
      $multiple = $element['#multiple'];
      $fids = [];
      $removed_names = [];
      $count = 0;
      foreach ($element['#files'] as $delta => $file) {
        if ($count >= $multiple) {
          unset($element['file_' . $delta]);
          unset($element['#files'][$delta]);
          $removed_names[] = $file
            ->getFilename();
          $file
            ->delete();
        }
        else {
          $fids[] = $delta;
        }
        $count++;
      }
      $element['fids']['#value'] = $fids;
      $element['#value']['fids'] = $fids;
      $args = [
        '%title' => $element['#title'],
        '@max' => $element['#multiple'],
        '@count' => $total_files,
        '%list' => implode(', ', $removed_names),
      ];
      $message = t('%title can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', $args);
      \Drupal::messenger()
        ->addWarning($message);
    }
    if (!empty($element['#multiple']) && !empty($element['#files']) && count($element['#files']) === $element['#multiple']) {
      $element['upload']['#access'] = FALSE;

      // We can't complete remove the upload button because it breaks
      // the Ajax callback. Instead, we are going visually hide it from
      // browsers with JavaScript disabled.
      $element['upload_button']['#attributes']['style'] = 'display:none';
    }

    // Preview uploaded file.
    if (!empty($element['#file_preview'])) {

      // Get the element's plugin object.

      /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
      $element_manager = \Drupal::service('plugin.manager.webform.element');

      /** @var \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase $element_plugin */
      $element_plugin = $element_manager
        ->getElementInstance($element);

      // Get the webform submission.

      /** @var \Drupal\webform\WebformSubmissionForm $form_object */
      $form_object = $form_state
        ->getFormObject();

      /** @var \Drupal\webform\webform_submission $webform_submission */
      $webform_submission = $form_object
        ->getEntity();

      // Create a temporary preview element with an overridden #format.
      $preview_element = [
        '#format' => $element['#file_preview'],
      ] + $element;

      // Convert '#theme': file_link to a container with a file preview.
      $fids = isset($element['#webform_key']) ? (array) $webform_submission
        ->getElementData($element['#webform_key']) : [];
      foreach ($fids as $delta => $fid) {
        $child_key = 'file_' . $fid;

        // Make sure the child element exists.
        if (!isset($element[$child_key])) {
          continue;
        }

        // Set multiple options delta.
        $options = [
          'delta' => $delta,
        ];
        $file = File::load((string) $fid);

        // Make sure the file entity exists.
        if (!$file) {
          continue;
        }

        // Don't allow anonymous temporary files to be previewed.
        // @see template_preprocess_file_link().
        // @see webform_preprocess_file_link().
        if ($file
          ->isTemporary() && $file
          ->getOwner()
          ->isAnonymous() && strpos($file
          ->getFileUri(), 'private://') === 0) {
          continue;
        }
        $preview = $element_plugin
          ->previewManagedFile($preview_element, $webform_submission, $options);
        if (isset($element[$child_key]['filename'])) {

          // Single file.
          // Covert file link to a container with preview.
          unset($element[$child_key]['filename']['#theme']);
          $element[$child_key]['filename']['#type'] = 'container';
          $element[$child_key]['filename']['#attributes']['class'][] = 'webform-managed-file-preview';
          $element[$child_key]['filename']['#attributes']['class'][] = Html::getClass($element['#type'] . '-preview');
          $element[$child_key]['filename']['preview'] = $preview;
        }
        elseif (isset($element[$child_key]['selected'])) {

          // Multiple files.
          // Convert file link checkbox #title to preview.
          $element[$child_key]['selected']['#wrapper_attributes']['class'][] = 'webform-managed-file-preview-wrapper';
          $element[$child_key]['selected']['#wrapper_attributes']['class'][] = Html::getClass($element['#type'] . '-preview-wrapper');
          $element[$child_key]['selected']['#label_attributes']['class'][] = 'webform-managed-file-preview';
          $element[$child_key]['selected']['#label_attributes']['class'][] = Html::getClass($element['#type'] . '-preview');
          $element[$child_key]['selected']['#title'] = \Drupal::service('renderer')
            ->render($preview);
        }
      }
    }

    // File placeholder.
    if (!empty($element['#file_placeholder']) && (empty($element['#value']) || empty($element['#value']['fids']))) {
      $element['file_placeholder'] = [
        '#type' => 'container',
        '#attributes' => [
          'class' => [
            'webform-managed-file-placeholder',
            Html::getClass($element['#type'] . '-placeholder'),
          ],
        ],
        // Display placeholder before file upload input.
        '#weight' => $element['upload']['#weight'] - 1,
        'content' => WebformHtmlEditor::checkMarkup($element['#file_placeholder']),
      ];
    }
    return $element;
  }

  /**
   * Preview a managed file element upload.
   *
   * @param array $element
   *   An element.
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   A webform submission.
   * @param array $options
   *   An array of options.
   *
   * @return string|array
   *   A preview.
   */
  public function previewManagedFile(array $element, WebformSubmissionInterface $webform_submission, array $options = []) {
    $build = $this
      ->formatHtmlItem($element, $webform_submission, $options);
    return is_array($build) ? $build : [
      '#markup' => $build,
    ];
  }

  /**
   * Form API callback. Consolidate the array of fids for this field into a single fids.
   */
  public static function validateManagedFile(array &$element, FormStateInterface $form_state, &$complete_form) {

    // Issue #3130448: Add custom #required_message support to
    // ManagedFile elements.
    // @see https://www.drupal.org/project/drupal/issues/3130448
    if (!empty($element['#required_error'])) {
      $errors = $form_state
        ->getErrors();
      $key = $element['#webform_key'];
      if (isset($errors[$key]) && $errors[$key] instanceof TranslatableMarkup && $errors[$key]
        ->getUntranslatedString() === '@name field is required.') {
        $errors[$key]
          ->__construct($element['#required_error']);
      }
    }
    if (!empty($element['#files'])) {
      $fids = array_keys($element['#files']);
      if (empty($element['#multiple'])) {
        $form_state
          ->setValueForElement($element, reset($fids));
      }
      else {
        $form_state
          ->setValueForElement($element, $fids);
      }
    }
    else {
      $form_state
        ->setValueForElement($element, NULL);
    }
  }

  /**
   * Form API callback. Validate file upload limit.
   *
   * @see \Drupal\webform\WebformSubmissionForm::validateForm
   */
  public static function validateManagedFileLimit(array &$element, FormStateInterface $form_state, &$complete_form) {

    // Set empty files to NULL and exit.
    if (empty($element['#files'])) {
      return;
    }

    // Only validate file limits for ajax uploads.
    $wrapper_format = \Drupal::request()
      ->get(MainContentViewSubscriber::WRAPPER_FORMAT);
    if (!$wrapper_format || !in_array($wrapper_format, [
      'drupal_ajax',
      'drupal_modal',
      'drupal_dialog',
    ])) {
      return;
    }
    $fids = array_keys($element['#files']);

    // Get WebformSubmissionForm object.
    $form_object = $form_state
      ->getFormObject();
    if (!$form_object instanceof WebformSubmissionForm) {
      return;
    }

    // Skip validation when removing file upload.
    $trigger_element = $form_state
      ->getTriggeringElement();
    $op = (string) $trigger_element['#value'];
    if (in_array($op, [
      (string) t('Remove'),
      (string) t('Remove selected'),
    ])) {
      return;
    }

    // Get file upload limit.

    /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */
    $webform_submission = $form_object
      ->getEntity();
    $file_limit = $webform_submission
      ->getWebform()
      ->getSetting('form_file_limit') ?: \Drupal::config('webform.settings')
      ->get('settings.default_form_file_limit') ?: '';
    $file_limit = Bytes::toInt($file_limit);

    // Track file size across all file upload elements.
    static $total_file_size = 0;

    /** @var \Drupal\file\FileInterface[] $files */
    $files = File::loadMultiple($fids);
    foreach ($files as $file) {
      $total_file_size += (int) $file
        ->getSize();
    }

    // If has access and total file size exceeds file limit then display error.
    if (Element::isVisibleElement($element) && $total_file_size > $file_limit) {
      $t_args = [
        '%quota' => format_size($file_limit),
      ];
      $message = t("This form's file upload quota of %quota has been exceeded. Please remove some files.", $t_args);
      $form_state
        ->setError($element, $message);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);

    // Remove unsupported inline title display.
    unset($form['form']['display_container']['title_display']['#options']['inline']);
    $form['file'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('File settings'),
    ];

    // Warn people about temporary files when saving of results is disabled.

    /** @var \Drupal\webform\WebformInterface $webform */
    $webform = $form_state
      ->getFormObject()
      ->getWebform();
    if ($webform
      ->isResultsDisabled()) {
      $temporary_maximum_age = $this->configFactory
        ->get('system.file')
        ->get('temporary_maximum_age');
      $temporary_interval = \Drupal::service('date.formatter')
        ->formatInterval($temporary_maximum_age);
      $form['file']['file_message'] = [
        '#type' => 'webform_message',
        '#message_message' => '<strong>' . $this
          ->t('Saving of results is disabled.') . '</strong> ' . $this
          ->t('Uploaded files will be temporarily stored on the server and referenced in the database for %interval.', [
          '%interval' => $temporary_interval,
        ]) . ' ' . $this
          ->t('Uploaded files should be attached to an email and/or remote posted to an external server.'),
        '#message_type' => 'warning',
        '#access' => TRUE,
      ];
    }
    $scheme_options = static::getVisibleStreamWrappers();
    $form['file']['uri_scheme'] = [
      '#type' => 'radios',
      '#title' => $this
        ->t('File upload destination'),
      '#description' => $this
        ->t('Select where the final files should be stored. Private file storage has more overhead than public files, but allows restricted access to files within this element.'),
      '#required' => TRUE,
      '#options' => $scheme_options,
    ];

    // Public files security warning.
    if (isset($scheme_options['public'])) {
      $form['file']['uri_public_warning'] = [
        '#type' => 'webform_message',
        '#message_type' => 'warning',
        '#message_message' => $this
          ->t('Public files upload destination is dangerous for webforms that are available to anonymous and/or untrusted users.') . ' ' . $this
          ->t('For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'),
        '#access' => TRUE,
        '#states' => [
          'visible' => [
            ':input[name="properties[uri_scheme]"]' => [
              'value' => 'public',
            ],
          ],
        ],
      ];
    }

    // Private files not set warning.
    if (!isset($scheme_options['private'])) {
      $form['file']['uri_private_warning'] = [
        '#type' => 'webform_message',
        '#message_type' => 'warning',
        '#message_message' => $this
          ->t('Private file system is not set. This must be changed in <a href="https://www.drupal.org/documentation/modules/file">settings.php</a>. For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'),
        '#access' => TRUE,
      ];
    }
    $max_filesize = \Drupal::config('webform.settings')
      ->get('file.default_max_filesize') ?: Environment::getUploadMaxSize();
    $max_filesize = Bytes::toInt($max_filesize);
    $max_filesize = $max_filesize / 1024 / 1024;
    $form['file']['file_help'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('File upload help display'),
      '#description' => $this
        ->t('Determines the placement of the file upload help .'),
      '#options' => [
        '' => $this
          ->t('Description'),
        'help' => $this
          ->t('Help'),
        'more' => $this
          ->t('More'),
        'none' => $this
          ->t('None'),
      ],
    ];
    $form['file']['file_placeholder'] = [
      '#type' => 'webform_html_editor',
      '#title' => $this
        ->t('File upload placeholder'),
      '#description' => $this
        ->t('The placeholder will be shown before a file is uploaded.'),
    ];
    $form['file']['file_preview'] = [
      '#type' => 'select',
      '#title' => $this
        ->t('File upload preview (Authenticated users only)'),
      '#description' => $this
        ->t('Select how the uploaded file previewed.') . '<br/><br/>' . $this
        ->t('Allowing anonymous users to preview files is dangerous.') . '<br/>' . $this
        ->t('For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'),
      '#options' => WebformOptionsHelper::appendValueToText($this
        ->getItemFormats()),
      '#empty_option' => '<' . $this
        ->t('no preview') . '>',
    ];
    $form['file']['max_filesize'] = [
      '#type' => 'number',
      '#title' => $this
        ->t('Maximum file size'),
      '#field_suffix' => $this
        ->t('MB (Max: @filesize MB)', [
        '@filesize' => $max_filesize,
      ]),
      '#placeholder' => $max_filesize,
      '#description' => $this
        ->t('Enter the max file size a user may upload.'),
      '#min' => 1,
      '#max' => $max_filesize,
      '#step' => 'any',
    ];
    $form['file']['file_extensions'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('Allowed file extensions'),
      '#description' => $this
        ->t('Separate extensions with a space or comma and do not include the leading dot.') . '<br/><br/>' . $this
        ->t('Defaults to: %value', [
        '%value' => $this
          ->getDefaultFileExtensions(),
      ]),
      '#maxlength' => 255,
    ];
    $form['file']['file_name'] = [
      '#type' => 'webform_checkbox_value',
      '#title' => $this
        ->t('Rename files'),
      '#description' => $this
        ->t('Rename uploaded files to this tokenized pattern. Do not include the extension here. The actual file extension will be automatically appended to this pattern.'),
      '#element' => [
        '#type' => 'textfield',
        '#title' => $this
          ->t('File name pattern'),
        '#description' => $this
          ->t('File names combined with their full URI can not exceed 255 characters. File names that exceed this limit will be truncated.'),
        '#maxlength' => NULL,
      ],
    ];
    $form['file']['sanitize'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Sanitize file name'),
      '#description' => $this
        ->t('If checked, file name will be transliterated, lower-cased and all special characters converted to dashes (-).'),
      '#return_value' => TRUE,
    ];
    $t_args = [
      '%file_rename' => $form['file']['file_name']['#title'],
      '%sanitization' => $form['file']['sanitize']['#title'],
    ];
    $form['file']['file_name_warning'] = [
      '#type' => 'webform_message',
      '#message_type' => 'warning',
      '#message_message' => $this
        ->t('For security reasons we advise to use %file_rename together with the %sanitization option.', $t_args),
      '#access' => TRUE,
      '#states' => [
        'visible' => [
          ':input[name="properties[file_name][checkbox]"]' => [
            'checked' => TRUE,
          ],
          ':input[name="properties[sanitize]"]' => [
            'checked' => FALSE,
          ],
        ],
      ],
    ];
    $form['file']['multiple'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Multiple'),
      '#description' => $this
        ->t('Check this option if the user should be allowed to upload multiple files.'),
      '#return_value' => TRUE,
    ];

    // Button.
    // @see webform_preprocess_file_managed_file()
    $form['file']['button'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Replace file upload input with an upload button'),
      '#description' => $this
        ->t('If checked the file upload input will be replaced with click-able label styled as button.'),
      '#return_value' => TRUE,
    ];
    $form['file']['button__title'] = [
      '#type' => 'textfield',
      '#title' => $this
        ->t('File upload button title'),
      '#description' => $this
        ->t('Defaults to: %value', [
        '%value' => $this
          ->t('Choose file'),
      ]),
      '#states' => [
        'visible' => [
          ':input[name="properties[button]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];
    $form['file']['button__attributes'] = [
      '#type' => 'webform_element_attributes',
      '#title' => $this
        ->t('File upload button'),
      '#classes' => $this->configFactory
        ->get('webform.settings')
        ->get('settings.button_classes'),
      '#class__description' => $this
        ->t("Apply classes to the button. Button classes default to 'button button-primary'."),
      '#states' => [
        'visible' => [
          ':input[name="properties[button]"]' => [
            'checked' => TRUE,
          ],
        ],
      ],
    ];

    // Hide default value, which is not applicable for file uploads.
    $form['default']['#access'] = FALSE;
    return $form;
  }

  /**
   * Delete a webform submission file's usage and mark it as temporary.
   *
   * Marks unused webform submission files as temporary.
   * In Drupal 8.4.x+ unused webform managed files are no longer
   * marked as temporary.
   *
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   A webform submission.
   * @param null|array $fids
   *   An array of file ids. If NULL all files are deleted.
   */
  public static function deleteFiles(WebformSubmissionInterface $webform_submission, array $fids = NULL) {

    // Make sure the file.module is enabled since this method is called from
    // \Drupal\webform\WebformSubmissionStorage::delete.
    if (!\Drupal::moduleHandler()
      ->moduleExists('file')) {
      return;
    }
    if ($fids === NULL) {
      $fids = \Drupal::database()
        ->select('file_usage', 'fu')
        ->fields('fu', [
        'fid',
      ])
        ->condition('module', 'webform')
        ->condition('type', 'webform_submission')
        ->condition('id', $webform_submission
        ->id())
        ->execute()
        ->fetchCol();
    }
    if (empty($fids)) {
      return;
    }

    /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
    $file_usage = \Drupal::service('file.usage');
    $make_unused_managed_files_temporary = \Drupal::config('webform.settings')
      ->get('file.make_unused_managed_files_temporary');
    $delete_temporary_managed_files = \Drupal::config('webform.settings')
      ->get('file.delete_temporary_managed_files');

    /** @var \Drupal\file\FileInterface[] $files */
    $files = File::loadMultiple($fids);
    foreach ($files as $file) {
      $file_usage
        ->delete($file, 'webform', 'webform_submission', $webform_submission
        ->id());

      // Make unused files temporary.
      if ($make_unused_managed_files_temporary && empty($file_usage
        ->listUsage($file)) && !$file
        ->isTemporary()) {
        $file
          ->setTemporary();
        $file
          ->save();
      }

      // Immediately delete temporary files.
      // This makes sure that the webform submission uploaded directory is
      // empty and can be deleted.
      // @see \Drupal\webform\WebformSubmissionStorage::delete
      if ($delete_temporary_managed_files && $file
        ->isTemporary()) {
        $file
          ->delete();
      }
    }
  }

  /**
   * Add a webform submission file's usage and mark it as permanent.
   *
   * @param array $element
   *   An element.
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   A webform submission.
   * @param array $fids
   *   An array of file ids.
   */
  public function addFiles(array $element, WebformSubmissionInterface $webform_submission, array $fids) {

    // Make sure the file.module is enabled since this method is called from
    // \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::postSave.
    if (!\Drupal::moduleHandler()
      ->moduleExists('file')) {
      return;
    }

    // Make sure there are files that need to added.
    if (empty($fids)) {
      return;
    }

    /** @var \Drupal\file\FileInterface[] $files */
    $files = $this->entityTypeManager
      ->getStorage('file')
      ->loadMultiple($fids);
    foreach ($files as $file) {
      $source_uri = $file
        ->getFileUri();
      $destination_uri = $this
        ->getFileDestinationUri($element, $file, $webform_submission);

      // Save file if there is a new destination URI.
      if ($source_uri !== $destination_uri) {
        $destination_uri = $this->fileSystem
          ->move($source_uri, $destination_uri);
        $file
          ->setFileUri($destination_uri);
        $file
          ->setFileName($this->fileSystem
          ->basename($destination_uri));
        $file
          ->save();
        $this->entityTypeManager
          ->getStorage('file')
          ->resetCache([
          $file
            ->id(),
        ]);
      }

      // Update file usage table.
      // Setting file usage will also make the file's status permanent.
      $this->fileUsage
        ->delete($file, 'webform', 'webform_submission', $webform_submission
        ->id());
      $this->fileUsage
        ->add($file, 'webform', 'webform_submission', $webform_submission
        ->id());
    }
  }

  /**
   * Determine the destination URI where to save an uploaded file.
   *
   * @param array $element
   *   Element whose destination URI is requested.
   * @param \Drupal\file\FileInterface $file
   *   File whose destination URI is requested.
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   Webform submission that contains the file whose destination URI is
   *   requested.
   *
   * @return string
   *   Destination URI under which the file should be saved.
   */
  protected function getFileDestinationUri(array $element, FileInterface $file, WebformSubmissionInterface $webform_submission) {
    $destination_folder = $this->fileSystem
      ->dirname($file
      ->getFileUri());
    $destination_filename = $file
      ->getFilename();
    $destination_extension = pathinfo($destination_filename, PATHINFO_EXTENSION);
    $destination_basename = substr(pathinfo($destination_filename, PATHINFO_BASENAME), 0, -strlen(".{$destination_extension}"));

    // Replace /_sid_/ token with the submission id.
    if (strpos($destination_folder, '/_sid_')) {
      $destination_folder = str_replace('/_sid_', '/' . $webform_submission
        ->id(), $destination_folder);
      $this->fileSystem
        ->prepareDirectory($destination_folder, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
    }

    // Replace tokens in file name.
    if (isset($element['#file_name']) && $element['#file_name']) {
      $destination_basename = $this->tokenManager
        ->replace($element['#file_name'], $webform_submission);
    }

    // Sanitize filename.
    // @see http://stackoverflow.com/questions/2021624/string-sanitizer-for-filename
    // @see \Drupal\webform_attachment\Element\WebformAttachmentBase::getFileName
    if (!empty($element['#sanitize'])) {
      $destination_extension = mb_strtolower($destination_extension);
      $destination_basename = mb_strtolower($destination_basename);
      $destination_basename = $this->transliteration
        ->transliterate($destination_basename, $this->languageManager
        ->getCurrentLanguage()
        ->getId(), '-');
      $destination_basename = preg_replace('([^\\w\\s\\d\\-_~,;:\\[\\]\\(\\].]|[\\.]{2,})', '', $destination_basename);
      $destination_basename = preg_replace('/\\s+/', '-', $destination_basename);
      $destination_basename = trim($destination_basename, '-');

      // If the basename is empty use the element's key, composite key, or type.
      if (empty($destination_basename)) {
        if (isset($element['#webform_key'])) {
          $destination_basename = $element['#webform_key'];
        }
        elseif (isset($element['#webform_composite_key'])) {
          $destination_basename = $element['#webform_composite_key'];
        }
        else {
          $destination_basename = $element['#type'];
        }
      }
    }

    // Make sure $destination_uri does not exceed 250 + _01 character limit for
    // the 'file_managed' table uri column.
    // @see file_validate_name_length()
    // @see https://drupal.stackexchange.com/questions/36760/overcoming-255-character-uri-limit-for-files-managed
    $filename_maxlength = 250;

    // Subtract the destination's folder length.
    $filename_maxlength -= mb_strlen($destination_folder);

    // Subtract the destination's extension length.
    $filename_maxlength -= mb_strlen($destination_extension);

    // Subtract the directory's forward slash and the extension's period.
    $filename_maxlength -= 2;

    // Truncate the base name.
    $destination_basename = mb_strimwidth($destination_basename, 0, $filename_maxlength);
    return $destination_folder . '/' . $destination_basename . '.' . $destination_extension;
  }

  /**
   * Check access for a file associated with a webform submission.
   *
   * @param \Drupal\file\FileInterface $file
   *   A file.
   * @param \Drupal\Core\Session\AccountInterface|null $account
   *   A user account.
   *
   * @return bool|null
   *   Returns NULL if the file is not attached to a webform submission.
   *   Returns FALSE if the user can't access the file.
   *   Returns TRUE if the user can access the file.
   */
  public static function accessFile(FileInterface $file, AccountInterface $account = NULL) {
    if (empty($file)) {
      return NULL;
    }

    /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */
    $file_usage = \Drupal::service('file.usage');
    $usage = $file_usage
      ->listUsage($file);

    // Check for webform submission usage.
    if (!isset($usage['webform']) || !isset($usage['webform']['webform_submission'])) {
      return NULL;
    }

    // Check entity ids.
    $entity_ids = array_keys($usage['webform']['webform_submission']);
    if (empty($entity_ids)) {
      return NULL;
    }

    // Check the first webform submission since files are can only applied to
    // one submission.
    $entity_id = reset($entity_ids);
    $webform_submission = WebformSubmission::load($entity_id);
    if (!$webform_submission) {
      return NULL;
    }
    return $webform_submission
      ->access('view', $account);
  }

  /**
   * Control access to webform submission private file downloads.
   *
   * @param string $uri
   *   The URI of the file.
   *
   * @return mixed
   *   Returns NULL if the file is not attached to a webform submission.
   *   Returns -1 if the user does not have permission to access a webform.
   *   Returns an associative array of headers.
   *
   * @see hook_file_download()
   * @see webform_file_download()
   */
  public static function accessFileDownload($uri) {
    $files = \Drupal::entityTypeManager()
      ->getStorage('file')
      ->loadByProperties([
      'uri' => $uri,
    ]);
    if (empty($files)) {
      return NULL;
    }

    /** @var \Drupal\file\FileInterface $file */
    $file = reset($files);
    $access = static::accessFile($file);
    if ($access === TRUE) {

      // Return file content headers.
      $headers = file_get_content_headers($file);

      /** @var \Drupal\Core\File\FileSystemInterface $file_system */
      $file_system = \Drupal::service('file_system');
      $filename = $file_system
        ->basename($uri);

      // Force blacklisted files to be downloaded instead of opening in the browser.
      if (in_array($headers['Content-Type'], static::$blacklistedMimeTypes)) {
        $headers['Content-Disposition'] = 'attachment; filename="' . Unicode::mimeHeaderEncode($filename) . '"';
      }
      else {
        $headers['Content-Disposition'] = 'inline; filename="' . Unicode::mimeHeaderEncode($filename) . '"';
      }
      return $headers;
    }
    elseif ($access === FALSE) {
      return -1;
    }
    else {
      return NULL;
    }
  }

  /**
   * Get visible stream wrappers.
   *
   * @return array
   *   An associative array of visible stream wrappers keyed by type.
   */
  public static function getVisibleStreamWrappers() {
    $stream_wrappers = \Drupal::service('stream_wrapper_manager')
      ->getNames(StreamWrapperInterface::WRITE_VISIBLE);
    if (!\Drupal::config('webform.settings')
      ->get('file.file_public')) {
      unset($stream_wrappers['public']);
    }
    return $stream_wrappers;
  }

  /**
   * {@inheritdoc}
   */
  public function getTargetType(array $element) {
    return 'file';
  }

  /**
   * {@inheritdoc}
   */
  public function getTargetEntity(array $element, WebformSubmissionInterface $webform_submission, array $options = []) {
    $value = $this
      ->getValue($element, $webform_submission, $options);
    if (empty($value)) {
      return NULL;
    }
    return $this
      ->getFile($element, $value, $options);
  }

  /**
   * {@inheritdoc}
   */
  public function getTargetEntities(array $element, WebformSubmissionInterface $webform_submission, array $options = []) {
    $value = $this
      ->getValue($element, $webform_submission, $options);
    if (empty($value)) {
      return NULL;
    }
    return $this
      ->getFiles($element, $value, $options);
  }

  /**
   * {@inheritdoc}
   */
  public function getAttachments(array $element, WebformSubmissionInterface $webform_submission, array $options = []) {
    $attachments = [];
    $files = $this
      ->getTargetEntities($element, $webform_submission, $options) ?: [];
    foreach ($files as $file) {
      $attachments[] = [
        'filecontent' => file_get_contents($file
          ->getFileUri()),
        'filename' => $file
          ->getFilename(),
        'filemime' => $file
          ->getMimeType(),
        // File URIs that are not supported return FALSE, when this happens
        // still use the file's URI as the file's path.
        'filepath' => \Drupal::service('file_system')
          ->realpath($file
          ->getFileUri()) ?: $file
          ->getFileUri(),
        // URI is used when debugging or resending messages.
        // @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::buildAttachments
        '_fileurl' => file_create_url($file
          ->getFileUri()),
      ];
    }
    return $attachments;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TrustedCallbackInterface::THROW_EXCEPTION constant Untrusted callbacks throw exceptions.
TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION constant Untrusted callbacks trigger silenced E_USER_DEPRECATION errors.
TrustedCallbackInterface::TRIGGER_WARNING constant Untrusted callbacks trigger E_USER_WARNING errors.
WebformCompositeFormElementTrait::preRenderWebformCompositeFormElement public static function Adds form element theming to an element if its title or description is set. 1
WebformElementBase::$configFactory protected property The configuration factory.
WebformElementBase::$currentUser protected property The current user.
WebformElementBase::$defaultProperties protected property An associative array of an element's default properties names and values.
WebformElementBase::$elementInfo protected property A element info manager.
WebformElementBase::$elementManager protected property The webform element manager.
WebformElementBase::$entityTypeManager protected property The entity type manager.
WebformElementBase::$librariesManager protected property The webform libraries manager.
WebformElementBase::$logger protected property A logger instance.
WebformElementBase::$submissionStorage protected property The webform submission storage.
WebformElementBase::$tokenManager protected property The token manager.
WebformElementBase::$translatableProperties protected property An indexed array of an element's translated properties.
WebformElementBase::alterForm public function Alter an element's associated form. Overrides WebformElementInterface::alterForm 1
WebformElementBase::build protected function Build an element as text or HTML. 4
WebformElementBase::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm 3
WebformElementBase::buildConfigurationFormTabs protected function Build configuration form tabs. 1
WebformElementBase::buildExportHeader public function Build an element's export header. Overrides WebformElementInterface::buildExportHeader 4
WebformElementBase::buildExportOptionsForm public function Get an element's export options webform. Overrides WebformElementInterface::buildExportOptionsForm 4
WebformElementBase::buildExportRecord public function Build an element's export row. Overrides WebformElementInterface::buildExportRecord 7
WebformElementBase::buildHtml public function Build an element as HTML element. Overrides WebformElementInterface::buildHtml 2
WebformElementBase::buildText public function Build an element as text element. Overrides WebformElementInterface::buildText 1
WebformElementBase::checkAccessRule protected function Checks an access rule against a user account's roles and id.
WebformElementBase::checkAccessRules public function Check element access (rules). Overrides WebformElementInterface::checkAccessRules
WebformElementBase::defineDefaultBaseProperties protected function Define default base properties used by all elements. 4
WebformElementBase::defineDefaultMultipleProperties protected function Define default multiple properties used by most elements. 1
WebformElementBase::finalize public function Finalize an element to be rendered within a webform. Overrides WebformElementInterface::finalize 1
WebformElementBase::format protected function Format an element's value as HTML or plain text. 2
WebformElementBase::formatCustomItem protected function Format an element's item using custom HTML or plain text. 2
WebformElementBase::formatCustomItems protected function Format an element's items using custom HTML or plain text.
WebformElementBase::formatHtml public function Format an element's value as HTML. Overrides WebformElementInterface::formatHtml 2
WebformElementBase::formatHtmlItems protected function Format an element's items as HTML. 2
WebformElementBase::formatTableColumn public function Format an element's table column value. Overrides WebformElementInterface::formatTableColumn 4
WebformElementBase::formatText public function Format an element's value as plain text. Overrides WebformElementInterface::formatText 2
WebformElementBase::formatTextItems protected function Format an element's items as text. 2
WebformElementBase::getAdminLabel public function Get an element's admin label (#admin_title, #title or #webform_key). Overrides WebformElementInterface::getAdminLabel
WebformElementBase::getConfigurationFormProperties public function Get an associative array of element properties from configuration webform. Overrides WebformElementInterface::getConfigurationFormProperties 3
WebformElementBase::getConfigurationFormProperty protected function Get configuration property value. 1
WebformElementBase::getDefaultBaseProperties Deprecated protected function Get default base properties used by all elements.
WebformElementBase::getDefaultKey public function Gets the element's default key. Overrides WebformElementInterface::getDefaultKey 1
WebformElementBase::getDefaultMultipleProperties Deprecated protected function Get default multiple properties used by most elements.
WebformElementBase::getDefaultProperties public function Get default properties. Overrides WebformElementInterface::getDefaultProperties
WebformElementBase::getDefaultProperty public function Get an element's default property value. Overrides WebformElementInterface::getDefaultProperty
WebformElementBase::getElementInfoDefaultProperty protected function Get a render element's default property.
WebformElementBase::getElementProperty public function Get an element's property value. Overrides WebformElementInterface::getElementProperty
WebformElementBase::getElementSelectorInputsOptions protected function Get an element's (sub)inputs selectors as options. 9
WebformElementBase::getElementSelectorInputValue public function Get an element's (sub)input selector value. Overrides WebformElementInterface::getElementSelectorInputValue 5
WebformElementBase::getElementSelectorSourceValues public function Get an element's selectors source values. Overrides WebformElementInterface::getElementSelectorSourceValues 3
WebformElementBase::getElementStateOptions public function Get an element's supported states as options. Overrides WebformElementInterface::getElementStateOptions 1
WebformElementBase::getExportDefaultOptions public function Get an element's default export options. Overrides WebformElementInterface::getExportDefaultOptions 5
WebformElementBase::getFormElementClassDefinition public function Get the Webform element's form element class definition. Overrides WebformElementInterface::getFormElementClassDefinition
WebformElementBase::getFormInlineContainer protected function Get form--inline container which is used for side-by-side element layout.
WebformElementBase::getInfo public function Retrieves the default properties for the defined element type. Overrides WebformElementInterface::getInfo
WebformElementBase::getItemFormat public function Get element's single value format name by looking for '#format' property, global settings, and finally default settings. Overrides WebformElementInterface::getItemFormat 1
WebformElementBase::getItemsDefaultFormat public function Get an element's default multiple value format name. Overrides WebformElementInterface::getItemsDefaultFormat 2
WebformElementBase::getItemsFormat public function Get element's multiple value format name by looking for '#format' property, global settings, and finally default settings. Overrides WebformElementInterface::getItemsFormat
WebformElementBase::getItemsFormats public function Get an element's available multiple value formats. Overrides WebformElementInterface::getItemsFormats 2
WebformElementBase::getKey public function Get an element's key/name. Overrides WebformElementInterface::getKey
WebformElementBase::getLabel public function Get an element's label (#title or #webform_key). Overrides WebformElementInterface::getLabel
WebformElementBase::getOffCanvasWidth public function Get configuration form's off-canvas width. Overrides WebformElementInterface::getOffCanvasWidth 1
WebformElementBase::getPluginApiLink public function Get link to element's API documentation. Overrides WebformElementInterface::getPluginApiLink
WebformElementBase::getPluginApiUrl public function Get the URL for the element's API documentation. Overrides WebformElementInterface::getPluginApiUrl
WebformElementBase::getPluginCategory public function Gets the category of the plugin instance. Overrides WebformElementInterface::getPluginCategory
WebformElementBase::getPluginDescription public function Gets the description of the plugin instance. Overrides WebformElementInterface::getPluginDescription
WebformElementBase::getPluginLabel public function Gets the label of the plugin instance. Overrides WebformElementInterface::getPluginLabel 3
WebformElementBase::getRawValue public function Get an element's submission raw value. Overrides WebformElementInterface::getRawValue
WebformElementBase::getRelatedTypes public function Get related element types. Overrides WebformElementInterface::getRelatedTypes 6
WebformElementBase::getTableColumn public function Get element's table column(s) settings. Overrides WebformElementInterface::getTableColumn 4
WebformElementBase::getTranslatableProperties public function Get translatable properties. Overrides WebformElementInterface::getTranslatableProperties
WebformElementBase::getTypeName public function Gets the type name (aka id) of the plugin instance with the 'webform_' prefix. Overrides WebformElementInterface::getTypeName
WebformElementBase::getValue public function Get an element's submission value. Overrides WebformElementInterface::getValue 1
WebformElementBase::hasCompositeFormElementWrapper protected function Determine if the element has a composite field wrapper.
WebformElementBase::hasMultipleValues public function Checks if the element value has multiple values. Overrides WebformElementInterface::hasMultipleValues 6
WebformElementBase::hasProperty public function Determine if the element supports a specified property. Overrides WebformElementInterface::hasProperty
WebformElementBase::hasValue public function Determine if an element's has a submission value. Overrides WebformElementInterface::hasValue 2
WebformElementBase::hasWrapper public function Checks if the element has a wrapper. Overrides WebformElementInterface::hasWrapper
WebformElementBase::hiddenElementAfterBuild public static function Webform element #after_build callback.
WebformElementBase::initialize public function Initialize an element to be displayed, rendered, or exported. Overrides WebformElementInterface::initialize 8
WebformElementBase::isComposite public function Checks if the element is a composite element. Overrides WebformElementInterface::isComposite
WebformElementBase::isContainer public function Checks if the element is a container that can contain elements. Overrides WebformElementInterface::isContainer 9
WebformElementBase::isDisabled public function Checks if the element is disabled. Overrides WebformElementInterface::isDisabled
WebformElementBase::isEmptyExcluded public function Checks if an empty element is excluded. Overrides WebformElementInterface::isEmptyExcluded 1
WebformElementBase::isExcluded public function Checks if the element is excluded via webform.settings. Overrides WebformElementInterface::isExcluded
WebformElementBase::isHidden public function Checks if the element is hidden. Overrides WebformElementInterface::isHidden 2
WebformElementBase::isInput public function Checks if the element carries a value. Overrides WebformElementInterface::isInput 11
WebformElementBase::isRoot public function Checks if the element is a root element. Overrides WebformElementInterface::isRoot 3
WebformElementBase::postCreate public function Acts on a webform submission element after it is created. Overrides WebformElementInterface::postCreate 1
WebformElementBase::postLoad public function Acts on loaded webform submission. Overrides WebformElementInterface::postLoad 1
WebformElementBase::preCreate public function Changes the values of an entity before it is created. Overrides WebformElementInterface::preCreate 1
WebformElementBase::preDelete public function 1
WebformElementBase::prefixExportHeader protected function Prefix an element's export header.
WebformElementBase::prepareCompositeFormElement protected function Replace Core's composite #pre_render with Webform's composite #pre_render.
WebformElementBase::prepareElementPreRenderCallbacks protected function Prepare an element's pre render callbacks. 3
WebformElementBase::prepareElementValidateCallbacks protected function Prepare an element's validation callbacks. 8
WebformElementBase::prepareMultipleWrapper protected function Set multiple element wrapper. 1
WebformElementBase::prepareWrapper protected function Set an elements #states and flexbox wrapper. 1
WebformElementBase::preRenderFixFlexboxWrapper public static function Fix flexbox wrapper.
WebformElementBase::preRenderFixStatesWrapper public static function Fix state wrapper.
WebformElementBase::preSave public function Acts on a webform submission element before the presave hook is invoked. Overrides WebformElementInterface::preSave 4
WebformElementBase::preview public function Generate a renderable preview of the element. Overrides WebformElementInterface::preview 37
WebformElementBase::replaceTokens public function Replace tokens for all element properties. Overrides WebformElementInterface::replaceTokens
WebformElementBase::setConfigurationFormDefaultValue protected function Set an element's configuration webform element default value. 2
WebformElementBase::setConfigurationFormDefaultValueRecursive protected function Set configuration webform default values recursively.
WebformElementBase::setElementDefaultCallback protected function Set element's default callback.
WebformElementBase::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm
WebformElementBase::trustedCallbacks public static function Lists the trusted callbacks provided by the implementing class. Overrides TrustedCallbackInterface::trustedCallbacks 1
WebformElementBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm 6
WebformElementBase::validateMinlength public static function Form API callback. Validate element #minlength value.
WebformElementBase::validateMultiple public static function Form API callback. Validate element #multiple > 1 value.
WebformElementBase::validateUnique public static function Form API callback. Validate element #unique value.
WebformElementBase::validateUniqueMultiple public static function Form API callback. Validate element #unique multiple values.
WebformEntityInjectionTrait::$webform protected property The webform.
WebformEntityInjectionTrait::$webformSubmission protected property The webform submission.
WebformEntityInjectionTrait::getWebform public function Get the webform that this handler is attached to.
WebformEntityInjectionTrait::getWebformSubmission public function Set webform and webform submission entity.
WebformEntityInjectionTrait::resetEntities public function Reset webform and webform submission entity.
WebformEntityInjectionTrait::setEntities public function
WebformEntityInjectionTrait::setWebform public function Set the webform that this is handler is attached to.
WebformEntityInjectionTrait::setWebformSubmission public function Get the webform submission that this handler is handling.
WebformManagedFileBase::$blacklistedMimeTypes protected static property List of blacklisted mime types that must be downloaded.
WebformManagedFileBase::$fileSystem protected property The 'file_system' service.
WebformManagedFileBase::$fileUsage protected property The 'file.usage' service.
WebformManagedFileBase::$languageManager protected property The 'language_manager' service.
WebformManagedFileBase::$transliteration protected property The 'transliteration' service.
WebformManagedFileBase::accessFile public static function Check access for a file associated with a webform submission.
WebformManagedFileBase::accessFileDownload public static function Control access to webform submission private file downloads.
WebformManagedFileBase::addFiles public function Add a webform submission file's usage and mark it as permanent.
WebformManagedFileBase::create public static function Creates an instance of the plugin. Overrides WebformElementBase::create
WebformManagedFileBase::defineDefaultProperties protected function Define an element's default properties. Overrides WebformElementBase::defineDefaultProperties 1
WebformManagedFileBase::defineTranslatableProperties protected function Define an element's translatable properties. Overrides WebformElementBase::defineTranslatableProperties
WebformManagedFileBase::deleteFiles public static function Delete a webform submission file's usage and mark it as temporary.
WebformManagedFileBase::displayDisabledWarning public function Display element disabled warning. Overrides WebformElementBase::displayDisabledWarning
WebformManagedFileBase::form public function Gets the actual configuration webform array to be built. Overrides WebformElementBase::form 1
WebformManagedFileBase::formatHtmlItem protected function Format an element's value as HTML. Overrides WebformElementBase::formatHtmlItem 1
WebformManagedFileBase::formatTextItem protected function Format an element's value as text. Overrides WebformElementBase::formatTextItem
WebformManagedFileBase::getAttachments public function Get email attachments. Overrides WebformElementAttachmentInterface::getAttachments 1
WebformManagedFileBase::getDefaultFileExtensions protected function Get the default allowed file extensions.
WebformManagedFileBase::getElementSelectorOptions public function Get an element's selectors as options. Overrides WebformElementBase::getElementSelectorOptions
WebformManagedFileBase::getFile protected function Get file.
WebformManagedFileBase::getFileDestinationUri protected function Determine the destination URI where to save an uploaded file.
WebformManagedFileBase::getFileExtensions protected function Get the allowed file extensions for an element.
WebformManagedFileBase::getFiles protected function Get files.
WebformManagedFileBase::getItemDefaultFormat public function Get an element's default single value format name. Overrides WebformElementBase::getItemDefaultFormat 1
WebformManagedFileBase::getItemFormats public function Get an element's available single value formats. Overrides WebformElementBase::getItemFormats 3
WebformManagedFileBase::getMaxFileSize protected function Get max file size for an element.
WebformManagedFileBase::getTargetEntities public function Get referenced entities. Overrides WebformElementEntityReferenceInterface::getTargetEntities
WebformManagedFileBase::getTargetEntity public function Get referenced entity. Overrides WebformElementEntityReferenceInterface::getTargetEntity
WebformManagedFileBase::getTargetType public function Get referenced entity type. Overrides WebformElementEntityReferenceInterface::getTargetType
WebformManagedFileBase::getTestValues public function Get test values for an element. Overrides WebformElementBase::getTestValues
WebformManagedFileBase::getUploadLocation protected function Get file upload location.
WebformManagedFileBase::getUriScheme protected function Get file upload URI scheme.
WebformManagedFileBase::getVisibleStreamWrappers public static function Get visible stream wrappers.
WebformManagedFileBase::hasManagedFiles public function Determine if the element is or includes a managed_file upload element. Overrides WebformElementBase::hasManagedFiles
WebformManagedFileBase::hasMultipleWrapper public function Checks if the element uses the 'webform_multiple' element. Overrides WebformElementBase::hasMultipleWrapper
WebformManagedFileBase::isEnabled public function Checks if the element is enabled. Overrides WebformElementBase::isEnabled
WebformManagedFileBase::isMultiline public function Checks if the element value could contain multiple lines. Overrides WebformElementBase::isMultiline
WebformManagedFileBase::postDelete public function Delete any additional value associated with an element. Overrides WebformElementBase::postDelete
WebformManagedFileBase::postSave public function Acts on a saved webform submission element before the insert or update hook is invoked. Overrides WebformElementBase::postSave
WebformManagedFileBase::prepare public function Prepare an element to be rendered within a webform. Overrides WebformElementBase::prepare 1
WebformManagedFileBase::previewManagedFile public function Preview a managed file element upload.
WebformManagedFileBase::processManagedFile public static function Process callback for managed file elements.
WebformManagedFileBase::setDefaultValue public function Set an element's default value using saved data. Overrides WebformElementBase::setDefaultValue
WebformManagedFileBase::supportsMultipleValues public function Checks if the element supports multiple values. Overrides WebformElementBase::supportsMultipleValues
WebformManagedFileBase::validateManagedFile public static function Form API callback. Consolidate the array of fids for this field into a single fids.
WebformManagedFileBase::validateManagedFileLimit public static function Form API callback. Validate file upload limit.
WebformManagedFileBase::__construct public function WebformManagedFileBase constructor. Overrides WebformElementBase::__construct