Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 193 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
CoreMagicVariables | |
0.00% |
0 / 192 |
|
0.00% |
0 / 3 |
8190 | |
0.00% |
0 / 1 |
expand | |
0.00% |
0 / 177 |
|
0.00% |
0 / 1 |
7656 | |||
makeTsLocal | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
applyUnitTimestampDeadline | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Magic variable implementations 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 DateTime; |
27 | use MediaWiki\Config\ServiceOptions; |
28 | use MediaWiki\MainConfigNames; |
29 | use MediaWiki\Specials\SpecialVersion; |
30 | use MediaWiki\Utils\MWTimestamp; |
31 | use Psr\Log\LoggerInterface; |
32 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
33 | |
34 | /** |
35 | * Expansions of core magic variables, used by the parser. |
36 | * @internal |
37 | * @ingroup Parser |
38 | */ |
39 | class CoreMagicVariables { |
40 | /** Map of (word ID => cache TTL hint) */ |
41 | private const CACHE_TTL_BY_ID = [ |
42 | 'currenttime' => 3600, |
43 | 'localtime' => 3600, |
44 | 'numberofarticles' => 3600, |
45 | 'numberoffiles' => 3600, |
46 | 'numberofedits' => 3600, |
47 | 'numberofusers' => 3600, |
48 | 'numberofactiveusers' => 3600, |
49 | 'numberofpages' => 3600, |
50 | 'currentversion' => 86400, |
51 | 'currenttimestamp' => 3600, |
52 | 'localtimestamp' => 3600, |
53 | 'pagesinnamespace' => 3600, |
54 | 'numberofadmins' => 3600, |
55 | 'numberingroup' => 3600, |
56 | ]; |
57 | |
58 | /** Map of (time unit => relative datetime specifier) */ |
59 | private const DEADLINE_DATE_SPEC_BY_UNIT = [ |
60 | 'Y' => 'first day of January next year midnight', |
61 | 'M' => 'first day of next month midnight', |
62 | 'D' => 'next day midnight', |
63 | // Note that this relative datetime specifier does not zero out |
64 | // minutes/seconds, but we will do so manually in |
65 | // ::applyUnitTimestampDeadline() when given the unit 'H' |
66 | 'H' => 'next hour' |
67 | ]; |
68 | /** Seconds of clock skew fudge factor for time-interval deadline TTLs */ |
69 | private const DEADLINE_TTL_CLOCK_FUDGE = 1; |
70 | /** Max seconds to "randomly" add to time-interval deadline TTLs to avoid stampedes */ |
71 | private const DEADLINE_TTL_STAGGER_MAX = 15; |
72 | /** Minimum time-interval deadline TTL */ |
73 | private const MIN_DEADLINE_TTL = 15; |
74 | |
75 | /** |
76 | * Expand the magic variable given by $index. |
77 | * @internal |
78 | * @param Parser $parser |
79 | * @param string $id The name of the variable, and equivalently, the magic |
80 | * word ID which was used to match the variable |
81 | * @param ConvertibleTimestamp $ts Timestamp to use when expanding magic variable |
82 | * @param ServiceOptions $svcOptions Service options for the parser |
83 | * @param LoggerInterface $logger |
84 | * @return string|null The expanded value, as wikitext, or null to |
85 | * indicate the given index wasn't a known magic variable. |
86 | */ |
87 | public static function expand( |
88 | // Fundamental options |
89 | Parser $parser, |
90 | string $id, |
91 | // Context passed over from the parser |
92 | ConvertibleTimestamp $ts, |
93 | ServiceOptions $svcOptions, |
94 | LoggerInterface $logger |
95 | ): ?string { |
96 | $pageLang = $parser->getTargetLanguage(); |
97 | |
98 | $cacheTTL = self::CACHE_TTL_BY_ID[$id] ?? -1; |
99 | if ( $cacheTTL > -1 ) { |
100 | $parser->getOutput()->updateCacheExpiry( $cacheTTL ); |
101 | } |
102 | |
103 | switch ( $id ) { |
104 | case '!': |
105 | return '|'; |
106 | case '=': |
107 | return '='; |
108 | case 'currentmonth': |
109 | self::applyUnitTimestampDeadline( $parser, $ts, 'M' ); |
110 | |
111 | return $pageLang->formatNumNoSeparators( $ts->format( 'm' ) ); |
112 | case 'currentmonth1': |
113 | self::applyUnitTimestampDeadline( $parser, $ts, 'M' ); |
114 | |
115 | return $pageLang->formatNumNoSeparators( $ts->format( 'n' ) ); |
116 | case 'currentmonthname': |
117 | self::applyUnitTimestampDeadline( $parser, $ts, 'M' ); |
118 | |
119 | return $pageLang->getMonthName( (int)$ts->format( 'n' ) ); |
120 | case 'currentmonthnamegen': |
121 | self::applyUnitTimestampDeadline( $parser, $ts, 'M' ); |
122 | |
123 | return $pageLang->getMonthNameGen( (int)$ts->format( 'n' ) ); |
124 | case 'currentmonthabbrev': |
125 | self::applyUnitTimestampDeadline( $parser, $ts, 'M' ); |
126 | |
127 | return $pageLang->getMonthAbbreviation( (int)$ts->format( 'n' ) ); |
128 | case 'currentday': |
129 | self::applyUnitTimestampDeadline( $parser, $ts, 'D' ); |
130 | |
131 | return $pageLang->formatNumNoSeparators( $ts->format( 'j' ) ); |
132 | case 'currentday2': |
133 | self::applyUnitTimestampDeadline( $parser, $ts, 'D' ); |
134 | |
135 | return $pageLang->formatNumNoSeparators( $ts->format( 'd' ) ); |
136 | case 'localmonth': |
137 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
138 | self::applyUnitTimestampDeadline( $parser, $localTs, 'M' ); |
139 | |
140 | return $pageLang->formatNumNoSeparators( $localTs->format( 'm' ) ); |
141 | case 'localmonth1': |
142 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
143 | self::applyUnitTimestampDeadline( $parser, $localTs, 'M' ); |
144 | |
145 | return $pageLang->formatNumNoSeparators( $localTs->format( 'n' ) ); |
146 | case 'localmonthname': |
147 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
148 | self::applyUnitTimestampDeadline( $parser, $localTs, 'M' ); |
149 | |
150 | return $pageLang->getMonthName( (int)$localTs->format( 'n' ) ); |
151 | case 'localmonthnamegen': |
152 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
153 | self::applyUnitTimestampDeadline( $parser, $localTs, 'M' ); |
154 | |
155 | return $pageLang->getMonthNameGen( (int)$localTs->format( 'n' ) ); |
156 | case 'localmonthabbrev': |
157 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
158 | self::applyUnitTimestampDeadline( $parser, $localTs, 'M' ); |
159 | |
160 | return $pageLang->getMonthAbbreviation( (int)$localTs->format( 'n' ) ); |
161 | case 'localday': |
162 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
163 | self::applyUnitTimestampDeadline( $parser, $localTs, 'D' ); |
164 | |
165 | return $pageLang->formatNumNoSeparators( $localTs->format( 'j' ) ); |
166 | case 'localday2': |
167 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
168 | self::applyUnitTimestampDeadline( $parser, $localTs, 'D' ); |
169 | |
170 | return $pageLang->formatNumNoSeparators( $localTs->format( 'd' ) ); |
171 | case 'pagename': |
172 | case 'pagenamee': |
173 | case 'fullpagename': |
174 | case 'fullpagenamee': |
175 | case 'subpagename': |
176 | case 'subpagenamee': |
177 | case 'rootpagename': |
178 | case 'rootpagenamee': |
179 | case 'basepagename': |
180 | case 'basepagenamee': |
181 | case 'talkpagename': |
182 | case 'talkpagenamee': |
183 | case 'subjectpagename': |
184 | case 'subjectpagenamee': |
185 | case 'pageid': |
186 | case 'revisionid': |
187 | case 'revisionuser': |
188 | case 'revisionday': |
189 | case 'revisionday2': |
190 | case 'revisionmonth': |
191 | case 'revisionmonth1': |
192 | case 'revisionyear': |
193 | case 'revisiontimestamp': |
194 | case 'namespace': |
195 | case 'namespacee': |
196 | case 'namespacenumber': |
197 | case 'talkspace': |
198 | case 'talkspacee': |
199 | case 'subjectspace': |
200 | case 'subjectspacee': |
201 | case 'cascadingsources': |
202 | # First argument of the corresponding parser function |
203 | # (second argument of the PHP implementation) is |
204 | # "title". |
205 | |
206 | # Note that for many of these {{FOO}} is subtly different |
207 | # from {{FOO:{{PAGENAME}}}}, so we can't pass $title here |
208 | # we have to explicitly use the "no arguments" form of the |
209 | # parser function by passing `null` to indicate a missing |
210 | # argument (which then defaults to the current page title). |
211 | return CoreParserFunctions::$id( $parser, null ); |
212 | case 'revisionsize': |
213 | return (string)$parser->getRevisionSize(); |
214 | case 'currentdayname': |
215 | self::applyUnitTimestampDeadline( $parser, $ts, 'D' ); |
216 | |
217 | return $pageLang->getWeekdayName( (int)$ts->format( 'w' ) + 1 ); |
218 | case 'currentyear': |
219 | self::applyUnitTimestampDeadline( $parser, $ts, 'Y' ); |
220 | |
221 | return $pageLang->formatNumNoSeparators( $ts->format( 'Y' ) ); |
222 | case 'currenttime': |
223 | return $pageLang->time( $ts->getTimestamp( TS_MW ), false, false ); |
224 | case 'currenthour': |
225 | self::applyUnitTimestampDeadline( $parser, $ts, 'H' ); |
226 | |
227 | return $pageLang->formatNumNoSeparators( $ts->format( 'H' ) ); |
228 | case 'currentweek': |
229 | self::applyUnitTimestampDeadline( $parser, $ts, 'D' ); |
230 | // @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to |
231 | // int to remove the padding |
232 | return $pageLang->formatNum( (int)$ts->format( 'W' ) ); |
233 | case 'currentdow': |
234 | self::applyUnitTimestampDeadline( $parser, $ts, 'D' ); |
235 | |
236 | return $pageLang->formatNum( $ts->format( 'w' ) ); |
237 | case 'localdayname': |
238 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
239 | self::applyUnitTimestampDeadline( $parser, $localTs, 'D' ); |
240 | |
241 | return $pageLang->getWeekdayName( (int)$localTs->format( 'w' ) + 1 ); |
242 | case 'localyear': |
243 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
244 | self::applyUnitTimestampDeadline( $parser, $localTs, 'Y' ); |
245 | |
246 | return $pageLang->formatNumNoSeparators( $localTs->format( 'Y' ) ); |
247 | case 'localtime': |
248 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
249 | |
250 | return $pageLang->time( |
251 | $localTs->format( 'YmdHis' ), |
252 | false, |
253 | false |
254 | ); |
255 | case 'localhour': |
256 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
257 | self::applyUnitTimestampDeadline( $parser, $localTs, 'H' ); |
258 | |
259 | return $pageLang->formatNumNoSeparators( $localTs->format( 'H' ) ); |
260 | case 'localweek': |
261 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
262 | self::applyUnitTimestampDeadline( $parser, $localTs, 'D' ); |
263 | // @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to |
264 | // int to remove the padding |
265 | return $pageLang->formatNum( (int)$localTs->format( 'W' ) ); |
266 | case 'localdow': |
267 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
268 | self::applyUnitTimestampDeadline( $parser, $localTs, 'D' ); |
269 | |
270 | return $pageLang->formatNum( $localTs->format( 'w' ) ); |
271 | case 'numberofarticles': |
272 | case 'numberoffiles': |
273 | case 'numberofusers': |
274 | case 'numberofactiveusers': |
275 | case 'numberofpages': |
276 | case 'numberofadmins': |
277 | case 'numberofedits': |
278 | # second argument is 'raw'; magic variables are "not raw" |
279 | return CoreParserFunctions::$id( $parser, null ); |
280 | case 'currenttimestamp': |
281 | return $ts->getTimestamp( TS_MW ); |
282 | case 'localtimestamp': |
283 | $localTs = self::makeTsLocal( $svcOptions, $ts ); |
284 | |
285 | return $localTs->format( 'YmdHis' ); |
286 | case 'currentversion': |
287 | return SpecialVersion::getVersion(); |
288 | case 'articlepath': |
289 | return (string)$svcOptions->get( MainConfigNames::ArticlePath ); |
290 | case 'sitename': |
291 | return (string)$svcOptions->get( MainConfigNames::Sitename ); |
292 | case 'server': |
293 | return (string)$svcOptions->get( MainConfigNames::Server ); |
294 | case 'servername': |
295 | return (string)$svcOptions->get( MainConfigNames::ServerName ); |
296 | case 'scriptpath': |
297 | return (string)$svcOptions->get( MainConfigNames::ScriptPath ); |
298 | case 'stylepath': |
299 | return (string)$svcOptions->get( MainConfigNames::StylePath ); |
300 | case 'directionmark': |
301 | return $pageLang->getDirMark(); |
302 | case 'contentlanguage': |
303 | return $parser->getContentLanguage()->getCode(); |
304 | case 'pagelanguage': |
305 | return $pageLang->getCode(); |
306 | case 'userlanguage': |
307 | if ( $svcOptions->get( MainConfigNames::ParserEnableUserLanguage ) ) { |
308 | return $parser->getOptions()->getUserLang(); |
309 | } else { |
310 | return $pageLang->getCode(); |
311 | } |
312 | case 'bcp47': |
313 | case 'dir': |
314 | case 'language': |
315 | # magic variables are the same as empty/default first argument |
316 | return CoreParserFunctions::$id( $parser ); |
317 | default: |
318 | // This is not one of the core magic variables |
319 | return null; |
320 | } |
321 | } |
322 | |
323 | /** |
324 | * Helper to convert a timestamp instance to local time |
325 | * @see MWTimestamp::getLocalInstance() |
326 | * @param ServiceOptions $svcOptions Service options for the parser |
327 | * @param ConvertibleTimestamp $ts Timestamp to convert |
328 | * @return ConvertibleTimestamp |
329 | */ |
330 | private static function makeTsLocal( $svcOptions, $ts ) { |
331 | $localtimezone = $svcOptions->get( MainConfigNames::Localtimezone ); |
332 | $ts->setTimezone( $localtimezone ); |
333 | return $ts; |
334 | } |
335 | |
336 | /** |
337 | * Adjust the cache expiry to account for a dynamic timestamp displayed in output |
338 | * |
339 | * @param Parser $parser |
340 | * @param ConvertibleTimestamp $ts Current timestamp with the display timezone |
341 | * @param string $unit The unit the timestamp is expressed in; one of ("Y", "M", "D", "H") |
342 | */ |
343 | private static function applyUnitTimestampDeadline( |
344 | Parser $parser, |
345 | ConvertibleTimestamp $ts, |
346 | string $unit |
347 | ) { |
348 | $tsUnix = (int)$ts->getTimestamp( TS_UNIX ); |
349 | |
350 | $date = new DateTime( "@$tsUnix" ); |
351 | $date->setTimezone( $ts->getTimezone() ); |
352 | $date->modify( self::DEADLINE_DATE_SPEC_BY_UNIT[$unit] ); |
353 | if ( $unit === 'H' ) { |
354 | // Zero out the minutes/seconds |
355 | $date->setTime( intval( $date->format( 'H' ), 10 ), 0, 0 ); |
356 | } else { |
357 | $date->setTime( 0, 0, 0 ); |
358 | } |
359 | $deadlineUnix = (int)$date->format( 'U' ); |
360 | |
361 | $ttl = max( $deadlineUnix - $tsUnix, self::MIN_DEADLINE_TTL ); |
362 | $ttl += self::DEADLINE_TTL_CLOCK_FUDGE; |
363 | $ttl += ( $tsUnix % self::DEADLINE_TTL_STAGGER_MAX ); |
364 | |
365 | $parser->getOutput()->updateCacheExpiry( $ttl ); |
366 | } |
367 | } |
368 | |
369 | /** @deprecated class alias since 1.43 */ |
370 | class_alias( CoreMagicVariables::class, 'CoreMagicVariables' ); |