Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 99 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
ParsoidDomProcessor | |
0.00% |
0 / 99 |
|
0.00% |
0 / 5 |
992 | |
0.00% |
0 / 1 |
wtPostprocess | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
90 | |||
processKartographerNode | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
272 | |||
updateSrc | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
updateSrcSet | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
updateUrl | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace Kartographer\Tag; |
4 | |
5 | use FormatJson; |
6 | use Kartographer\ParsoidUtils; |
7 | use Kartographer\SimpleStyleParser; |
8 | use MediaWiki\MediaWikiServices; |
9 | use MediaWiki\Parser\ParserOutputStringSets; |
10 | use MediaWiki\Title\Title; |
11 | use stdClass; |
12 | use Wikimedia\Parsoid\DOM\Element; |
13 | use Wikimedia\Parsoid\DOM\Node; |
14 | use Wikimedia\Parsoid\Ext\DOMDataUtils; |
15 | use Wikimedia\Parsoid\Ext\DOMProcessor; |
16 | use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI; |
17 | use Wikimedia\Parsoid\Utils\DOMCompat; |
18 | use Wikimedia\Parsoid\Utils\DOMTraverser; |
19 | |
20 | /** |
21 | * @license MIT |
22 | */ |
23 | class ParsoidDomProcessor extends DOMProcessor { |
24 | |
25 | /** @inheritDoc */ |
26 | public function wtPostprocess( ParsoidExtensionAPI $extApi, Node $root, array $options ): void { |
27 | if ( !( $root instanceof Element ) ) { |
28 | return; |
29 | } |
30 | |
31 | $state = [ |
32 | 'broken' => 0, |
33 | 'interactiveGroups' => [], |
34 | 'requestedGroups' => [], |
35 | 'counters' => [], |
36 | 'maplinks' => 0, |
37 | 'mapframes' => 0, |
38 | 'data' => [], |
39 | ]; |
40 | |
41 | // FIXME This only selects data-mw-kartographer nodes without exploring HTML that may be stored in |
42 | // attributes. We need to expand the traversal to find these as well. |
43 | $kartnodes = DOMCompat::querySelectorAll( $root, '*[data-mw-kartographer]' ); |
44 | |
45 | // let's avoid adding data to the page if there's no kartographer nodes! |
46 | if ( !$kartnodes ) { |
47 | return; |
48 | } |
49 | |
50 | $mapServer = MediaWikiServices::getInstance()->getMainConfig()->get( 'KartographerMapServer' ); |
51 | $extApi->getMetadata()->addModuleStyles( [ 'ext.kartographer.style' ] ); |
52 | $extApi->getMetadata()->appendOutputStrings( ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC, [ $mapServer ] ); |
53 | |
54 | $traverser = new DOMTraverser( false, true ); |
55 | $traverser->addHandler( null, function ( $node ) use ( &$state, $extApi ) { |
56 | if ( $node instanceof Element && $node->hasAttribute( 'data-mw-kartographer' ) ) { |
57 | $this->processKartographerNode( $node, $extApi, $state ); |
58 | } |
59 | return true; |
60 | } ); |
61 | $traverser->traverse( $extApi, $root ); |
62 | |
63 | if ( $state['broken'] > 0 ) { |
64 | ParsoidUtils::addCategory( $extApi, 'kartographer-broken-category' ); |
65 | } |
66 | if ( $state['maplinks'] + $state['mapframes'] > $state['broken'] ) { |
67 | ParsoidUtils::addCategory( $extApi, 'kartographer-tracking-category' ); |
68 | } |
69 | |
70 | $interactive = $state['interactiveGroups']; |
71 | $state['interactiveGroups'] = array_keys( $state['interactiveGroups'] ); |
72 | $state['requestedGroups'] = array_keys( $state['requestedGroups'] ); |
73 | $state['parsoidIntVersion'] = |
74 | MediaWikiServices::getInstance()->getMainConfig()->get( 'KartographerParsoidVersion' ); |
75 | $extApi->getMetadata()->setExtensionData( 'kartographer', $state ); |
76 | |
77 | foreach ( $interactive as $req ) { |
78 | if ( !isset( $state['data'][$req] ) ) { |
79 | $state['data'][$req] = []; |
80 | } |
81 | } |
82 | $extApi->getMetadata()->setJsConfigVar( 'wgKartographerLiveData', $state['data'] ?? [] ); |
83 | } |
84 | |
85 | private function processKartographerNode( Element $kartnode, ParsoidExtensionAPI $extApi, array &$state ): void { |
86 | $tagName = $kartnode->getAttribute( 'data-mw-kartographer' ) ?? ''; |
87 | if ( $tagName !== '' ) { |
88 | $state[$tagName . 's' ]++; |
89 | } |
90 | |
91 | $markerStr = $kartnode->getAttribute( 'data-kart' ); |
92 | $kartnode->removeAttribute( 'data-kart' ); |
93 | if ( $markerStr === 'error' ) { |
94 | $state['broken']++; |
95 | return; |
96 | } |
97 | $marker = json_decode( $markerStr ?? '' ); |
98 | |
99 | $state['requestedGroups'] = array_merge( $state['requestedGroups'], $marker->showGroups ?? [] ); |
100 | if ( $tagName === ParsoidMapFrame::TAG ) { |
101 | $state['interactiveGroups'] = array_merge( $state['interactiveGroups'], $marker->showGroups ?? [] ); |
102 | } |
103 | |
104 | if ( !$marker || !$marker->geometries || !$marker->geometries[0] instanceof stdClass ) { |
105 | return; |
106 | } |
107 | [ $counter, $props ] = SimpleStyleParser::updateMarkerSymbolCounters( $marker->geometries, |
108 | $state['counters'] ); |
109 | if ( $tagName === ParsoidMapLink::TAG && $counter ) { |
110 | if ( !isset( DOMDataUtils::getDataMw( $kartnode )->attrs->text ) ) { |
111 | $text = $extApi->getTopLevelDoc()->createTextNode( $counter ); |
112 | $kartnode->replaceChild( $text, $kartnode->firstChild ); |
113 | } |
114 | } |
115 | |
116 | $data = $marker->geometries; |
117 | |
118 | if ( $counter ) { |
119 | // If we have a counter, we update the marker data prior to encoding the groupId, and we remove |
120 | // the (previously computed) groupId from showGroups |
121 | if ( ( $marker->groupId[0] ?? '' ) === '_' ) { |
122 | // TODO unclear if this is necessary or if we could simply set it to []. |
123 | $marker->showGroups = array_values( array_diff( $marker->showGroups, [ $marker->groupId ] ) ); |
124 | $marker->groupId = null; |
125 | } |
126 | } |
127 | |
128 | $groupId = $marker->groupId ?? null; |
129 | if ( $groupId === null ) { |
130 | // This hash calculation MUST be the same as in LegacyTagHandler::saveData |
131 | $groupId = '_' . sha1( FormatJson::encode( $marker->geometries, false, FormatJson::ALL_OK ) ); |
132 | $marker->groupId = $groupId; |
133 | $marker->showGroups[] = $groupId; |
134 | $kartnode->setAttribute( 'data-overlays', FormatJson::encode( $marker->showGroups ) ); |
135 | $img = $kartnode->firstChild; |
136 | // this should always be the case, but let make phan aware of it |
137 | if ( $img instanceof Element ) { |
138 | $this->updateSrc( $img, $groupId, $extApi ); |
139 | $this->updateSrcSet( $img, $groupId, $extApi ); |
140 | } |
141 | } |
142 | |
143 | // There is no way to ever add anything to a private group starting with `_` |
144 | if ( isset( $state['data'][$groupId] ) && !str_starts_with( $groupId, '_' ) ) { |
145 | $state['data'][$groupId] = array_merge( $state['data'][$groupId], $data ); |
146 | } else { |
147 | $state['data'][$groupId] = $data; |
148 | } |
149 | } |
150 | |
151 | private function updateSrc( Element $firstChild, string $groupId, ParsoidExtensionAPI $extApi ): void { |
152 | $src = $firstChild->getAttribute( 'src' ) ?? ''; |
153 | if ( $src !== '' ) { |
154 | $src = $this->updateUrl( $src, $extApi, $groupId ); |
155 | $firstChild->setAttribute( 'src', $src ); |
156 | } |
157 | } |
158 | |
159 | private function updateSrcSet( Element $firstChild, string $groupId, ParsoidExtensionAPI $extApi ): void { |
160 | $srcset = $firstChild->getAttribute( 'srcset' ) ?? ''; |
161 | if ( $srcset !== '' ) { |
162 | $arr = explode( ', ', $srcset ); |
163 | $srcsets = []; |
164 | foreach ( $arr as $plop ) { |
165 | $toks = explode( ' ', $plop ); |
166 | $toks[0] = $this->updateUrl( $toks[0], $extApi, $groupId ); |
167 | $srcsets[] = implode( ' ', $toks ); |
168 | } |
169 | $firstChild->setAttribute( 'srcset', implode( ', ', $srcsets ) ); |
170 | } |
171 | } |
172 | |
173 | private function updateUrl( string $src, ParsoidExtensionAPI $extApi, string $groupId ): string { |
174 | $url = explode( '?', $src ); |
175 | $attrs = wfCgiToArray( $url[1] ); |
176 | |
177 | $config = MediaWikiServices::getInstance()->getMainConfig(); |
178 | $linkTarget = $extApi->getPageConfig()->getLinkTarget(); |
179 | $pagetitle = Title::newFromLinkTarget( $linkTarget )->getPrefixedText(); |
180 | $revisionId = $extApi->getPageConfig()->getRevisionId(); |
181 | $attrs = array_merge( $attrs, |
182 | MapFrameAttributeGenerator::getUrlAttrs( $config, $pagetitle, $revisionId, [ $groupId ] ) |
183 | ); |
184 | |
185 | return $url[0] . '?' . wfArrayToCgi( $attrs ); |
186 | } |
187 | |
188 | } |