migrate_plus-8.x-5.x-dev/src/Plugin/migrate/process/DomApplyStyles.php

src/Plugin/migrate/process/DomApplyStyles.php
<?php

declare(strict_types=1);

namespace Drupal\migrate_plus\Plugin\migrate\process;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Apply Editor styles to configured elements.
 *
 * Replace HTML elements with elements and classes specified in the Styles menu
 * of the WYSIWYG editor.
 *
 * Available configuration keys:
 * - format: the text format to inspect for style options (optional,
 *   defaults to 'basic_html').
 * - rules: an array of keyed arrays, with the following keys:
 *   - xpath: an XPath expression for the elements to replace.
 *   - style: the label of the item in the Styles menu to use.
 *   - depth: the number of parent elements to remove (optional, defaults to 0).
 *
 * Example:
 *
 * @code
 * process:
 *   'body/value':
 *     -
 *       plugin: dom
 *       method: import
 *       source: 'body/0/value'
 *     -
 *       plugin: dom_apply_styles
 *       format: full_html
 *       rules:
 *         -
 *           xpath: '//b'
 *           style: Bold
 *         -
 *           xpath: '//span/i'
 *           style: Italic
 *           depth: 1
 *     -
 *       plugin: dom
 *       method: export
 * @endcode
 *
 * This will replace <b>...</b> with whatever style is labeled "Bold" in the
 * Full HTML text format, perhaps <strong class="foo">...</strong>.
 * It will also replace <span><i>...</i></span> with the style labeled "Italic"
 * in that text format, perhaps <em class="foo bar">...</em>.
 * You may get unexpected results if there is anything between the two opening
 * tags or between the two closing tags. That is, the code assumes that
 * '<span><i>' is closed with '</i></span>' exactly.
 *
 * @MigrateProcessPlugin(
 *   id = "dom_apply_styles"
 * )
 */
class DomApplyStyles extends DomProcessBase implements ContainerFactoryPluginInterface {

  /**
   * The config factory.
   */
  protected ConfigFactory $configFactory;

  /**
   * Styles from the WYSIWYG editor.
   */
  protected array $styles = [];

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactory $config_factory) {
    $configuration += ['format' => 'basic_html'];
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->configFactory = $config_factory;
    $this->setStyles($configuration['format']);
    $this->validateRules();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL): self {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('config.factory')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property): \DOMDocument {
    $this->init($value, $destination_property);

    foreach ($this->configuration['rules'] as $rule) {
      $this->apply($rule);
    }

    return $this->document;
  }

  /**
   * Retrieve the list of styles based on configuration.
   *
   * The styles configuration is a string: styles are separated by "\r\n", and
   * each one has the format 'element(\.class)*|label'.
   * Convert this to an array with 'label' => 'element.class', and save as
   * $this->styles.
   *
   * @param string $format
   *   The text format from which to get configured styles.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  protected function setStyles($format): void {
    if (empty($format) || !is_string($format)) {
      $message = 'The "format" option must be a non-empty string.';
      throw new InvalidPluginDefinitionException($this->getPluginId(), $message);
    }
    $editor_config = $this->configFactory->get("editor.editor.$format");
    if ($editor_config->get('editor') === 'ckeditor') {
      $editor_styles = $editor_config->get('settings.plugins.stylescombo.styles') ?? '';
      foreach (explode("\r\n", $editor_styles) as $rule) {
        if (preg_match('/(.*)\|(.*)/', $rule, $matches)) {
          $this->styles[$matches[2]] = $matches[1];
        }
      }
    }
    elseif ($editor_config->get('editor') === 'ckeditor5') {
      $editor_styles = $editor_config->get('settings.plugins.ckeditor5_style.styles') ?? [];
      foreach ($editor_styles as $editor_style) {
        if (preg_match('/<(.*) class="(.*)">/', $editor_style['element'], $matches)) {
          $this->styles[$editor_style['label']] = $matches[1] . '.' . $matches[2];
        }
      }
    }
  }

  /**
   * Validate the configured rules.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  protected function validateRules(): void {
    if (!array_key_exists('rules', $this->configuration) || !is_array($this->configuration['rules'])) {
      $message = 'The "rules" option must be an array.';
      throw new InvalidPluginDefinitionException($this->getPluginId(), $message);
    }
    foreach ($this->configuration['rules'] as $rule) {
      if (empty($rule['xpath']) || empty($rule['style'])) {
        $message = 'The "xpath" and "style" options are required for each rule.';
        throw new InvalidPluginDefinitionException($this->getPluginId(), $message);
      }
      if (empty($this->styles[$rule['style']])) {
        $message = sprintf('The style "%s" is not defined.', $rule['style']);
        throw new InvalidPluginDefinitionException($this->getPluginId(), $message);
      }
    }
  }

  /**
   * Apply a rule to the document.
   *
   * Search $this->document for elements matching 'xpath' and replace them with
   * the HTML elements and classes in $this->styles specified by 'style'.
   * If 'depth' is positive, then replace additional parent elements as well.
   *
   * @param string[] $rule
   *   An array with keys 'xpath', 'style', and (optional) 'depth'.
   */
  protected function apply(array $rule): void {
    // An entry in $this->styles has the format element(\.class)*: for example,
    // 'p' or 'a.button' or 'div.col-xs-6.col-md-4'.
    // @see setStyles()
    [$element, $classes] = explode('.', $this->styles[$rule['style']] . '.', 2);
    $classes = trim(str_replace('.', ' ', $classes));

    foreach ($this->xpath->query($rule['xpath']) as $node) {
      $new_node = $this->document->createElement($element);
      foreach ($node->childNodes as $child) {
        $new_node->appendChild($child->cloneNode(TRUE));
      }
      if ($classes) {
        $new_node->setAttribute('class', $classes);
      }
      $old_node = $node;
      if (!empty($rule['depth'])) {
        for ($i = 0; $i < $rule['depth']; $i++) {
          $old_node = $old_node->parentNode;
        }
      }
      $old_node->parentNode->replaceChild($new_node, $old_node);
    }
  }

}

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

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