Merge "Check install user's password as sysop/bureaucrat"
[lhc/web/wiklou.git] / includes / parser / CoreParserFunctions.php
1 <?php
2 /**
3 * Parser functions 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 /**
25 * Various core parser functions, registered in Parser::firstCallInit()
26 * @ingroup Parser
27 */
28 class CoreParserFunctions {
29 /**
30 * @param Parser $parser
31 * @return void
32 */
33 public static function register( $parser ) {
34 global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
35
36 # Syntax for arguments (see Parser::setFunctionHook):
37 # "name for lookup in localized magic words array",
38 # function callback,
39 # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
40 # instead of {{#int:...}})
41 $noHashFunctions = array(
42 'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
43 'localurl', 'localurle', 'fullurl', 'fullurle', 'canonicalurl',
44 'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural', 'bidi',
45 'numberofpages', 'numberofusers', 'numberofactiveusers',
46 'numberofarticles', 'numberoffiles', 'numberofadmins',
47 'numberingroup', 'numberofedits', 'language',
48 'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
49 'pagesincategory', 'pagesize', 'protectionlevel',
50 'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
51 'subjectspace', 'subjectspacee', 'pagename', 'pagenamee',
52 'fullpagename', 'fullpagenamee', 'rootpagename', 'rootpagenamee',
53 'basepagename', 'basepagenamee', 'subpagename', 'subpagenamee',
54 'talkpagename', 'talkpagenamee', 'subjectpagename',
55 'subjectpagenamee', 'pageid', 'revisionid', 'revisionday',
56 'revisionday2', 'revisionmonth', 'revisionmonth1', 'revisionyear',
57 'revisiontimestamp', 'revisionuser', 'cascadingsources',
58 );
59 foreach ( $noHashFunctions as $func ) {
60 $parser->setFunctionHook( $func, array( __CLASS__, $func ), Parser::SFH_NO_HASH );
61 }
62
63 $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), Parser::SFH_NO_HASH );
64 $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), Parser::SFH_NO_HASH );
65 $parser->setFunctionHook( 'special', array( __CLASS__, 'special' ) );
66 $parser->setFunctionHook( 'speciale', array( __CLASS__, 'speciale' ) );
67 $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), Parser::SFH_OBJECT_ARGS );
68 $parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) );
69
70 if ( $wgAllowDisplayTitle ) {
71 $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), Parser::SFH_NO_HASH );
72 }
73 if ( $wgAllowSlowParserFunctions ) {
74 $parser->setFunctionHook(
75 'pagesinnamespace',
76 array( __CLASS__, 'pagesinnamespace' ),
77 Parser::SFH_NO_HASH
78 );
79 }
80 }
81
82 /**
83 * @param Parser $parser
84 * @param string $part1
85 * @return array
86 */
87 public static function intFunction( $parser, $part1 = '' /*, ... */ ) {
88 if ( strval( $part1 ) !== '' ) {
89 $args = array_slice( func_get_args(), 2 );
90 $message = wfMessage( $part1, $args )
91 ->inLanguage( $parser->getOptions()->getUserLangObj() )->plain();
92
93 return array( $message, 'noparse' => false );
94 } else {
95 return array( 'found' => false );
96 }
97 }
98
99 /**
100 * @param Parser $parser
101 * @param string $date
102 * @param string $defaultPref
103 *
104 * @return string
105 */
106 public static function formatDate( $parser, $date, $defaultPref = null ) {
107 $lang = $parser->getFunctionLang();
108 $df = DateFormatter::getInstance( $lang );
109
110 $date = trim( $date );
111
112 $pref = $parser->getOptions()->getDateFormat();
113
114 // Specify a different default date format other than the normal default
115 // if the user has 'default' for their setting
116 if ( $pref == 'default' && $defaultPref ) {
117 $pref = $defaultPref;
118 }
119
120 $date = $df->reformat( $pref, $date, array( 'match-whole' ) );
121 return $date;
122 }
123
124 public static function ns( $parser, $part1 = '' ) {
125 global $wgContLang;
126 if ( intval( $part1 ) || $part1 == "0" ) {
127 $index = intval( $part1 );
128 } else {
129 $index = $wgContLang->getNsIndex( str_replace( ' ', '_', $part1 ) );
130 }
131 if ( $index !== false ) {
132 return $wgContLang->getFormattedNsText( $index );
133 } else {
134 return array( 'found' => false );
135 }
136 }
137
138 public static function nse( $parser, $part1 = '' ) {
139 $ret = self::ns( $parser, $part1 );
140 if ( is_string( $ret ) ) {
141 $ret = wfUrlencode( str_replace( ' ', '_', $ret ) );
142 }
143 return $ret;
144 }
145
146 /**
147 * urlencodes a string according to one of three patterns: (bug 22474)
148 *
149 * By default (for HTTP "query" strings), spaces are encoded as '+'.
150 * Or to encode a value for the HTTP "path", spaces are encoded as '%20'.
151 * For links to "wiki"s, or similar software, spaces are encoded as '_',
152 *
153 * @param Parser $parser
154 * @param string $s The text to encode.
155 * @param string $arg (optional): The type of encoding.
156 * @return string
157 */
158 public static function urlencode( $parser, $s = '', $arg = null ) {
159 static $magicWords = null;
160 if ( is_null( $magicWords ) ) {
161 $magicWords = new MagicWordArray( array( 'url_path', 'url_query', 'url_wiki' ) );
162 }
163 switch ( $magicWords->matchStartToEnd( $arg ) ) {
164
165 // Encode as though it's a wiki page, '_' for ' '.
166 case 'url_wiki':
167 $func = 'wfUrlencode';
168 $s = str_replace( ' ', '_', $s );
169 break;
170
171 // Encode for an HTTP Path, '%20' for ' '.
172 case 'url_path':
173 $func = 'rawurlencode';
174 break;
175
176 // Encode for HTTP query, '+' for ' '.
177 case 'url_query':
178 default:
179 $func = 'urlencode';
180 }
181 // See T105242, where the choice to kill markers and various
182 // other options were discussed.
183 return $func( $parser->killMarkers( $s ) );
184 }
185
186 public static function lcfirst( $parser, $s = '' ) {
187 global $wgContLang;
188 return $wgContLang->lcfirst( $s );
189 }
190
191 public static function ucfirst( $parser, $s = '' ) {
192 global $wgContLang;
193 return $wgContLang->ucfirst( $s );
194 }
195
196 /**
197 * @param Parser $parser
198 * @param string $s
199 * @return string
200 */
201 public static function lc( $parser, $s = '' ) {
202 global $wgContLang;
203 return $parser->markerSkipCallback( $s, array( $wgContLang, 'lc' ) );
204 }
205
206 /**
207 * @param Parser $parser
208 * @param string $s
209 * @return string
210 */
211 public static function uc( $parser, $s = '' ) {
212 global $wgContLang;
213 return $parser->markerSkipCallback( $s, array( $wgContLang, 'uc' ) );
214 }
215
216 public static function localurl( $parser, $s = '', $arg = null ) {
217 return self::urlFunction( 'getLocalURL', $s, $arg );
218 }
219
220 public static function localurle( $parser, $s = '', $arg = null ) {
221 $temp = self::urlFunction( 'getLocalURL', $s, $arg );
222 if ( !is_string( $temp ) ) {
223 return $temp;
224 } else {
225 return htmlspecialchars( $temp );
226 }
227 }
228
229 public static function fullurl( $parser, $s = '', $arg = null ) {
230 return self::urlFunction( 'getFullURL', $s, $arg );
231 }
232
233 public static function fullurle( $parser, $s = '', $arg = null ) {
234 $temp = self::urlFunction( 'getFullURL', $s, $arg );
235 if ( !is_string( $temp ) ) {
236 return $temp;
237 } else {
238 return htmlspecialchars( $temp );
239 }
240 }
241
242 public static function canonicalurl( $parser, $s = '', $arg = null ) {
243 return self::urlFunction( 'getCanonicalURL', $s, $arg );
244 }
245
246 public static function canonicalurle( $parser, $s = '', $arg = null ) {
247 $temp = self::urlFunction( 'getCanonicalURL', $s, $arg );
248 if ( !is_string( $temp ) ) {
249 return $temp;
250 } else {
251 return htmlspecialchars( $temp );
252 }
253 }
254
255 public static function urlFunction( $func, $s = '', $arg = null ) {
256 $title = Title::newFromText( $s );
257 # Due to order of execution of a lot of bits, the values might be encoded
258 # before arriving here; if that's true, then the title can't be created
259 # and the variable will fail. If we can't get a decent title from the first
260 # attempt, url-decode and try for a second.
261 if ( is_null( $title ) ) {
262 $title = Title::newFromURL( urldecode( $s ) );
263 }
264 if ( !is_null( $title ) ) {
265 # Convert NS_MEDIA -> NS_FILE
266 if ( $title->getNamespace() == NS_MEDIA ) {
267 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
268 }
269 if ( !is_null( $arg ) ) {
270 $text = $title->$func( $arg );
271 } else {
272 $text = $title->$func();
273 }
274 return $text;
275 } else {
276 return array( 'found' => false );
277 }
278 }
279
280 /**
281 * @param Parser $parser
282 * @param string $num
283 * @param string $arg
284 * @return string
285 */
286 public static function formatnum( $parser, $num = '', $arg = null ) {
287 if ( self::matchAgainstMagicword( 'rawsuffix', $arg ) ) {
288 $func = array( $parser->getFunctionLang(), 'parseFormattedNumber' );
289 } elseif ( self::matchAgainstMagicword( 'nocommafysuffix', $arg ) ) {
290 $func = array( $parser->getFunctionLang(), 'formatNumNoSeparators' );
291 } else {
292 $func = array( $parser->getFunctionLang(), 'formatNum' );
293 }
294 return $parser->markerSkipCallback( $num, $func );
295 }
296
297 /**
298 * @param Parser $parser
299 * @param string $case
300 * @param string $word
301 * @return string
302 */
303 public static function grammar( $parser, $case = '', $word = '' ) {
304 $word = $parser->killMarkers( $word );
305 return $parser->getFunctionLang()->convertGrammar( $word, $case );
306 }
307
308 /**
309 * @param Parser $parser
310 * @param string $username
311 * @return string
312 */
313 public static function gender( $parser, $username ) {
314 $forms = array_slice( func_get_args(), 2 );
315
316 // Some shortcuts to avoid loading user data unnecessarily
317 if ( count( $forms ) === 0 ) {
318 return '';
319 } elseif ( count( $forms ) === 1 ) {
320 return $forms[0];
321 }
322
323 $username = trim( $username );
324
325 // default
326 $gender = User::getDefaultOption( 'gender' );
327
328 // allow prefix.
329 $title = Title::newFromText( $username );
330
331 if ( $title && $title->getNamespace() == NS_USER ) {
332 $username = $title->getText();
333 }
334
335 // check parameter, or use the ParserOptions if in interface message
336 $user = User::newFromName( $username );
337 if ( $user ) {
338 $gender = GenderCache::singleton()->getGenderOf( $user, __METHOD__ );
339 } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) {
340 $gender = GenderCache::singleton()->getGenderOf( $parser->getOptions()->getUser(), __METHOD__ );
341 }
342 $ret = $parser->getFunctionLang()->gender( $gender, $forms );
343 return $ret;
344 }
345
346 /**
347 * @param Parser $parser
348 * @param string $text
349 * @return string
350 */
351 public static function plural( $parser, $text = '' ) {
352 $forms = array_slice( func_get_args(), 2 );
353 $text = $parser->getFunctionLang()->parseFormattedNumber( $text );
354 settype( $text, ctype_digit( $text ) ? 'int' : 'float' );
355 return $parser->getFunctionLang()->convertPlural( $text, $forms );
356 }
357
358 /**
359 * @param Parser $parser
360 * @param string $text
361 * @return string
362 */
363 public static function bidi( $parser, $text = '' ) {
364 return $parser->getFunctionLang()->embedBidi( $text );
365 }
366
367 /**
368 * Override the title of the page when viewed, provided we've been given a
369 * title which will normalise to the canonical title
370 *
371 * @param Parser $parser Parent parser
372 * @param string $text Desired title text
373 * @param string $uarg
374 * @return string
375 */
376 public static function displaytitle( $parser, $text = '', $uarg = '' ) {
377 global $wgRestrictDisplayTitle;
378
379 static $magicWords = null;
380 if ( is_null( $magicWords ) ) {
381 $magicWords = new MagicWordArray( array( 'displaytitle_noerror', 'displaytitle_noreplace' ) );
382 }
383 $arg = $magicWords->matchStartToEnd( $uarg );
384
385 // parse a limited subset of wiki markup (just the single quote items)
386 $text = $parser->doQuotes( $text );
387
388 // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
389 $text = $parser->killMarkers( $text );
390
391 // list of disallowed tags for DISPLAYTITLE
392 // these will be escaped even though they are allowed in normal wiki text
393 $bad = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr',
394 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br' );
395
396 // disallow some styles that could be used to bypass $wgRestrictDisplayTitle
397 if ( $wgRestrictDisplayTitle ) {
398 $htmlTagsCallback = function ( &$params ) {
399 $decoded = Sanitizer::decodeTagAttributes( $params );
400
401 if ( isset( $decoded['style'] ) ) {
402 // this is called later anyway, but we need it right now for the regexes below to be safe
403 // calling it twice doesn't hurt
404 $decoded['style'] = Sanitizer::checkCss( $decoded['style'] );
405
406 if ( preg_match( '/(display|user-select|visibility)\s*:/i', $decoded['style'] ) ) {
407 $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */';
408 }
409 }
410
411 $params = Sanitizer::safeEncodeTagAttributes( $decoded );
412 };
413 } else {
414 $htmlTagsCallback = null;
415 }
416
417 // only requested titles that normalize to the actual title are allowed through
418 // if $wgRestrictDisplayTitle is true (it is by default)
419 // mimic the escaping process that occurs in OutputPage::setPageTitle
420 $text = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags(
421 $text,
422 $htmlTagsCallback,
423 array(),
424 array(),
425 $bad
426 ) );
427 $title = Title::newFromText( Sanitizer::stripAllTags( $text ) );
428
429 if ( !$wgRestrictDisplayTitle ||
430 ( $title instanceof Title
431 && !$title->hasFragment()
432 && $title->equals( $parser->mTitle ) )
433 ) {
434 $old = $parser->mOutput->getProperty( 'displaytitle' );
435 if ( $old === false || $arg !== 'displaytitle_noreplace' ) {
436 $parser->mOutput->setDisplayTitle( $text );
437 }
438 if ( $old !== false && $old !== $text && !$arg ) {
439 $converter = $parser->getConverterLanguage()->getConverter();
440 return '<span class="error">' .
441 wfMessage( 'duplicate-displaytitle',
442 // Message should be parsed, but these params should only be escaped.
443 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
444 $converter->markNoConversion( wfEscapeWikiText( $text ) )
445 )->inContentLanguage()->text() .
446 '</span>';
447 }
448 }
449
450 return '';
451 }
452
453 /**
454 * Matches the given value against the value of given magic word
455 *
456 * @param string $magicword Magic word key
457 * @param string $value Value to match
458 * @return bool True on successful match
459 */
460 private static function matchAgainstMagicword( $magicword, $value ) {
461 $value = trim( strval( $value ) );
462 if ( $value === '' ) {
463 return false;
464 }
465 $mwObject = MagicWord::get( $magicword );
466 return $mwObject->matchStartToEnd( $value );
467 }
468
469 public static function formatRaw( $num, $raw ) {
470 if ( self::matchAgainstMagicword( 'rawsuffix', $raw ) ) {
471 return $num;
472 } else {
473 global $wgContLang;
474 return $wgContLang->formatNum( $num );
475 }
476 }
477 public static function numberofpages( $parser, $raw = null ) {
478 return self::formatRaw( SiteStats::pages(), $raw );
479 }
480 public static function numberofusers( $parser, $raw = null ) {
481 return self::formatRaw( SiteStats::users(), $raw );
482 }
483 public static function numberofactiveusers( $parser, $raw = null ) {
484 return self::formatRaw( SiteStats::activeUsers(), $raw );
485 }
486 public static function numberofarticles( $parser, $raw = null ) {
487 return self::formatRaw( SiteStats::articles(), $raw );
488 }
489 public static function numberoffiles( $parser, $raw = null ) {
490 return self::formatRaw( SiteStats::images(), $raw );
491 }
492 public static function numberofadmins( $parser, $raw = null ) {
493 return self::formatRaw( SiteStats::numberingroup( 'sysop' ), $raw );
494 }
495 public static function numberofedits( $parser, $raw = null ) {
496 return self::formatRaw( SiteStats::edits(), $raw );
497 }
498 public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
499 return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw );
500 }
501 public static function numberingroup( $parser, $name = '', $raw = null ) {
502 return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw );
503 }
504
505 /**
506 * Given a title, return the namespace name that would be given by the
507 * corresponding magic word
508 * Note: function name changed to "mwnamespace" rather than "namespace"
509 * to not break PHP 5.3
510 * @param Parser $parser
511 * @param string $title
512 * @return mixed|string
513 */
514 public static function mwnamespace( $parser, $title = null ) {
515 $t = Title::newFromText( $title );
516 if ( is_null( $t ) ) {
517 return '';
518 }
519 return str_replace( '_', ' ', $t->getNsText() );
520 }
521 public static function namespacee( $parser, $title = null ) {
522 $t = Title::newFromText( $title );
523 if ( is_null( $t ) ) {
524 return '';
525 }
526 return wfUrlencode( $t->getNsText() );
527 }
528 public static function namespacenumber( $parser, $title = null ) {
529 $t = Title::newFromText( $title );
530 if ( is_null( $t ) ) {
531 return '';
532 }
533 return $t->getNamespace();
534 }
535 public static function talkspace( $parser, $title = null ) {
536 $t = Title::newFromText( $title );
537 if ( is_null( $t ) || !$t->canTalk() ) {
538 return '';
539 }
540 return str_replace( '_', ' ', $t->getTalkNsText() );
541 }
542 public static function talkspacee( $parser, $title = null ) {
543 $t = Title::newFromText( $title );
544 if ( is_null( $t ) || !$t->canTalk() ) {
545 return '';
546 }
547 return wfUrlencode( $t->getTalkNsText() );
548 }
549 public static function subjectspace( $parser, $title = null ) {
550 $t = Title::newFromText( $title );
551 if ( is_null( $t ) ) {
552 return '';
553 }
554 return str_replace( '_', ' ', $t->getSubjectNsText() );
555 }
556 public static function subjectspacee( $parser, $title = null ) {
557 $t = Title::newFromText( $title );
558 if ( is_null( $t ) ) {
559 return '';
560 }
561 return wfUrlencode( $t->getSubjectNsText() );
562 }
563
564 /**
565 * Functions to get and normalize pagenames, corresponding to the magic words
566 * of the same names
567 * @param Parser $parser
568 * @param string $title
569 * @return string
570 */
571 public static function pagename( $parser, $title = null ) {
572 $t = Title::newFromText( $title );
573 if ( is_null( $t ) ) {
574 return '';
575 }
576 return wfEscapeWikiText( $t->getText() );
577 }
578 public static function pagenamee( $parser, $title = null ) {
579 $t = Title::newFromText( $title );
580 if ( is_null( $t ) ) {
581 return '';
582 }
583 return wfEscapeWikiText( $t->getPartialURL() );
584 }
585 public static function fullpagename( $parser, $title = null ) {
586 $t = Title::newFromText( $title );
587 if ( is_null( $t ) || !$t->canTalk() ) {
588 return '';
589 }
590 return wfEscapeWikiText( $t->getPrefixedText() );
591 }
592 public static function fullpagenamee( $parser, $title = null ) {
593 $t = Title::newFromText( $title );
594 if ( is_null( $t ) || !$t->canTalk() ) {
595 return '';
596 }
597 return wfEscapeWikiText( $t->getPrefixedURL() );
598 }
599 public static function subpagename( $parser, $title = null ) {
600 $t = Title::newFromText( $title );
601 if ( is_null( $t ) ) {
602 return '';
603 }
604 return wfEscapeWikiText( $t->getSubpageText() );
605 }
606 public static function subpagenamee( $parser, $title = null ) {
607 $t = Title::newFromText( $title );
608 if ( is_null( $t ) ) {
609 return '';
610 }
611 return wfEscapeWikiText( $t->getSubpageUrlForm() );
612 }
613 public static function rootpagename( $parser, $title = null ) {
614 $t = Title::newFromText( $title );
615 if ( is_null( $t ) ) {
616 return '';
617 }
618 return wfEscapeWikiText( $t->getRootText() );
619 }
620 public static function rootpagenamee( $parser, $title = null ) {
621 $t = Title::newFromText( $title );
622 if ( is_null( $t ) ) {
623 return '';
624 }
625 return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getRootText() ) ) );
626 }
627 public static function basepagename( $parser, $title = null ) {
628 $t = Title::newFromText( $title );
629 if ( is_null( $t ) ) {
630 return '';
631 }
632 return wfEscapeWikiText( $t->getBaseText() );
633 }
634 public static function basepagenamee( $parser, $title = null ) {
635 $t = Title::newFromText( $title );
636 if ( is_null( $t ) ) {
637 return '';
638 }
639 return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) ) );
640 }
641 public static function talkpagename( $parser, $title = null ) {
642 $t = Title::newFromText( $title );
643 if ( is_null( $t ) || !$t->canTalk() ) {
644 return '';
645 }
646 return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() );
647 }
648 public static function talkpagenamee( $parser, $title = null ) {
649 $t = Title::newFromText( $title );
650 if ( is_null( $t ) || !$t->canTalk() ) {
651 return '';
652 }
653 return wfEscapeWikiText( $t->getTalkPage()->getPrefixedURL() );
654 }
655 public static function subjectpagename( $parser, $title = null ) {
656 $t = Title::newFromText( $title );
657 if ( is_null( $t ) ) {
658 return '';
659 }
660 return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() );
661 }
662 public static function subjectpagenamee( $parser, $title = null ) {
663 $t = Title::newFromText( $title );
664 if ( is_null( $t ) ) {
665 return '';
666 }
667 return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedURL() );
668 }
669
670 /**
671 * Return the number of pages, files or subcats in the given category,
672 * or 0 if it's nonexistent. This is an expensive parser function and
673 * can't be called too many times per page.
674 * @param Parser $parser
675 * @param string $name
676 * @param string $arg1
677 * @param string $arg2
678 * @return string
679 */
680 public static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) {
681 global $wgContLang;
682 static $magicWords = null;
683 if ( is_null( $magicWords ) ) {
684 $magicWords = new MagicWordArray( array(
685 'pagesincategory_all',
686 'pagesincategory_pages',
687 'pagesincategory_subcats',
688 'pagesincategory_files'
689 ) );
690 }
691 static $cache = array();
692
693 // split the given option to its variable
694 if ( self::matchAgainstMagicword( 'rawsuffix', $arg1 ) ) {
695 //{{pagesincategory:|raw[|type]}}
696 $raw = $arg1;
697 $type = $magicWords->matchStartToEnd( $arg2 );
698 } else {
699 //{{pagesincategory:[|type[|raw]]}}
700 $type = $magicWords->matchStartToEnd( $arg1 );
701 $raw = $arg2;
702 }
703 if ( !$type ) { //backward compatibility
704 $type = 'pagesincategory_all';
705 }
706
707 $title = Title::makeTitleSafe( NS_CATEGORY, $name );
708 if ( !$title ) { # invalid title
709 return self::formatRaw( 0, $raw );
710 }
711 $wgContLang->findVariantLink( $name, $title, true );
712
713 // Normalize name for cache
714 $name = $title->getDBkey();
715
716 if ( !isset( $cache[$name] ) ) {
717 $category = Category::newFromTitle( $title );
718
719 $allCount = $subcatCount = $fileCount = $pagesCount = 0;
720 if ( $parser->incrementExpensiveFunctionCount() ) {
721 // $allCount is the total number of cat members,
722 // not the count of how many members are normal pages.
723 $allCount = (int)$category->getPageCount();
724 $subcatCount = (int)$category->getSubcatCount();
725 $fileCount = (int)$category->getFileCount();
726 $pagesCount = $allCount - $subcatCount - $fileCount;
727 }
728 $cache[$name]['pagesincategory_all'] = $allCount;
729 $cache[$name]['pagesincategory_pages'] = $pagesCount;
730 $cache[$name]['pagesincategory_subcats'] = $subcatCount;
731 $cache[$name]['pagesincategory_files'] = $fileCount;
732 }
733
734 $count = $cache[$name][$type];
735 return self::formatRaw( $count, $raw );
736 }
737
738 /**
739 * Return the size of the given page, or 0 if it's nonexistent. This is an
740 * expensive parser function and can't be called too many times per page.
741 *
742 * @param Parser $parser
743 * @param string $page Name of page to check (Default: empty string)
744 * @param string $raw Should number be human readable with commas or just number
745 * @return string
746 */
747 public static function pagesize( $parser, $page = '', $raw = null ) {
748 $title = Title::newFromText( $page );
749
750 if ( !is_object( $title ) ) {
751 return self::formatRaw( 0, $raw );
752 }
753
754 // fetch revision from cache/database and return the value
755 $rev = self::getCachedRevisionObject( $parser, $title );
756 $length = $rev ? $rev->getSize() : 0;
757 return self::formatRaw( $length, $raw );
758 }
759
760 /**
761 * Returns the requested protection level for the current page. This
762 * is an expensive parser function and can't be called too many times
763 * per page, unless the protection levels for the given title have
764 * already been retrieved
765 *
766 * @param Parser $parser
767 * @param string $type
768 * @param string $title
769 *
770 * @return string
771 */
772 public static function protectionlevel( $parser, $type = '', $title = '' ) {
773 $titleObject = Title::newFromText( $title );
774 if ( !( $titleObject instanceof Title ) ) {
775 $titleObject = $parser->mTitle;
776 }
777 if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
778 $restrictions = $titleObject->getRestrictions( strtolower( $type ) );
779 # Title::getRestrictions returns an array, its possible it may have
780 # multiple values in the future
781 return implode( $restrictions, ',' );
782 }
783 return '';
784 }
785
786 /**
787 * Gives language names.
788 * @param Parser $parser
789 * @param string $code Language code (of which to get name)
790 * @param string $inLanguage Language code (in which to get name)
791 * @return string
792 */
793 public static function language( $parser, $code = '', $inLanguage = '' ) {
794 $code = strtolower( $code );
795 $inLanguage = strtolower( $inLanguage );
796 $lang = Language::fetchLanguageName( $code, $inLanguage );
797 return $lang !== '' ? $lang : wfBCP47( $code );
798 }
799
800 /**
801 * Unicode-safe str_pad with the restriction that $length is forced to be <= 500
802 * @param Parser $parser
803 * @param string $string
804 * @param int $length
805 * @param string $padding
806 * @param int $direction
807 * @return string
808 */
809 public static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
810 $padding = $parser->killMarkers( $padding );
811 $lengthOfPadding = mb_strlen( $padding );
812 if ( $lengthOfPadding == 0 ) {
813 return $string;
814 }
815
816 # The remaining length to add counts down to 0 as padding is added
817 $length = min( $length, 500 ) - mb_strlen( $string );
818 # $finalPadding is just $padding repeated enough times so that
819 # mb_strlen( $string ) + mb_strlen( $finalPadding ) == $length
820 $finalPadding = '';
821 while ( $length > 0 ) {
822 # If $length < $lengthofPadding, truncate $padding so we get the
823 # exact length desired.
824 $finalPadding .= mb_substr( $padding, 0, $length );
825 $length -= $lengthOfPadding;
826 }
827
828 if ( $direction == STR_PAD_LEFT ) {
829 return $finalPadding . $string;
830 } else {
831 return $string . $finalPadding;
832 }
833 }
834
835 public static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) {
836 return self::pad( $parser, $string, $length, $padding, STR_PAD_LEFT );
837 }
838
839 public static function padright( $parser, $string = '', $length = 0, $padding = '0' ) {
840 return self::pad( $parser, $string, $length, $padding );
841 }
842
843 /**
844 * @param Parser $parser
845 * @param string $text
846 * @return string
847 */
848 public static function anchorencode( $parser, $text ) {
849 $text = $parser->killMarkers( $text );
850 return (string)substr( $parser->guessSectionNameFromWikiText( $text ), 1 );
851 }
852
853 public static function special( $parser, $text ) {
854 list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text );
855 if ( $page ) {
856 $title = SpecialPage::getTitleFor( $page, $subpage );
857 return $title->getPrefixedText();
858 } else {
859 // unknown special page, just use the given text as its title, if at all possible
860 $title = Title::makeTitleSafe( NS_SPECIAL, $text );
861 return $title ? $title->getPrefixedText() : self::special( $parser, 'Badtitle' );
862 }
863 }
864
865 public static function speciale( $parser, $text ) {
866 return wfUrlencode( str_replace( ' ', '_', self::special( $parser, $text ) ) );
867 }
868
869 /**
870 * @param Parser $parser
871 * @param string $text The sortkey to use
872 * @param string $uarg Either "noreplace" or "noerror" (in en)
873 * both suppress errors, and noreplace does nothing if
874 * a default sortkey already exists.
875 * @return string
876 */
877 public static function defaultsort( $parser, $text, $uarg = '' ) {
878 static $magicWords = null;
879 if ( is_null( $magicWords ) ) {
880 $magicWords = new MagicWordArray( array( 'defaultsort_noerror', 'defaultsort_noreplace' ) );
881 }
882 $arg = $magicWords->matchStartToEnd( $uarg );
883
884 $text = trim( $text );
885 if ( strlen( $text ) == 0 ) {
886 return '';
887 }
888 $old = $parser->getCustomDefaultSort();
889 if ( $old === false || $arg !== 'defaultsort_noreplace' ) {
890 $parser->setDefaultSort( $text );
891 }
892
893 if ( $old === false || $old == $text || $arg ) {
894 return '';
895 } else {
896 $converter = $parser->getConverterLanguage()->getConverter();
897 return '<span class="error">' .
898 wfMessage( 'duplicate-defaultsort',
899 // Message should be parsed, but these params should only be escaped.
900 $converter->markNoConversion( wfEscapeWikiText( $old ) ),
901 $converter->markNoConversion( wfEscapeWikiText( $text ) )
902 )->inContentLanguage()->text() .
903 '</span>';
904 }
905 }
906
907 /**
908 * Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}}
909 * or {{filepath|300|nowiki}} or {{filepath|300px}}, {{filepath|200x300px}},
910 * {{filepath|nowiki|200x300px}}, {{filepath|200x300px|nowiki}}.
911 *
912 * @param Parser $parser
913 * @param string $name
914 * @param string $argA
915 * @param string $argB
916 * @return array|string
917 */
918 public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
919 $file = wfFindFile( $name );
920
921 if ( $argA == 'nowiki' ) {
922 // {{filepath: | option [| size] }}
923 $isNowiki = true;
924 $parsedWidthParam = $parser->parseWidthParam( $argB );
925 } else {
926 // {{filepath: [| size [|option]] }}
927 $parsedWidthParam = $parser->parseWidthParam( $argA );
928 $isNowiki = ( $argB == 'nowiki' );
929 }
930
931 if ( $file ) {
932 $url = $file->getFullUrl();
933
934 // If a size is requested...
935 if ( count( $parsedWidthParam ) ) {
936 $mto = $file->transform( $parsedWidthParam );
937 // ... and we can
938 if ( $mto && !$mto->isError() ) {
939 // ... change the URL to point to a thumbnail.
940 $url = wfExpandUrl( $mto->getUrl(), PROTO_RELATIVE );
941 }
942 }
943 if ( $isNowiki ) {
944 return array( $url, 'nowiki' => true );
945 }
946 return $url;
947 } else {
948 return '';
949 }
950 }
951
952 /**
953 * Parser function to extension tag adaptor
954 * @param Parser $parser
955 * @param PPFrame $frame
956 * @param PPNode[] $args
957 * @return string
958 */
959 public static function tagObj( $parser, $frame, $args ) {
960 if ( !count( $args ) ) {
961 return '';
962 }
963 $tagName = strtolower( trim( $frame->expand( array_shift( $args ) ) ) );
964
965 if ( count( $args ) ) {
966 $inner = $frame->expand( array_shift( $args ) );
967 } else {
968 $inner = null;
969 }
970
971 $attributes = array();
972 foreach ( $args as $arg ) {
973 $bits = $arg->splitArg();
974 if ( strval( $bits['index'] ) === '' ) {
975 $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
976 $value = trim( $frame->expand( $bits['value'] ) );
977 if ( preg_match( '/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m ) ) {
978 $value = isset( $m[1] ) ? $m[1] : '';
979 }
980 $attributes[$name] = $value;
981 }
982 }
983
984 $stripList = $parser->getStripList();
985 if ( !in_array( $tagName, $stripList ) ) {
986 // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
987 $attrText = '';
988 foreach ( $attributes as $name => $value ) {
989 $attrText .= ' ' . htmlspecialchars( $name ) . '="' . htmlspecialchars( $value ) . '"';
990 }
991 if ( $inner === null ) {
992 return "<$tagName$attrText/>";
993 }
994 return "<$tagName$attrText>$inner</$tagName>";
995 }
996
997 $params = array(
998 'name' => $tagName,
999 'inner' => $inner,
1000 'attributes' => $attributes,
1001 'close' => "</$tagName>",
1002 );
1003 return $parser->extensionSubstitution( $params, $frame );
1004 }
1005
1006 /**
1007 * Fetched the current revision of the given title and return this.
1008 * Will increment the expensive function count and
1009 * add a template link to get the value refreshed on changes.
1010 * For a given title, which is equal to the current parser title,
1011 * the revision object from the parser is used, when that is the current one
1012 *
1013 * @param Parser $parser
1014 * @param Title $title
1015 * @return Revision
1016 * @since 1.23
1017 */
1018 private static function getCachedRevisionObject( $parser, $title = null ) {
1019 if ( is_null( $title ) ) {
1020 return null;
1021 }
1022
1023 // Use the revision from the parser itself, when param is the current page
1024 // and the revision is the current one
1025 if ( $title->equals( $parser->getTitle() ) ) {
1026 $parserRev = $parser->getRevisionObject();
1027 if ( $parserRev && $parserRev->isCurrent() ) {
1028 // force reparse after edit with vary-revision flag
1029 $parser->getOutput()->setFlag( 'vary-revision' );
1030 wfDebug( __METHOD__ . ": use current revision from parser, setting vary-revision...\n" );
1031 return $parserRev;
1032 }
1033 }
1034
1035 // Normalize name for cache
1036 $page = $title->getPrefixedDBkey();
1037
1038 if ( !( $parser->currentRevisionCache && $parser->currentRevisionCache->has( $page ) )
1039 && !$parser->incrementExpensiveFunctionCount() ) {
1040 return null;
1041 }
1042 $rev = $parser->fetchCurrentRevisionOfTitle( $title );
1043 $pageID = $rev ? $rev->getPage() : 0;
1044 $revID = $rev ? $rev->getId() : 0;
1045
1046 // Register dependency in templatelinks
1047 $parser->getOutput()->addTemplate( $title, $pageID, $revID );
1048
1049 return $rev;
1050 }
1051
1052 /**
1053 * Get the pageid of a specified page
1054 * @param Parser $parser
1055 * @param string $title Title to get the pageid from
1056 * @return int|null|string
1057 * @since 1.23
1058 */
1059 public static function pageid( $parser, $title = null ) {
1060 $t = Title::newFromText( $title );
1061 if ( is_null( $t ) ) {
1062 return '';
1063 }
1064 // Use title from parser to have correct pageid after edit
1065 if ( $t->equals( $parser->getTitle() ) ) {
1066 $t = $parser->getTitle();
1067 return $t->getArticleID();
1068 }
1069
1070 // These can't have ids
1071 if ( !$t->canExist() || $t->isExternal() ) {
1072 return 0;
1073 }
1074
1075 // Check the link cache, maybe something already looked it up.
1076 $linkCache = LinkCache::singleton();
1077 $pdbk = $t->getPrefixedDBkey();
1078 $id = $linkCache->getGoodLinkID( $pdbk );
1079 if ( $id != 0 ) {
1080 $parser->mOutput->addLink( $t, $id );
1081 return $id;
1082 }
1083 if ( $linkCache->isBadLink( $pdbk ) ) {
1084 $parser->mOutput->addLink( $t, 0 );
1085 return $id;
1086 }
1087
1088 // We need to load it from the DB, so mark expensive
1089 if ( $parser->incrementExpensiveFunctionCount() ) {
1090 $id = $t->getArticleID();
1091 $parser->mOutput->addLink( $t, $id );
1092 return $id;
1093 }
1094 return null;
1095 }
1096
1097 /**
1098 * Get the id from the last revision of a specified page.
1099 * @param Parser $parser
1100 * @param string $title Title to get the id from
1101 * @return int|null|string
1102 * @since 1.23
1103 */
1104 public static function revisionid( $parser, $title = null ) {
1105 $t = Title::newFromText( $title );
1106 if ( is_null( $t ) ) {
1107 return '';
1108 }
1109 // fetch revision from cache/database and return the value
1110 $rev = self::getCachedRevisionObject( $parser, $t );
1111 return $rev ? $rev->getId() : '';
1112 }
1113
1114 /**
1115 * Get the day from the last revision of a specified page.
1116 * @param Parser $parser
1117 * @param string $title Title to get the day from
1118 * @return string
1119 * @since 1.23
1120 */
1121 public static function revisionday( $parser, $title = null ) {
1122 $t = Title::newFromText( $title );
1123 if ( is_null( $t ) ) {
1124 return '';
1125 }
1126 // fetch revision from cache/database and return the value
1127 $rev = self::getCachedRevisionObject( $parser, $t );
1128 return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'j' ) : '';
1129 }
1130
1131 /**
1132 * Get the day with leading zeros from the last revision of a specified page.
1133 * @param Parser $parser
1134 * @param string $title Title to get the day from
1135 * @return string
1136 * @since 1.23
1137 */
1138 public static function revisionday2( $parser, $title = null ) {
1139 $t = Title::newFromText( $title );
1140 if ( is_null( $t ) ) {
1141 return '';
1142 }
1143 // fetch revision from cache/database and return the value
1144 $rev = self::getCachedRevisionObject( $parser, $t );
1145 return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'd' ) : '';
1146 }
1147
1148 /**
1149 * Get the month with leading zeros from the last revision of a specified page.
1150 * @param Parser $parser
1151 * @param string $title Title to get the month from
1152 * @return string
1153 * @since 1.23
1154 */
1155 public static function revisionmonth( $parser, $title = null ) {
1156 $t = Title::newFromText( $title );
1157 if ( is_null( $t ) ) {
1158 return '';
1159 }
1160 // fetch revision from cache/database and return the value
1161 $rev = self::getCachedRevisionObject( $parser, $t );
1162 return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'm' ) : '';
1163 }
1164
1165 /**
1166 * Get the month from the last revision of a specified page.
1167 * @param Parser $parser
1168 * @param string $title Title to get the month from
1169 * @return string
1170 * @since 1.23
1171 */
1172 public static function revisionmonth1( $parser, $title = null ) {
1173 $t = Title::newFromText( $title );
1174 if ( is_null( $t ) ) {
1175 return '';
1176 }
1177 // fetch revision from cache/database and return the value
1178 $rev = self::getCachedRevisionObject( $parser, $t );
1179 return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'n' ) : '';
1180 }
1181
1182 /**
1183 * Get the year from the last revision of a specified page.
1184 * @param Parser $parser
1185 * @param string $title Title to get the year from
1186 * @return string
1187 * @since 1.23
1188 */
1189 public static function revisionyear( $parser, $title = null ) {
1190 $t = Title::newFromText( $title );
1191 if ( is_null( $t ) ) {
1192 return '';
1193 }
1194 // fetch revision from cache/database and return the value
1195 $rev = self::getCachedRevisionObject( $parser, $t );
1196 return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'Y' ) : '';
1197 }
1198
1199 /**
1200 * Get the timestamp from the last revision of a specified page.
1201 * @param Parser $parser
1202 * @param string $title Title to get the timestamp from
1203 * @return string
1204 * @since 1.23
1205 */
1206 public static function revisiontimestamp( $parser, $title = null ) {
1207 $t = Title::newFromText( $title );
1208 if ( is_null( $t ) ) {
1209 return '';
1210 }
1211 // fetch revision from cache/database and return the value
1212 $rev = self::getCachedRevisionObject( $parser, $t );
1213 return $rev ? MWTimestamp::getLocalInstance( $rev->getTimestamp() )->format( 'YmdHis' ) : '';
1214 }
1215
1216 /**
1217 * Get the user from the last revision of a specified page.
1218 * @param Parser $parser
1219 * @param string $title Title to get the user from
1220 * @return string
1221 * @since 1.23
1222 */
1223 public static function revisionuser( $parser, $title = null ) {
1224 $t = Title::newFromText( $title );
1225 if ( is_null( $t ) ) {
1226 return '';
1227 }
1228 // fetch revision from cache/database and return the value
1229 $rev = self::getCachedRevisionObject( $parser, $t );
1230 return $rev ? $rev->getUserText() : '';
1231 }
1232
1233 /**
1234 * Returns the sources of any cascading protection acting on a specified page.
1235 * Pages will not return their own title unless they transclude themselves.
1236 * This is an expensive parser function and can't be called too many times per page,
1237 * unless cascading protection sources for the page have already been loaded.
1238 *
1239 * @param Parser $parser
1240 * @param string $title
1241 *
1242 * @return string
1243 * @since 1.23
1244 */
1245 public static function cascadingsources( $parser, $title = '' ) {
1246 $titleObject = Title::newFromText( $title );
1247 if ( !( $titleObject instanceof Title ) ) {
1248 $titleObject = $parser->mTitle;
1249 }
1250 if ( $titleObject->areCascadeProtectionSourcesLoaded()
1251 || $parser->incrementExpensiveFunctionCount()
1252 ) {
1253 $names = array();
1254 $sources = $titleObject->getCascadeProtectionSources();
1255 foreach ( $sources[0] as $sourceTitle ) {
1256 $names[] = $sourceTitle->getPrefixedText();
1257 }
1258 return implode( $names, '|' );
1259 }
1260 return '';
1261 }
1262
1263 }