g2-8.x-1.x-dev/src/Plugin/Filter/Definition.php

src/Plugin/Filter/Definition.php
<?php

declare(strict_types=1);

namespace Drupal\g2\Plugin\Filter;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\filter\Attribute\Filter;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\filter\Plugin\FilterInterface;
use Drupal\g2\G2;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a filter to expand <dfn> entries to G2 glossary links.
 *
 * @Filter(
 *   id = "g2_definition",
 *   title = @Translation("Convert <code>&lt;dfn/&gt</code> elements into links
 *   to definitions in the G2 glossary."), type =
 *   Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
 *   settings = {}, description=@Translation("Converts <code>&lt;dfn&gt;some
 *   entry&lt;/dfn&gt;</code> elements into links to the matching G2 entry, or
 *   a homonyms disambiguation page if multiple entries match the given string.
 *   This filter <em>must</em> be applied after the automatic
 *   <code>&lt;dfn/&gt;</code> wrapping filter."), weight = 0,
 * )
 *
 * @phpstan-consistent-constructor
 */
#[Filter(
  id: "g2_definition",
  title: new TranslatableMarkup("G2 Definition"),
  // @todo Or TYPE_TRANSFORM_REVERSIBLE ?
  type: FilterInterface::TYPE_MARKUP_LANGUAGE,
  description: new TranslatableMarkup("Convert <code>&lt;dfn/&gt</code> elements into links to definitions in the G2 glossary."),
  weight: 0,
  settings: [],
)]
class Definition extends FilterBase implements ContainerFactoryPluginInterface {

  /**
   * Cache metadata set and used during the ::process step.
   *
   * @var \Drupal\Core\Render\BubbleableMetadata|null
   */
  protected ?BubbleableMetadata $metadata;

  /**
   * The config.factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * The entity_type.manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $etm;

  /**
   * The core renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected RendererInterface $renderer;

  /**
   * Constructor.
   *
   * @param array<string,mixed> $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin ID.
   * @param array<string,mixed> $plugin_definition
   *   The plugin definition.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The core config.factory service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $etm
   *   The core entity_type.manager service.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The core renderer service.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    array $plugin_definition,
    ConfigFactoryInterface $configFactory,
    EntityTypeManagerInterface $etm,
    RendererInterface $renderer,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->configFactory = $configFactory;
    $this->etm = $etm;
    $this->renderer = $renderer;
  }

  /**
   * Static factory.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The container.
   * @param array<string,mixed> $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin ID.
   * @param array<string,mixed> $plugin_definition
   *   The plugin definition.
   *
   * @return static
   *   The plugin instance.
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    $config = $container->get(G2::SVC_CONF);
    $etm = $container->get(G2::SVC_ETM);
    $renderer = $container->get('renderer');

    return new static($configuration, $plugin_id, $plugin_definition, $config, $etm, $renderer);
  }

  /**
   * {@inheritDoc}
   */
  public function prepare($text, $langcode): string {
    $text = parent::prepare($text, $langcode);
    $text = preg_replace('@<dfn>(.+?)</dfn>@s', "[g2-dfn]\\1[/g2-dfn]",
      $text);
    if (empty($text)) {
      return '';
    }
    assert(is_string($text));
    return $text;
  }

  /**
   * {@inheritDoc}
   */
  public function process($text, $langcode) {
    $settings = $this->configFactory->get(G2::CONFIG_NAME);
    $target = $settings->get(G2::VARREMOTEG2);
    // Arrow functions to prevent cloning $this in [$this, "doXXX"].
    // The parentheses are for PHPCS not correctly handling arrow functions.
    $method = empty($target)
      /** @var array<string,string> $matches */
      ? (fn(array $matches): string => $this->doLocalProcess($matches))
      : (fn(array $matches): string => $this->doRemoteProcess($matches));

    $this->metadata = new BubbleableMetadata();
    $text = preg_replace_callback('@\[g2-dfn\](.+?)\[/g2-dfn\]@s', $method, $text);
    if (!is_string($text)) {
      $text = '';
    }
    $result = new FilterProcessResult($text);
    $result->addCacheableDependency($this->metadata);
    unset($this->metadata);

    return $result;
  }

  /**
   * Translate glossary linking elements (<dfn>) to local links)
   *
   * This function generates absolute links, for the benefit of the WOTD RSS
   * feed If this feed is not used, it is possible to use the (shorter)
   * relative URLs by swapping comments.
   *
   * @param mixed[] $entry
   *   A 2-entry array, the first being the complete prepared text,
   *   and the second being its content.
   *
   * @return string
   *   HTML.
   */
  protected function doLocalProcess(array $entry): string {
    /** @var string $text */
    $text = $entry[1] ?? '';
    $tooltipsLevel = $this->configFactory->get(G2::CONFIG_NAME)
      ->get(G2::VARTOOLTIPS);

    if ($tooltipsLevel === G2::TOOLTIPS_NONE) {
      $tooltip = '';
    }
    else {
      $nodes = $this->loadEntries($text);
      $count = count($nodes);
      switch ($count) {
        case 0:
          $tooltip = $this->t(
            'No entry found for @entry', [
              '@entry' => $text,
            ]
          );
          break;

        case 1:
          if ($tooltipsLevel == G2::TOOLTIPS_TITLES) {
            /** @var \Drupal\node\NodeInterface $node */
            $node = reset($nodes);
            if (empty($this->metadata)) {
              $this->metadata = new BubbleableMetadata();
            }
            $this->metadata->addCacheableDependency($node);
            $tooltip = $node->label();
          }
          else {
            $builder = $this->etm->getViewBuilder(G2::TYPE);
            /** @var \Drupal\node\NodeInterface $node */
            $node = reset($nodes);
            if (empty($this->metadata)) {
              $this->metadata = new BubbleableMetadata();
            }
            $this->metadata->addCacheableDependency($node);
            $tooltipRA = $builder->view($node, G2::VM_TOOLTIPS);
            $tooltipHTML = $this->renderer
              ->renderRoot($tooltipRA);
            $tooltip = preg_replace('/(\s)\s+/m', '$1',
              trim(strip_tags("$tooltipHTML")));
          }
          break;

        default:
          $tooltip = $this->formatPlural($count,
            '@entry', '@count entries for @entry', [
              '@count' => $count,
              '@entry' => $text,
            ]
          );
          break;
      }
    }

    $attributes = ['class' => 'g2-dfn-link'];
    if (!empty($tooltip)) {
      $attributes['title'] = $tooltip;
    }

    $link = Link::createFromRoute(
      $text,
      G2::ROUTE_HOMONYMS,
      ['g2_match' => $text],
      ['absolute' => TRUE, 'attributes' => $attributes]
    );
    return "{$link->toString()}";
  }

  /**
   * Loader for G2_entries.
   *
   * @param string $title
   *   The title to look for.
   *
   * @return array<int,\Drupal\node\NodeInterface>
   *   Nodes matching the title.
   *
   * @see \Drupal\g2\Plugin\Filter\Definition::doProcess
   */
  public function loadEntries(string $title) {
    $storage = $this->etm->getStorage(G2::TYPE);
    $nids = $storage->getQuery()
      ->accessCheck()
      ->condition('type', G2::BUNDLE)
      ->condition('status', NodeInterface::PUBLISHED)
      ->condition('title', $title)
      ->execute();
    if (empty($nids)) {
      return [];
    }

    $nodes = $storage->loadMultiple($nids);
    return $nodes;
  }

  /**
   * Translate glossary linking elements (<dfn>) to remote links)
   *
   * This function generates absolute links, for the benefit of the WOTD RSS
   * feed If this feed is not used, it is possible to use the (shorter)
   * relative URLs by swapping comments.
   *
   * @param mixed[] $entry
   *   A 2-entry array, the first being the complete prepared text,
   *   and the second being its content.
   *
   * @return string
   *   A <a href> string.
   */
  protected function doRemoteProcess(array $entry): string {
    /** @var string $text */
    $text = $entry[1] ?? '';
    // When this method is called, we know this is not empty.
    $mixedTarget = $this->configFactory->get(G2::CONFIG_NAME)
      ->get(G2::VARREMOTEG2);
    assert(is_scalar($mixedTarget) || $mixedTarget instanceof \Stringable);
    $target = (string) $mixedTarget;

    $path = urlencode(G2::encodeTerminal($text));
    // We do not have access to the caching metadata on the remote site with
    // the current version of the API.
    $url = Url::fromUri("$target/$path", [
      'absolute' => TRUE,
      'attributes' => ['class' => 'g2-dfn-link'],
    ]);
    $ret = Link::fromTextAndUrl($text, $url)->toString()->__toString();
    return $ret;
  }

  /**
   * {@inheritDoc}
   */
  public function tips($long = FALSE): ?TranslatableMarkup {
    $ret = $long
      ? $this->t('Wrap &lt;dfn&gt; elements around the terms for which you want a link to the available glossary entries. If you have enabled the automatic filter, you only have to do this for entries you placed on the glossary stop list.')
      : $this->t('You may link to glossary entries manually using &lt;dfn&gt; elements. Especially useful for those on the stop list.');
    return $ret;
  }

}

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

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