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