Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.59% covered (danger)
1.59%
1 / 63
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RefGroup
1.59% covered (danger)
1.59%
1 / 63
33.33% covered (danger)
33.33%
1 / 3
200.81
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createLinkback
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 renderLine
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2declare( strict_types = 1 );
3
4namespace Cite\Parsoid;
5
6use Wikimedia\Parsoid\DOM\Document;
7use Wikimedia\Parsoid\DOM\Element;
8use Wikimedia\Parsoid\Ext\DOMDataUtils;
9use Wikimedia\Parsoid\Ext\DOMUtils;
10use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
11use Wikimedia\Parsoid\Utils\DOMCompat;
12
13/**
14 * Helper class used by `<references>` implementation.
15 * @license GPL-2.0-or-later
16 */
17class RefGroup {
18
19    public string $name;
20    /** @var RefGroupItem[] */
21    public array $refs = [];
22    /** @var array<string,RefGroupItem> Lookup map only for named refs */
23    public array $indexByName = [];
24
25    public function __construct( string $group = '' ) {
26        $this->name = $group;
27    }
28
29    /**
30     * Generate leading linkbacks
31     */
32    private static function createLinkback(
33        ParsoidExtensionAPI $extApi, string $target, ?string $group,
34        string $text, Document $ownerDoc
35    ): Element {
36        $a = $ownerDoc->createElement( 'a' );
37        $span = $ownerDoc->createElement( 'span' );
38        $a->setAttribute( 'href', $extApi->getPageUri() . '#' . $target );
39        $span->setAttribute( 'class', 'mw-linkback-text' );
40        if ( $group ) {
41            $a->setAttribute( 'data-mw-group', $group );
42        }
43        $span->appendChild( $ownerDoc->createTextNode( $text . ' ' ) );
44        $a->appendChild( $span );
45        return $a;
46    }
47
48    public function renderLine(
49        ParsoidExtensionAPI $extApi, Element $refsList, RefGroupItem $ref
50    ): void {
51        $ownerDoc = $refsList->ownerDocument;
52
53        // Generate the li and set ref content first, so the HTML gets parsed.
54        // We then append the rest of the ref nodes before the first node
55        $li = $ownerDoc->createElement( 'li' );
56        $refDir = $ref->dir;
57        $refTarget = $ref->target;
58        $refContentId = $ref->contentId;
59        $refGroup = $ref->group;
60        DOMUtils::addAttributes( $li, [
61                'about' => '#' . $refTarget,
62                'id' => $refTarget,
63                'class' => ( $refDir === 'rtl' || $refDir === 'ltr' ) ? 'mw-cite-dir-' . $refDir : null
64            ]
65        );
66        $reftextSpan = $ownerDoc->createElement( 'span' );
67        DOMUtils::addAttributes(
68            $reftextSpan,
69            [
70                'id' => 'mw-reference-text-' . $refTarget,
71                // Add both mw-reference-text & reference-text for b/c.
72                // We will remove duplicate classes in the future.
73                'class' => 'mw-reference-text reference-text',
74            ]
75        );
76        if ( $refContentId ) {
77            // `sup` is the wrapper created by Ref::sourceToDom()'s call to
78            // `extApi->extTagToDOM()`.  Only its contents are relevant.
79            $sup = $extApi->getContentDOM( $refContentId )->firstChild;
80            DOMUtils::migrateChildren( $sup, $reftextSpan );
81            '@phan-var Element $sup';  /** @var Element $sup */
82            DOMCompat::remove( $sup );
83            $extApi->clearContentDOM( $refContentId );
84        }
85        $li->appendChild( $reftextSpan );
86
87        // It seems counter-productive to go through hoops to not display all the errors considering that rendering
88        // only the first one is considered deprecated in the legacy code. However, displaying the same error
89        // multiple times for the same reference is also useless.
90        // Hence, we avoid displaying the same error multiple times, and keep count of multiple errors under this
91        // constraint; if we still display multiple errors where the legacy parser would only display one, we add
92        // the page to the cite-tracking-category-cite-diffing-error category.
93        $reported = [];
94        $errorCount = 0;
95        foreach ( $ref->nodes as $node ) {
96            foreach ( DOMDataUtils::getDataMw( $node )->errors ?? [] as $error ) {
97                if ( in_array( $error, $reported ) ) {
98                    continue;
99                }
100                $reported[] = $error;
101                $errorCount++;
102                $errorFragment = ErrorUtils::renderParsoidError( $extApi, $error->key, $error->params );
103                $li->appendChild( $errorFragment );
104                if ( ErrorUtils::isDiffingError( $error->key ) || $errorCount > 1 ) {
105                    $extApi->addTrackingCategory( 'cite-tracking-category-cite-diffing-error' );
106                }
107            }
108        }
109
110        // mw:referencedBy is added to the <span> for the named refs case
111        // and to the <a> tag to the unnamed refs case. This difference
112        // is used by CSS to style backlinks in MediaWiki:Common.css
113        // of various wikis.
114        $linkbackSpan = $ownerDoc->createElement( 'span' );
115        if ( count( $ref->linkbacks ) === 1 ) {
116            $linkback = self::createLinkback( $extApi, $ref->id, $refGroup, "↑", $ownerDoc );
117            DOMUtils::addRel( $linkback, 'mw:referencedBy' );
118            $linkbackSpan->appendChild( $linkback );
119        } else {
120            DOMUtils::addRel( $linkbackSpan, 'mw:referencedBy' );
121            foreach ( $ref->linkbacks as $i => $lb ) {
122                $linkbackSpan->appendChild(
123                    self::createLinkback( $extApi, $lb, $refGroup, (string)( $i + 1 ), $ownerDoc )
124                );
125            }
126        }
127        DOMCompat::getClassList( $linkbackSpan )->add( 'mw-cite-backlink' );
128        $li->insertBefore( $linkbackSpan, $reftextSpan );
129
130        // Space before content node
131        $li->insertBefore( $ownerDoc->createTextNode( ' ' ), $reftextSpan );
132
133        // Add it to the ref list
134        $refsList->appendChild( $li );
135
136        // Backward-compatibility: add newline (T372889)
137        $refsList->appendChild( $ownerDoc->createTextNode( "\n" ) );
138    }
139}