You are here

public function GeneralFileLinkFormatter::viewElements in Formatter Suite 8

Builds a renderable array for a field value.

Parameters

\Drupal\Core\Field\FieldItemListInterface $items: The field values to be rendered.

string $langcode: The language that should be used to render the field.

Return value

array A renderable array for $items, as an array of child elements keyed by consecutive numeric indexes starting from 0.

Overrides FormatterInterface::viewElements

File

src/Plugin/Field/FieldFormatter/GeneralFileLinkFormatter.php, line 701

Class

GeneralFileLinkFormatter
Formats a file field as one or more links.

Namespace

Drupal\formatter_suite\Plugin\Field\FieldFormatter

Code

public function viewElements(FieldItemListInterface $items, $langcode) {
  if ($items
    ->isEmpty() === TRUE) {
    return [];
  }
  $this
    ->sanitizeSettings();
  $showSize = $this
    ->getSetting('showSize');
  $showIcon = $this
    ->getSetting('showIcon');
  $showLink = $this
    ->getSetting('showLink');
  $entity = $items
    ->getEntity();

  // Prepare custom classes, if any.
  $addClasses = $this
    ->getSetting('classes');
  if (empty($addClasses) === TRUE) {
    $addClasses = [];
  }
  else {

    // Security: Class names are entered by an administrator.
    // They may not include anything but CSS-compatible words, and
    // certainly no HTML.
    //
    // Here, the class text is stripped of HTML tags as a start.
    // A regex tosses unacceptable characters in a CSS class name.
    $addClasses = strip_tags($addClasses);
    $addClasses = mb_ereg_replace('[^_a-zA-Z0-9- ]', '', $addClasses);
    if ($addClasses === FALSE) {
      $addClasses = [];
    }
    $addClasses = explode(' ', $addClasses);
  }
  $elements = [];
  $storage = $this->entityTypeManager
    ->getStorage('file');
  foreach ($items as $delta => $item) {

    //
    // Get the file, URL, size, and MIME type.
    // ---------------------------------------
    // Get the File entity indicated by the file field. If the entity
    // fails to load, something has become disconnected.
    $fileId = $item->target_id;
    $file = $storage
      ->load($fileId);
    if ($file === NULL) {
      $url = Url::fromRoute('<none>');
      $fileSize = 0;
      $mime = 'application/octet-stream';
      $filename = $this
        ->t('(Missing file @id)', [
        '@id' => $fileId,
      ]);
    }
    else {
      $url = Url::fromUri(file_create_url($file
        ->getFileUri()));
      $fileSize = $file
        ->getSize();
      $mime = $file
        ->getMimeType();
      $filename = $file
        ->getFilename();
    }

    // Format the file size.
    // - Kilo/mega/giga with a "K" unit of 1000.
    // - Use abbreviatios, not full words.
    // - Use 3 digits.
    if ($showSize === TRUE) {
      $fileSizeMarkup = ' (' . Utilities::formatBytes($fileSize, 1000, FALSE, 3) . ')';
    }
    else {
      $fileSizeMarkup = '';
    }

    // Add MIME-type based icon to classes.
    $classes = $addClasses;
    if ($showIcon === TRUE) {
      $classes[] = 'file';
      $classes[] = 'file--mime-' . strtr($mime, [
        '/' => '-',
        '.' => '-',
      ]);
      $classes[] = 'file--' . file_icon_class($mime);
    }

    //
    // Get the link title.
    // -------------------
    // Use custom text, text from the link, or the file name. If custom
    // text is selected, but there isn't any, fall through to text from
    // the link. If there is none of that, fall through to the file name.
    switch ($this
      ->getSetting('titleStyle')) {
      case 'text_custom':

        // Security: A custom title is entered by an administrator.
        // It may legitimately include HTML entities and minor HTML, but
        // it should not include dangerous HTML.
        //
        // Because it may include HTML, we cannot pass it directly as
        // the '#title' on a link, which will escape the HTML.
        //
        // We therefore use an Xss admin filter to remove any egreggious
        // HTML (such as scripts and styles), and then FormattableMarkup()
        // to mark the resulting text as safe.
        $title = Xss::filterAdmin($this
          ->getSetting('titleCustomText'));
        if (empty($title) === FALSE) {
          $title .= $fileSizeMarkup;
          $title = new FormattableMarkup($title, []);
          break;
        }

      // Fall-through and use the field's description as the title text.
      default:
      case 'text_from_link':

        // Security: Description text from the link field is entered by
        // a user. It may legitimately include HTML entities and minor
        // HTML, but it should not include dangerous HTML.
        //
        // Because it may include HTML, we cannot pass it directly as the
        // '#title' on a link, which will escape the HTML.
        //
        // We therefore use an Xss strict filter to remove any egreggious
        // HTML (such as scripts and styles, broken HTML, etc), and then
        // FormattableMarkup() to mark the resulting text as safe.
        $title = Xss::filter($item->description);
        if (empty($title) === FALSE) {
          $title .= $fileSizeMarkup;
          $title = new FormattableMarkup($title, []);
          break;
        }

      // Fall-through and use the filename as the title text.
      case 'text_from_filename':

        // Security: Filenames from the File entity are entered by a
        // user. They may include any characters that the underlying
        // file syste supports, which includes HTML '<', '>', etc.
        //
        // Passing this text as '#title' on a link will automatically
        // escape special characters and insure they do not cause harm.
        // We therefore don't need to do any special filtering here.
        $title = (string) $filename;
        $title .= $fileSizeMarkup;
        break;
    }

    //
    // Build the link.
    // ---------------
    // If the link is disabled, show the title text within a <span>.
    // Otherwise, build a URL and create a link.
    if ($showLink === FALSE) {
      $elements[$delta] = [
        '#type' => 'html_tag',
        '#tag' => 'span',
        '#value' => $title,
        '#attached' => [
          'library' => [
            'file/drupal.file',
          ],
        ],
        '#attributes' => [
          'class' => $classes,
        ],
        '#cache' => [
          'tags' => $entity
            ->getCacheTags(),
        ],
      ];
    }
    else {
      $rel = '';
      $target = '';
      $download = FALSE;
      $urlOptions = [];
      switch ($this
        ->getSetting('openLinkIn')) {
        case '_self':
          $target = '_self';
          break;
        case '_blank':
          $target = '_blank';
          break;
        case 'download':
          $download = TRUE;
          break;
      }
      $topic = $this
        ->getSetting('linkTopic');
      if ($topic !== 'any') {
        $rel .= $topic;
      }
      if (empty($rel) === FALSE) {
        $urlOptions['attributes']['rel'] = $rel;
      }
      if (empty($target) === FALSE) {
        $urlOptions['attributes']['target'] = $target;
      }
      if ($download === TRUE) {
        $urlOptions['attributes']['download'] = '';
      }
      $url
        ->setOptions($urlOptions);
      $elements[$delta] = [
        '#type' => 'link',
        '#title' => $title,
        '#options' => $url
          ->getOptions(),
        '#url' => $url,
        '#attached' => [
          'library' => [
            'file/drupal.file',
          ],
        ],
        '#attributes' => [
          'class' => $classes,
          'type' => $mime . '; length=' . $fileSize,
          'title' => $filename,
        ],
        '#cache' => [
          'tags' => $entity
            ->getCacheTags(),
        ],
      ];
      if (empty($items[$delta]->_attributes) === FALSE) {

        // There are item attributes. Add them to the link's options.
        $elements[$delta]['#options'] += [
          'attributes' => $items[$delta]->_attributes,
        ];

        // And remove them from the item since they are now included
        // on the link.
        unset($items[$delta]->_attributes);
      }
    }
  }
  if (empty($elements) === TRUE) {
    return [];
  }

  //
  // Add multi-value field processing.
  // ---------------------------------
  // If the field has multiple values, redirect to a theme and pass
  // the list style and separator.
  $isMultiple = $this->fieldDefinition
    ->getFieldStorageDefinition()
    ->isMultiple();
  if ($isMultiple === TRUE) {

    // Replace the 'field' theme with one that supports lists.
    $elements['#theme'] = 'formatter_suite_field_list';

    // Set the list style.
    $elements['#list_style'] = $this
      ->getSetting('listStyle');

    // Set the list separator.
    //
    // Security: The list separator is entered by an administrator.
    // It may legitimately include HTML entities and minor HTML, but
    // it should not include dangerous HTML.
    //
    // Because it may include HTML, we cannot pass it as-is and let a TWIG
    // template use {{ }}, which will process the text and corrupt any
    // entered HTML or HTML entities.
    //
    // We therefore use an Xss admin filter to remove any egreggious HTML
    // (such as scripts and styles), and then FormattableMarkup() to mark the
    // resulting text as safe.
    $listSeparator = Xss::filterAdmin($this
      ->getSetting('listSeparator'));
    $elements['#list_separator'] = new FormattableMarkup($listSeparator, []);
  }
  return $elements;
}