Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.59% |
73 / 78 |
|
57.14% |
4 / 7 |
CRAP | |
0.00% |
0 / 1 |
LegacyTagHandler | |
93.59% |
73 / 78 |
|
57.14% |
4 / 7 |
24.15 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
handle | |
97.06% |
33 / 34 |
|
0.00% |
0 / 1 |
6 | |||
render | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
saveData | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
4.01 | |||
finalParseStep | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
8 | |||
getTargetLanguage | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
3.47 | |||
getTargetLanguageCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOutput | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * |
4 | * @license MIT |
5 | * @file |
6 | * |
7 | * @author Yuri Astrakhan |
8 | */ |
9 | |
10 | namespace Kartographer\Tag; |
11 | |
12 | use FormatJson; |
13 | use Kartographer\ParserFunctionTracker; |
14 | use Kartographer\PartialWikitextParser; |
15 | use Kartographer\SimpleStyleParser; |
16 | use Kartographer\State; |
17 | use Language; |
18 | use MediaWiki\Config\Config; |
19 | use MediaWiki\Config\ConfigException; |
20 | use MediaWiki\Languages\LanguageNameUtils; |
21 | use MediaWiki\Logger\LoggerFactory; |
22 | use MediaWiki\Title\Title; |
23 | use Parser; |
24 | use PPFrame; |
25 | use Wikimedia\Parsoid\Core\ContentMetadataCollector; |
26 | |
27 | /** |
28 | * Base class for all <map...> tags |
29 | * |
30 | * @license MIT |
31 | */ |
32 | abstract class LegacyTagHandler { |
33 | |
34 | /** |
35 | * Lower case name of the XML-style parser tag, e.g. "mapframe". Currently expected to start |
36 | * with "map…" by the {@see State} class. |
37 | */ |
38 | public const TAG = ''; |
39 | |
40 | protected MapTagArgumentValidator $args; |
41 | protected Config $config; |
42 | protected Parser $parser; |
43 | private Language $targetLanguage; |
44 | private LanguageNameUtils $languageCodeValidator; |
45 | |
46 | public function __construct( |
47 | Config $config, |
48 | LanguageNameUtils $languageCodeValidator |
49 | ) { |
50 | $this->config = $config; |
51 | $this->languageCodeValidator = $languageCodeValidator; |
52 | } |
53 | |
54 | /** |
55 | * Entry point for all tags |
56 | * |
57 | * @param string|null $input |
58 | * @param array<string,string> $args |
59 | * @param Parser $parser |
60 | * @param PPFrame $frame |
61 | * @return string |
62 | */ |
63 | public function handle( ?string $input, array $args, Parser $parser, PPFrame $frame ): string { |
64 | $mapServer = $this->config->get( 'KartographerMapServer' ); |
65 | if ( !$mapServer ) { |
66 | throw new ConfigException( '$wgKartographerMapServer doesn\'t have a default, please set your own' ); |
67 | } |
68 | |
69 | $this->parser = $parser; |
70 | // Can only be StubUserLang on special pages, but these can't contain <map…> tags |
71 | $this->targetLanguage = $parser->getTargetLanguage(); |
72 | $options = $parser->getOptions(); |
73 | $isPreview = $options->getIsPreview() || $options->getIsSectionPreview(); |
74 | $parserOutput = $parser->getOutput(); |
75 | |
76 | $parserOutput->addModuleStyles( [ 'ext.kartographer.style' ] ); |
77 | $parserOutput->addExtraCSPDefaultSrc( $mapServer ); |
78 | $state = State::getOrCreate( $parserOutput ); |
79 | $state->incrementUsage( static::TAG ); |
80 | |
81 | $this->args = new MapTagArgumentValidator( |
82 | static::TAG, |
83 | $args, |
84 | $this->config, |
85 | $this->getTargetLanguage(), |
86 | $this->languageCodeValidator |
87 | ); |
88 | $status = $this->args->status; |
89 | $geometries = []; |
90 | if ( $status->isOK() ) { |
91 | $status = SimpleStyleParser::newFromParser( $parser, $frame )->parse( $input ); |
92 | if ( $status->isOK() ) { |
93 | $geometries = $status->getValue()['data']; |
94 | } |
95 | } |
96 | |
97 | if ( !$status->isGood() ) { |
98 | $state->incrementBrokenTags(); |
99 | State::setState( $parserOutput, $state ); |
100 | |
101 | $errorReporter = new ErrorReporter( $this->getTargetLanguageCode() ); |
102 | return $errorReporter->getHtml( $status, static::TAG ); |
103 | } |
104 | |
105 | $this->saveData( $state, $geometries ); |
106 | |
107 | $result = $this->render( new PartialWikitextParser( $parser, $frame ), !$isPreview ); |
108 | |
109 | State::setState( $parserOutput, $state ); |
110 | return $result; |
111 | } |
112 | |
113 | /** |
114 | * When overridden in a descendant class, returns tag HTML |
115 | * |
116 | * @param PartialWikitextParser $parser |
117 | * @param bool $serverMayRenderOverlays If the map server should attempt to render GeoJSON |
118 | * overlays via their group id |
119 | * @return string |
120 | */ |
121 | abstract protected function render( PartialWikitextParser $parser, bool $serverMayRenderOverlays ): string; |
122 | |
123 | protected function saveData( State $state, array $geometries ): void { |
124 | $state->addRequestedGroups( $this->args->showGroups ); |
125 | |
126 | if ( !$geometries ) { |
127 | return; |
128 | } |
129 | |
130 | // Merge existing data with the new tag's data under the same group name |
131 | |
132 | // For all GeoJSON items whose marker-symbol value begins with '-counter' and '-letter', |
133 | // recursively replace them with an automatically incremented marker icon. |
134 | $counters = $state->getCounters(); |
135 | $marker = SimpleStyleParser::updateMarkerSymbolCounters( $geometries, $counters ); |
136 | if ( $marker ) { |
137 | $this->args->setFirstMarkerProperties( ...$marker ); |
138 | } |
139 | $state->setCounters( $counters ); |
140 | |
141 | if ( $this->args->groupId === null ) { |
142 | // This hash calculation MUST be the same as in ParsoidDomProcessor::wtPostprocess |
143 | $groupId = '_' . sha1( FormatJson::encode( $geometries, false, FormatJson::ALL_OK ) ); |
144 | $this->args->groupId = $groupId; |
145 | $this->args->showGroups[] = $groupId; |
146 | // no need to array_unique() because it's impossible to manually add a private group |
147 | } else { |
148 | $groupId = (string)$this->args->groupId; |
149 | } |
150 | |
151 | $state->addData( $groupId, $geometries ); |
152 | } |
153 | |
154 | /** |
155 | * Handles the last step of parse process |
156 | * |
157 | * @param State $state |
158 | * @param ContentMetadataCollector $parserOutput |
159 | * @param bool $outputAllLiveData |
160 | * @param ParserFunctionTracker $tracker |
161 | */ |
162 | public static function finalParseStep( |
163 | State $state, |
164 | ContentMetadataCollector $parserOutput, |
165 | bool $outputAllLiveData, |
166 | ParserFunctionTracker $tracker |
167 | ): void { |
168 | foreach ( $state->getUsages() as $key => $count ) { |
169 | // Resulting page property names are "kartographer_links" and "kartographer_frames" |
170 | $name = 'kartographer_' . preg_replace( '/^map/', '', $key ); |
171 | $parserOutput->setNumericPageProperty( $name, $count ); |
172 | } |
173 | |
174 | $tracker->addTrackingCategories( [ |
175 | 'kartographer-broken-category' => $state->hasBrokenTags(), |
176 | 'kartographer-tracking-category' => $state->hasValidTags(), |
177 | ] ); |
178 | |
179 | // https://phabricator.wikimedia.org/T145615 - include all data in previews |
180 | $data = $state->getData(); |
181 | if ( $data && $outputAllLiveData ) { |
182 | $parserOutput->setJsConfigVar( 'wgKartographerLiveData', $data ); |
183 | } else { |
184 | $interact = $state->getInteractiveGroups(); |
185 | $requested = $state->getRequestedGroups(); |
186 | if ( $interact || $requested ) { |
187 | $liveData = array_intersect_key( $data, array_flip( $interact ) ); |
188 | // Prevent pointless API requests for missing groups |
189 | foreach ( $requested as $groupId ) { |
190 | if ( !isset( $data[$groupId] ) ) { |
191 | $liveData[$groupId] = []; |
192 | } |
193 | } |
194 | $parserOutput->setJsConfigVar( 'wgKartographerLiveData', (object)$liveData ); |
195 | } |
196 | } |
197 | } |
198 | |
199 | private function getTargetLanguage(): Language { |
200 | // Log if the user language is different from the page language (T311592) |
201 | $page = $this->parser->getPage(); |
202 | if ( $page ) { |
203 | $pageLang = Title::castFromPageReference( $page )->getPageLanguage(); |
204 | if ( $this->targetLanguage->getCode() !== $pageLang->getCode() ) { |
205 | LoggerFactory::getInstance( 'Kartographer' )->notice( 'Target language (' . |
206 | $this->targetLanguage->getCode() . ') is different than page language (' . |
207 | $pageLang->getCode() . ') (T311592)' ); |
208 | } |
209 | } |
210 | |
211 | return $this->targetLanguage; |
212 | } |
213 | |
214 | protected function getTargetLanguageCode(): string { |
215 | return $this->getTargetLanguage()->getCode(); |
216 | } |
217 | |
218 | protected function getOutput(): ContentMetadataCollector { |
219 | return $this->parser->getOutput(); |
220 | } |
221 | |
222 | } |