]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/MessageCache.php
MediaWiki 1.14.0
[autoinstalls/mediawiki.git] / includes / MessageCache.php
1 <?php
2 /**
3  * @file
4  * @ingroup Cache
5  */
6
7 /**
8  *
9  */
10 define( 'MSG_LOAD_TIMEOUT', 60);
11 define( 'MSG_LOCK_TIMEOUT', 10);
12 define( 'MSG_WAIT_TIMEOUT', 10);
13 define( 'MSG_CACHE_VERSION', 1 );
14
15 /**
16  * Message cache
17  * Performs various MediaWiki namespace-related functions
18  * @ingroup Cache
19  */
20 class MessageCache {
21         // Holds loaded messages that are defined in MediaWiki namespace.
22         var $mCache;
23
24         var $mUseCache, $mDisable, $mExpiry;
25         var $mKeys, $mParserOptions, $mParser;
26         var $mExtensionMessages = array();
27         var $mInitialised = false;
28         var $mAllMessagesLoaded = array(); // Extension messages
29
30         // Variable for tracking which variables are loaded
31         var $mLoadedLanguages = array();
32
33         function __construct( &$memCached, $useDB, $expiry, /*ignored*/ $memcPrefix ) {
34                 $this->mUseCache = !is_null( $memCached );
35                 $this->mMemc = &$memCached;
36                 $this->mDisable = !$useDB;
37                 $this->mExpiry = $expiry;
38                 $this->mDisableTransform = false;
39                 $this->mKeys = false; # initialised on demand
40                 $this->mInitialised = true;
41                 $this->mParser = null;
42         }
43
44
45         /**
46          * ParserOptions is lazy initialised.
47          */
48         function getParserOptions() {
49                 if ( !$this->mParserOptions ) {
50                         $this->mParserOptions = new ParserOptions;
51                 }
52                 return $this->mParserOptions;
53         }
54
55         /**
56          * Try to load the cache from a local file.
57          * Actual format of the file depends on the $wgLocalMessageCacheSerialized
58          * setting.
59          *
60          * @param $hash String: the hash of contents, to check validity.
61          * @param $code Mixed: Optional language code, see documenation of load().
62          * @return false on failure.
63          */
64         function loadFromLocal( $hash, $code ) {
65                 global $wgLocalMessageCache, $wgLocalMessageCacheSerialized;
66
67                 $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code";
68
69                 # Check file existence
70                 wfSuppressWarnings();
71                 $file = fopen( $filename, 'r' );
72                 wfRestoreWarnings();
73                 if ( !$file ) {
74                         return false; // No cache file
75                 }
76
77                 if ( $wgLocalMessageCacheSerialized ) {
78                         // Check to see if the file has the hash specified
79                         $localHash = fread( $file, 32 );
80                         if ( $hash === $localHash ) {
81                                 // All good, get the rest of it
82                                 $serialized = '';
83                                 while ( !feof( $file ) ) {
84                                         $serialized .= fread( $file, 100000 );
85                                 }
86                                 fclose( $file );
87                                 return $this->setCache( unserialize( $serialized ), $code );
88                         } else {
89                                 fclose( $file );
90                                 return false; // Wrong hash
91                         }
92                 } else {
93                         $localHash=substr(fread($file,40),8);
94                         fclose($file);
95                         if ($hash!=$localHash) {
96                                 return false; // Wrong hash
97                         }
98
99                         # Require overwrites the member variable or just shadows it?
100                         require( $filename );
101                         return $this->setCache( $this->mCache, $code );
102                 }
103         }
104
105         /**
106          * Save the cache to a local file.
107          */
108         function saveToLocal( $serialized, $hash, $code ) {
109                 global $wgLocalMessageCache;
110
111                 $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code";
112                 wfMkdirParents( $wgLocalMessageCache ); // might fail
113
114                 wfSuppressWarnings();
115                 $file = fopen( $filename, 'w' );
116                 wfRestoreWarnings();
117
118                 if ( !$file ) {
119                         wfDebug( "Unable to open local cache file for writing\n" );
120                         return;
121                 }
122
123                 fwrite( $file, $hash . $serialized );
124                 fclose( $file );
125                 @chmod( $filename, 0666 );
126         }
127
128         function saveToScript( $array, $hash, $code ) {
129                 global $wgLocalMessageCache;
130
131                 $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code";
132                 $tempFilename = $filename . '.tmp';
133                 wfMkdirParents( $wgLocalMessageCache ); // might fail
134
135                 wfSuppressWarnings();
136                 $file = fopen( $tempFilename, 'w');
137                 wfRestoreWarnings();
138
139                 if ( !$file ) {
140                         wfDebug( "Unable to open local cache file for writing\n" );
141                         return;
142                 }
143
144                 fwrite($file,"<?php\n//$hash\n\n \$this->mCache = array(");
145
146                 foreach ($array as $key => $message) {
147                         $key = $this->escapeForScript($key);
148                         $messages = $this->escapeForScript($message);
149                         fwrite($file, "'$key' => '$message',\n");
150                 }
151
152                 fwrite($file,");\n?>");
153                 fclose($file);
154                 rename($tempFilename, $filename);
155         }
156
157         function escapeForScript($string) {
158                 $string = str_replace( '\\', '\\\\', $string );
159                 $string = str_replace( '\'', '\\\'', $string );
160                 return $string;
161         }
162
163         /**
164          * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
165          */
166         function setCache( $cache, $code ) {
167                 if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
168                         $this->mCache[$code] = $cache;
169                         return true;
170                 } else {
171                         return false;
172                 }
173         }
174
175         /**
176          * Loads messages from caches or from database in this order:
177          * (1) local message cache (if $wgLocalMessageCache is enabled)
178          * (2) memcached
179          * (3) from the database.
180          *
181          * When succesfully loading from (2) or (3), all higher level caches are
182          * updated for the newest version.
183          *
184          * Nothing is loaded if  member variable mDisabled is true, either manually
185          * set by calling code or if message loading fails (is this possible?).
186          *
187          * Returns true if cache is already populated or it was succesfully populated,
188          * or false if populating empty cache fails. Also returns true if MessageCache
189          * is disabled.
190          *
191          * @param $code String: language to which load messages
192          */
193         function load( $code = false ) {
194                 global $wgLocalMessageCache;
195
196                 if ( !$this->mUseCache ) {
197                         return true;
198                 }
199
200                 if( !is_string( $code ) ) {
201                         # This isn't really nice, so at least make a note about it and try to
202                         # fall back
203                         wfDebug( __METHOD__ . " called without providing a language code\n" );
204                         $code = 'en';
205                 }
206
207                 # Don't do double loading...
208                 if ( isset($this->mLoadedLanguages[$code]) ) return true;
209
210                 # 8 lines of code just to say (once) that message cache is disabled
211                 if ( $this->mDisable ) {
212                         static $shownDisabled = false;
213                         if ( !$shownDisabled ) {
214                                 wfDebug( __METHOD__ . ": disabled\n" );
215                                 $shownDisabled = true;
216                         }
217                         return true;
218                 }
219
220                 # Loading code starts
221                 wfProfileIn( __METHOD__ );
222                 $success = false; # Keep track of success
223                 $where = array(); # Debug info, delayed to avoid spamming debug log too much
224                 $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
225
226
227                 # (1) local cache
228                 # Hash of the contents is stored in memcache, to detect if local cache goes
229                 # out of date (due to update in other thread?)
230                 if ( $wgLocalMessageCache !== false ) {
231                         wfProfileIn( __METHOD__ . '-fromlocal' );
232
233                         $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
234                         if ( $hash ) {
235                                 $success = $this->loadFromLocal( $hash, $code );
236                                 if ( $success ) $where[] = 'got from local cache';
237                         }
238                         wfProfileOut( __METHOD__ . '-fromlocal' );
239                 }
240
241                 # (2) memcache
242                 # Fails if nothing in cache, or in the wrong version.
243                 if ( !$success ) {
244                         wfProfileIn( __METHOD__ . '-fromcache' );
245                         $cache = $this->mMemc->get( $cacheKey );
246                         $success = $this->setCache( $cache, $code );
247                         if ( $success ) {
248                                 $where[] = 'got from global cache';
249                                 $this->saveToCaches( $cache, false, $code );
250                         }
251                         wfProfileOut( __METHOD__ . '-fromcache' );
252                 }
253
254
255                 # (3)
256                 # Nothing in caches... so we need create one and store it in caches
257                 if ( !$success ) {
258                         $where[] = 'cache is empty';
259                         $where[] = 'loading from database';
260
261                         $this->lock($cacheKey);
262
263                         # Limit the concurrency of loadFromDB to a single process 
264                         # This prevents the site from going down when the cache expires
265                         $statusKey = wfMemcKey( 'messages', $code, 'status' );
266                         $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
267                         if ( $success ) {
268                                 $cache = $this->loadFromDB( $code );
269                                 $success = $this->setCache( $cache, $code );
270                         }
271                         if ( $success ) {
272                                 $success = $this->saveToCaches( $cache, true, $code );
273                                 if ( $success ) {
274                                         $this->mMemc->delete( $statusKey );
275                                 } else {
276                                         $this->mMemc->set( $statusKey, 'error', 60*5 );
277                                         wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
278                                 }
279                         }
280                         $this->unlock($cacheKey);
281                 }
282
283                 if ( !$success ) {
284                         # Bad luck... this should not happen
285                         $where[] = 'loading FAILED - cache is disabled';
286                         $info = implode( ', ', $where );
287                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
288                         $this->mDisable = true;
289                         $this->mCache = false;
290                 } else {
291                         # All good, just record the success
292                         $info = implode( ', ', $where );
293                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
294                         $this->mLoadedLanguages[$code] = true;
295                 }
296                 wfProfileOut( __METHOD__ );
297                 return $success;
298         }
299
300         /**
301          * Loads cacheable messages from the database. Messages bigger than
302          * $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
303          * on-demand from the database later.
304          *
305          * @param $code Optional language code, see documenation of load().
306          * @return Array: Loaded messages for storing in caches.
307          */
308         function loadFromDB( $code = false ) {
309                 wfProfileIn( __METHOD__ );
310                 global $wgMaxMsgCacheEntrySize, $wgContLanguageCode;
311                 $dbr = wfGetDB( DB_SLAVE );
312                 $cache = array();
313
314                 # Common conditions
315                 $conds = array(
316                         'page_is_redirect' => 0,
317                         'page_namespace' => NS_MEDIAWIKI,
318                 );
319
320                 if ( $code ) {
321                         # Is this fast enough. Should not matter if the filtering is done in the
322                         # database or in code.
323                         if ( $code !== $wgContLanguageCode ) {
324                                 # Messages for particular language
325                                 $escapedCode = $dbr->escapeLike( $code );
326                                 $conds[] = "page_title like '%%/$escapedCode'";
327                         } else {
328                                 # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
329                                 # other than language code.
330                                 $conds[] = "page_title not like '%%/%%'";
331                         }
332                 }
333
334                 # Conditions to fetch oversized pages to ignore them
335                 $bigConds = $conds;
336                 $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
337
338                 # Load titles for all oversized pages in the MediaWiki namespace
339                 $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ );
340                 while ( $row = $dbr->fetchObject( $res ) ) {
341                         $cache[$row->page_title] = '!TOO BIG';
342                 }
343                 $dbr->freeResult( $res );
344
345                 # Conditions to load the remaining pages with their contents
346                 $smallConds = $conds;
347                 $smallConds[] = 'page_latest=rev_id';
348                 $smallConds[] = 'rev_text_id=old_id';
349                 $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
350
351                 $res = $dbr->select( array( 'page', 'revision', 'text' ),
352                         array( 'page_title', 'old_text', 'old_flags' ),
353                         $smallConds, __METHOD__ );
354
355                 for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) {
356                         $cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
357                 }
358                 $dbr->freeResult( $res );
359
360                 $cache['VERSION'] = MSG_CACHE_VERSION;
361                 wfProfileOut( __METHOD__ );
362                 return $cache;
363         }
364
365         /**
366          * Updates cache as necessary when message page is changed
367          *
368          * @param $title String: name of the page changed.
369          * @param $text Mixed: new contents of the page.
370          */
371         public function replace( $title, $text ) {
372                 global $wgMaxMsgCacheEntrySize;
373                 wfProfileIn( __METHOD__ );
374
375
376                 list( , $code ) = $this->figureMessage( $title );
377
378                 $cacheKey = wfMemcKey( 'messages', $code );
379                 $this->load($code);
380                 $this->lock($cacheKey);
381
382                 if ( is_array($this->mCache[$code]) ) {
383                         $titleKey = wfMemcKey( 'messages', 'individual', $title );
384
385                         if ( $text === false ) {
386                                 # Article was deleted
387                                 unset( $this->mCache[$code][$title] );
388                                 $this->mMemc->delete( $titleKey );
389
390                         } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
391                                 # Check for size
392                                 $this->mCache[$code][$title] = '!TOO BIG';
393                                 $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
394
395                         } else {
396                                 $this->mCache[$code][$title] = ' ' . $text;
397                                 $this->mMemc->delete( $titleKey );
398                         }
399
400                         # Update caches
401                         $this->saveToCaches( $this->mCache[$code], true, $code );
402                 }
403                 $this->unlock($cacheKey);
404
405                 // Also delete cached sidebar... just in case it is affected
406                 global $parserMemc;
407                 $sidebarKey = wfMemcKey( 'sidebar', $code );
408                 $parserMemc->delete( $sidebarKey );
409
410                 wfProfileOut( __METHOD__ );
411         }
412
413         /**
414          * Shortcut to update caches.
415          *
416          * @param $cache Array: cached messages with a version.
417          * @param $cacheKey String: Identifier for the cache.
418          * @param $memc Bool: Wether to update or not memcache.
419          * @param $code String: Language code.
420          * @return False on somekind of error.
421          */
422         protected function saveToCaches( $cache, $memc = true, $code = false ) {
423                 wfProfileIn( __METHOD__ );
424                 global $wgLocalMessageCache, $wgLocalMessageCacheSerialized;
425
426                 $cacheKey = wfMemcKey( 'messages', $code );
427
428                 $i = 0;
429                 if ( $memc ) {
430                         # Save in memcached
431                         # Keep trying if it fails, this is kind of important
432
433                         for ($i=0; $i<20 &&
434                                 !$this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
435                                 $i++ ) {
436                                 usleep(mt_rand(500000,1500000));
437                         }
438                 }
439
440                 # Save to local cache
441                 if ( $wgLocalMessageCache !== false ) {
442                         $serialized = serialize( $cache );
443                         $hash = md5( $serialized );
444                         $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
445                         if ($wgLocalMessageCacheSerialized) {
446                                 $this->saveToLocal( $serialized, $hash, $code );
447                         } else {
448                                 $this->saveToScript( $cache, $hash, $code );
449                         }
450                 }
451
452                 if ( $i == 20 ) {
453                         $success = false;
454                 } else {
455                         $success = true;
456                 }
457                 wfProfileOut( __METHOD__ );
458                 return $success;
459         }
460
461         /**
462          * Returns success
463          * Represents a write lock on the messages key
464          */
465         function lock($key) {
466                 if ( !$this->mUseCache ) {
467                         return true;
468                 }
469
470                 $lockKey = $key . ':lock';
471                 for ($i=0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
472                         sleep(1);
473                 }
474
475                 return $i >= MSG_WAIT_TIMEOUT;
476         }
477
478         function unlock($key) {
479                 if ( !$this->mUseCache ) {
480                         return;
481                 }
482
483                 $lockKey = $key . ':lock';
484                 $this->mMemc->delete( $lockKey );
485         }
486
487         /**
488          * Get a message from either the content language or the user language.
489          *
490          * @param string $key The message cache key
491          * @param bool $useDB Get the message from the DB, false to use only the localisation
492          * @param string $langcode Code of the language to get the message for, if
493          *                         it is a valid code create a language for that
494          *                         language, if it is a string but not a valid code
495          *                         then make a basic language object, if it is a
496          *                         false boolean then use the current users
497          *                         language (as a fallback for the old parameter
498          *                         functionality), or if it is a true boolean then
499          *                         use the wikis content language (also as a
500          *                         fallback).
501          * @param bool $isFullKey Specifies whether $key is a two part key "lang/msg".
502          */
503         function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
504                 global $wgContLanguageCode, $wgContLang;
505
506                 $lang = wfGetLangObj( $langcode );
507                 $langcode = $lang->getCode();
508
509                 # If uninitialised, someone is trying to call this halfway through Setup.php
510                 if( !$this->mInitialised ) {
511                         return '&lt;' . htmlspecialchars($key) . '&gt;';
512                 }
513
514                 $message = false;
515
516                 # Normalise title-case input
517                 $lckey = $wgContLang->lcfirst( $key );
518                 $lckey = str_replace( ' ', '_', $lckey );
519
520                 # Try the MediaWiki namespace
521                 if( !$this->mDisable && $useDB ) {
522                         $title = $wgContLang->ucfirst( $lckey );
523                         if(!$isFullKey && ($langcode != $wgContLanguageCode) ) {
524                                 $title .= '/' . $langcode;
525                         }
526                         $message = $this->getMsgFromNamespace( $title, $langcode );
527                 }
528
529                 # Try the extension array
530                 if ( $message === false && isset( $this->mExtensionMessages[$langcode][$lckey] ) ) {
531                         $message = $this->mExtensionMessages[$langcode][$lckey];
532                 }
533                 if ( $message === false && isset( $this->mExtensionMessages['en'][$lckey] ) ) {
534                         $message = $this->mExtensionMessages['en'][$lckey];
535                 }
536
537                 # Try the array in the language object
538                 if ( $message === false ) {
539                         $message = $lang->getMessage( $lckey );
540                         if ( is_null( $message ) ) {
541                                 $message = false;
542                         }
543                 }
544
545                 # Try the array of another language
546                 $pos = strrpos( $lckey, '/' );
547                 if( $message === false && $pos !== false) {
548                         $mkey = substr( $lckey, 0, $pos );
549                         $code = substr( $lckey, $pos+1 );
550                         if ( $code ) {
551                                 # We may get calls for things that are http-urls from sidebar
552                                 # Let's not load nonexistent languages for those
553                                 $validCodes = array_keys( Language::getLanguageNames() );
554                                 if ( in_array( $code, $validCodes ) ) {
555                                         $message = Language::getMessageFor( $mkey, $code );
556                                         if ( is_null( $message ) ) {
557                                                 $message = false;
558                                         }
559                                 }
560                         }
561                 }
562
563                 # Is this a custom message? Try the default language in the db...
564                 if( ($message === false || $message === '-' ) &&
565                         !$this->mDisable && $useDB &&
566                         !$isFullKey && ($langcode != $wgContLanguageCode) ) {
567                         $message = $this->getMsgFromNamespace( $wgContLang->ucfirst( $lckey ), $wgContLanguageCode );
568                 }
569
570                 # Final fallback
571                 if( $message === false ) {
572                         return '&lt;' . htmlspecialchars($key) . '&gt;';
573                 }
574                 return $message;
575         }
576
577         /**
578          * Get a message from the MediaWiki namespace, with caching. The key must
579          * first be converted to two-part lang/msg form if necessary.
580          *
581          * @param $title String: Message cache key with initial uppercase letter.
582          * @param $code String: code denoting the language to try.
583          */
584         function getMsgFromNamespace( $title, $code ) {
585                 $type = false;
586                 $message = false;
587
588                 if ( $this->mUseCache ) {
589                         $this->load( $code );
590                         if (isset( $this->mCache[$code][$title] ) ) {
591                                 $entry = $this->mCache[$code][$title];
592                                 $type = substr( $entry, 0, 1 );
593                                 if ( $type == ' ' ) {
594                                         return substr( $entry, 1 );
595                                 }
596                         }
597                 }
598
599                 # Call message hooks, in case they are defined
600                 wfRunHooks('MessagesPreLoad', array( $title, &$message ) );
601                 if ( $message !== false ) {
602                         return $message;
603                 }
604
605                 # If there is no cache entry and no placeholder, it doesn't exist
606                 if ( $type !== '!' ) {
607                         return false;
608                 }
609
610                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
611
612                 # Try the individual message cache
613                 if ( $this->mUseCache ) {
614                         $entry = $this->mMemc->get( $titleKey );
615                         if ( $entry ) {
616                                 $type = substr( $entry, 0, 1 );
617
618                                 if ( $type === ' ' ) {
619                                         # Ok!
620                                         $message = substr( $entry, 1 );
621                                         $this->mCache[$code][$title] = $entry;
622                                         return $message;
623                                 } elseif ( $entry === '!NONEXISTENT' ) {
624                                         return false;
625                                 } else {
626                                         # Corrupt/obsolete entry, delete it
627                                         $this->mMemc->delete( $titleKey );
628                                 }
629
630                         }
631                 }
632
633                 # Try loading it from the DB
634                 $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
635                 if( $revision ) {
636                         $message = $revision->getText();
637                         if ($this->mUseCache) {
638                                 $this->mCache[$code][$title] = ' ' . $message;
639                                 $this->mMemc->set( $titleKey, $message, $this->mExpiry );
640                         }
641                 } else {
642                         # Negative caching
643                         # Use some special text instead of false, because false gets converted to '' somewhere
644                         $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
645                         $this->mCache[$code][$title] = false;
646                 }
647                 return $message;
648         }
649
650         function transform( $message, $interface = false, $language = null ) {
651                 // Avoid creating parser if nothing to transfrom
652                 if( strpos( $message, '{{' ) === false ) {
653                         return $message;
654                 }
655
656                 global $wgParser, $wgParserConf;
657                 if ( !$this->mParser && isset( $wgParser ) ) {
658                         # Do some initialisation so that we don't have to do it twice
659                         $wgParser->firstCallInit();
660                         # Clone it and store it
661                         $class = $wgParserConf['class'];
662                         if ( $class == 'Parser_DiffTest' ) {
663                                 # Uncloneable
664                                 $this->mParser = new $class( $wgParserConf );
665                         } else {
666                                 $this->mParser = clone $wgParser;
667                         }
668                         #wfDebug( __METHOD__ . ": following contents triggered transform: $message\n" );
669                 }
670                 if ( $this->mParser ) {
671                         $popts = $this->getParserOptions();
672                         $popts->setInterfaceMessage( $interface );
673                         $popts->setTargetLanguage( $language );
674                         $message = $this->mParser->transformMsg( $message, $popts );
675                 }
676                 return $message;
677         }
678
679         function disable() { $this->mDisable = true; }
680         function enable() { $this->mDisable = false; }
681  
682         /** @deprecated */
683         function disableTransform(){
684                 wfDeprecated( __METHOD__ );
685         }
686         function enableTransform() {
687                 wfDeprecated( __METHOD__ );
688         }
689         function setTransform( $x ) {
690                 wfDeprecated( __METHOD__ );
691         }
692         function getTransform() {
693                 wfDeprecated( __METHOD__ );
694                 return false;
695         }
696
697         /**
698          * Add a message to the cache
699          *
700          * @param mixed $key
701          * @param mixed $value
702          * @param string $lang The messages language, English by default
703          */
704         function addMessage( $key, $value, $lang = 'en' ) {
705                 $this->mExtensionMessages[$lang][$key] = $value;
706         }
707
708         /**
709          * Add an associative array of message to the cache
710          *
711          * @param array $messages An associative array of key => values to be added
712          * @param string $lang The messages language, English by default
713          */
714         function addMessages( $messages, $lang = 'en' ) {
715                 wfProfileIn( __METHOD__ );
716                 if ( !is_array( $messages ) ) {
717                         throw new MWException( __METHOD__.': Invalid message array' );
718                 }
719                 if ( isset( $this->mExtensionMessages[$lang] ) ) {
720                         $this->mExtensionMessages[$lang] = $messages + $this->mExtensionMessages[$lang];
721                 } else {
722                         $this->mExtensionMessages[$lang] = $messages;
723                 }
724                 wfProfileOut( __METHOD__ );
725         }
726
727         /**
728          * Add a 2-D array of messages by lang. Useful for extensions.
729          *
730          * @param array $messages The array to be added
731          */
732         function addMessagesByLang( $messages ) {
733                 wfProfileIn( __METHOD__ );
734                 foreach ( $messages as $key => $value ) {
735                         $this->addMessages( $value, $key );
736                 }
737                 wfProfileOut( __METHOD__ );
738         }
739
740         /**
741          * Get the extension messages for a specific language. Only English, interface
742          * and content language are guaranteed to be loaded.
743          *
744          * @param string $lang The messages language, English by default
745          */
746         function getExtensionMessagesFor( $lang = 'en' ) {
747                 wfProfileIn( __METHOD__ );
748                 $messages = array();
749                 if ( isset( $this->mExtensionMessages[$lang] ) ) {
750                         $messages = $this->mExtensionMessages[$lang];
751                 }
752                 if ( $lang != 'en' ) {
753                         $messages = $messages + $this->mExtensionMessages['en'];
754                 }
755                 wfProfileOut( __METHOD__ );
756                 return $messages;
757         }
758
759         /**
760          * Clear all stored messages. Mainly used after a mass rebuild.
761          */
762         function clear() {
763                 if( $this->mUseCache ) {
764                         $langs = Language::getLanguageNames( false );
765                         foreach ( array_keys($langs) as $code ) {
766                                 # Global cache
767                                 $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
768                                 # Invalidate all local caches
769                                 $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
770                         }
771                 }
772         }
773
774         function loadAllMessages( $lang = false ) {
775                 global $wgExtensionMessagesFiles;
776                 $key = $lang === false ? '*' : $lang;
777                 if ( isset( $this->mAllMessagesLoaded[$key] ) ) {
778                         return;
779                 }
780                 $this->mAllMessagesLoaded[$key] = true;
781
782                 # Some extensions will load their messages when you load their class file
783                 wfLoadAllExtensions();
784                 # Others will respond to this hook
785                 wfRunHooks( 'LoadAllMessages' );
786                 # Some register their messages in $wgExtensionMessagesFiles
787                 foreach ( $wgExtensionMessagesFiles as $name => $file ) {
788                         wfLoadExtensionMessages( $name, $lang );
789                 }
790                 # Still others will respond to neither, they are EVIL. We sometimes need to know!
791         }
792
793         /**
794          * Load messages from a given file
795          * 
796          * @param string $filename Filename of file to load.
797          * @param string $langcode Language to load messages for, or false for 
798      *                         default behvaiour (en, content language and user
799      *                         language).
800          */
801         function loadMessagesFile( $filename, $langcode = false ) {
802                 global $wgLang, $wgContLang;
803                 $messages = $magicWords = false;
804                 require( $filename );
805
806                 $validCodes = Language::getLanguageNames();
807                 if( is_string( $langcode ) && array_key_exists( $langcode, $validCodes ) ) {
808                         # Load messages for given language code.
809                         $this->processMessagesArray( $messages, $langcode );
810                 } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $validCodes ) ) {
811                         wfDebug( "Invalid language '$langcode' code passed to MessageCache::loadMessagesFile()" );
812                 } else {
813                         # Load only languages that are usually used, and merge all
814                         # fallbacks, except English.
815                         $langs = array_unique( array( 'en', $wgContLang->getCode(), $wgLang->getCode() ) );
816                         foreach( $langs as $code ) {
817                                 $this->processMessagesArray( $messages, $code );
818                         }
819                 }
820
821                 if ( $magicWords !== false ) {
822                         global $wgContLang;
823                         $wgContLang->addMagicWordsByLang( $magicWords );
824                 }
825         }
826
827         /**
828          * Process an array of messages, loading it into the message cache.
829          *
830          * @param array $messages Messages array.
831          * @param string $langcode Language code to process.
832          */
833         function processMessagesArray( $messages, $langcode ) {
834                 $fallbackCode = $langcode;
835                 $mergedMessages = array();
836                 do {
837                         if ( isset($messages[$fallbackCode]) ) {
838                                 $mergedMessages += $messages[$fallbackCode];
839                         }
840                         $fallbackCode = Language::getFallbackfor( $fallbackCode );
841                 } while( $fallbackCode && $fallbackCode !== 'en' );
842                 
843                 if ( !empty($mergedMessages) )
844                         $this->addMessages( $mergedMessages, $langcode );
845         }
846
847         public function figureMessage( $key ) {
848                 global $wgContLanguageCode;
849                 $pieces = explode( '/', $key );
850                 if( count( $pieces ) < 2 )
851                         return array( $key, $wgContLanguageCode );
852
853                 $lang = array_pop( $pieces );
854                 $validCodes = Language::getLanguageNames();
855                 if( !array_key_exists( $lang, $validCodes ) )
856                         return array( $key, $wgContLanguageCode );
857
858                 $message = implode( '/', $pieces );
859                 return array( $message, $lang );
860         }
861
862 }