1<?php
2
3/*
4 * This file is part of the league/commonmark package.
5 *
6 * (c) Colin O'Dell <colinodell@gmail.com>
7 * (c) Rezo Zero / Ambroise Maupate
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13declare(strict_types=1);
14
15namespace League\CommonMark\Extension\Footnote\Event;
16
17use League\CommonMark\Block\Element\Document;
18use League\CommonMark\Event\DocumentParsedEvent;
19use League\CommonMark\Extension\Footnote\Node\Footnote;
20use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
21use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
22use League\CommonMark\Reference\Reference;
23use League\CommonMark\Util\ConfigurationAwareInterface;
24use League\CommonMark\Util\ConfigurationInterface;
25
26final class GatherFootnotesListener implements ConfigurationAwareInterface
27{
28    /** @var ConfigurationInterface */
29    private $config;
30
31    public function onDocumentParsed(DocumentParsedEvent $event): void
32    {
33        $document = $event->getDocument();
34        $walker = $document->walker();
35
36        $footnotes = [];
37        while ($event = $walker->next()) {
38            if (!$event->isEntering()) {
39                continue;
40            }
41
42            $node = $event->getNode();
43            if (!$node instanceof Footnote) {
44                continue;
45            }
46
47            // Look for existing reference with footnote label
48            $ref = $document->getReferenceMap()->getReference($node->getReference()->getLabel());
49            if ($ref !== null) {
50                // Use numeric title to get footnotes order
51                $footnotes[\intval($ref->getTitle())] = $node;
52            } else {
53                // Footnote call is missing, append footnote at the end
54                $footnotes[INF] = $node;
55            }
56
57            /*
58             * Look for all footnote refs pointing to this footnote
59             * and create each footnote backrefs.
60             */
61            $backrefs = $document->getData(
62                '#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $node->getReference()->getDestination(),
63                []
64            );
65            /** @var Reference $backref */
66            foreach ($backrefs as $backref) {
67                $node->addBackref(new FootnoteBackref(new Reference(
68                    $backref->getLabel(),
69                    '#' . $this->config->get('footnote/ref_id_prefix', 'fnref:') . $backref->getLabel(),
70                    $backref->getTitle()
71                )));
72            }
73        }
74
75        // Only add a footnote container if there are any
76        if (\count($footnotes) === 0) {
77            return;
78        }
79
80        $container = $this->getFootnotesContainer($document);
81
82        \ksort($footnotes);
83        foreach ($footnotes as $footnote) {
84            $container->appendChild($footnote);
85        }
86    }
87
88    private function getFootnotesContainer(Document $document): FootnoteContainer
89    {
90        $footnoteContainer = new FootnoteContainer();
91        $document->appendChild($footnoteContainer);
92
93        return $footnoteContainer;
94    }
95
96    public function setConfiguration(ConfigurationInterface $config): void
97    {
98        $this->config = $config;
99    }
100}
101