Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.59% |
1 / 63 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
RefGroup | |
1.59% |
1 / 63 |
|
33.33% |
1 / 3 |
200.81 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createLinkback | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
renderLine | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
132 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Cite\Parsoid; |
5 | |
6 | use Wikimedia\Parsoid\DOM\Document; |
7 | use Wikimedia\Parsoid\DOM\Element; |
8 | use Wikimedia\Parsoid\Ext\DOMDataUtils; |
9 | use Wikimedia\Parsoid\Ext\DOMUtils; |
10 | use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI; |
11 | use Wikimedia\Parsoid\Utils\DOMCompat; |
12 | |
13 | /** |
14 | * Helper class used by `<references>` implementation. |
15 | * @license GPL-2.0-or-later |
16 | */ |
17 | class 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 | } |