Merge "resourceloader: Only store sources' load.php urls"
[lhc/web/wiklou.git] / includes / MagicWord.php
1 <?php
2 /**
3 * File for magic words.
4 *
5 * See docs/magicword.txt.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @file
23 * @ingroup Parser
24 */
25
26 /**
27 * This class encapsulates "magic words" such as "#redirect", __NOTOC__, etc.
28 *
29 * @par Usage:
30 * @code
31 * if (MagicWord::get( 'redirect' )->match( $text ) ) {
32 * // some code
33 * }
34 * @endcode
35 *
36 * Possible future improvements:
37 * * Simultaneous searching for a number of magic words
38 * * MagicWord::$mObjects in shared memory
39 *
40 * Please avoid reading the data out of one of these objects and then writing
41 * special case code. If possible, add another match()-like function here.
42 *
43 * To add magic words in an extension, use $magicWords in a file listed in
44 * $wgExtensionMessagesFiles[].
45 *
46 * @par Example:
47 * @code
48 * $magicWords = array();
49 *
50 * $magicWords['en'] = array(
51 * 'magicwordkey' => array( 0, 'case_insensitive_magic_word' ),
52 * 'magicwordkey2' => array( 1, 'CASE_sensitive_magic_word2' ),
53 * );
54 * @endcode
55 *
56 * For magic words which are also Parser variables, add a MagicWordwgVariableIDs
57 * hook. Use string keys.
58 *
59 * @ingroup Parser
60 */
61 class MagicWord {
62 /**#@-*/
63
64 /** @var int */
65 public $mId;
66
67 /** @var array */
68 public $mSynonyms;
69
70 /** @var bool */
71 public $mCaseSensitive;
72
73 /** @var string */
74 private $mRegex = '';
75
76 /** @var string */
77 private $mRegexStart = '';
78
79 /** @var string */
80 private $mRegexStartToEnd = '';
81
82 /** @var string */
83 private $mBaseRegex = '';
84
85 /** @var string */
86 private $mVariableRegex = '';
87
88 /** @var string */
89 private $mVariableStartToEndRegex = '';
90
91 /** @var bool */
92 private $mModified = false;
93
94 /** @var bool */
95 private $mFound = false;
96
97 static public $mVariableIDsInitialised = false;
98 static public $mVariableIDs = array(
99 '!',
100 'currentmonth',
101 'currentmonth1',
102 'currentmonthname',
103 'currentmonthnamegen',
104 'currentmonthabbrev',
105 'currentday',
106 'currentday2',
107 'currentdayname',
108 'currentyear',
109 'currenttime',
110 'currenthour',
111 'localmonth',
112 'localmonth1',
113 'localmonthname',
114 'localmonthnamegen',
115 'localmonthabbrev',
116 'localday',
117 'localday2',
118 'localdayname',
119 'localyear',
120 'localtime',
121 'localhour',
122 'numberofarticles',
123 'numberoffiles',
124 'numberofedits',
125 'articlepath',
126 'pageid',
127 'sitename',
128 'server',
129 'servername',
130 'scriptpath',
131 'stylepath',
132 'pagename',
133 'pagenamee',
134 'fullpagename',
135 'fullpagenamee',
136 'namespace',
137 'namespacee',
138 'namespacenumber',
139 'currentweek',
140 'currentdow',
141 'localweek',
142 'localdow',
143 'revisionid',
144 'revisionday',
145 'revisionday2',
146 'revisionmonth',
147 'revisionmonth1',
148 'revisionyear',
149 'revisiontimestamp',
150 'revisionuser',
151 'revisionsize',
152 'subpagename',
153 'subpagenamee',
154 'talkspace',
155 'talkspacee',
156 'subjectspace',
157 'subjectspacee',
158 'talkpagename',
159 'talkpagenamee',
160 'subjectpagename',
161 'subjectpagenamee',
162 'numberofusers',
163 'numberofactiveusers',
164 'numberofpages',
165 'currentversion',
166 'rootpagename',
167 'rootpagenamee',
168 'basepagename',
169 'basepagenamee',
170 'currenttimestamp',
171 'localtimestamp',
172 'directionmark',
173 'contentlanguage',
174 'numberofadmins',
175 'numberofviews',
176 'cascadingsources',
177 );
178
179 /* Array of caching hints for ParserCache */
180 static public $mCacheTTLs = array(
181 'currentmonth' => 86400,
182 'currentmonth1' => 86400,
183 'currentmonthname' => 86400,
184 'currentmonthnamegen' => 86400,
185 'currentmonthabbrev' => 86400,
186 'currentday' => 3600,
187 'currentday2' => 3600,
188 'currentdayname' => 3600,
189 'currentyear' => 86400,
190 'currenttime' => 3600,
191 'currenthour' => 3600,
192 'localmonth' => 86400,
193 'localmonth1' => 86400,
194 'localmonthname' => 86400,
195 'localmonthnamegen' => 86400,
196 'localmonthabbrev' => 86400,
197 'localday' => 3600,
198 'localday2' => 3600,
199 'localdayname' => 3600,
200 'localyear' => 86400,
201 'localtime' => 3600,
202 'localhour' => 3600,
203 'numberofarticles' => 3600,
204 'numberoffiles' => 3600,
205 'numberofedits' => 3600,
206 'currentweek' => 3600,
207 'currentdow' => 3600,
208 'localweek' => 3600,
209 'localdow' => 3600,
210 'numberofusers' => 3600,
211 'numberofactiveusers' => 3600,
212 'numberofpages' => 3600,
213 'currentversion' => 86400,
214 'currenttimestamp' => 3600,
215 'localtimestamp' => 3600,
216 'pagesinnamespace' => 3600,
217 'numberofadmins' => 3600,
218 'numberofviews' => 3600,
219 'numberingroup' => 3600,
220 );
221
222 static public $mDoubleUnderscoreIDs = array(
223 'notoc',
224 'nogallery',
225 'forcetoc',
226 'toc',
227 'noeditsection',
228 'newsectionlink',
229 'nonewsectionlink',
230 'hiddencat',
231 'index',
232 'noindex',
233 'staticredirect',
234 'notitleconvert',
235 'nocontentconvert',
236 );
237
238 static public $mSubstIDs = array(
239 'subst',
240 'safesubst',
241 );
242
243 static public $mObjects = array();
244 static public $mDoubleUnderscoreArray = null;
245
246 /**#@-*/
247
248 function __construct( $id = 0, $syn = array(), $cs = false ) {
249 $this->mId = $id;
250 $this->mSynonyms = (array)$syn;
251 $this->mCaseSensitive = $cs;
252 }
253
254 /**
255 * Factory: creates an object representing an ID
256 *
257 * @param int $id
258 *
259 * @return MagicWord
260 */
261 static function &get( $id ) {
262 if ( !isset( self::$mObjects[$id] ) ) {
263 $mw = new MagicWord();
264 $mw->load( $id );
265 self::$mObjects[$id] = $mw;
266 }
267 return self::$mObjects[$id];
268 }
269
270 /**
271 * Get an array of parser variable IDs
272 *
273 * @return array
274 */
275 static function getVariableIDs() {
276 if ( !self::$mVariableIDsInitialised ) {
277 # Get variable IDs
278 wfRunHooks( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) );
279 self::$mVariableIDsInitialised = true;
280 }
281 return self::$mVariableIDs;
282 }
283
284 /**
285 * Get an array of parser substitution modifier IDs
286 * @return array
287 */
288 static function getSubstIDs() {
289 return self::$mSubstIDs;
290 }
291
292 /**
293 * Allow external reads of TTL array
294 *
295 * @param int $id
296 * @return int
297 */
298 static function getCacheTTL( $id ) {
299 if ( array_key_exists( $id, self::$mCacheTTLs ) ) {
300 return self::$mCacheTTLs[$id];
301 } else {
302 return -1;
303 }
304 }
305
306 /**
307 * Get a MagicWordArray of double-underscore entities
308 *
309 * @return MagicWordArray
310 */
311 static function getDoubleUnderscoreArray() {
312 if ( is_null( self::$mDoubleUnderscoreArray ) ) {
313 wfRunHooks( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) );
314 self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs );
315 }
316 return self::$mDoubleUnderscoreArray;
317 }
318
319 /**
320 * Clear the self::$mObjects variable
321 * For use in parser tests
322 */
323 public static function clearCache() {
324 self::$mObjects = array();
325 }
326
327 /**
328 * Initialises this object with an ID
329 *
330 * @param int $id
331 * @throws MWException
332 */
333 function load( $id ) {
334 global $wgContLang;
335 wfProfileIn( __METHOD__ );
336 $this->mId = $id;
337 $wgContLang->getMagic( $this );
338 if ( !$this->mSynonyms ) {
339 $this->mSynonyms = array( 'brionmademeputthishere' );
340 wfProfileOut( __METHOD__ );
341 throw new MWException( "Error: invalid magic word '$id'" );
342 }
343 wfProfileOut( __METHOD__ );
344 }
345
346 /**
347 * Preliminary initialisation
348 * @private
349 */
350 function initRegex() {
351 // Sort the synonyms by length, descending, so that the longest synonym
352 // matches in precedence to the shortest
353 $synonyms = $this->mSynonyms;
354 usort( $synonyms, array( $this, 'compareStringLength' ) );
355
356 $escSyn = array();
357 foreach ( $synonyms as $synonym ) {
358 // In case a magic word contains /, like that's going to happen;)
359 $escSyn[] = preg_quote( $synonym, '/' );
360 }
361 $this->mBaseRegex = implode( '|', $escSyn );
362
363 $case = $this->mCaseSensitive ? '' : 'iu';
364 $this->mRegex = "/{$this->mBaseRegex}/{$case}";
365 $this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
366 $this->mRegexStartToEnd = "/^(?:{$this->mBaseRegex})$/{$case}";
367 $this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
368 $this->mVariableStartToEndRegex = str_replace( "\\$1", "(.*?)",
369 "/^(?:{$this->mBaseRegex})$/{$case}" );
370 }
371
372 /**
373 * A comparison function that returns -1, 0 or 1 depending on whether the
374 * first string is longer, the same length or shorter than the second
375 * string.
376 *
377 * @param string $s1
378 * @param string $s2
379 *
380 * @return int
381 */
382 function compareStringLength( $s1, $s2 ) {
383 $l1 = strlen( $s1 );
384 $l2 = strlen( $s2 );
385 if ( $l1 < $l2 ) {
386 return 1;
387 } elseif ( $l1 > $l2 ) {
388 return -1;
389 } else {
390 return 0;
391 }
392 }
393
394 /**
395 * Gets a regex representing matching the word
396 *
397 * @return string
398 */
399 function getRegex() {
400 if ( $this->mRegex == '' ) {
401 $this->initRegex();
402 }
403 return $this->mRegex;
404 }
405
406 /**
407 * Gets the regexp case modifier to use, i.e. i or nothing, to be used if
408 * one is using MagicWord::getBaseRegex(), otherwise it'll be included in
409 * the complete expression
410 *
411 * @return string
412 */
413 function getRegexCase() {
414 if ( $this->mRegex === '' ) {
415 $this->initRegex();
416 }
417
418 return $this->mCaseSensitive ? '' : 'iu';
419 }
420
421 /**
422 * Gets a regex matching the word, if it is at the string start
423 *
424 * @return string
425 */
426 function getRegexStart() {
427 if ( $this->mRegex == '' ) {
428 $this->initRegex();
429 }
430 return $this->mRegexStart;
431 }
432
433 /**
434 * Gets a regex matching the word from start to end of a string
435 *
436 * @return string
437 * @since 1.23
438 */
439 function getRegexStartToEnd() {
440 if ( $this->mRegexStartToEnd == '' ) {
441 $this->initRegex();
442 }
443 return $this->mRegexStartToEnd;
444 }
445
446 /**
447 * regex without the slashes and what not
448 *
449 * @return string
450 */
451 function getBaseRegex() {
452 if ( $this->mRegex == '' ) {
453 $this->initRegex();
454 }
455 return $this->mBaseRegex;
456 }
457
458 /**
459 * Returns true if the text contains the word
460 *
461 * @param string $text
462 *
463 * @return bool
464 */
465 function match( $text ) {
466 return (bool)preg_match( $this->getRegex(), $text );
467 }
468
469 /**
470 * Returns true if the text starts with the word
471 *
472 * @param string $text
473 *
474 * @return bool
475 */
476 function matchStart( $text ) {
477 return (bool)preg_match( $this->getRegexStart(), $text );
478 }
479
480 /**
481 * Returns true if the text matched the word
482 *
483 * @param string $text
484 *
485 * @return bool
486 * @since 1.23
487 */
488 function matchStartToEnd( $text ) {
489 return (bool)preg_match( $this->getRegexStartToEnd(), $text );
490 }
491
492 /**
493 * Returns NULL if there's no match, the value of $1 otherwise
494 * The return code is the matched string, if there's no variable
495 * part in the regex and the matched variable part ($1) if there
496 * is one.
497 *
498 * @param string $text
499 *
500 * @return string
501 */
502 function matchVariableStartToEnd( $text ) {
503 $matches = array();
504 $matchcount = preg_match( $this->getVariableStartToEndRegex(), $text, $matches );
505 if ( $matchcount == 0 ) {
506 return null;
507 } else {
508 # multiple matched parts (variable match); some will be empty because of
509 # synonyms. The variable will be the second non-empty one so remove any
510 # blank elements and re-sort the indices.
511 # See also bug 6526
512
513 $matches = array_values( array_filter( $matches ) );
514
515 if ( count( $matches ) == 1 ) {
516 return $matches[0];
517 } else {
518 return $matches[1];
519 }
520 }
521 }
522
523 /**
524 * Returns true if the text matches the word, and alters the
525 * input string, removing all instances of the word
526 *
527 * @param string $text
528 *
529 * @return bool
530 */
531 function matchAndRemove( &$text ) {
532 $this->mFound = false;
533 $text = preg_replace_callback(
534 $this->getRegex(),
535 array( &$this, 'pregRemoveAndRecord' ),
536 $text
537 );
538
539 return $this->mFound;
540 }
541
542 /**
543 * @param string $text
544 * @return bool
545 */
546 function matchStartAndRemove( &$text ) {
547 $this->mFound = false;
548 $text = preg_replace_callback(
549 $this->getRegexStart(),
550 array( &$this, 'pregRemoveAndRecord' ),
551 $text
552 );
553
554 return $this->mFound;
555 }
556
557 /**
558 * Used in matchAndRemove()
559 *
560 * @return string
561 */
562 function pregRemoveAndRecord() {
563 $this->mFound = true;
564 return '';
565 }
566
567 /**
568 * Replaces the word with something else
569 *
570 * @param string $replacement
571 * @param string $subject
572 * @param int $limit
573 *
574 * @return string
575 */
576 function replace( $replacement, $subject, $limit = -1 ) {
577 $res = preg_replace(
578 $this->getRegex(),
579 StringUtils::escapeRegexReplacement( $replacement ),
580 $subject,
581 $limit
582 );
583 $this->mModified = $res !== $subject;
584 return $res;
585 }
586
587 /**
588 * Variable handling: {{SUBST:xxx}} style words
589 * Calls back a function to determine what to replace xxx with
590 * Input word must contain $1
591 *
592 * @param string $text
593 * @param callable $callback
594 *
595 * @return string
596 */
597 function substituteCallback( $text, $callback ) {
598 $res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
599 $this->mModified = $res !== $text;
600 return $res;
601 }
602
603 /**
604 * Matches the word, where $1 is a wildcard
605 *
606 * @return string
607 */
608 function getVariableRegex() {
609 if ( $this->mVariableRegex == '' ) {
610 $this->initRegex();
611 }
612 return $this->mVariableRegex;
613 }
614
615 /**
616 * Matches the entire string, where $1 is a wildcard
617 *
618 * @return string
619 */
620 function getVariableStartToEndRegex() {
621 if ( $this->mVariableStartToEndRegex == '' ) {
622 $this->initRegex();
623 }
624 return $this->mVariableStartToEndRegex;
625 }
626
627 /**
628 * Accesses the synonym list directly
629 *
630 * @param int $i
631 *
632 * @return string
633 */
634 function getSynonym( $i ) {
635 return $this->mSynonyms[$i];
636 }
637
638 /**
639 * @return array
640 */
641 function getSynonyms() {
642 return $this->mSynonyms;
643 }
644
645 /**
646 * Returns true if the last call to replace() or substituteCallback()
647 * returned a modified text, otherwise false.
648 *
649 * @return bool
650 */
651 function getWasModified() {
652 return $this->mModified;
653 }
654
655 /**
656 * $magicarr is an associative array of (magic word ID => replacement)
657 * This method uses the php feature to do several replacements at the same time,
658 * thereby gaining some efficiency. The result is placed in the out variable
659 * $result. The return value is true if something was replaced.
660 * @todo Should this be static? It doesn't seem to be used at all
661 *
662 * @param array $magicarr
663 * @param string $subject
664 * @param string $result
665 *
666 * @return bool
667 */
668 function replaceMultiple( $magicarr, $subject, &$result ) {
669 $search = array();
670 $replace = array();
671 foreach ( $magicarr as $id => $replacement ) {
672 $mw = MagicWord::get( $id );
673 $search[] = $mw->getRegex();
674 $replace[] = $replacement;
675 }
676
677 $result = preg_replace( $search, $replace, $subject );
678 return $result !== $subject;
679 }
680
681 /**
682 * Adds all the synonyms of this MagicWord to an array, to allow quick
683 * lookup in a list of magic words
684 *
685 * @param array $array
686 * @param string $value
687 */
688 function addToArray( &$array, $value ) {
689 global $wgContLang;
690 foreach ( $this->mSynonyms as $syn ) {
691 $array[$wgContLang->lc( $syn )] = $value;
692 }
693 }
694
695 /**
696 * @return bool
697 */
698 function isCaseSensitive() {
699 return $this->mCaseSensitive;
700 }
701
702 /**
703 * @return int
704 */
705 function getId() {
706 return $this->mId;
707 }
708 }
709
710 /**
711 * Class for handling an array of magic words
712 * @ingroup Parser
713 */
714 class MagicWordArray {
715 /** @var array */
716 public $names = array();
717
718 /** @var array */
719 private $hash;
720
721 private $baseRegex;
722
723 private $regex;
724
725 /** @todo Unused? */
726 private $matches;
727
728 /**
729 * @param array $names
730 */
731 function __construct( $names = array() ) {
732 $this->names = $names;
733 }
734
735 /**
736 * Add a magic word by name
737 *
738 * @param string $name
739 */
740 public function add( $name ) {
741 $this->names[] = $name;
742 $this->hash = $this->baseRegex = $this->regex = null;
743 }
744
745 /**
746 * Add a number of magic words by name
747 *
748 * @param array $names
749 */
750 public function addArray( $names ) {
751 $this->names = array_merge( $this->names, array_values( $names ) );
752 $this->hash = $this->baseRegex = $this->regex = null;
753 }
754
755 /**
756 * Get a 2-d hashtable for this array
757 * @return array
758 */
759 function getHash() {
760 if ( is_null( $this->hash ) ) {
761 global $wgContLang;
762 $this->hash = array( 0 => array(), 1 => array() );
763 foreach ( $this->names as $name ) {
764 $magic = MagicWord::get( $name );
765 $case = intval( $magic->isCaseSensitive() );
766 foreach ( $magic->getSynonyms() as $syn ) {
767 if ( !$case ) {
768 $syn = $wgContLang->lc( $syn );
769 }
770 $this->hash[$case][$syn] = $name;
771 }
772 }
773 }
774 return $this->hash;
775 }
776
777 /**
778 * Get the base regex
779 * @return array
780 */
781 function getBaseRegex() {
782 if ( is_null( $this->baseRegex ) ) {
783 $this->baseRegex = array( 0 => '', 1 => '' );
784 foreach ( $this->names as $name ) {
785 $magic = MagicWord::get( $name );
786 $case = intval( $magic->isCaseSensitive() );
787 foreach ( $magic->getSynonyms() as $i => $syn ) {
788 // Group name must start with a non-digit in PCRE 8.34+
789 $it = strtr( $i, '0123456789', 'abcdefghij' );
790 $group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
791 if ( $this->baseRegex[$case] === '' ) {
792 $this->baseRegex[$case] = $group;
793 } else {
794 $this->baseRegex[$case] .= '|' . $group;
795 }
796 }
797 }
798 }
799 return $this->baseRegex;
800 }
801
802 /**
803 * Get an unanchored regex that does not match parameters
804 * @return array
805 */
806 function getRegex() {
807 if ( is_null( $this->regex ) ) {
808 $base = $this->getBaseRegex();
809 $this->regex = array( '', '' );
810 if ( $this->baseRegex[0] !== '' ) {
811 $this->regex[0] = "/{$base[0]}/iuS";
812 }
813 if ( $this->baseRegex[1] !== '' ) {
814 $this->regex[1] = "/{$base[1]}/S";
815 }
816 }
817 return $this->regex;
818 }
819
820 /**
821 * Get a regex for matching variables with parameters
822 *
823 * @return string
824 */
825 function getVariableRegex() {
826 return str_replace( "\\$1", "(.*?)", $this->getRegex() );
827 }
828
829 /**
830 * Get a regex anchored to the start of the string that does not match parameters
831 *
832 * @return array
833 */
834 function getRegexStart() {
835 $base = $this->getBaseRegex();
836 $newRegex = array( '', '' );
837 if ( $base[0] !== '' ) {
838 $newRegex[0] = "/^(?:{$base[0]})/iuS";
839 }
840 if ( $base[1] !== '' ) {
841 $newRegex[1] = "/^(?:{$base[1]})/S";
842 }
843 return $newRegex;
844 }
845
846 /**
847 * Get an anchored regex for matching variables with parameters
848 *
849 * @return array
850 */
851 function getVariableStartToEndRegex() {
852 $base = $this->getBaseRegex();
853 $newRegex = array( '', '' );
854 if ( $base[0] !== '' ) {
855 $newRegex[0] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[0]})$/iuS" );
856 }
857 if ( $base[1] !== '' ) {
858 $newRegex[1] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[1]})$/S" );
859 }
860 return $newRegex;
861 }
862
863 /**
864 * @since 1.20
865 * @return array
866 */
867 public function getNames() {
868 return $this->names;
869 }
870
871 /**
872 * Parse a match array from preg_match
873 * Returns array(magic word ID, parameter value)
874 * If there is no parameter value, that element will be false.
875 *
876 * @param array $m
877 *
878 * @throws MWException
879 * @return array
880 */
881 function parseMatch( $m ) {
882 reset( $m );
883 while ( list( $key, $value ) = each( $m ) ) {
884 if ( $key === 0 || $value === '' ) {
885 continue;
886 }
887 $parts = explode( '_', $key, 2 );
888 if ( count( $parts ) != 2 ) {
889 // This shouldn't happen
890 // continue;
891 throw new MWException( __METHOD__ . ': bad parameter name' );
892 }
893 list( /* $synIndex */, $magicName ) = $parts;
894 $paramValue = next( $m );
895 return array( $magicName, $paramValue );
896 }
897 // This shouldn't happen either
898 throw new MWException( __METHOD__ . ': parameter not found' );
899 }
900
901 /**
902 * Match some text, with parameter capture
903 * Returns an array with the magic word name in the first element and the
904 * parameter in the second element.
905 * Both elements are false if there was no match.
906 *
907 * @param string $text
908 *
909 * @return array
910 */
911 public function matchVariableStartToEnd( $text ) {
912 $regexes = $this->getVariableStartToEndRegex();
913 foreach ( $regexes as $regex ) {
914 if ( $regex !== '' ) {
915 $m = array();
916 if ( preg_match( $regex, $text, $m ) ) {
917 return $this->parseMatch( $m );
918 }
919 }
920 }
921 return array( false, false );
922 }
923
924 /**
925 * Match some text, without parameter capture
926 * Returns the magic word name, or false if there was no capture
927 *
928 * @param string $text
929 *
930 * @return string|bool False on failure
931 */
932 public function matchStartToEnd( $text ) {
933 $hash = $this->getHash();
934 if ( isset( $hash[1][$text] ) ) {
935 return $hash[1][$text];
936 }
937 global $wgContLang;
938 $lc = $wgContLang->lc( $text );
939 if ( isset( $hash[0][$lc] ) ) {
940 return $hash[0][$lc];
941 }
942 return false;
943 }
944
945 /**
946 * Returns an associative array, ID => param value, for all items that match
947 * Removes the matched items from the input string (passed by reference)
948 *
949 * @param string $text
950 *
951 * @return array
952 */
953 public function matchAndRemove( &$text ) {
954 $found = array();
955 $regexes = $this->getRegex();
956 foreach ( $regexes as $regex ) {
957 if ( $regex === '' ) {
958 continue;
959 }
960 preg_match_all( $regex, $text, $matches, PREG_SET_ORDER );
961 foreach ( $matches as $m ) {
962 list( $name, $param ) = $this->parseMatch( $m );
963 $found[$name] = $param;
964 }
965 $text = preg_replace( $regex, '', $text );
966 }
967 return $found;
968 }
969
970 /**
971 * Return the ID of the magic word at the start of $text, and remove
972 * the prefix from $text.
973 * Return false if no match found and $text is not modified.
974 * Does not match parameters.
975 *
976 * @param string $text
977 *
978 * @return int|bool False on failure
979 */
980 public function matchStartAndRemove( &$text ) {
981 $regexes = $this->getRegexStart();
982 foreach ( $regexes as $regex ) {
983 if ( $regex === '' ) {
984 continue;
985 }
986 if ( preg_match( $regex, $text, $m ) ) {
987 list( $id, ) = $this->parseMatch( $m );
988 if ( strlen( $m[0] ) >= strlen( $text ) ) {
989 $text = '';
990 } else {
991 $text = substr( $text, strlen( $m[0] ) );
992 }
993 return $id;
994 }
995 }
996 return false;
997 }
998 }