Marked replaceMultiple() as deprecated
[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 'cascadingsources',
176 );
177
178 /* Array of caching hints for ParserCache */
179 static public $mCacheTTLs = array(
180 'currentmonth' => 86400,
181 'currentmonth1' => 86400,
182 'currentmonthname' => 86400,
183 'currentmonthnamegen' => 86400,
184 'currentmonthabbrev' => 86400,
185 'currentday' => 3600,
186 'currentday2' => 3600,
187 'currentdayname' => 3600,
188 'currentyear' => 86400,
189 'currenttime' => 3600,
190 'currenthour' => 3600,
191 'localmonth' => 86400,
192 'localmonth1' => 86400,
193 'localmonthname' => 86400,
194 'localmonthnamegen' => 86400,
195 'localmonthabbrev' => 86400,
196 'localday' => 3600,
197 'localday2' => 3600,
198 'localdayname' => 3600,
199 'localyear' => 86400,
200 'localtime' => 3600,
201 'localhour' => 3600,
202 'numberofarticles' => 3600,
203 'numberoffiles' => 3600,
204 'numberofedits' => 3600,
205 'currentweek' => 3600,
206 'currentdow' => 3600,
207 'localweek' => 3600,
208 'localdow' => 3600,
209 'numberofusers' => 3600,
210 'numberofactiveusers' => 3600,
211 'numberofpages' => 3600,
212 'currentversion' => 86400,
213 'currenttimestamp' => 3600,
214 'localtimestamp' => 3600,
215 'pagesinnamespace' => 3600,
216 'numberofadmins' => 3600,
217 'numberingroup' => 3600,
218 );
219
220 static public $mDoubleUnderscoreIDs = array(
221 'notoc',
222 'nogallery',
223 'forcetoc',
224 'toc',
225 'noeditsection',
226 'newsectionlink',
227 'nonewsectionlink',
228 'hiddencat',
229 'index',
230 'noindex',
231 'staticredirect',
232 'notitleconvert',
233 'nocontentconvert',
234 );
235
236 static public $mSubstIDs = array(
237 'subst',
238 'safesubst',
239 );
240
241 static public $mObjects = array();
242 static public $mDoubleUnderscoreArray = null;
243
244 /**#@-*/
245
246 function __construct( $id = 0, $syn = array(), $cs = false ) {
247 $this->mId = $id;
248 $this->mSynonyms = (array)$syn;
249 $this->mCaseSensitive = $cs;
250 }
251
252 /**
253 * Factory: creates an object representing an ID
254 *
255 * @param int $id
256 *
257 * @return MagicWord
258 */
259 static function &get( $id ) {
260 if ( !isset( self::$mObjects[$id] ) ) {
261 $mw = new MagicWord();
262 $mw->load( $id );
263 self::$mObjects[$id] = $mw;
264 }
265 return self::$mObjects[$id];
266 }
267
268 /**
269 * Get an array of parser variable IDs
270 *
271 * @return array
272 */
273 static function getVariableIDs() {
274 if ( !self::$mVariableIDsInitialised ) {
275 # Get variable IDs
276 Hooks::run( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) );
277 self::$mVariableIDsInitialised = true;
278 }
279 return self::$mVariableIDs;
280 }
281
282 /**
283 * Get an array of parser substitution modifier IDs
284 * @return array
285 */
286 static function getSubstIDs() {
287 return self::$mSubstIDs;
288 }
289
290 /**
291 * Allow external reads of TTL array
292 *
293 * @param int $id
294 * @return int
295 */
296 static function getCacheTTL( $id ) {
297 if ( array_key_exists( $id, self::$mCacheTTLs ) ) {
298 return self::$mCacheTTLs[$id];
299 } else {
300 return -1;
301 }
302 }
303
304 /**
305 * Get a MagicWordArray of double-underscore entities
306 *
307 * @return MagicWordArray
308 */
309 static function getDoubleUnderscoreArray() {
310 if ( is_null( self::$mDoubleUnderscoreArray ) ) {
311 Hooks::run( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) );
312 self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs );
313 }
314 return self::$mDoubleUnderscoreArray;
315 }
316
317 /**
318 * Clear the self::$mObjects variable
319 * For use in parser tests
320 */
321 public static function clearCache() {
322 self::$mObjects = array();
323 }
324
325 /**
326 * Initialises this object with an ID
327 *
328 * @param int $id
329 * @throws MWException
330 */
331 function load( $id ) {
332 global $wgContLang;
333 wfProfileIn( __METHOD__ );
334 $this->mId = $id;
335 $wgContLang->getMagic( $this );
336 if ( !$this->mSynonyms ) {
337 $this->mSynonyms = array( 'brionmademeputthishere' );
338 wfProfileOut( __METHOD__ );
339 throw new MWException( "Error: invalid magic word '$id'" );
340 }
341 wfProfileOut( __METHOD__ );
342 }
343
344 /**
345 * Preliminary initialisation
346 * @private
347 */
348 function initRegex() {
349 // Sort the synonyms by length, descending, so that the longest synonym
350 // matches in precedence to the shortest
351 $synonyms = $this->mSynonyms;
352 usort( $synonyms, array( $this, 'compareStringLength' ) );
353
354 $escSyn = array();
355 foreach ( $synonyms as $synonym ) {
356 // In case a magic word contains /, like that's going to happen;)
357 $escSyn[] = preg_quote( $synonym, '/' );
358 }
359 $this->mBaseRegex = implode( '|', $escSyn );
360
361 $case = $this->mCaseSensitive ? '' : 'iu';
362 $this->mRegex = "/{$this->mBaseRegex}/{$case}";
363 $this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
364 $this->mRegexStartToEnd = "/^(?:{$this->mBaseRegex})$/{$case}";
365 $this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
366 $this->mVariableStartToEndRegex = str_replace( "\\$1", "(.*?)",
367 "/^(?:{$this->mBaseRegex})$/{$case}" );
368 }
369
370 /**
371 * A comparison function that returns -1, 0 or 1 depending on whether the
372 * first string is longer, the same length or shorter than the second
373 * string.
374 *
375 * @param string $s1
376 * @param string $s2
377 *
378 * @return int
379 */
380 function compareStringLength( $s1, $s2 ) {
381 $l1 = strlen( $s1 );
382 $l2 = strlen( $s2 );
383 if ( $l1 < $l2 ) {
384 return 1;
385 } elseif ( $l1 > $l2 ) {
386 return -1;
387 } else {
388 return 0;
389 }
390 }
391
392 /**
393 * Gets a regex representing matching the word
394 *
395 * @return string
396 */
397 function getRegex() {
398 if ( $this->mRegex == '' ) {
399 $this->initRegex();
400 }
401 return $this->mRegex;
402 }
403
404 /**
405 * Gets the regexp case modifier to use, i.e. i or nothing, to be used if
406 * one is using MagicWord::getBaseRegex(), otherwise it'll be included in
407 * the complete expression
408 *
409 * @return string
410 */
411 function getRegexCase() {
412 if ( $this->mRegex === '' ) {
413 $this->initRegex();
414 }
415
416 return $this->mCaseSensitive ? '' : 'iu';
417 }
418
419 /**
420 * Gets a regex matching the word, if it is at the string start
421 *
422 * @return string
423 */
424 function getRegexStart() {
425 if ( $this->mRegex == '' ) {
426 $this->initRegex();
427 }
428 return $this->mRegexStart;
429 }
430
431 /**
432 * Gets a regex matching the word from start to end of a string
433 *
434 * @return string
435 * @since 1.23
436 */
437 function getRegexStartToEnd() {
438 if ( $this->mRegexStartToEnd == '' ) {
439 $this->initRegex();
440 }
441 return $this->mRegexStartToEnd;
442 }
443
444 /**
445 * regex without the slashes and what not
446 *
447 * @return string
448 */
449 function getBaseRegex() {
450 if ( $this->mRegex == '' ) {
451 $this->initRegex();
452 }
453 return $this->mBaseRegex;
454 }
455
456 /**
457 * Returns true if the text contains the word
458 *
459 * @param string $text
460 *
461 * @return bool
462 */
463 function match( $text ) {
464 return (bool)preg_match( $this->getRegex(), $text );
465 }
466
467 /**
468 * Returns true if the text starts with the word
469 *
470 * @param string $text
471 *
472 * @return bool
473 */
474 function matchStart( $text ) {
475 return (bool)preg_match( $this->getRegexStart(), $text );
476 }
477
478 /**
479 * Returns true if the text matched the word
480 *
481 * @param string $text
482 *
483 * @return bool
484 * @since 1.23
485 */
486 function matchStartToEnd( $text ) {
487 return (bool)preg_match( $this->getRegexStartToEnd(), $text );
488 }
489
490 /**
491 * Returns NULL if there's no match, the value of $1 otherwise
492 * The return code is the matched string, if there's no variable
493 * part in the regex and the matched variable part ($1) if there
494 * is one.
495 *
496 * @param string $text
497 *
498 * @return string
499 */
500 function matchVariableStartToEnd( $text ) {
501 $matches = array();
502 $matchcount = preg_match( $this->getVariableStartToEndRegex(), $text, $matches );
503 if ( $matchcount == 0 ) {
504 return null;
505 } else {
506 # multiple matched parts (variable match); some will be empty because of
507 # synonyms. The variable will be the second non-empty one so remove any
508 # blank elements and re-sort the indices.
509 # See also bug 6526
510
511 $matches = array_values( array_filter( $matches ) );
512
513 if ( count( $matches ) == 1 ) {
514 return $matches[0];
515 } else {
516 return $matches[1];
517 }
518 }
519 }
520
521 /**
522 * Returns true if the text matches the word, and alters the
523 * input string, removing all instances of the word
524 *
525 * @param string $text
526 *
527 * @return bool
528 */
529 function matchAndRemove( &$text ) {
530 $this->mFound = false;
531 $text = preg_replace_callback(
532 $this->getRegex(),
533 array( &$this, 'pregRemoveAndRecord' ),
534 $text
535 );
536
537 return $this->mFound;
538 }
539
540 /**
541 * @param string $text
542 * @return bool
543 */
544 function matchStartAndRemove( &$text ) {
545 $this->mFound = false;
546 $text = preg_replace_callback(
547 $this->getRegexStart(),
548 array( &$this, 'pregRemoveAndRecord' ),
549 $text
550 );
551
552 return $this->mFound;
553 }
554
555 /**
556 * Used in matchAndRemove()
557 *
558 * @return string
559 */
560 function pregRemoveAndRecord() {
561 $this->mFound = true;
562 return '';
563 }
564
565 /**
566 * Replaces the word with something else
567 *
568 * @param string $replacement
569 * @param string $subject
570 * @param int $limit
571 *
572 * @return string
573 */
574 function replace( $replacement, $subject, $limit = -1 ) {
575 $res = preg_replace(
576 $this->getRegex(),
577 StringUtils::escapeRegexReplacement( $replacement ),
578 $subject,
579 $limit
580 );
581 $this->mModified = $res !== $subject;
582 return $res;
583 }
584
585 /**
586 * Variable handling: {{SUBST:xxx}} style words
587 * Calls back a function to determine what to replace xxx with
588 * Input word must contain $1
589 *
590 * @param string $text
591 * @param callable $callback
592 *
593 * @return string
594 */
595 function substituteCallback( $text, $callback ) {
596 $res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
597 $this->mModified = $res !== $text;
598 return $res;
599 }
600
601 /**
602 * Matches the word, where $1 is a wildcard
603 *
604 * @return string
605 */
606 function getVariableRegex() {
607 if ( $this->mVariableRegex == '' ) {
608 $this->initRegex();
609 }
610 return $this->mVariableRegex;
611 }
612
613 /**
614 * Matches the entire string, where $1 is a wildcard
615 *
616 * @return string
617 */
618 function getVariableStartToEndRegex() {
619 if ( $this->mVariableStartToEndRegex == '' ) {
620 $this->initRegex();
621 }
622 return $this->mVariableStartToEndRegex;
623 }
624
625 /**
626 * Accesses the synonym list directly
627 *
628 * @param int $i
629 *
630 * @return string
631 */
632 function getSynonym( $i ) {
633 return $this->mSynonyms[$i];
634 }
635
636 /**
637 * @return array
638 */
639 function getSynonyms() {
640 return $this->mSynonyms;
641 }
642
643 /**
644 * Returns true if the last call to replace() or substituteCallback()
645 * returned a modified text, otherwise false.
646 *
647 * @return bool
648 */
649 function getWasModified() {
650 return $this->mModified;
651 }
652
653 /**
654 * $magicarr is an associative array of (magic word ID => replacement)
655 * This method uses the php feature to do several replacements at the same time,
656 * thereby gaining some efficiency. The result is placed in the out variable
657 * $result. The return value is true if something was replaced.
658 * @deprecated since 1.25, unused
659 *
660 * @param array $magicarr
661 * @param string $subject
662 * @param string $result
663 *
664 * @return bool
665 */
666 function replaceMultiple( $magicarr, $subject, &$result ) {
667 wfDeprecated( __METHOD__, '1.25' );
668 $search = array();
669 $replace = array();
670 foreach ( $magicarr as $id => $replacement ) {
671 $mw = MagicWord::get( $id );
672 $search[] = $mw->getRegex();
673 $replace[] = $replacement;
674 }
675
676 $result = preg_replace( $search, $replace, $subject );
677 return $result !== $subject;
678 }
679
680 /**
681 * Adds all the synonyms of this MagicWord to an array, to allow quick
682 * lookup in a list of magic words
683 *
684 * @param array $array
685 * @param string $value
686 */
687 function addToArray( &$array, $value ) {
688 global $wgContLang;
689 foreach ( $this->mSynonyms as $syn ) {
690 $array[$wgContLang->lc( $syn )] = $value;
691 }
692 }
693
694 /**
695 * @return bool
696 */
697 function isCaseSensitive() {
698 return $this->mCaseSensitive;
699 }
700
701 /**
702 * @return int
703 */
704 function getId() {
705 return $this->mId;
706 }
707 }
708
709 /**
710 * Class for handling an array of magic words
711 * @ingroup Parser
712 */
713 class MagicWordArray {
714 /** @var array */
715 public $names = array();
716
717 /** @var array */
718 private $hash;
719
720 private $baseRegex;
721
722 private $regex;
723
724 /** @todo Unused? */
725 private $matches;
726
727 /**
728 * @param array $names
729 */
730 function __construct( $names = array() ) {
731 $this->names = $names;
732 }
733
734 /**
735 * Add a magic word by name
736 *
737 * @param string $name
738 */
739 public function add( $name ) {
740 $this->names[] = $name;
741 $this->hash = $this->baseRegex = $this->regex = null;
742 }
743
744 /**
745 * Add a number of magic words by name
746 *
747 * @param array $names
748 */
749 public function addArray( $names ) {
750 $this->names = array_merge( $this->names, array_values( $names ) );
751 $this->hash = $this->baseRegex = $this->regex = null;
752 }
753
754 /**
755 * Get a 2-d hashtable for this array
756 * @return 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 * @return array
779 */
780 function getBaseRegex() {
781 if ( is_null( $this->baseRegex ) ) {
782 $this->baseRegex = array( 0 => '', 1 => '' );
783 foreach ( $this->names as $name ) {
784 $magic = MagicWord::get( $name );
785 $case = intval( $magic->isCaseSensitive() );
786 foreach ( $magic->getSynonyms() as $i => $syn ) {
787 // Group name must start with a non-digit in PCRE 8.34+
788 $it = strtr( $i, '0123456789', 'abcdefghij' );
789 $group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
790 if ( $this->baseRegex[$case] === '' ) {
791 $this->baseRegex[$case] = $group;
792 } else {
793 $this->baseRegex[$case] .= '|' . $group;
794 }
795 }
796 }
797 }
798 return $this->baseRegex;
799 }
800
801 /**
802 * Get an unanchored regex that does not match parameters
803 * @return array
804 */
805 function getRegex() {
806 if ( is_null( $this->regex ) ) {
807 $base = $this->getBaseRegex();
808 $this->regex = array( '', '' );
809 if ( $this->baseRegex[0] !== '' ) {
810 $this->regex[0] = "/{$base[0]}/iuS";
811 }
812 if ( $this->baseRegex[1] !== '' ) {
813 $this->regex[1] = "/{$base[1]}/S";
814 }
815 }
816 return $this->regex;
817 }
818
819 /**
820 * Get a regex for matching variables with parameters
821 *
822 * @return string
823 */
824 function getVariableRegex() {
825 return str_replace( "\\$1", "(.*?)", $this->getRegex() );
826 }
827
828 /**
829 * Get a regex anchored to the start of the string that does not match parameters
830 *
831 * @return array
832 */
833 function getRegexStart() {
834 $base = $this->getBaseRegex();
835 $newRegex = array( '', '' );
836 if ( $base[0] !== '' ) {
837 $newRegex[0] = "/^(?:{$base[0]})/iuS";
838 }
839 if ( $base[1] !== '' ) {
840 $newRegex[1] = "/^(?:{$base[1]})/S";
841 }
842 return $newRegex;
843 }
844
845 /**
846 * Get an anchored regex for matching variables with parameters
847 *
848 * @return array
849 */
850 function getVariableStartToEndRegex() {
851 $base = $this->getBaseRegex();
852 $newRegex = array( '', '' );
853 if ( $base[0] !== '' ) {
854 $newRegex[0] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[0]})$/iuS" );
855 }
856 if ( $base[1] !== '' ) {
857 $newRegex[1] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[1]})$/S" );
858 }
859 return $newRegex;
860 }
861
862 /**
863 * @since 1.20
864 * @return array
865 */
866 public function getNames() {
867 return $this->names;
868 }
869
870 /**
871 * Parse a match array from preg_match
872 * Returns array(magic word ID, parameter value)
873 * If there is no parameter value, that element will be false.
874 *
875 * @param array $m
876 *
877 * @throws MWException
878 * @return array
879 */
880 function parseMatch( $m ) {
881 reset( $m );
882 while ( list( $key, $value ) = each( $m ) ) {
883 if ( $key === 0 || $value === '' ) {
884 continue;
885 }
886 $parts = explode( '_', $key, 2 );
887 if ( count( $parts ) != 2 ) {
888 // This shouldn't happen
889 // continue;
890 throw new MWException( __METHOD__ . ': bad parameter name' );
891 }
892 list( /* $synIndex */, $magicName ) = $parts;
893 $paramValue = next( $m );
894 return array( $magicName, $paramValue );
895 }
896 // This shouldn't happen either
897 throw new MWException( __METHOD__ . ': parameter not found' );
898 }
899
900 /**
901 * Match some text, with parameter capture
902 * Returns an array with the magic word name in the first element and the
903 * parameter in the second element.
904 * Both elements are false if there was no match.
905 *
906 * @param string $text
907 *
908 * @return array
909 */
910 public function matchVariableStartToEnd( $text ) {
911 $regexes = $this->getVariableStartToEndRegex();
912 foreach ( $regexes as $regex ) {
913 if ( $regex !== '' ) {
914 $m = array();
915 if ( preg_match( $regex, $text, $m ) ) {
916 return $this->parseMatch( $m );
917 }
918 }
919 }
920 return array( false, false );
921 }
922
923 /**
924 * Match some text, without parameter capture
925 * Returns the magic word name, or false if there was no capture
926 *
927 * @param string $text
928 *
929 * @return string|bool False on failure
930 */
931 public function matchStartToEnd( $text ) {
932 $hash = $this->getHash();
933 if ( isset( $hash[1][$text] ) ) {
934 return $hash[1][$text];
935 }
936 global $wgContLang;
937 $lc = $wgContLang->lc( $text );
938 if ( isset( $hash[0][$lc] ) ) {
939 return $hash[0][$lc];
940 }
941 return false;
942 }
943
944 /**
945 * Returns an associative array, ID => param value, for all items that match
946 * Removes the matched items from the input string (passed by reference)
947 *
948 * @param string $text
949 *
950 * @return array
951 */
952 public function matchAndRemove( &$text ) {
953 $found = array();
954 $regexes = $this->getRegex();
955 foreach ( $regexes as $regex ) {
956 if ( $regex === '' ) {
957 continue;
958 }
959 preg_match_all( $regex, $text, $matches, PREG_SET_ORDER );
960 foreach ( $matches as $m ) {
961 list( $name, $param ) = $this->parseMatch( $m );
962 $found[$name] = $param;
963 }
964 $text = preg_replace( $regex, '', $text );
965 }
966 return $found;
967 }
968
969 /**
970 * Return the ID of the magic word at the start of $text, and remove
971 * the prefix from $text.
972 * Return false if no match found and $text is not modified.
973 * Does not match parameters.
974 *
975 * @param string $text
976 *
977 * @return int|bool False on failure
978 */
979 public function matchStartAndRemove( &$text ) {
980 $regexes = $this->getRegexStart();
981 foreach ( $regexes as $regex ) {
982 if ( $regex === '' ) {
983 continue;
984 }
985 if ( preg_match( $regex, $text, $m ) ) {
986 list( $id, ) = $this->parseMatch( $m );
987 if ( strlen( $m[0] ) >= strlen( $text ) ) {
988 $text = '';
989 } else {
990 $text = substr( $text, strlen( $m[0] ) );
991 }
992 return $id;
993 }
994 }
995 return false;
996 }
997 }