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