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