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