Remove obvious function-level profiling
[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 $this->mId = $id;
334 $wgContLang->getMagic( $this );
335 if ( !$this->mSynonyms ) {
336 $this->mSynonyms = array( 'brionmademeputthishere' );
337 throw new MWException( "Error: invalid magic word '$id'" );
338 }
339 }
340
341 /**
342 * Preliminary initialisation
343 * @private
344 */
345 function initRegex() {
346 // Sort the synonyms by length, descending, so that the longest synonym
347 // matches in precedence to the shortest
348 $synonyms = $this->mSynonyms;
349 usort( $synonyms, array( $this, 'compareStringLength' ) );
350
351 $escSyn = array();
352 foreach ( $synonyms as $synonym ) {
353 // In case a magic word contains /, like that's going to happen;)
354 $escSyn[] = preg_quote( $synonym, '/' );
355 }
356 $this->mBaseRegex = implode( '|', $escSyn );
357
358 $case = $this->mCaseSensitive ? '' : 'iu';
359 $this->mRegex = "/{$this->mBaseRegex}/{$case}";
360 $this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
361 $this->mRegexStartToEnd = "/^(?:{$this->mBaseRegex})$/{$case}";
362 $this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
363 $this->mVariableStartToEndRegex = str_replace( "\\$1", "(.*?)",
364 "/^(?:{$this->mBaseRegex})$/{$case}" );
365 }
366
367 /**
368 * A comparison function that returns -1, 0 or 1 depending on whether the
369 * first string is longer, the same length or shorter than the second
370 * string.
371 *
372 * @param string $s1
373 * @param string $s2
374 *
375 * @return int
376 */
377 function compareStringLength( $s1, $s2 ) {
378 $l1 = strlen( $s1 );
379 $l2 = strlen( $s2 );
380 if ( $l1 < $l2 ) {
381 return 1;
382 } elseif ( $l1 > $l2 ) {
383 return -1;
384 } else {
385 return 0;
386 }
387 }
388
389 /**
390 * Gets a regex representing matching the word
391 *
392 * @return string
393 */
394 function getRegex() {
395 if ( $this->mRegex == '' ) {
396 $this->initRegex();
397 }
398 return $this->mRegex;
399 }
400
401 /**
402 * Gets the regexp case modifier to use, i.e. i or nothing, to be used if
403 * one is using MagicWord::getBaseRegex(), otherwise it'll be included in
404 * the complete expression
405 *
406 * @return string
407 */
408 function getRegexCase() {
409 if ( $this->mRegex === '' ) {
410 $this->initRegex();
411 }
412
413 return $this->mCaseSensitive ? '' : 'iu';
414 }
415
416 /**
417 * Gets a regex matching the word, if it is at the string start
418 *
419 * @return string
420 */
421 function getRegexStart() {
422 if ( $this->mRegex == '' ) {
423 $this->initRegex();
424 }
425 return $this->mRegexStart;
426 }
427
428 /**
429 * Gets a regex matching the word from start to end of a string
430 *
431 * @return string
432 * @since 1.23
433 */
434 function getRegexStartToEnd() {
435 if ( $this->mRegexStartToEnd == '' ) {
436 $this->initRegex();
437 }
438 return $this->mRegexStartToEnd;
439 }
440
441 /**
442 * regex without the slashes and what not
443 *
444 * @return string
445 */
446 function getBaseRegex() {
447 if ( $this->mRegex == '' ) {
448 $this->initRegex();
449 }
450 return $this->mBaseRegex;
451 }
452
453 /**
454 * Returns true if the text contains the word
455 *
456 * @param string $text
457 *
458 * @return bool
459 */
460 function match( $text ) {
461 return (bool)preg_match( $this->getRegex(), $text );
462 }
463
464 /**
465 * Returns true if the text starts with the word
466 *
467 * @param string $text
468 *
469 * @return bool
470 */
471 function matchStart( $text ) {
472 return (bool)preg_match( $this->getRegexStart(), $text );
473 }
474
475 /**
476 * Returns true if the text matched the word
477 *
478 * @param string $text
479 *
480 * @return bool
481 * @since 1.23
482 */
483 function matchStartToEnd( $text ) {
484 return (bool)preg_match( $this->getRegexStartToEnd(), $text );
485 }
486
487 /**
488 * Returns NULL if there's no match, the value of $1 otherwise
489 * The return code is the matched string, if there's no variable
490 * part in the regex and the matched variable part ($1) if there
491 * is one.
492 *
493 * @param string $text
494 *
495 * @return string
496 */
497 function matchVariableStartToEnd( $text ) {
498 $matches = array();
499 $matchcount = preg_match( $this->getVariableStartToEndRegex(), $text, $matches );
500 if ( $matchcount == 0 ) {
501 return null;
502 } else {
503 # multiple matched parts (variable match); some will be empty because of
504 # synonyms. The variable will be the second non-empty one so remove any
505 # blank elements and re-sort the indices.
506 # See also bug 6526
507
508 $matches = array_values( array_filter( $matches ) );
509
510 if ( count( $matches ) == 1 ) {
511 return $matches[0];
512 } else {
513 return $matches[1];
514 }
515 }
516 }
517
518 /**
519 * Returns true if the text matches the word, and alters the
520 * input string, removing all instances of the word
521 *
522 * @param string $text
523 *
524 * @return bool
525 */
526 function matchAndRemove( &$text ) {
527 $this->mFound = false;
528 $text = preg_replace_callback(
529 $this->getRegex(),
530 array( &$this, 'pregRemoveAndRecord' ),
531 $text
532 );
533
534 return $this->mFound;
535 }
536
537 /**
538 * @param string $text
539 * @return bool
540 */
541 function matchStartAndRemove( &$text ) {
542 $this->mFound = false;
543 $text = preg_replace_callback(
544 $this->getRegexStart(),
545 array( &$this, 'pregRemoveAndRecord' ),
546 $text
547 );
548
549 return $this->mFound;
550 }
551
552 /**
553 * Used in matchAndRemove()
554 *
555 * @return string
556 */
557 function pregRemoveAndRecord() {
558 $this->mFound = true;
559 return '';
560 }
561
562 /**
563 * Replaces the word with something else
564 *
565 * @param string $replacement
566 * @param string $subject
567 * @param int $limit
568 *
569 * @return string
570 */
571 function replace( $replacement, $subject, $limit = -1 ) {
572 $res = preg_replace(
573 $this->getRegex(),
574 StringUtils::escapeRegexReplacement( $replacement ),
575 $subject,
576 $limit
577 );
578 $this->mModified = $res !== $subject;
579 return $res;
580 }
581
582 /**
583 * Variable handling: {{SUBST:xxx}} style words
584 * Calls back a function to determine what to replace xxx with
585 * Input word must contain $1
586 *
587 * @param string $text
588 * @param callable $callback
589 *
590 * @return string
591 */
592 function substituteCallback( $text, $callback ) {
593 $res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
594 $this->mModified = $res !== $text;
595 return $res;
596 }
597
598 /**
599 * Matches the word, where $1 is a wildcard
600 *
601 * @return string
602 */
603 function getVariableRegex() {
604 if ( $this->mVariableRegex == '' ) {
605 $this->initRegex();
606 }
607 return $this->mVariableRegex;
608 }
609
610 /**
611 * Matches the entire string, where $1 is a wildcard
612 *
613 * @return string
614 */
615 function getVariableStartToEndRegex() {
616 if ( $this->mVariableStartToEndRegex == '' ) {
617 $this->initRegex();
618 }
619 return $this->mVariableStartToEndRegex;
620 }
621
622 /**
623 * Accesses the synonym list directly
624 *
625 * @param int $i
626 *
627 * @return string
628 */
629 function getSynonym( $i ) {
630 return $this->mSynonyms[$i];
631 }
632
633 /**
634 * @return array
635 */
636 function getSynonyms() {
637 return $this->mSynonyms;
638 }
639
640 /**
641 * Returns true if the last call to replace() or substituteCallback()
642 * returned a modified text, otherwise false.
643 *
644 * @return bool
645 */
646 function getWasModified() {
647 return $this->mModified;
648 }
649
650 /**
651 * $magicarr is an associative array of (magic word ID => replacement)
652 * This method uses the php feature to do several replacements at the same time,
653 * thereby gaining some efficiency. The result is placed in the out variable
654 * $result. The return value is true if something was replaced.
655 * @todo Should this be static? It doesn't seem to be used at all
656 *
657 * @param array $magicarr
658 * @param string $subject
659 * @param string $result
660 *
661 * @return bool
662 */
663 function replaceMultiple( $magicarr, $subject, &$result ) {
664 $search = array();
665 $replace = array();
666 foreach ( $magicarr as $id => $replacement ) {
667 $mw = MagicWord::get( $id );
668 $search[] = $mw->getRegex();
669 $replace[] = $replacement;
670 }
671
672 $result = preg_replace( $search, $replace, $subject );
673 return $result !== $subject;
674 }
675
676 /**
677 * Adds all the synonyms of this MagicWord to an array, to allow quick
678 * lookup in a list of magic words
679 *
680 * @param array $array
681 * @param string $value
682 */
683 function addToArray( &$array, $value ) {
684 global $wgContLang;
685 foreach ( $this->mSynonyms as $syn ) {
686 $array[$wgContLang->lc( $syn )] = $value;
687 }
688 }
689
690 /**
691 * @return bool
692 */
693 function isCaseSensitive() {
694 return $this->mCaseSensitive;
695 }
696
697 /**
698 * @return int
699 */
700 function getId() {
701 return $this->mId;
702 }
703 }
704
705 /**
706 * Class for handling an array of magic words
707 * @ingroup Parser
708 */
709 class MagicWordArray {
710 /** @var array */
711 public $names = array();
712
713 /** @var array */
714 private $hash;
715
716 private $baseRegex;
717
718 private $regex;
719
720 /** @todo Unused? */
721 private $matches;
722
723 /**
724 * @param array $names
725 */
726 function __construct( $names = array() ) {
727 $this->names = $names;
728 }
729
730 /**
731 * Add a magic word by name
732 *
733 * @param string $name
734 */
735 public function add( $name ) {
736 $this->names[] = $name;
737 $this->hash = $this->baseRegex = $this->regex = null;
738 }
739
740 /**
741 * Add a number of magic words by name
742 *
743 * @param array $names
744 */
745 public function addArray( $names ) {
746 $this->names = array_merge( $this->names, array_values( $names ) );
747 $this->hash = $this->baseRegex = $this->regex = null;
748 }
749
750 /**
751 * Get a 2-d hashtable for this array
752 * @return array
753 */
754 function getHash() {
755 if ( is_null( $this->hash ) ) {
756 global $wgContLang;
757 $this->hash = array( 0 => array(), 1 => array() );
758 foreach ( $this->names as $name ) {
759 $magic = MagicWord::get( $name );
760 $case = intval( $magic->isCaseSensitive() );
761 foreach ( $magic->getSynonyms() as $syn ) {
762 if ( !$case ) {
763 $syn = $wgContLang->lc( $syn );
764 }
765 $this->hash[$case][$syn] = $name;
766 }
767 }
768 }
769 return $this->hash;
770 }
771
772 /**
773 * Get the base regex
774 * @return array
775 */
776 function getBaseRegex() {
777 if ( is_null( $this->baseRegex ) ) {
778 $this->baseRegex = array( 0 => '', 1 => '' );
779 foreach ( $this->names as $name ) {
780 $magic = MagicWord::get( $name );
781 $case = intval( $magic->isCaseSensitive() );
782 foreach ( $magic->getSynonyms() as $i => $syn ) {
783 // Group name must start with a non-digit in PCRE 8.34+
784 $it = strtr( $i, '0123456789', 'abcdefghij' );
785 $group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
786 if ( $this->baseRegex[$case] === '' ) {
787 $this->baseRegex[$case] = $group;
788 } else {
789 $this->baseRegex[$case] .= '|' . $group;
790 }
791 }
792 }
793 }
794 return $this->baseRegex;
795 }
796
797 /**
798 * Get an unanchored regex that does not match parameters
799 * @return array
800 */
801 function getRegex() {
802 if ( is_null( $this->regex ) ) {
803 $base = $this->getBaseRegex();
804 $this->regex = array( '', '' );
805 if ( $this->baseRegex[0] !== '' ) {
806 $this->regex[0] = "/{$base[0]}/iuS";
807 }
808 if ( $this->baseRegex[1] !== '' ) {
809 $this->regex[1] = "/{$base[1]}/S";
810 }
811 }
812 return $this->regex;
813 }
814
815 /**
816 * Get a regex for matching variables with parameters
817 *
818 * @return string
819 */
820 function getVariableRegex() {
821 return str_replace( "\\$1", "(.*?)", $this->getRegex() );
822 }
823
824 /**
825 * Get a regex anchored to the start of the string that does not match parameters
826 *
827 * @return array
828 */
829 function getRegexStart() {
830 $base = $this->getBaseRegex();
831 $newRegex = array( '', '' );
832 if ( $base[0] !== '' ) {
833 $newRegex[0] = "/^(?:{$base[0]})/iuS";
834 }
835 if ( $base[1] !== '' ) {
836 $newRegex[1] = "/^(?:{$base[1]})/S";
837 }
838 return $newRegex;
839 }
840
841 /**
842 * Get an anchored regex for matching variables with parameters
843 *
844 * @return array
845 */
846 function getVariableStartToEndRegex() {
847 $base = $this->getBaseRegex();
848 $newRegex = array( '', '' );
849 if ( $base[0] !== '' ) {
850 $newRegex[0] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[0]})$/iuS" );
851 }
852 if ( $base[1] !== '' ) {
853 $newRegex[1] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[1]})$/S" );
854 }
855 return $newRegex;
856 }
857
858 /**
859 * @since 1.20
860 * @return array
861 */
862 public function getNames() {
863 return $this->names;
864 }
865
866 /**
867 * Parse a match array from preg_match
868 * Returns array(magic word ID, parameter value)
869 * If there is no parameter value, that element will be false.
870 *
871 * @param array $m
872 *
873 * @throws MWException
874 * @return array
875 */
876 function parseMatch( $m ) {
877 reset( $m );
878 while ( list( $key, $value ) = each( $m ) ) {
879 if ( $key === 0 || $value === '' ) {
880 continue;
881 }
882 $parts = explode( '_', $key, 2 );
883 if ( count( $parts ) != 2 ) {
884 // This shouldn't happen
885 // continue;
886 throw new MWException( __METHOD__ . ': bad parameter name' );
887 }
888 list( /* $synIndex */, $magicName ) = $parts;
889 $paramValue = next( $m );
890 return array( $magicName, $paramValue );
891 }
892 // This shouldn't happen either
893 throw new MWException( __METHOD__ . ': parameter not found' );
894 }
895
896 /**
897 * Match some text, with parameter capture
898 * Returns an array with the magic word name in the first element and the
899 * parameter in the second element.
900 * Both elements are false if there was no match.
901 *
902 * @param string $text
903 *
904 * @return array
905 */
906 public function matchVariableStartToEnd( $text ) {
907 $regexes = $this->getVariableStartToEndRegex();
908 foreach ( $regexes as $regex ) {
909 if ( $regex !== '' ) {
910 $m = array();
911 if ( preg_match( $regex, $text, $m ) ) {
912 return $this->parseMatch( $m );
913 }
914 }
915 }
916 return array( false, false );
917 }
918
919 /**
920 * Match some text, without parameter capture
921 * Returns the magic word name, or false if there was no capture
922 *
923 * @param string $text
924 *
925 * @return string|bool False on failure
926 */
927 public function matchStartToEnd( $text ) {
928 $hash = $this->getHash();
929 if ( isset( $hash[1][$text] ) ) {
930 return $hash[1][$text];
931 }
932 global $wgContLang;
933 $lc = $wgContLang->lc( $text );
934 if ( isset( $hash[0][$lc] ) ) {
935 return $hash[0][$lc];
936 }
937 return false;
938 }
939
940 /**
941 * Returns an associative array, ID => param value, for all items that match
942 * Removes the matched items from the input string (passed by reference)
943 *
944 * @param string $text
945 *
946 * @return array
947 */
948 public function matchAndRemove( &$text ) {
949 $found = array();
950 $regexes = $this->getRegex();
951 foreach ( $regexes as $regex ) {
952 if ( $regex === '' ) {
953 continue;
954 }
955 preg_match_all( $regex, $text, $matches, PREG_SET_ORDER );
956 foreach ( $matches as $m ) {
957 list( $name, $param ) = $this->parseMatch( $m );
958 $found[$name] = $param;
959 }
960 $text = preg_replace( $regex, '', $text );
961 }
962 return $found;
963 }
964
965 /**
966 * Return the ID of the magic word at the start of $text, and remove
967 * the prefix from $text.
968 * Return false if no match found and $text is not modified.
969 * Does not match parameters.
970 *
971 * @param string $text
972 *
973 * @return int|bool False on failure
974 */
975 public function matchStartAndRemove( &$text ) {
976 $regexes = $this->getRegexStart();
977 foreach ( $regexes as $regex ) {
978 if ( $regex === '' ) {
979 continue;
980 }
981 if ( preg_match( $regex, $text, $m ) ) {
982 list( $id, ) = $this->parseMatch( $m );
983 if ( strlen( $m[0] ) >= strlen( $text ) ) {
984 $text = '';
985 } else {
986 $text = substr( $text, strlen( $m[0] ) );
987 }
988 return $id;
989 }
990 }
991 return false;
992 }
993 }