Merge "Documentation improvements in ChangeTags.php"
[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 */
758 function getHash() {
759 if ( is_null( $this->hash ) ) {
760 global $wgContLang;
761 $this->hash = array( 0 => array(), 1 => array() );
762 foreach ( $this->names as $name ) {
763 $magic = MagicWord::get( $name );
764 $case = intval( $magic->isCaseSensitive() );
765 foreach ( $magic->getSynonyms() as $syn ) {
766 if ( !$case ) {
767 $syn = $wgContLang->lc( $syn );
768 }
769 $this->hash[$case][$syn] = $name;
770 }
771 }
772 }
773 return $this->hash;
774 }
775
776 /**
777 * Get the base regex
778 */
779 function getBaseRegex() {
780 if ( is_null( $this->baseRegex ) ) {
781 $this->baseRegex = array( 0 => '', 1 => '' );
782 foreach ( $this->names as $name ) {
783 $magic = MagicWord::get( $name );
784 $case = intval( $magic->isCaseSensitive() );
785 foreach ( $magic->getSynonyms() as $i => $syn ) {
786 // Group name must start with a non-digit in PCRE 8.34+
787 $it = strtr( $i, '0123456789', 'abcdefghij' );
788 $group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
789 if ( $this->baseRegex[$case] === '' ) {
790 $this->baseRegex[$case] = $group;
791 } else {
792 $this->baseRegex[$case] .= '|' . $group;
793 }
794 }
795 }
796 }
797 return $this->baseRegex;
798 }
799
800 /**
801 * Get an unanchored regex that does not match parameters
802 */
803 function getRegex() {
804 if ( is_null( $this->regex ) ) {
805 $base = $this->getBaseRegex();
806 $this->regex = array( '', '' );
807 if ( $this->baseRegex[0] !== '' ) {
808 $this->regex[0] = "/{$base[0]}/iuS";
809 }
810 if ( $this->baseRegex[1] !== '' ) {
811 $this->regex[1] = "/{$base[1]}/S";
812 }
813 }
814 return $this->regex;
815 }
816
817 /**
818 * Get a regex for matching variables with parameters
819 *
820 * @return string
821 */
822 function getVariableRegex() {
823 return str_replace( "\\$1", "(.*?)", $this->getRegex() );
824 }
825
826 /**
827 * Get a regex anchored to the start of the string that does not match parameters
828 *
829 * @return array
830 */
831 function getRegexStart() {
832 $base = $this->getBaseRegex();
833 $newRegex = array( '', '' );
834 if ( $base[0] !== '' ) {
835 $newRegex[0] = "/^(?:{$base[0]})/iuS";
836 }
837 if ( $base[1] !== '' ) {
838 $newRegex[1] = "/^(?:{$base[1]})/S";
839 }
840 return $newRegex;
841 }
842
843 /**
844 * Get an anchored regex for matching variables with parameters
845 *
846 * @return array
847 */
848 function getVariableStartToEndRegex() {
849 $base = $this->getBaseRegex();
850 $newRegex = array( '', '' );
851 if ( $base[0] !== '' ) {
852 $newRegex[0] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[0]})$/iuS" );
853 }
854 if ( $base[1] !== '' ) {
855 $newRegex[1] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[1]})$/S" );
856 }
857 return $newRegex;
858 }
859
860 /**
861 * @since 1.20
862 * @return array
863 */
864 public function getNames() {
865 return $this->names;
866 }
867
868 /**
869 * Parse a match array from preg_match
870 * Returns array(magic word ID, parameter value)
871 * If there is no parameter value, that element will be false.
872 *
873 * @param array $m
874 *
875 * @throws MWException
876 * @return array
877 */
878 function parseMatch( $m ) {
879 reset( $m );
880 while ( list( $key, $value ) = each( $m ) ) {
881 if ( $key === 0 || $value === '' ) {
882 continue;
883 }
884 $parts = explode( '_', $key, 2 );
885 if ( count( $parts ) != 2 ) {
886 // This shouldn't happen
887 // continue;
888 throw new MWException( __METHOD__ . ': bad parameter name' );
889 }
890 list( /* $synIndex */, $magicName ) = $parts;
891 $paramValue = next( $m );
892 return array( $magicName, $paramValue );
893 }
894 // This shouldn't happen either
895 throw new MWException( __METHOD__ . ': parameter not found' );
896 }
897
898 /**
899 * Match some text, with parameter capture
900 * Returns an array with the magic word name in the first element and the
901 * parameter in the second element.
902 * Both elements are false if there was no match.
903 *
904 * @param string $text
905 *
906 * @return array
907 */
908 public function matchVariableStartToEnd( $text ) {
909 $regexes = $this->getVariableStartToEndRegex();
910 foreach ( $regexes as $regex ) {
911 if ( $regex !== '' ) {
912 $m = array();
913 if ( preg_match( $regex, $text, $m ) ) {
914 return $this->parseMatch( $m );
915 }
916 }
917 }
918 return array( false, false );
919 }
920
921 /**
922 * Match some text, without parameter capture
923 * Returns the magic word name, or false if there was no capture
924 *
925 * @param string $text
926 *
927 * @return string|bool False on failure
928 */
929 public function matchStartToEnd( $text ) {
930 $hash = $this->getHash();
931 if ( isset( $hash[1][$text] ) ) {
932 return $hash[1][$text];
933 }
934 global $wgContLang;
935 $lc = $wgContLang->lc( $text );
936 if ( isset( $hash[0][$lc] ) ) {
937 return $hash[0][$lc];
938 }
939 return false;
940 }
941
942 /**
943 * Returns an associative array, ID => param value, for all items that match
944 * Removes the matched items from the input string (passed by reference)
945 *
946 * @param string $text
947 *
948 * @return array
949 */
950 public function matchAndRemove( &$text ) {
951 $found = array();
952 $regexes = $this->getRegex();
953 foreach ( $regexes as $regex ) {
954 if ( $regex === '' ) {
955 continue;
956 }
957 preg_match_all( $regex, $text, $matches, PREG_SET_ORDER );
958 foreach ( $matches as $m ) {
959 list( $name, $param ) = $this->parseMatch( $m );
960 $found[$name] = $param;
961 }
962 $text = preg_replace( $regex, '', $text );
963 }
964 return $found;
965 }
966
967 /**
968 * Return the ID of the magic word at the start of $text, and remove
969 * the prefix from $text.
970 * Return false if no match found and $text is not modified.
971 * Does not match parameters.
972 *
973 * @param string $text
974 *
975 * @return int|bool False on failure
976 */
977 public function matchStartAndRemove( &$text ) {
978 $regexes = $this->getRegexStart();
979 foreach ( $regexes as $regex ) {
980 if ( $regex === '' ) {
981 continue;
982 }
983 if ( preg_match( $regex, $text, $m ) ) {
984 list( $id, ) = $this->parseMatch( $m );
985 if ( strlen( $m[0] ) >= strlen( $text ) ) {
986 $text = '';
987 } else {
988 $text = substr( $text, strlen( $m[0] ) );
989 }
990 return $id;
991 }
992 }
993 return false;
994 }
995 }