Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 193
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
CoreMagicVariables
0.00% covered (danger)
0.00%
0 / 192
0.00% covered (danger)
0.00%
0 / 3
8190
0.00% covered (danger)
0.00%
0 / 1
 expand
0.00% covered (danger)
0.00%
0 / 177
0.00% covered (danger)
0.00%
0 / 1
7656
 makeTsLocal
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 applyUnitTimestampDeadline
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
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
24namespace MediaWiki\Parser;
25
26use DateTime;
27use MediaWiki\Config\ServiceOptions;
28use MediaWiki\MainConfigNames;
29use MediaWiki\Specials\SpecialVersion;
30use MediaWiki\Utils\MWTimestamp;
31use Psr\Log\LoggerInterface;
32use Wikimedia\Timestamp\ConvertibleTimestamp;
33
34/**
35 * Expansions of core magic variables, used by the parser.
36 * @internal
37 * @ingroup Parser
38 */
39class 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 */
370class_alias( CoreMagicVariables::class, 'CoreMagicVariables' );