smart_trim-8.x-1.3/src/Plugin/Field/FieldFormatter/SmartTrimFormatter.php

src/Plugin/Field/FieldFormatter/SmartTrimFormatter.php
<?php

namespace Drupal\smart_trim\Plugin\Field\FieldFormatter;

use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Utility\Token;
use Drupal\smart_trim\TruncateHTML;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the 'smart_trim' formatter.
 *
 * @FieldFormatter(
 *   id = "smart_trim",
 *   label = @Translation("Smart trimmed"),
 *   field_types = {
 *     "text",
 *     "text_long",
 *     "text_with_summary",
 *     "string",
 *     "string_long"
 *   }
 * )
 */
class SmartTrimFormatter extends FormatterBase implements ContainerFactoryPluginInterface {

  /**
   * The truncate HTML service.
   *
   * @var \Drupal\smart_trim\TruncateHTML
   */
  protected TruncateHTML $truncateHtml;

  /**
   * Token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  protected Token $token;

  /**
   * Module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * Constructs a FormatterBase object.
   *
   * @param string $plugin_id
   *   The plugin_id for the formatter.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The definition of the field to which the formatter is associated.
   * @param array $settings
   *   The formatter settings.
   * @param string $label
   *   The formatter label display setting.
   * @param string $view_mode
   *   The view mode.
   * @param array $third_party_settings
   *   Any third party settings.
   * @param \Drupal\smart_trim\TruncateHTML $truncate_html
   *   The truncate HTML service.
   * @param \Drupal\Core\Utility\Token $token
   *   The token service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler service.
   */
  final public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, TruncateHTML $truncate_html, Token $token, ModuleHandlerInterface $module_handler) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
    $this->truncateHtml = $truncate_html;
    $this->token = $token;
    $this->moduleHandler = $module_handler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $plugin_id,
      $plugin_definition,
      $configuration['field_definition'],
      $configuration['settings'],
      $configuration['label'],
      $configuration['view_mode'],
      $configuration['third_party_settings'],
      $container->get('smart_trim.truncate_html'),
      $container->get('token'),
      $container->get('module_handler'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings(): array {
    return [
      'trim_length' => '600',
      'trim_type' => 'chars',
      'trim_suffix' => '',
      'wrap_output' => FALSE,
      'wrap_class' => 'trimmed',
      'more' => [
        'display_link' => FALSE,
        'class' => 'more-link',
        'link_trim_only' => FALSE,
        'target_blank' => FALSE,
        'text' => 'More',
        'aria_label' => 'Read more about [node:title]',
      ],
      'summary_handler' => 'full',
      'trim_options' => [],
    ] + parent::defaultSettings();
  }

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

    $field_name = $this->fieldDefinition->getFieldStorageDefinition()->getName();

    $element['trim_length'] = [
      '#title' => $this->t('Trim length'),
      '#type' => 'textfield',
      '#size' => 10,
      '#default_value' => $this->getSetting('trim_length'),
      '#min' => 0,
      '#required' => TRUE,
    ];

    $element['trim_type'] = [
      '#title' => $this->t('Trim units'),
      '#type' => 'select',
      '#options' => [
        'chars' => $this->t("Characters"),
        'words' => $this->t("Words"),
      ],
      '#default_value' => $this->getSetting('trim_type'),
    ];

    $element['trim_suffix'] = [
      '#title' => $this->t('Suffix'),
      '#type' => 'textfield',
      '#size' => 10,
      '#default_value' => $this->getSetting('trim_suffix'),
      '#description' => $this->t('Unicode character identifiers of the form \u2026 allowed.'),
    ];

    if ($this->fieldDefinition->getType() == 'text_with_summary') {
      $element['summary_handler'] = [
        '#title' => $this->t('Summary'),
        '#type' => 'select',
        '#options' => [
          'full' => $this->t("Use summary if present, and do not trim"),
          'trim' => $this->t("Use summary if present, honor trim settings"),
          'ignore' => $this->t("Do not use summary"),
        ],
        '#default_value' => $this->getSetting('summary_handler'),
      ];
    }

    $trim_options_value = $this->getSetting('trim_options');
    $element['trim_options'] = [
      '#title' => $this->t('Additional options'),
      '#type' => 'checkboxes',
      '#options' => [
        'text' => $this->t('Strip HTML'),
        'trim_zero' => $this->t('Honor a zero trim length'),
        'replace_tokens' => $this->t('Replace tokens before trimming'),
      ],
      '#default_value' => empty($trim_options_value) ? [] : array_keys(array_filter($trim_options_value)),
    ];

    $element['wrap_output'] = [
      '#title' => $this->t('Wrap trimmed content?'),
      '#type' => 'checkbox',
      '#default_value' => $this->getSetting('wrap_output'),
      '#description' => $this->t('Adds a wrapper div to trimmed content. This option is deprecated and will be removed in Smart Trim 3.0.0. Please override the smart-trim.html.twig template file to customize output.'),
    ];

    $element['wrap_class'] = [
      '#title' => $this->t('Wrapped content class.'),
      '#type' => 'textfield',
      '#size' => 20,
      '#default_value' => $this->getSetting('wrap_class'),
      '#description' => $this->t('If wrapping, define the class name here.'),
      '#states' => [
        'visible' => [
          ':input[name="fields[' . $field_name . '][settings_edit_form][settings][wrap_output]"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $element['more'] = [
      '#type' => 'details',
      '#open' => TRUE,
      '#title' => $this->t('<em>More</em> link'),
      '#weight' => 10,
    ];

    $default_settings = self::defaultSettings();
    $more_settings = $this->getSetting('more');
    $more_states = [
      'visible' => [
        ':input[name="fields[' . $field_name . '][settings_edit_form][settings][more][display_link]"]' => ['checked' => TRUE],
      ],
    ];
    $more_required_states = $more_states + [
      'required' => [
        ':input[name="fields[' . $field_name . '][settings_edit_form][settings][more][display_link]"]' => ['checked' => TRUE],
      ],
    ];

    $element['more']['display_link'] = [
      '#title' => $this->t('Display <em>More</em> link?'),
      '#type' => 'checkbox',
      '#default_value' => $more_settings['display_link'] ?? $default_settings['more']['display_link'],
      '#description' => $this->t('Displays a link to the entity (if one exists)'),
    ];

    $element['more']['link_trim_only'] = [
      '#title' => $this->t('Display <em>More</em> link only when content is trimmed?'),
      '#type' => 'checkbox',
      '#default_value' => $more_settings['link_trim_only'] ?? $default_settings['more']['link_trim_only'],
      '#description' => $this->t('Only display <em>More</em> link if content is actually trimmed.'),
      '#states' => $more_states,
    ];

    $element['more']['target_blank'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Open <em>More</em> link in new window'),
      '#return_value' => '_blank',
      '#default_value' => $more_settings['target_blank'] ?? $default_settings['more']['target_blank'],
      '#states' => $more_states,
    ];

    $element['more']['text'] = [
      '#title' => $this->t('<em>More</em> link text'),
      '#type' => 'textfield',
      '#size' => 20,
      '#default_value' => $more_settings['text'] ?? $default_settings['more']['text'],
      '#description' => $this->t('If displaying <em>More</em> link, enter the text for the link. This field supports tokens.'),
      '#states' => $more_required_states,
    ];

    $element['more']['aria_label'] = [
      '#title' => $this->t('<em>More</em> link aria-label'),
      '#type' => 'textfield',
      '#size' => 30,
      '#default_value' => $more_settings['aria_label'] ?? $default_settings['more']['aria_label'],
      '#description' => $this->t('If displaying <em>More</em> link, provide additional context for screen-reader users. In most cases, the aria-label value will be announced instead of the link text. This field supports tokens.'),
      '#states' => $more_states,
    ];

    $element['more']['class'] = [
      '#title' => $this->t('<em>More</em> link class'),
      '#type' => 'textfield',
      '#size' => 20,
      '#default_value' => $more_settings['class'] ?? $default_settings['more']['class'],
      '#description' => $this->t('If displaying <em>More</em> link, add a custom class for formatting.'),
      '#states' => $more_states,
    ];

    $element['more']['token_browser'] = [
      '#theme' => 'token_tree_link',
      '#type' => 'token_tree_link',
      '#token_types' => [$this->fieldDefinition->getTargetEntityTypeId()],
      '#states' => $more_states,
    ];

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary(): array {
    $summary = [];

    $type = $this->t('words');
    if ($this->getSetting('trim_type') == 'chars') {
      $type = $this->t('characters');
    }
    $trim_string = $this->getSetting('trim_length') . ' ' . $type;
    $summary[] = $trim_string;

    if (mb_strlen((trim($this->getSetting('trim_suffix'))))) {
      $trim_string = $this->t("Suffix: %suffix", ['%suffix' => trim($this->getSetting('trim_suffix'))]);
      $summary[] = $trim_string;
    }

    // Summary message line regarding "more" link.
    $more_settings = $this->getSetting('more');
    if ($more_settings['display_link'] ?? FALSE) {
      // Add more text to summary.
      $summary[] = $this->t(
        "<em>More</em> link enabled, text: %text",
        ['%text' => $more_settings['text'] ?? '']
      );

      if ($more_settings['link_trim_only'] ?? FALSE) {
        $summary[] = $this->t("Only display <em>More</em> link when trimmed");
      }

      if ($more_settings['target_blank'] ?? FALSE) {
        $summary[] = $this->t("Open <em>More</em> link in new window");
      }

      $summary[] = $this->t(
        "<em>More</em> link aria-label: %label",
        ['%label' => $more_settings['aria_label'] ?? '']
      );

      $summary[] = $this->t(
        "<em>More</em> link class: %class",
        ['%class' => $more_settings['class'] ?? '']
      );
    }

    if ($this->getSetting('trim_options')) {
      $options = $this->getSetting('trim_options');
      foreach ($options as $key => $option) {
        if ($option) {
          switch ($key) {
            case 'text':
              $trim_string = $this->t('Strip HTML');
              $summary[] = $trim_string;
              break;

            case 'trim_zero':
              $trim_string = $this->t('Honor a zero trim length');
              $summary[] = $trim_string;
              break;

            case 'replace_tokens':
              $trim_string = $this->t('Replace tokens before trimming');
              $summary[] = $trim_string;
              break;

          }
        }
      }
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode = NULL): array {
    $element = [];
    $setting_trim_options = $this->getSetting('trim_options');
    $settings_summary_handler = $this->getSetting('summary_handler');
    $entity = $items->getEntity();
    $tokenData = [$entity->getEntityTypeId() => $entity];

    foreach ($items as $delta => $item) {
      if ($settings_summary_handler != 'ignore' && !empty($item->summary)) {
        $output = trim($item->summary);
      }
      else {
        $output = trim($item->value);
      }

      // Process additional options (currently only HTML on/off).
      if (!empty($setting_trim_options)) {
        // Allow a zero length trim.
        if (!empty($setting_trim_options['trim_zero']) && $this->getSetting('trim_length') == 0) {
          // If the summary is empty, trim to zero length.
          if (empty($item->summary)) {
            $output = '';
          }
          elseif ($settings_summary_handler != 'full') {
            $output = '';
          }
        }

        // Replace tokens before trimming.
        if (!empty($setting_trim_options['replace_tokens'])) {
          $output = $this->token->replace($output, $tokenData, [
            'langcode' => $langcode,
          ]);
        }

        if (!empty($setting_trim_options['text'])) {
          // Strip caption.
          $output = preg_replace('/<figcaption[^>]*>.*?<\/figcaption>/is', ' ', $output);

          // Strip script.
          $output = preg_replace('/<script[^>]*>.*?<\/script>/is', ' ', $output);

          // Strip style.
          $output = preg_replace('/<style[^>]*>.*?<\/style>/is', ' ', $output);

          // Strip tags.
          // Add space before each opening tag to ensure words don't run
          // together. Used logic from:
          // https://stackoverflow.com/questions/12824899/strip-tags-replace-tags-by-space-rather-than-deleting-them
          $output = preg_replace('/<(\w+)(?![^>]*\/>)[^>]*>/', ' <\1>', $output);
          $output = strip_tags($output);
          $output = str_replace('  ', ' ', $output);
          $output = trim($output);

          // Strip out line breaks.
          $output = preg_replace('/\n|\r|\t/m', ' ', $output);

          // Strip out non-breaking spaces.
          $output = str_replace('&nbsp;', ' ', $output);
          $output = str_replace("\xc2\xa0", ' ', $output);

          // Strip out extra spaces.
          $output = trim(preg_replace('/\s\s+/', ' ', $output));
        }
      }

      // Store original output for later comparison.
      $original_output = $output;

      // Make the trim, provided we're not showing a full summary.
      if ($this->getSetting('summary_handler') != 'full' || empty($item->summary)) {
        $length = $this->getSetting('trim_length');
        $ellipse = $this->getSetting('trim_suffix');
        if ($this->getSetting('trim_type') == 'words') {
          $output = $this->truncateHtml->truncateWords($output, $length, $ellipse);
        }
        else {
          $output = $this->truncateHtml->truncateChars($output, $length, $ellipse);
        }
      }

      // Re-encode HTML entities.
      $output = html_entity_decode($output, ENT_QUOTES, 'UTF-8');

      $element[$delta] = [
        '#theme' => 'smart_trim',
        '#output' => [
          '#type' => 'processed_text',
          '#text' => $output,
          '#format' => $item->format,
        ],
        '#is_trimmed' => trim($original_output) != $output,
        '#wrap_output' => $this->getSetting('wrap_output'),
        '#wrapper_class' => $this->getSetting('wrap_class'),
        '#field' => $item->getParent()->getName(),
        '#entity_type' => $item->getParent()->getEntity()->getEntityTypeId(),
        '#entity_bundle' => $item->getParent()->getEntity()->bundle(),
      ];

      // Add the link, if there is one!
      // The entity must have an id already. Content entities usually get their
      // IDs by saving them. In some cases, eg: Inline Entity Form preview there
      // is no ID until everything is saved.
      // https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Entity!Entity.php/function/Entity%3A%3AtoUrl/8.2.x
      $more_settings = $this->getSetting('more');
      if (($more_settings['display_link'] ?? FALSE) && $entity->id() && $entity->hasLinkTemplate('canonical')) {
        if (
          strpos(strrev($output), strrev('<!--break-->')) !== 0 &&
          (($more_settings['link_trim_only'] ?? FALSE) !== TRUE) ||
          ($original_output != $output)
        ) {
          // @todo Change to use config translation when #2546212 merged in core.
          // phpcs:ignore
          $more = $this->t($more_settings['text']);
          $this->token->replace($more, $tokenData, [
            'langcode' => $langcode,
          ]);
          $class = $more_settings['class'];
          $target = $more_settings['target_blank'];
          $link = $entity->toLink($more);

          // Allow other modules to modify the read more link before it's
          // created.
          $this->moduleHandler->invokeAll('smart_trim_link_modify', [
            $entity,
            &$more,
            &$link,
          ]);

          // Update the link if the $more was changed in the hook.
          $link->setText($more);

          $project_link = $link->toRenderable();
          $project_link['#attributes']['class'] = [$class];

          // Ensure we don't create an empty aria-label attribute.
          $aria_label = $more_settings['aria_label'];
          if ($aria_label) {
            $project_link['#attributes']['aria-label'] = $this->token->replace($aria_label, $tokenData, [
              'langcode' => $langcode,
            ]);
          }

          if ($target) {
            $project_link['#attributes']['target'] = "_blank";
          }

          $element[$delta]['#more'] = $project_link;
          $element[$delta]['#more_wrapper_class'] = $class;
        }
      }
    }
    return $element;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc