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