GetDKAN/dkan

View on GitHub
modules/json_form_widget/src/Element/UploadOrLink.php

Summary

Maintainability
A
0 mins
Test Coverage
F
0%
<?php

namespace Drupal\json_form_widget\Element;

use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\file\Element\ManagedFile;
use Drupal\file\Entity\File;
use Drupal\json_form_widget\Entity\RemoteFile;

/**
 * Provides a new Element for uploading or linking to files.
 *
 * @FormElement("upload_or_link")
 * @codeCoverageIgnore
 */
class UploadOrLink extends ManagedFile {

  /**
   * File URL item type: file upload.
   */
  const TYPE_UPLOAD = 'upload';

  /**
   * File URL item type: URL to remote file..
   */
  const TYPE_REMOTE = 'remote';

  /**
   * Inherited.
   *
   * {@inheritDoc}
   *
   * @codeCoverageIgnore
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#input' => TRUE,
      '#process' => [[$class, 'processManagedFile']],
      '#element_validate' => [[$class, 'validateManagedFile']],
      '#pre_render' => [[$class, 'preRenderManagedFile']],
      '#theme' => 'file_managed_file',
      '#theme_wrappers' => ['form_element'],
      '#progress_message' => NULL,
      '#upload_validators' => [],
      '#upload_location' => NULL,
      '#size' => 22,
      '#multiple' => FALSE,
      '#extended' => FALSE,
      '#attached' => [
        'library' => ['file/drupal.file'],
      ],
      '#accept' => NULL,
    ];
  }

  /**
   * Render API callback: Expands the managed_file element type.
   *
   * Expands file_managed type to include option for links to remote files/urls.
   */
  public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
    $element['#uri'] = static::getDefaultUri($element, $form_state);
    // Build element.
    $element = parent::processManagedFile($element, $form_state, $complete_form);
    $file_url_type = static::getUrlType($element);
    $element = static::unsetFilesWhenRemoving($form_state, $element);

    $file_url_remote = static::setRemoteFile($element, $form_state);
    $file_url_remote_is_valid = isset($file_url_remote) && UrlHelper::isValid($file_url_remote, TRUE);
    $is_remote = $file_url_remote_is_valid && $file_url_type == static::TYPE_REMOTE;
    if ($is_remote) {
      $element = static::loadRemoteFile($element, $file_url_remote);
    }

    $access_file_url_elements = (empty($element['#files']) && !$file_url_remote_is_valid) || !$file_url_type;
    $element['#uri'] = !isset($element['#uri']) ? $file_url_remote : $element['#uri'];

    $file_url_type_selector = ':input[name="' . $element['#name'] . '[file_url_type]"]';
    $remote_visible = [$file_url_type_selector => ['value' => static::TYPE_REMOTE]];

    $element['file_url_type'] = static::getFileUrlTypeElement($file_url_type, $access_file_url_elements);
    $element['file_url_remote'] = static::getFileUrlRemoteElement($file_url_remote, $access_file_url_elements, $remote_visible);
    $element = static::overrideUploadSubfield($element, $file_url_type_selector);

    return $element;
  }

  /**
   * Helper function to set file_url_remote variable.
   */
  private static function setRemoteFile($element, $form_state) {
    $file_url_remote = '';
    if (isset($element['#value']['file_url_remote'])) {
      $file_url_remote = $element['#value']['file_url_remote'];
    }
    elseif (isset($element['#uri'])) {
      $file_url_remote = $element['#uri'];
    }

    return static::setPreviousFormFiles($element, $form_state, $file_url_remote);
  }

  /**
   * Helper function to previous remote files.
   */
  private static function setPreviousFormFiles($element, $form_state, $file_url_remote = NULL) {
    $previous_files = $form_state->get('previous_files') ?? [];
    $element_key = $element['#array_parents'][6];

    if ($file_url_remote == NULL && isset($previous_files[$element_key])) {
      $file_url_remote = $previous_files[$element_key];
    }
    elseif ($file_url_remote != NULL && !isset($previous_files[$element_key])) {
      $previous_files[$element_key] = $file_url_remote;
    }
    elseif ($file_url_remote != NULL && isset($previous_files[$element_key]) && $previous_files[$element_key] != $file_url_remote) {
      unset($previous_files[$element_key]);
      $previous_files[$element_key] = $file_url_remote;
    }
    $form_state->set('previous_files', $previous_files);

    return $file_url_remote != NULL ? $file_url_remote : '';
  }

  /**
   * Return file_url_type element.
   */
  private static function getFileUrlTypeElement($file_url_type, $access_file_url_elements) {
    return [
      '#type' => 'radios',
      '#options' => [
        static::TYPE_UPLOAD => t('Upload Data File'),
        static::TYPE_REMOTE => t('Link to Data File'),
      ],
      '#default_value' => $file_url_type,
      '#prefix' => '<div class="container-inline">',
      '#suffix' => '</div>',
      '#access' => $access_file_url_elements,
      '#weight' => 5,
    ];
  }

  /**
   * Return file_url_remote element.
   */
  private static function getFileUrlRemoteElement($file_url_remote, $access_file_url_elements, $remote_visible) {
    return [
      '#type' => 'url',
      '#title' => t('Remote URL'),
      '#title_display' => 'invisible',
      '#description' => t('This must be an external URL such as <em>http://example.com</em>.'),
      '#default_value' => $file_url_remote,
      // Only show this field when the 'remote' radio is selected.
      '#states' => ['visible' => $remote_visible],
      '#access' => $access_file_url_elements,
      '#weight' => 15,
    ];
  }

  /**
   * Helper function to return element without files when removing.
   */
  private static function unsetFilesWhenRemoving($form_state, $element) {
    $triggering_element = $form_state->getTriggeringElement();
    $previous_files = $form_state->get('previous_files') ?? [];
    $element_key = $element['#array_parents'][6];
    $button = is_array($triggering_element) ? array_pop($triggering_element['#array_parents']) : '';
    $count = $form_state->get('json_form_widget_info')['distribution']['count'];

    if ($button == 'remove_button') {
      unset($element['#files']);
      unset($previous_files[$element_key]);
      $element = static::unsetFids($element);
    }

    if ($button == 'remove' && isset($previous_files[$element_key]) &&  $element_key == $count) {
      $previous_files = static::unsetPreviousFiles($previous_files, $element_key);
    }

    $form_state->set('previous_files', $previous_files);
    return $element;
  }

  /**
   * Helper function to unset previous_files.
   */
  private static function unsetPreviousFiles($previous_files, $element_key) {
    if (isset($previous_files[$element_key])) {
      unset($previous_files[$element_key]);
    }
    return $previous_files;
  }

  /**
   * Helper function to unsetFids.
   */
  private static function unsetFids($element) {
    foreach ($element['#value']['fids'] as $fid) {
      unset($element['file_' . $fid]);
    }
    $element['#value']['fids'] = [];
    return $element;
  }

  /**
   * Load remote file into element.
   */
  private static function loadRemoteFile($element, $file_url_remote) {
    $remote_file = RemoteFile::load($file_url_remote);
    $element['#files'] = [$file_url_remote => $remote_file];
    $file_link = [
      '#type' => 'link',
      '#title' => $remote_file->getFileUri(),
      '#url' => Url::fromUri($remote_file->getFileUri()),
    ];
    $element["file_{$file_url_remote}"]['filename'] = $file_link + ['#weight' => -10];
    $element['#value']['file_url_type'] = static::TYPE_REMOTE;
    $element['#value']['file_url_remote'] = $file_url_remote;
    $element['#value']['upload'] = NULL;
    return $element;
  }

  /**
   * Helper function to override upload subelement.
   */
  private static function overrideUploadSubfield($element, $file_url_type_selector) {
    // Only show this field when the 'upload' radio is selected. Add also a
    // wrapper around file upload, so states knows what field to target.
    $selector_fids = ':input[name="' . $element['#name'] . '[fids]"]';
    $upload_visible = [
      [$selector_fids => ['empty' => FALSE]],
      'or',
      [$file_url_type_selector => ['value' => static::TYPE_UPLOAD]],
    ];
    $element['upload']['#states']['visible'] = $upload_visible;
    $element['upload']['#theme_wrappers'][] = 'form_element';
    $element['upload']['#description'] = [
      '#theme' => 'file_upload_help',
      '#description' => '',
      '#upload_validators' => $element['#upload_validators'],
    ];
    $element['upload']['#weight'] = 10;

    // Make sure the upload button is the last in form element.
    $element['upload_button']['#weight'] = 20;
    return $element;
  }

  /**
   * Render API callback: Validates the upload_or_link element.
   */
  public static function validateManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
    $uri = static::getDefaultUri($element, $form_state);
    if (static::getUrlType($element) === static::TYPE_UPLOAD) {
      parent::validateManagedFile($element, $form_state, $complete_form);
      if ($element_parents = $form_state->get('upload_or_link_element')) {
        $element_parents[] = $element['#parents'];
        $form_state->set('upload_or_link_element', $element_parents);
      }
      else {
        $form_state->set('upload_or_link_element', [$element['#parents']]);
      }
    }
    $form_state->setValueForElement($element, $uri);
  }

  /**
   * Helper function for getting the url type.
   */
  protected static function getUrlType($element) {
    $type = static::TYPE_REMOTE;
    if (isset($element['#value']['file_url_type'])) {
      $type = $element['#value']['file_url_type'];
    }
    elseif (!empty($element['#value']['fids'])) {
      $type = static::TYPE_UPLOAD;
    }
    return $type;
  }

  /**
   * Helper function for getting the default URI.
   */
  protected static function getDefaultUri($element, FormStateInterface $form_state) {
    $triggering = $form_state->getTriggeringElement();
    $button = is_array($triggering) ? array_pop($triggering['#array_parents']) : '';
    if ($button == 'remove_button') {
      return '';
    }

    if (static::getUrlType($element) == static::TYPE_UPLOAD) {
      return static::getLocalFileUrl($element);
    }
    elseif (!empty($element['#value']['file_url_remote'])) {
      $uri = $element['#value']['file_url_remote'];
      return $uri;
    }

    return $element['#uri'] ?? NULL;
  }

  /**
   * Helper function to get the URL of a local file.
   */
  protected static function getLocalFileUrl($element) {
    $fids = $element['#value']['fids'];
    foreach ($fids as $fid) {
      if ($file = File::load($fid)) {
        $uri = $file->getFileUri();
        return \Drupal::service('file_url_generator')->generateAbsoluteString($uri);
      }
    }
    return $element['#uri'];
  }

  /**
   * Render API callback: Hides display of the upload or remove controls.
   *
   * Upload controls are hidden when a file is already uploaded. Remove controls
   * are hidden when there is no file attached. Controls are hidden here instead
   * of in \Drupal\file\Element\ManagedFile::processManagedFile(), because
   * #access for these buttons depends on the managed_file element's #value. See
   * the documentation of \Drupal\Core\Form\FormBuilderInterface::doBuildForm()
   * for more detailed information about the relationship between #process,
   * #value, and #access.
   *
   * Because #access is set here, it affects display only and does not prevent
   * JavaScript or other untrusted code from submitting the form as though
   * access were enabled. The form processing functions for these elements
   * should not assume that the buttons can't be "clicked" just because they are
   * not displayed.
   *
   * @see \Drupal\file\Element\ManagedFile::processManagedFile()
   * @see \Drupal\Core\Form\FormBuilderInterface::doBuildForm()
   */
  public static function preRenderManagedFile($element) {
    // If we already have a file, we don't want to show the upload controls.
    if (!empty($element['#value']['fids'])) {
      if (!$element['#multiple']) {
        $element['upload']['#access'] = FALSE;
        $element['upload_button']['#access'] = FALSE;
      }
    }
    // If we don't already have a file, there is nothing to remove.
    elseif (empty($element['#value']['file_url_remote'])) {
      $element['remove_button']['#access'] = FALSE;
    }
    return $element;
  }

}