* Documentation: @private => @access private
[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 /** Actual keyword to be used is set in Language.php */
14
15 $magicWords = array(
16 'MAG_REDIRECT',
17 'MAG_NOTOC',
18 'MAG_START',
19 'MAG_CURRENTMONTH',
20 'MAG_CURRENTMONTHNAME',
21 'MAG_CURRENTMONTHNAMEGEN',
22 'MAG_CURRENTMONTHABBREV',
23 'MAG_CURRENTDAY',
24 'MAG_CURRENTDAYNAME',
25 'MAG_CURRENTYEAR',
26 'MAG_CURRENTTIME',
27 'MAG_NUMBEROFARTICLES',
28 'MAG_SUBST',
29 'MAG_MSG',
30 'MAG_MSGNW',
31 'MAG_NOEDITSECTION',
32 'MAG_END',
33 'MAG_IMG_THUMBNAIL',
34 'MAG_IMG_RIGHT',
35 'MAG_IMG_LEFT',
36 'MAG_IMG_NONE',
37 'MAG_IMG_WIDTH',
38 'MAG_IMG_CENTER',
39 'MAG_INT',
40 'MAG_FORCETOC',
41 'MAG_SITENAME',
42 'MAG_NS',
43 'MAG_LOCALURL',
44 'MAG_LOCALURLE',
45 'MAG_SERVER',
46 'MAG_IMG_FRAMED',
47 'MAG_PAGENAME',
48 'MAG_PAGENAMEE',
49 'MAG_NAMESPACE',
50 'MAG_NAMESPACEE',
51 'MAG_TOC',
52 'MAG_GRAMMAR',
53 'MAG_NOTITLECONVERT',
54 'MAG_NOCONTENTCONVERT',
55 'MAG_CURRENTWEEK',
56 'MAG_CURRENTDOW',
57 'MAG_REVISIONID',
58 'MAG_SCRIPTPATH',
59 'MAG_SERVERNAME',
60 'MAG_NUMBEROFFILES',
61 'MAG_IMG_MANUALTHUMB',
62 'MAG_PLURAL',
63 'MAG_FULLURL',
64 'MAG_FULLURLE',
65 'MAG_LCFIRST',
66 'MAG_UCFIRST',
67 'MAG_LC',
68 'MAG_UC',
69 'MAG_FULLPAGENAME',
70 'MAG_FULLPAGENAMEE',
71 );
72 if ( ! defined( 'MEDIAWIKI_INSTALL' ) )
73 wfRunHooks( 'MagicWordMagicWords', array( &$magicWords ) );
74
75 for ( $i = 0; $i < count( $magicWords ); ++$i )
76 define( $magicWords[$i], $i );
77
78 $wgVariableIDs = array(
79 MAG_CURRENTMONTH,
80 MAG_CURRENTMONTHNAME,
81 MAG_CURRENTMONTHNAMEGEN,
82 MAG_CURRENTMONTHABBREV,
83 MAG_CURRENTDAY,
84 MAG_CURRENTDAYNAME,
85 MAG_CURRENTYEAR,
86 MAG_CURRENTTIME,
87 MAG_NUMBEROFARTICLES,
88 MAG_NUMBEROFFILES,
89 MAG_SITENAME,
90 MAG_SERVER,
91 MAG_SERVERNAME,
92 MAG_SCRIPTPATH,
93 MAG_PAGENAME,
94 MAG_PAGENAMEE,
95 MAG_FULLPAGENAME,
96 MAG_FULLPAGENAMEE,
97 MAG_NAMESPACE,
98 MAG_NAMESPACEE,
99 MAG_CURRENTWEEK,
100 MAG_CURRENTDOW,
101 MAG_REVISIONID,
102 );
103 if ( ! defined( 'MEDIAWIKI_INSTALL' ) )
104 wfRunHooks( 'MagicWordwgVariableIDs', array( &$wgVariableIDs ) );
105
106 /**
107 * This class encapsulates "magic words" such as #redirect, __NOTOC__, etc.
108 * Usage:
109 * if (MagicWord::get( MAG_REDIRECT )->match( $text ) )
110 *
111 * Possible future improvements:
112 * * Simultaneous searching for a number of magic words
113 * * $wgMagicWords in shared memory
114 *
115 * Please avoid reading the data out of one of these objects and then writing
116 * special case code. If possible, add another match()-like function here.
117 *
118 * @package MediaWiki
119 */
120 class MagicWord {
121 /**#@+
122 * @access private
123 */
124 var $mId, $mSynonyms, $mCaseSensitive, $mRegex;
125 var $mRegexStart, $mBaseRegex, $mVariableRegex;
126 var $mModified;
127 /**#@-*/
128
129 function MagicWord($id = 0, $syn = '', $cs = false) {
130 $this->mId = $id;
131 $this->mSynonyms = (array)$syn;
132 $this->mCaseSensitive = $cs;
133 $this->mRegex = '';
134 $this->mRegexStart = '';
135 $this->mVariableRegex = '';
136 $this->mVariableStartToEndRegex = '';
137 $this->mModified = false;
138 }
139
140 /**
141 * Factory: creates an object representing an ID
142 * @static
143 */
144 function &get( $id ) {
145 global $wgMagicWords;
146
147 if ( !is_array( $wgMagicWords ) ) {
148 wfDebugDieBacktrace( "Incorrect initialisation order, \$wgMagicWords does not exist\n" );
149 }
150 if (!array_key_exists( $id, $wgMagicWords ) ) {
151 $mw = new MagicWord();
152 $mw->load( $id );
153 $wgMagicWords[$id] = $mw;
154 }
155 return $wgMagicWords[$id];
156 }
157
158 # Initialises this object with an ID
159 function load( $id ) {
160 global $wgContLang;
161 $this->mId = $id;
162 $wgContLang->getMagic( $this );
163 }
164
165 /**
166 * Preliminary initialisation
167 * @access private
168 */
169 function initRegex() {
170 #$variableClass = Title::legalChars();
171 # This was used for matching "$1" variables, but different uses of the feature will have
172 # different restrictions, which should be checked *after* the MagicWord has been matched,
173 # not here. - IMSoP
174 $escSyn = array_map( 'preg_quote', $this->mSynonyms );
175 $this->mBaseRegex = implode( '|', $escSyn );
176 $case = $this->mCaseSensitive ? '' : 'i';
177 $this->mRegex = "/{$this->mBaseRegex}/{$case}";
178 $this->mRegexStart = "/^(?:{$this->mBaseRegex})/{$case}";
179 $this->mVariableRegex = str_replace( "\\$1", "(.*?)", $this->mRegex );
180 $this->mVariableStartToEndRegex = str_replace( "\\$1", "(.*?)",
181 "/^(?:{$this->mBaseRegex})$/{$case}" );
182 }
183
184 /**
185 * Gets a regex representing matching the word
186 */
187 function getRegex() {
188 if ($this->mRegex == '' ) {
189 $this->initRegex();
190 }
191 return $this->mRegex;
192 }
193
194 /**
195 * Gets a regex matching the word, if it is at the string start
196 */
197 function getRegexStart() {
198 if ($this->mRegex == '' ) {
199 $this->initRegex();
200 }
201 return $this->mRegexStart;
202 }
203
204 /**
205 * regex without the slashes and what not
206 */
207 function getBaseRegex() {
208 if ($this->mRegex == '') {
209 $this->initRegex();
210 }
211 return $this->mBaseRegex;
212 }
213
214 /**
215 * Returns true if the text contains the word
216 * @return bool
217 */
218 function match( $text ) {
219 return preg_match( $this->getRegex(), $text );
220 }
221
222 /**
223 * Returns true if the text starts with the word
224 * @return bool
225 */
226 function matchStart( $text ) {
227 return preg_match( $this->getRegexStart(), $text );
228 }
229
230 /**
231 * Returns NULL if there's no match, the value of $1 otherwise
232 * The return code is the matched string, if there's no variable
233 * part in the regex and the matched variable part ($1) if there
234 * is one.
235 */
236 function matchVariableStartToEnd( $text ) {
237 $matches = array();
238 $matchcount = preg_match( $this->getVariableStartToEndRegex(), $text, $matches );
239 if ( $matchcount == 0 ) {
240 return NULL;
241 } elseif ( count($matches) == 1 ) {
242 return $matches[0];
243 } else {
244 # multiple matched parts (variable match); some will be empty because of synonyms
245 # the variable will be the second non-empty one so remove any blank elements and re-sort the indices
246 $matches = array_values(array_filter($matches));
247 return $matches[1];
248 }
249 }
250
251
252 /**
253 * Returns true if the text matches the word, and alters the
254 * input string, removing all instances of the word
255 */
256 function matchAndRemove( &$text ) {
257 global $wgMagicFound;
258 $wgMagicFound = false;
259 $text = preg_replace_callback( $this->getRegex(), 'pregRemoveAndRecord', $text );
260 return $wgMagicFound;
261 }
262
263 function matchStartAndRemove( &$text ) {
264 global $wgMagicFound;
265 $wgMagicFound = false;
266 $text = preg_replace_callback( $this->getRegexStart(), 'pregRemoveAndRecord', $text );
267 return $wgMagicFound;
268 }
269
270
271 /**
272 * Replaces the word with something else
273 */
274 function replace( $replacement, $subject ) {
275 $res = preg_replace( $this->getRegex(), wfRegexReplacement( $replacement ), $subject );
276 $this->mModified = !($res === $subject);
277 return $res;
278 }
279
280 /**
281 * Variable handling: {{SUBST:xxx}} style words
282 * Calls back a function to determine what to replace xxx with
283 * Input word must contain $1
284 */
285 function substituteCallback( $text, $callback ) {
286 $res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
287 $this->mModified = !($res === $text);
288 return $res;
289 }
290
291 /**
292 * Matches the word, where $1 is a wildcard
293 */
294 function getVariableRegex() {
295 if ( $this->mVariableRegex == '' ) {
296 $this->initRegex();
297 }
298 return $this->mVariableRegex;
299 }
300
301 /**
302 * Matches the entire string, where $1 is a wildcard
303 */
304 function getVariableStartToEndRegex() {
305 if ( $this->mVariableStartToEndRegex == '' ) {
306 $this->initRegex();
307 }
308 return $this->mVariableStartToEndRegex;
309 }
310
311 /**
312 * Accesses the synonym list directly
313 */
314 function getSynonym( $i ) {
315 return $this->mSynonyms[$i];
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
357 /**
358 * Used in matchAndRemove()
359 * @access private
360 **/
361 function pregRemoveAndRecord( $match ) {
362 global $wgMagicFound;
363 $wgMagicFound = true;
364 return '';
365 }
366
367 ?>