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