bibcite_footnotes-8.x-1.0-beta3/src/Plugin/Filter/ReferenceFootnotesFilter.php

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

namespace Drupal\bibcite_footnotes\Plugin\Filter;

use Drupal\Core\Form\FormStateInterface;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\bibcite_footnotes\CitationTools;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;

/**
 * Reference Footnotes filter.
 *
 * @Filter(
 *   id = "filter_reference_footnotes",
 *   module = "bibcite_footnotes",
 *   title = @Translation("Inline citation filter"),
 *   description = @Translation("Convert bibcite-footnote tags to rendered citations."),
 *   type = \Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
 *   cache = FALSE,
 *   settings = {
 *     "enable_bidirectional_links" = TRUE,
 *     "backlink_symbol" = "↑",
 *     "backlink_position" = "after",
 *     "enable_bibliography_section" = TRUE,
 *     "bibliography_section_label" = "Bibliography",
 *   },
 *   weight = 0
 * )
 */
class ReferenceFootnotesFilter extends FilterBase implements ContainerFactoryPluginInterface
{

  /**
   * The citation tools service.
   *
   * @var \Drupal\bibcite_footnotes\CitationTools
   */
  protected $citationTools;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, CitationTools $citation_tools)
  {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->citationTools = $citation_tools;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition)
  {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('bibcite_footnotes.citation_tools')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function settings()
  {
    $settings = parent::settings();

    // Ensure default values are set
    $settings += [
      'enable_bidirectional_links' => TRUE,
      'backlink_symbol' => '↑',
      'backlink_position' => 'after',
      "enable_bibliography_section" => TRUE,
      "bibliography_section_label" => $this->t("Bibliography"),
    ];

    return $settings;
  }

  /**
   * Create the settings form for the filter.
   *
   * @param array $form
   *   A minimally prepopulated form array.
   * @param FormStateInterface $form_state
   *   The state of the (entire) configuration form.
   *
   * @return array
   *   The $form array with additional form elements for the settings of
   *   this filter. The submitted form values should match $this->settings.
   */
  public function settingsForm(array $form, FormStateInterface $form_state)
  {
    // Get the current settings with defaults
    $settings = $this->settings;

    $form['enable_bidirectional_links'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable bidirectional links between citations and bibliography'),
      '#description' => $this->t('When enabled, citations will link to bibliography items and bibliography items will have backlinks to each citation.'),
      '#default_value' => $settings['enable_bidirectional_links'],
    ];

    $form['backlink_symbol'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Backlink symbol'),
      '#description' => $this->t('The symbol used for backlinks from bibliography to citations (e.g., ↑, ↖, ↩)'),
      '#default_value' => $settings['backlink_symbol'],

      '#size' => 10,
      '#states' => [
        'required' => [
          ':input[name="filters[filter_reference_footnotes][settings][enable_bidirectional_links]"]' => ['checked' => TRUE],
        ],

        'visible' => [
          ':input[name="filters[filter_reference_footnotes][settings][enable_bidirectional_links]"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['backlink_position'] = [
      '#type' => 'select',
      '#title' => $this->t('Backlink position'),
      '#description' => $this->t('Position of backlinks relative to bibliography entry.'),
      '#options' => [
        'before' => $this->t('Before entry'),
        'after' => $this->t('After entry'),
      ],
      '#default_value' => $settings['backlink_position'],
      '#states' => [
        'visible' => [
          ':input[name="filters[filter_reference_footnotes][settings][enable_bidirectional_links]"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['enable_bibliography_section'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable Bibliography section'),
      '#description' => $this->t('When enabled, a Bibliography section will be printed at the bottom of the content.'
        . '<br>Alternatively, the Works Cited Views style plugin can be used to render a bibliography section inside a block.'),
      '#default_value' => $settings['enable_bibliography_section'],
    ];

    $form['bibliography_section_label'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Bibliography section label'),
      '#description' => $this->t('The label for the inline bibliography section, rendered in an &lt;h3&gt; tag.'),
      '#default_value' => $settings['bibliography_section_label'],
      '#size' => 20,
      '#states' => [
        'required' => [
          ':input[name="filters[filter_reference_footnotes][settings][enable_bibliography_section]"]' => ['checked' => TRUE],
        ],

        'visible' => [
          ':input[name="filters[filter_reference_footnotes][settings][enable_bibliography_section]"]' => ['checked' => TRUE],
        ],
      ],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function process($text, $langcode)
  {
    $result = new FilterProcessResult($text);

    if (strpos($text, 'bibcite-footnote') === FALSE) {
      return $result;
    }

    $dom = new \DOMDocument();

    // Enable internal encoding handling
    $dom->encoding = 'UTF-8';

    // Load HTML without XML declaration
    @$dom->loadHTML(
      mb_convert_encoding($text, 'HTML-ENTITIES', 'UTF-8'),
      LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
    );

    $xpath = new \DOMXPath($dom);
    $footnotes = $xpath->query('//bibcite-footnote');

    if ($footnotes->length === 0) {
      return $result;
    }

    // Process all citations and remove invalid footnotes
    $citationData = $this->processAllCitations($footnotes);

    // Replace all valid footnotes with rendered citations
    $this->replaceAllFootnotes($dom, $citationData['bibliography'], $citationData['citations']);

    // Remove any remaining invalid footnotes
    $this->removeInvalidFootnotes($dom);

    $bibliography = array_values($citationData['bibliography']);
    $citation_items = array_values($citationData['citations']);

    // Add full bibliography section at the end of the content
    $this->addBibliographySection($dom, $bibliography, $citation_items);

    $result->setProcessedText($dom->saveHTML());

    // Add cache context for filter settings
    $result->addCacheableDependency($this);

    return $result;
  }

  /**
   * Process all footnote elements and prepare citation data
   */
  private function processAllCitations(\DOMNodeList $footnotes): array
  {
    $citations = [];
    $bibliography = [];
    $citation_id_counts = [];

    foreach ($footnotes as $footnote) {
      $entity_id = $footnote->getAttribute('data-entity-id');
      $page_range = $footnote->getAttribute('data-page-range');

      if (empty($entity_id)) {
        // Skip invalid footnotes (they will be removed later)
        continue;
      }

      $citation = $this->citationTools->getRenderableReference($entity_id);

      // Check if we got valid citation data
      if (empty($citation['#data']) || empty($citation['#data']['citation-label'])) {
        // Invalid reference, skip this footnote
        continue;
      }

      $citation_label = $citation['#data']['citation-label'];

      // Generate unique citation ID
      $citation_count = $citation_id_counts[$citation_label] ?? 0;
      $citation_id = $citation_label . '_' . $citation_count;
      $citation['#data']['id'] = $citation_label;

      // Store citation data
      $citations[$citation_id] = [
        'id' => $citation_label,
        'locator' => $page_range,
        'label' => 'page',
        'fragment' => $footnote
      ];
      if (empty($bibliography[$citation_label])) {
        $bibliography[$citation_label] = $citation['#data'];
      }
      $citation_id_counts[$citation_label] = $citation_count + 1;
    }

    return [
      'citations' => $citations,
      'bibliography' => $bibliography
    ];
  }

  /**
   * Replace all footnote elements with rendered citations
   */
  private function replaceAllFootnotes(\DOMDocument $dom, array $bibliography, array $citations): void
  {
    $bibliography = array_values($bibliography);
    foreach ($citations as $citation_id => $citation_data) {
      $stripped_citation = $citation_data;
      unset($stripped_citation['fragment']);

      // Pass filter settings to the render function
      $rendered_citation = $this->renderCitation(
        $bibliography,
        $stripped_citation,
        $this->settings
      );

      $fragment = $dom->createDocumentFragment();
      $fragment->appendXML('<span class="bibcite-citation">' . $rendered_citation . '</span>');

      $citation_data['fragment']->parentNode->replaceChild($fragment, $citation_data['fragment']);
    }
  }

  /**
   * Remove invalid footnotes (those without entity-id or with invalid references)
   */
  private function removeInvalidFootnotes(\DOMDocument $dom): void
  {
    $xpath = new \DOMXPath($dom);
    $remainingFootnotes = $xpath->query('//bibcite-footnote');

    foreach ($remainingFootnotes as $footnote) {
      // Remove the footnote element from its parent
      if ($footnote->parentNode) {
        $footnote->parentNode->removeChild($footnote);
      }
    }
  }

  /**
   * Add full bibliography section at the end of the content
   */
  private function addBibliographySection(\DOMDocument $dom, array $bibliography, array $citation_list): void
  {
    if (empty($bibliography)) {
      return;
    }

    // Create bibliography render array with filter settings
    $bibliography_render = [
      '#theme' => 'bibcite_footnotes_works_cited',
      '#data' => $bibliography,
      '#citation-items' => $citation_list,
      '#filter_settings' => $this->settings, // Pass settings to theme
    ];

    $rendered_bibliography = \Drupal::service('renderer')->render($bibliography_render);

    // Create the footnotes section
    if (empty($this->settings['enable_bibliography_section'])
      || $this->settings['enable_bibliography_section']) { // On by default for kernel tests.

      $label = !empty($this->settings['bibliography_section_label'])  ? $this->settings['bibliography_section_label'] : $this->t('Bibliography');
      $footnotes_section = $dom->createDocumentFragment();
      $footnotes_section->appendXML('
          <div class="bibcite-footnotes-section">
              <h3>' . $label .'</h3>
              <div class="bibcite-bibliography">' . $rendered_bibliography . '</div>
          </div>
      ');

      // Use XPath to find the last element in the document
      $xpath = new \DOMXPath($dom);
      $allElements = $xpath->query('//*');

      if ($allElements->length > 0) {
        $lastElement = $allElements->item($allElements->length - 1);
        $lastElement->parentNode->insertBefore($footnotes_section, $lastElement->nextSibling);
      } else {
        // Fallback: append to document element
        $dom->documentElement->appendChild($footnotes_section);
      }
    }
  }

  /**
   * Render a single citation with full bibliographical context
   */
  private function renderCitation(array $bibliography, array $citation_items, array $filter_settings = []): string
  {
    $citation['#theme'] = 'bibcite_footnotes_citation';
    $citation['#data'] = $bibliography;
    $citation['#citation-items'] = [$citation_items];
    $citation['#filter_settings'] = $filter_settings; // Pass settings to theme

    return \Drupal::service('renderer')->render($citation);
  }

  /**
   * {@inheritdoc}
   */
  public function tips($long = FALSE)
  {
    return $this->t('Use the citation button in the editor to insert citations.');
  }
}

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

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