Got rid of the MagicWord indexing constants (MAG_xxx), replaced them by string indexi...
[lhc/web/wiklou.git] / includes / MagicWord.php
1 <?php
2 /**
3 * File for magic words
4 * @package MediaWiki
5 * @subpackage Parser
6 */
7
8 /**
9 * private
10 */
11 $wgMagicFound = false;
12
13 /**
14 * To add magic words in an extension, use the LanguageGetMagic hook. For
15 * magic words which are also Parser variables, add a MagicWordwgVariableIDs
16 * hook. Use string keys. Do use the interface immediately below,
17 * MagicWordMagicWords. It is deprecated.
18 */
19
20 $magicWords = array();
21 if ( ! defined( 'MEDIAWIKI_INSTALL' ) )
22 wfRunHooks( 'MagicWordMagicWords', array( &$magicWords ) );
23
24 for ( $i = 0; $i < count( $magicWords ); ++$i )
25 define( $magicWords[$i], $magicWords[$i] );
26
27
28 $wgVariableIDs = array(
29 'currentmonth',
30 'currentmonthname',
31 'currentmonthnamegen',
32 'currentmonthabbrev',
33 'currentday',
34 'currentday2',
35 'currentdayname',
36 'currentyear',
37 'currenttime',
38 'numberofarticles',
39 'numberoffiles',
40 'sitename',
41 'server',
42 'servername',
43 'scriptpath',
44 'pagename',
45 'pagenamee',
46 'fullpagename',
47 'fullpagenamee',
48 'namespace',
49 'namespacee',
50 'currentweek',
51 'currentdow',
52 'revisionid',
53 'subpagename',
54 'subpagenamee',
55 'displaytitle',
56 'talkspace',
57 'talkspacee',
58 'subjectspace',
59 'subjectspacee',
60 'talkpagename',
61 'talkpagenamee',
62 'subjectpagename',
63 'subjectpagenamee',
64 'numberofusers',
65 'rawsuffix',
66 'newsectionlink',
67 'numberofpages',
68 'currentversion',
69 'basepagename',
70 'basepagenamee',
71 'urlencode',
72 'currenttimestamp',
73 'directionmark',
74 'language',
75 'contentlanguage',
76 'pagesinnamespace',
77 'numberofadmins',
78 );
79 if ( ! defined( 'MEDIAWIKI_INSTALL' ) )
80 wfRunHooks( 'MagicWordwgVariableIDs', array( &$wgVariableIDs ) );
81
82 /**
83 * This class encapsulates "magic words" such as #redirect, __NOTOC__, etc.
84 * Usage:
85 * if (MagicWord::get( 'redirect' )->match( $text ) )
86 *
87 * Possible future improvements:
88 * * Simultaneous searching for a number of magic words
89 * * $wgMagicWords in shared memory
90 *
91 * Please avoid reading the data out of one of these objects and then writing
92 * special case code. If possible, add another match()-like function here.
93 *
94 * @package MediaWiki
95 */
96 class MagicWord {
97 /**#@+
98 * @private
99 */
100 var $mId, $mSynonyms, $mCaseSensitive, $mRegex;
101 var $mRegexStart, $mBaseRegex, $mVariableRegex;
102 var $mModified;
103 /**#@-*/
104
105 function MagicWord($id = 0, $syn = '', $cs = false) {
106 $this->mId = $id;
107 $this->mSynonyms = (array)$syn;
108 $this->mCaseSensitive = $cs;
109 $this->mRegex = '';
110 $this->mRegexStart = '';
111 $this->mVariableRegex = '';
112 $this->mVariableStartToEndRegex = '';
113 $this->mModified = false;
114 }
115
116 /**
117 * Factory: creates an object representing an ID
118 * @static
119 */
120 static function &get( $id ) {
121 global $wgMagicWords;
122
123 if ( !is_array( $wgMagicWords ) ) {
124 throw new MWException( "Incorrect initialisation order, \$wgMagicWords does not exist\n" );
125 }
126 if (!array_key_exists( $id, $wgMagicWords ) ) {
127 $mw = new MagicWord();
128 $mw->load( $id );
129 $wgMagicWords[$id] = $mw;
130 }
131 return $wgMagicWords[$id];
132 }
133
134 # Initialises this object with an ID
135 function load( $id ) {
136 global $wgContLang;
137 $this->mId = $id;
138 $wgContLang->getMagic( $this );
139 }
140
141 /**
142 * Preliminary initialisation
143 * @private
144 */
145 function initRegex() {
146 #$variableClass = Title::legalChars();
147 # This was used for matching "$1" variables, but different uses of the feature will have
148 # different restrictions, which should be checked *after* the MagicWord has been matched,
149 # not here. - IMSoP
150
151 $escSyn = array();
152 foreach ( $this->mSynonyms as $synonym )
153 // In case a magic word contains /, like that's going to happen;)
154 $escSyn[] = preg_quote( $synonym, '/' );
155 $this->mBaseRegex = implode( '|', $escSyn );
156
157 $case = $this->mCaseSensitive ? '' : 'i';
158 $this->mRegex = "/{$this->mBaseRegex}/{$case}";
159 $this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
160 $this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
161 $this->mVariableStartToEndRegex = str_replace( "\\$1", "(.*?)",
162 "/^(?:{$this->mBaseRegex})$/{$case}" );
163 }
164
165 /**
166 * Gets a regex representing matching the word
167 */
168 function getRegex() {
169 if ($this->mRegex == '' ) {
170 $this->initRegex();
171 }
172 return $this->mRegex;
173 }
174
175 /**
176 * Gets the regexp case modifier to use, i.e. i or nothing, to be used if
177 * one is using MagicWord::getBaseRegex(), otherwise it'll be included in
178 * the complete expression
179 */
180 function getRegexCase() {
181 if ( $this->mRegex === '' )
182 $this->initRegex();
183
184 return $this->mCaseSensitive ? '' : 'i';
185 }
186
187 /**
188 * Gets a regex matching the word, if it is at the string start
189 */
190 function getRegexStart() {
191 if ($this->mRegex == '' ) {
192 $this->initRegex();
193 }
194 return $this->mRegexStart;
195 }
196
197 /**
198 * regex without the slashes and what not
199 */
200 function getBaseRegex() {
201 if ($this->mRegex == '') {
202 $this->initRegex();
203 }
204 return $this->mBaseRegex;
205 }
206
207 /**
208 * Returns true if the text contains the word
209 * @return bool
210 */
211 function match( $text ) {
212 return preg_match( $this->getRegex(), $text );
213 }
214
215 /**
216 * Returns true if the text starts with the word
217 * @return bool
218 */
219 function matchStart( $text ) {
220 return preg_match( $this->getRegexStart(), $text );
221 }
222
223 /**
224 * Returns NULL if there's no match, the value of $1 otherwise
225 * The return code is the matched string, if there's no variable
226 * part in the regex and the matched variable part ($1) if there
227 * is one.
228 */
229 function matchVariableStartToEnd( $text ) {
230 $matches = array();
231 $matchcount = preg_match( $this->getVariableStartToEndRegex(), $text, $matches );
232 if ( $matchcount == 0 ) {
233 return NULL;
234 } else {
235 # multiple matched parts (variable match); some will be empty because of
236 # synonyms. The variable will be the second non-empty one so remove any
237 # blank elements and re-sort the indices.
238 # See also bug 6526
239
240 $matches = array_values(array_filter($matches));
241
242 if ( count($matches) == 1 ) { return $matches[0]; }
243 else { return $matches[1]; }
244 }
245 }
246
247
248 /**
249 * Returns true if the text matches the word, and alters the
250 * input string, removing all instances of the word
251 */
252 function matchAndRemove( &$text ) {
253 global $wgMagicFound;
254 $wgMagicFound = false;
255 $text = preg_replace_callback( $this->getRegex(), 'pregRemoveAndRecord', $text );
256 return $wgMagicFound;
257 }
258
259 function matchStartAndRemove( &$text ) {
260 global $wgMagicFound;
261 $wgMagicFound = false;
262 $text = preg_replace_callback( $this->getRegexStart(), 'pregRemoveAndRecord', $text );
263 return $wgMagicFound;
264 }
265
266
267 /**
268 * Replaces the word with something else
269 */
270 function replace( $replacement, $subject, $limit=-1 ) {
271 $res = preg_replace( $this->getRegex(), wfRegexReplacement( $replacement ), $subject, $limit );
272 $this->mModified = !($res === $subject);
273 return $res;
274 }
275
276 /**
277 * Variable handling: {{SUBST:xxx}} style words
278 * Calls back a function to determine what to replace xxx with
279 * Input word must contain $1
280 */
281 function substituteCallback( $text, $callback ) {
282 $res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
283 $this->mModified = !($res === $text);
284 return $res;
285 }
286
287 /**
288 * Matches the word, where $1 is a wildcard
289 */
290 function getVariableRegex() {
291 if ( $this->mVariableRegex == '' ) {
292 $this->initRegex();
293 }
294 return $this->mVariableRegex;
295 }
296
297 /**
298 * Matches the entire string, where $1 is a wildcard
299 */
300 function getVariableStartToEndRegex() {
301 if ( $this->mVariableStartToEndRegex == '' ) {
302 $this->initRegex();
303 }
304 return $this->mVariableStartToEndRegex;
305 }
306
307 /**
308 * Accesses the synonym list directly
309 */
310 function getSynonym( $i ) {
311 return $this->mSynonyms[$i];
312 }
313
314 function getSynonyms() {
315 return $this->mSynonyms;
316 }
317
318 /**
319 * Returns true if the last call to replace() or substituteCallback()
320 * returned a modified text, otherwise false.
321 */
322 function getWasModified(){
323 return $this->mModified;
324 }
325
326 /**
327 * $magicarr is an associative array of (magic word ID => replacement)
328 * This method uses the php feature to do several replacements at the same time,
329 * thereby gaining some efficiency. The result is placed in the out variable
330 * $result. The return value is true if something was replaced.
331 * @static
332 **/
333 function replaceMultiple( $magicarr, $subject, &$result ){
334 $search = array();
335 $replace = array();
336 foreach( $magicarr as $id => $replacement ){
337 $mw = MagicWord::get( $id );
338 $search[] = $mw->getRegex();
339 $replace[] = $replacement;
340 }
341
342 $result = preg_replace( $search, $replace, $subject );
343 return !($result === $subject);
344 }
345
346 /**
347 * Adds all the synonyms of this MagicWord to an array, to allow quick
348 * lookup in a list of magic words
349 */
350 function addToArray( &$array, $value ) {
351 foreach ( $this->mSynonyms as $syn ) {
352 $array[$syn] = $value;
353 }
354 }
355
356 function isCaseSensitive() {
357 return $this->mCaseSensitive;
358 }
359 }
360
361 /**
362 * Used in matchAndRemove()
363 * @private
364 **/
365 function pregRemoveAndRecord( $match ) {
366 global $wgMagicFound;
367 $wgMagicFound = true;
368 return '';
369 }
370
371 ?>