Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.18% |
69 / 71 |
|
85.71% |
6 / 7 |
CRAP | |
0.00% |
0 / 1 |
CoreTagHooks | |
98.57% |
69 / 70 |
|
85.71% |
6 / 7 |
20 | |
0.00% |
0 / 1 |
register | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
pre | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
html | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
3.01 | |||
nowiki | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
gallery | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
indicator | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
langconvert | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
9 |
1 | <?php |
2 | /** |
3 | * Tag hooks provided by MediaWiki core |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup Parser |
22 | */ |
23 | |
24 | namespace MediaWiki\Parser; |
25 | |
26 | use MediaWiki\Config\ServiceOptions; |
27 | use MediaWiki\Html\Html; |
28 | use MediaWiki\Language\LanguageCode; |
29 | use MediaWiki\MainConfigNames; |
30 | use MediaWiki\MediaWikiServices; |
31 | use UnexpectedValueException; |
32 | use Wikimedia\StringUtils\StringUtils; |
33 | |
34 | /** |
35 | * Various tag hooks, registered in every Parser |
36 | * @ingroup Parser |
37 | */ |
38 | class CoreTagHooks { |
39 | |
40 | /** |
41 | * @internal |
42 | */ |
43 | public const REGISTER_OPTIONS = [ |
44 | // See documentation for the corresponding config options |
45 | MainConfigNames::RawHtml, |
46 | ]; |
47 | |
48 | /** |
49 | * @param Parser $parser |
50 | * @param ServiceOptions $options |
51 | * |
52 | * @return void |
53 | * @internal |
54 | */ |
55 | public static function register( Parser $parser, ServiceOptions $options ) { |
56 | $options->assertRequiredOptions( self::REGISTER_OPTIONS ); |
57 | $rawHtml = $options->get( MainConfigNames::RawHtml ); |
58 | $parser->setHook( 'pre', [ __CLASS__, 'pre' ] ); |
59 | $parser->setHook( 'nowiki', [ __CLASS__, 'nowiki' ] ); |
60 | $parser->setHook( 'gallery', [ __CLASS__, 'gallery' ] ); |
61 | $parser->setHook( 'indicator', [ __CLASS__, 'indicator' ] ); |
62 | $parser->setHook( 'langconvert', [ __CLASS__, 'langconvert' ] ); |
63 | if ( $rawHtml ) { |
64 | $parser->setHook( 'html', [ __CLASS__, 'html' ] ); |
65 | } |
66 | } |
67 | |
68 | /** |
69 | * Core parser tag hook function for 'pre'. |
70 | * Text is treated roughly as 'nowiki' wrapped in an HTML 'pre' tag; |
71 | * valid HTML attributes are passed on. |
72 | * |
73 | * @param ?string $content |
74 | * @param array $attribs |
75 | * @param Parser $parser |
76 | * @return string HTML |
77 | * @internal |
78 | */ |
79 | public static function pre( ?string $content, array $attribs, Parser $parser ): string { |
80 | // Backwards-compatibility hack |
81 | $content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $content ?? '', 'i' ); |
82 | |
83 | $attribs = array_map( static fn ( $s ) => $parser->killMarkers( $s ), $attribs ); |
84 | $attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' ); |
85 | // We need to let both '"' and '&' through, |
86 | // for strip markers and entities respectively. |
87 | $content = str_replace( |
88 | [ '>', '<' ], |
89 | [ '>', '<' ], |
90 | $content |
91 | ); |
92 | return Html::rawElement( 'pre', $attribs, $content ); |
93 | } |
94 | |
95 | /** |
96 | * Core parser tag hook function for 'html', used only when |
97 | * $wgRawHtml is enabled. |
98 | * |
99 | * This is potentially unsafe and should be used only in very careful |
100 | * circumstances, as the contents are emitted as raw HTML. |
101 | * |
102 | * Uses undocumented extended tag hook return values, introduced in r61913. |
103 | * |
104 | * @suppress SecurityCheck-XSS |
105 | * @param ?string $content |
106 | * @param array $attributes |
107 | * @param Parser $parser |
108 | * @return array|string Output of tag hook |
109 | * @internal |
110 | */ |
111 | public static function html( ?string $content, array $attributes, Parser $parser ) { |
112 | $rawHtml = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::RawHtml ); |
113 | if ( $rawHtml ) { |
114 | if ( $parser->getOptions()->getAllowUnsafeRawHtml() ) { |
115 | return [ $content ?? '', 'markerType' => 'nowiki' ]; |
116 | } else { |
117 | // In a system message where raw html is |
118 | // not allowed (but it is allowed in other |
119 | // contexts). |
120 | return Html::rawElement( |
121 | 'span', |
122 | [ 'class' => 'error' ], |
123 | // Using ->text() not ->parse() as |
124 | // a paranoia measure against a loop. |
125 | $parser->msg( 'rawhtml-notallowed' )->escaped() |
126 | ); |
127 | } |
128 | } else { |
129 | throw new UnexpectedValueException( '<html> extension tag encountered unexpectedly' ); |
130 | } |
131 | } |
132 | |
133 | /** |
134 | * Core parser tag hook function for 'nowiki'. Text within this section |
135 | * gets interpreted as a string of text with HTML-compatible character |
136 | * references, and wiki markup within it will not be expanded. |
137 | * |
138 | * Uses undocumented extended tag hook return values, introduced in r61913. |
139 | * |
140 | * Uses custom html escaping which phan-taint-check won't recognize |
141 | * hence we suppress the error. |
142 | * @suppress SecurityCheck-XSS |
143 | * |
144 | * @param ?string $content |
145 | * @param array $attributes |
146 | * @param Parser $parser |
147 | * @return array |
148 | * @internal |
149 | */ |
150 | public static function nowiki( ?string $content, array $attributes, Parser $parser ): array { |
151 | $content = strtr( $content ?? '', [ |
152 | // lang converter |
153 | '-{' => '-{', |
154 | '}-' => '}-', |
155 | // html tags |
156 | '<' => '<', |
157 | '>' => '>' |
158 | // Note: Both '"' and '&' are not converted. |
159 | // This allows strip markers and entities through. |
160 | ] ); |
161 | return [ $content, 'markerType' => 'nowiki' ]; |
162 | } |
163 | |
164 | /** |
165 | * Core parser tag hook function for 'gallery'. |
166 | * |
167 | * Renders a thumbnail list of the given images, with optional captions. |
168 | * Full syntax documented on the wiki: |
169 | * |
170 | * https://www.mediawiki.org/wiki/Help:Images#Gallery_syntax |
171 | * |
172 | * @todo break Parser::renderImageGallery out here too. |
173 | * |
174 | * @param ?string $content |
175 | * @param array $attributes |
176 | * @param Parser $parser |
177 | * @return string HTML |
178 | * @internal |
179 | */ |
180 | public static function gallery( ?string $content, array $attributes, Parser $parser ): string { |
181 | return $parser->renderImageGallery( $content ?? '', $attributes ); |
182 | } |
183 | |
184 | /** |
185 | * XML-style tag for page status indicators: icons (or short text snippets) usually displayed in |
186 | * the top-right corner of the page, outside of the main content. |
187 | * |
188 | * @param ?string $content |
189 | * @param array $attributes |
190 | * @param Parser $parser |
191 | * @param PPFrame $frame |
192 | * @return string |
193 | * @since 1.25 |
194 | * @internal |
195 | */ |
196 | public static function indicator( ?string $content, array $attributes, Parser $parser, PPFrame $frame ): string { |
197 | if ( !isset( $attributes['name'] ) || trim( $attributes['name'] ) === '' ) { |
198 | return '<span class="error">' . |
199 | $parser->msg( 'invalid-indicator-name' )->parse() . |
200 | '</span>'; |
201 | } |
202 | |
203 | $parser->getOutput()->setIndicator( |
204 | trim( $parser->killMarkers( $attributes['name'] ) ), |
205 | Parser::stripOuterParagraph( $parser->recursiveTagParseFully( $content ?? '', $frame ) ) |
206 | ); |
207 | |
208 | return ''; |
209 | } |
210 | |
211 | /** |
212 | * Returns content converted into the requested language variant, using LanguageConverter. |
213 | * |
214 | * @param ?string $content |
215 | * @param array $attributes |
216 | * @param Parser $parser |
217 | * @param PPFrame $frame |
218 | * @return string |
219 | * @since 1.36 |
220 | * @internal |
221 | */ |
222 | public static function langconvert( ?string $content, array $attributes, Parser $parser, PPFrame $frame ): string { |
223 | if ( isset( $attributes['from'] ) && isset( $attributes['to'] ) ) { |
224 | $fromArg = trim( $attributes['from'] ); |
225 | $toArg = trim( $attributes['to'] ); |
226 | $fromLangCode = explode( '-', $fromArg )[0]; |
227 | if ( $fromLangCode && $fromLangCode === explode( '-', $toArg )[0] ) { |
228 | $lang = MediaWikiServices::getInstance()->getLanguageFactory() |
229 | ->getLanguage( $fromLangCode ); |
230 | $converter = MediaWikiServices::getInstance()->getLanguageConverterFactory() |
231 | ->getLanguageConverter( $lang ); |
232 | |
233 | # ensure that variants are available, |
234 | # and the variants are valid BCP 47 codes |
235 | if ( $converter->hasVariants() |
236 | && strcasecmp( $fromArg, LanguageCode::bcp47( $fromArg ) ) === 0 |
237 | && strcasecmp( $toArg, LanguageCode::bcp47( $toArg ) ) === 0 |
238 | ) { |
239 | $toVariant = $converter->validateVariant( $toArg ); |
240 | |
241 | if ( $toVariant ) { |
242 | return $converter->autoConvert( |
243 | $parser->recursiveTagParse( $content ?? '', $frame ), |
244 | $toVariant |
245 | ); |
246 | } |
247 | } |
248 | } |
249 | } |
250 | |
251 | return Html::rawElement( |
252 | 'span', |
253 | [ 'class' => 'error' ], |
254 | $parser->msg( 'invalid-langconvert-attrs' )->parse() |
255 | ); |
256 | } |
257 | |
258 | } |
259 | |
260 | /** @deprecated class alias since 1.43 */ |
261 | class_alias( CoreTagHooks::class, 'CoreTagHooks' ); |