g2-8.x-1.x-dev/src/Plugin/Filter/Automatic.php
src/Plugin/Filter/Automatic.php
<?php
declare(strict_types=1);
namespace Drupal\g2\Plugin\Filter;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
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\g2\Matcher;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Automatic' filter.
*
* @Filter(
* id = "g2_automatic",
* title = @Translation("Automatically wrap G2 entries found in content with <code><dfn/></code> elements"),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
* settings = {
* "stop_list" = "",
* },
* description=@Translation("Automatically wrap recognized G2 entries in <code><dfn/></code> elements, except for those in your configured stop list. This filter is only useful along with the <code><dfn/></code> conversion filter, and <em>must</em> be applied before it."),
* weight = -1,
* )
*
* @phpstan-consistent-constructor
*/
#[Filter(
id: "g2_automatic",
title: new TranslatableMarkup("G2 Automatic definitions"),
type: FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
description: new TranslatableMarkup("Automatically wrap G2 entries found in content with <code><dfn/></code> elements"),
weight: -1,
settings: [
"stop_list" => "",
]
)]
class Automatic extends FilterBase implements ContainerFactoryPluginInterface {
/**
* The name of the single settings for this filter.
*/
const STOP = 'stop_list';
/**
* The logger.channel.g2 service.
*
* @var \Psr\Log\LoggerInterface
*/
protected LoggerInterface $logger;
/**
* The g2.matcher service.
*
* @var \Drupal\g2\Matcher
*/
protected Matcher $matcher;
/**
* 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\g2\Matcher $matcher
* The g2.matcher service.
* @param \Psr\Log\LoggerInterface $logger
* The logger.channel.g2 service.
*/
public function __construct(
array $configuration,
string $plugin_id,
array $plugin_definition,
Matcher $matcher,
LoggerInterface $logger,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->logger = $logger;
$this->matcher = $matcher;
}
/**
* 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 {
$logger = $container->get(G2::SVC_LOGGER);
$matcher = $container->get(G2::SVC_MATCHER);
return new static($configuration, $plugin_id, $plugin_definition, $matcher, $logger);
}
/**
* {@inheritDoc}
*
* @throws \DOMException
*/
public function prepare($text, $langcode): string {
if (!Unicode::validateUtf8($text)) {
$this->logger
->error('The text is apparently not valid UTF-8 charset: @text.', [
'@text' => $text,
]);
return $text;
}
$stopList = $this->getConfiguration()['settings'][self::STOP] ?? [];
$stopList = $this->getNormalizedStopList($stopList);
return Matcher::handleSource($text,
$this->matcher->getMultiStringMatcher(),
$stopList);
}
/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
return new FilterProcessResult($text);
}
/**
* {@inheritdoc}
*/
public function tips($long = FALSE): ?TranslatableMarkup {
return $long
? $this->t('Glossary entries which are not in the glossary stop list will be automatically wrapped in a <dfn> element during rendering, just as if you had tagged them yourself.')
: $this->t('Glossary entries are automatically wrapped in <dfn> to generate links.');
}
/**
* Return a deduplicated, trimmed, sorted array version of the stop list.
*
* @param mixed $stopList
* The stop list to normalize. May be null, string, or array.
*
* @return string[]
* The normalized version of the same stop list.
*/
protected function getNormalizedStopList(mixed $stopList = NULL): array {
// No existing config or submitted a NULL.
if ($stopList === NULL) {
$stopList = $this->settings[static::STOP] ?? [];
}
// Handle legacy string configuration.
if (is_string($stopList)) {
// Web submissions add CR, just ignore them.
$stopList = str_replace("\r", "", $stopList);
$stopList = explode("\n", $stopList);
}
// At this point, we know we have a well-formed array. Let's clean it.
$stopList = array_map(fn($item) => trim($item), $stopList);
$stopList = array_filter($stopList);
sort($stopList);
$stopList = array_unique($stopList);
return $stopList;
}
/**
* Builder for the plugin settings subform.
*
* @param array<string,mixed> $form
* The original form.
* @param \Drupal\Core\Form\FormStateInterface $formState
* The form state.
*
* @return array<string,mixed>
* The modified form.
*/
public function settingsForm(array $form, FormStateInterface $formState) {
$form[self::STOP] = [
'#type' => 'textarea',
'#title' => $this->t('Stop list'),
'#default_value' => implode("\n", $this->getNormalizedStopList()),
'#description' => $this->t('Enter G2 entries that must never be automatically wrapped in a <dfn/> element, one per line. You will still be able to define them by adding the <dfn/> element manually.'),
'#element_validate' => [[$this, 'validateStopList']],
];
return $form;
}
/**
* Form element validation handler.
*
* Deduplicates and sorts entries.
*
* @param array<string,mixed> $element
* The stop_list form element.
* @param \Drupal\Core\Form\FormStateInterface $formState
* The form state.
*/
public function validateStopList(array &$element, FormStateInterface $formState): void {
$rawValue = $element['#value'] ?? '';
$stopList = $this->getNormalizedStopList($rawValue);
$value = implode("\r\n", $stopList);
$formState->setValueForElement($element, $stopList);
if ($value !== $rawValue) {
$this->messenger()
->addStatus("Your G2 stop list has been filtered and reordered.");
}
}
}
