You are here

EntityListTrait.php in Formatter Suite 8

File

src/Plugin/Field/FieldFormatter/EntityListTrait.php
View source
<?php

namespace Drupal\formatter_suite\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\formatter_suite\Branding;

/**
 * Formats multiple fields as a list.
 *
 * For multi-value fields, Drupal invokes the formatter with the list of
 * field values and each one is formatted on its own. They are then added
 * to the page and separated only by spaces.
 *
 * This formatter presents a list of values as a group and formats them
 * as one of:
 *   - Single line list using <div> around the group, and <span> for each value.
 *   - Numbered list using <ol> around the group, and <li> for each value.
 *   - Bulleted list using <ul> around the group, and <li> for each value.
 *   - Non-bulleted list using <div> around the group, and <div> for each value.
 *
 * Optional list separator text is added between each value, such as a comma.
 *
 * @ingroup formatter_suite
 */
trait EntityListTrait {

  /*---------------------------------------------------------------------
   *
   * Configuration.
   *
   *---------------------------------------------------------------------*/

  /**
   * Returns an array of list styles.
   *
   * @return string[]
   *   Returns an associative array with internal names as keys and
   *   human-readable translated names as values.
   */
  protected static function getListStyles() {
    return [
      'span' => t('Single line list'),
      'ol' => t('Numbered list'),
      'ul' => t('Bulleted list'),
      'div' => t('Non-bulleted list'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return array_merge([
      'listStyle' => 'span',
      'listSeparator' => ',',
    ], parent::defaultSettings());
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = parent::settingsSummary();
    $this
      ->sanitizeListSettings();
    $listStyles = $this
      ->getListStyles();
    $listStyle = $this
      ->getSetting('listStyle');
    $listSeparator = $this
      ->getSetting('listSeparator');

    // Summarize.
    $text = $listStyles[$listStyle];
    if ($listStyle === 'span' && empty($listSeparator) === FALSE) {
      $text .= $this
        ->t(', with separator');
    }
    $summary[] = $text;
    return $summary;
  }

  /*---------------------------------------------------------------------
   *
   * Settings form.
   *
   *---------------------------------------------------------------------*/

  /**
   * Returns a brief description of the formatter.
   *
   * @return string
   *   Returns a brief translated description of the formatter.
   */
  protected function getDescription() {
    return $this
      ->t('Format multi-value fields as a list.');
  }

  /**
   * Post-processes the settings form after it has been built.
   *
   * This may be used by formatters that use this trait to adjust the
   * form after it has been updated with list features.
   *
   * @param array $elements
   *   The form's elements.
   */
  protected function postProcessSettingsForm(array $elements) {
    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $formState) {

    // Get the parent's form.
    $this
      ->sanitizeListSettings();
    $elements = parent::settingsForm($form, $formState);

    // Below, some checkboxes and select choices show/hide other form
    // elements. We use Drupal's obscure 'states' feature, which adds
    // Javascript to elements to auto show/hide based upon a set of
    // simple conditions.
    //
    // Those conditions need to reference the form elements to check
    // (e.g. a checkbox), but the element ID and name are automatically
    // generated by the parent form. We cannot set them, or predict them,
    // so we cannot use them. We could use a class, but this form may be
    // shown multiple times on the same page, so a simple class would not be
    // unique. Instead, we create classes for this form only by adding a
    // random number marker to the end of the class name.
    $marker = rand();

    // Add branding.
    $elements = Branding::addFieldFormatterBranding($elements);
    $elements['#attached']['library'][] = 'formatter_suite/formatter_suite.fieldformatter';

    // Add description.
    //
    // Use a large negative weight to insure it comes first.
    $elements['description'] = [
      '#type' => 'html_tag',
      '#tag' => 'div',
      '#value' => $this
        ->getDescription(),
      '#weight' => -1000,
      '#attributes' => [
        'class' => [
          'formatter_suite-settings-description',
        ],
      ],
    ];
    $weight = 100;

    // Prompt for each setting.
    $elements['sectionBreak'] = [
      '#markup' => '<div class="formatter_suite-section-break"></div>',
      '#weight' => $weight++,
    ];
    $elements['listStyle'] = [
      '#title' => $this
        ->t('List style'),
      '#type' => 'select',
      '#options' => $this
        ->getListStyles(),
      '#default_value' => $this
        ->getSetting('listStyle'),
      '#weight' => $weight++,
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-list-style',
        ],
      ],
      '#attributes' => [
        'class' => [
          'listStyle-' . $marker,
        ],
      ],
    ];
    $elements['listSeparator'] = [
      '#title' => $this
        ->t('Separator'),
      '#type' => 'textfield',
      '#size' => 10,
      '#default_value' => $this
        ->getSetting('listSeparator'),
      '#weight' => $weight++,
      '#attributes' => [
        'autocomplete' => 'off',
        'autocapitalize' => 'none',
        'spellcheck' => 'false',
        'autocorrect' => 'off',
      ],
      '#wrapper_attributes' => [
        'class' => [
          'formatter_suite-list-separator',
        ],
      ],
      '#states' => [
        'visible' => [
          '.listStyle-' . $marker => [
            'value' => 'span',
          ],
        ],
      ],
    ];
    return $this
      ->postProcessSettingsForm($elements);
  }

  /**
   * Check current settings and insure they make sense.
   */
  protected function sanitizeListSettings() {
    $listStyle = $this
      ->getSetting('listStyle');
    $listStyles = $this
      ->getListStyles();
    $defaults = $this
      ->defaultSettings();
    if (empty($listStyle) === TRUE || isset($listStyles[$listStyle]) === FALSE) {
      $listStyle = $defaults['listStyle'];
      $this
        ->setSetting('listStyle', $listStyle);
    }
  }

  /*---------------------------------------------------------------------
   *
   * View.
   *
   *---------------------------------------------------------------------*/

  /**
   * {@inheritdoc}
   */
  public function view(FieldItemListInterface $items, $langcode = NULL) {
    if ($items
      ->isEmpty() === TRUE) {
      return [];
    }

    // Let the parent class set up a render array for all items.
    $this
      ->sanitizeListSettings();
    $build = parent::view($items, $langcode);
    if (empty($build) === TRUE) {
      return [];
    }

    // Replace the 'field' theme with ours, which supports lists.
    $build['#theme'] = 'formatter_suite_field_list';

    // Set the list style.
    $build['#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 directly to t()
    // or let a TWIG template use {{ }}, both of which will process
    // the text and corrupt any entered HTML or HTML entities.
    //
    // We therefore use an Xss filter to remove any egreggious HTML
    // (such as scripts), and then FormattableMarkup() to mark the
    // resulting text as safe.
    $listSeparator = Xss::filterAdmin($this
      ->getSetting('listSeparator'));
    $build['#list_separator'] = new FormattableMarkup($listSeparator, []);
    return $build;
  }

}

Traits

Namesort descending Description
EntityListTrait Formats multiple fields as a list.