MediaWiki  REL1_21
MessageCache.php
Go to the documentation of this file.
00001 <?php
00027 define( 'MSG_LOAD_TIMEOUT', 60 );
00028 define( 'MSG_LOCK_TIMEOUT', 10 );
00029 define( 'MSG_WAIT_TIMEOUT', 10 );
00030 define( 'MSG_CACHE_VERSION', 1 );
00031 
00037 class MessageCache {
00045         protected $mCache;
00046 
00047         // Should  mean that database cannot be used, but check
00048         protected $mDisable;
00049 
00051         protected $mExpiry;
00052 
00057         protected $mParserOptions, $mParser;
00058 
00060         protected $mLoadedLanguages = array();
00061 
00067         private static $instance;
00068 
00072         protected $mInParser = false;
00073 
00080         public static function singleton() {
00081                 if ( is_null( self::$instance ) ) {
00082                         global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
00083                         self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
00084                 }
00085                 return self::$instance;
00086         }
00087 
00093         public static function destroyInstance() {
00094                 self::$instance = null;
00095         }
00096 
00097         function __construct( $memCached, $useDB, $expiry ) {
00098                 if ( !$memCached ) {
00099                         $memCached = wfGetCache( CACHE_NONE );
00100                 }
00101 
00102                 $this->mMemc = $memCached;
00103                 $this->mDisable = !$useDB;
00104                 $this->mExpiry = $expiry;
00105         }
00106 
00112         function getParserOptions() {
00113                 if ( !$this->mParserOptions ) {
00114                         $this->mParserOptions = new ParserOptions;
00115                         $this->mParserOptions->setEditSection( false );
00116                 }
00117                 return $this->mParserOptions;
00118         }
00119 
00129         function loadFromLocal( $hash, $code ) {
00130                 global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
00131 
00132                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00133 
00134                 # Check file existence
00135                 wfSuppressWarnings();
00136                 $file = fopen( $filename, 'r' );
00137                 wfRestoreWarnings();
00138                 if ( !$file ) {
00139                         return false; // No cache file
00140                 }
00141 
00142                 if ( $wgLocalMessageCacheSerialized ) {
00143                         // Check to see if the file has the hash specified
00144                         $localHash = fread( $file, 32 );
00145                         if ( $hash === $localHash ) {
00146                                 // All good, get the rest of it
00147                                 $serialized = '';
00148                                 while ( !feof( $file ) ) {
00149                                         $serialized .= fread( $file, 100000 );
00150                                 }
00151                                 fclose( $file );
00152                                 return $this->setCache( unserialize( $serialized ), $code );
00153                         } else {
00154                                 fclose( $file );
00155                                 return false; // Wrong hash
00156                         }
00157                 } else {
00158                         $localHash = substr( fread( $file, 40 ), 8 );
00159                         fclose( $file );
00160                         if ( $hash != $localHash ) {
00161                                 return false; // Wrong hash
00162                         }
00163 
00164                         # Require overwrites the member variable or just shadows it?
00165                         require( $filename );
00166                         return $this->setCache( $this->mCache, $code );
00167                 }
00168         }
00169 
00173         function saveToLocal( $serialized, $hash, $code ) {
00174                 global $wgCacheDirectory;
00175 
00176                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00177                 wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
00178 
00179                 wfSuppressWarnings();
00180                 $file = fopen( $filename, 'w' );
00181                 wfRestoreWarnings();
00182 
00183                 if ( !$file ) {
00184                         wfDebug( "Unable to open local cache file for writing\n" );
00185                         return;
00186                 }
00187 
00188                 fwrite( $file, $hash . $serialized );
00189                 fclose( $file );
00190                 wfSuppressWarnings();
00191                 chmod( $filename, 0666 );
00192                 wfRestoreWarnings();
00193         }
00194 
00195         function saveToScript( $array, $hash, $code ) {
00196                 global $wgCacheDirectory;
00197 
00198                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00199                 $tempFilename = $filename . '.tmp';
00200                 wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
00201 
00202                 wfSuppressWarnings();
00203                 $file = fopen( $tempFilename, 'w' );
00204                 wfRestoreWarnings();
00205 
00206                 if ( !$file ) {
00207                         wfDebug( "Unable to open local cache file for writing\n" );
00208                         return;
00209                 }
00210 
00211                 fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
00212 
00213                 foreach ( $array as $key => $message ) {
00214                         $key = $this->escapeForScript( $key );
00215                         $message = $this->escapeForScript( $message );
00216                         fwrite( $file, "'$key' => '$message',\n" );
00217                 }
00218 
00219                 fwrite( $file, ");\n?>" );
00220                 fclose( $file);
00221                 rename( $tempFilename, $filename );
00222         }
00223 
00224         function escapeForScript( $string ) {
00225                 $string = str_replace( '\\', '\\\\', $string );
00226                 $string = str_replace( '\'', '\\\'', $string );
00227                 return $string;
00228         }
00229 
00235         function setCache( $cache, $code ) {
00236                 if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
00237                         $this->mCache[$code] = $cache;
00238                         return true;
00239                 } else {
00240                         return false;
00241                 }
00242         }
00243 
00264         function load( $code = false ) {
00265                 global $wgUseLocalMessageCache;
00266 
00267                 $exception = null; // deferred error
00268 
00269                 if( !is_string( $code ) ) {
00270                         # This isn't really nice, so at least make a note about it and try to
00271                         # fall back
00272                         wfDebug( __METHOD__ . " called without providing a language code\n" );
00273                         $code = 'en';
00274                 }
00275 
00276                 # Don't do double loading...
00277                 if ( isset( $this->mLoadedLanguages[$code] ) ) {
00278                         return true;
00279                 }
00280 
00281                 # 8 lines of code just to say (once) that message cache is disabled
00282                 if ( $this->mDisable ) {
00283                         static $shownDisabled = false;
00284                         if ( !$shownDisabled ) {
00285                                 wfDebug( __METHOD__ . ": disabled\n" );
00286                                 $shownDisabled = true;
00287                         }
00288                         return true;
00289                 }
00290 
00291                 # Loading code starts
00292                 wfProfileIn( __METHOD__ );
00293                 $success = false; # Keep track of success
00294                 $where = array(); # Debug info, delayed to avoid spamming debug log too much
00295                 $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
00296 
00297                 # (1) local cache
00298                 # Hash of the contents is stored in memcache, to detect if local cache goes
00299                 # out of date (due to update in other thread?)
00300                 if ( $wgUseLocalMessageCache ) {
00301                         wfProfileIn( __METHOD__ . '-fromlocal' );
00302 
00303                         $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
00304                         if ( $hash ) {
00305                                 $success = $this->loadFromLocal( $hash, $code );
00306                                 if ( $success ) $where[] = 'got from local cache';
00307                         }
00308                         wfProfileOut( __METHOD__ . '-fromlocal' );
00309                 }
00310 
00311                 # (2) memcache
00312                 # Fails if nothing in cache, or in the wrong version.
00313                 if ( !$success ) {
00314                         wfProfileIn( __METHOD__ . '-fromcache' );
00315                         $cache = $this->mMemc->get( $cacheKey );
00316                         $success = $this->setCache( $cache, $code );
00317                         if ( $success ) {
00318                                 $where[] = 'got from global cache';
00319                                 $this->saveToCaches( $cache, false, $code );
00320                         }
00321                         wfProfileOut( __METHOD__ . '-fromcache' );
00322                 }
00323 
00324                 # (3)
00325                 # Nothing in caches... so we need create one and store it in caches
00326                 if ( !$success ) {
00327                         $where[] = 'cache is empty';
00328                         $where[] = 'loading from database';
00329 
00330                         if ( $this->lock( $cacheKey ) ) {
00331                                 $that = $this;
00332                                 $osc = new ScopedCallback( function() use ( $that, $cacheKey ) {
00333                                         $that->unlock( $cacheKey );
00334                                 } );
00335                         }
00336                         # Limit the concurrency of loadFromDB to a single process
00337                         # This prevents the site from going down when the cache expires
00338                         $statusKey = wfMemcKey( 'messages', $code, 'status' );
00339                         $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
00340                         if ( $success ) { // acquired lock
00341                                 $cache = $this->mMemc;
00342                                 $isc = new ScopedCallback( function() use ( $cache, $statusKey ) {
00343                                         $cache->delete( $statusKey );
00344                                 } );
00345                                 $cache = $this->loadFromDB( $code );
00346                                 $success = $this->setCache( $cache, $code );
00347                                 if ( $success ) { // messages loaded
00348                                         $success = $this->saveToCaches( $cache, true, $code );
00349                                         $isc = null; // unlock
00350                                         if ( !$success ) {
00351                                                 $this->mMemc->set( $statusKey, 'error', 60 * 5 );
00352                                                 wfDebug( __METHOD__ . ": set() error: restart memcached server!\n" );
00353                                                 $exception = new MWException( "Could not save cache for '$code'." );
00354                                         }
00355                                 } else {
00356                                         $isc = null; // unlock
00357                                         $exception = new MWException( "Could not load cache from DB for '$code'." );
00358                                 }
00359                         } else {
00360                                 $exception = new MWException( "Could not acquire '$statusKey' lock." );
00361                         }
00362                         $osc = null; // unlock
00363                 }
00364 
00365                 if ( !$success ) {
00366                         $this->mDisable = true;
00367                         $this->mCache = false;
00368                         // This used to go on, but that led to lots of nasty side
00369                         // effects like gadgets and sidebar getting cached with their
00370                         // default content
00371                         if ( $exception instanceof Exception ) {
00372                                 throw $exception;
00373                         } else {
00374                                 throw new MWException( "MessageCache failed to load messages" );
00375                         }
00376                 } else {
00377                         # All good, just record the success
00378                         $info = implode( ', ', $where );
00379                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
00380                         $this->mLoadedLanguages[$code] = true;
00381                 }
00382                 wfProfileOut( __METHOD__ );
00383                 return $success;
00384         }
00385 
00394         function loadFromDB( $code ) {
00395                 wfProfileIn( __METHOD__ );
00396                 global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
00397                 $dbr = wfGetDB( DB_SLAVE );
00398                 $cache = array();
00399 
00400                 # Common conditions
00401                 $conds = array(
00402                         'page_is_redirect' => 0,
00403                         'page_namespace' => NS_MEDIAWIKI,
00404                 );
00405 
00406                 $mostused = array();
00407                 if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
00408                         if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
00409                                 $this->load( $wgLanguageCode );
00410                         }
00411                         $mostused = array_keys( $this->mCache[$wgLanguageCode] );
00412                         foreach ( $mostused as $key => $value ) {
00413                                 $mostused[$key] = "$value/$code";
00414                         }
00415                 }
00416 
00417                 if ( count( $mostused ) ) {
00418                         $conds['page_title'] = $mostused;
00419                 } elseif ( $code !== $wgLanguageCode ) {
00420                         $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code );
00421                 } else {
00422                         # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
00423                         # other than language code.
00424                         $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
00425                 }
00426 
00427                 # Conditions to fetch oversized pages to ignore them
00428                 $bigConds = $conds;
00429                 $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
00430 
00431                 # Load titles for all oversized pages in the MediaWiki namespace
00432                 $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
00433                 foreach ( $res as $row ) {
00434                         $cache[$row->page_title] = '!TOO BIG';
00435                 }
00436 
00437                 # Conditions to load the remaining pages with their contents
00438                 $smallConds = $conds;
00439                 $smallConds[] = 'page_latest=rev_id';
00440                 $smallConds[] = 'rev_text_id=old_id';
00441                 $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
00442 
00443                 $res = $dbr->select(
00444                         array( 'page', 'revision', 'text' ),
00445                         array( 'page_title', 'old_text', 'old_flags' ),
00446                         $smallConds,
00447                         __METHOD__ . "($code)-small"
00448                 );
00449 
00450                 foreach ( $res as $row ) {
00451                         $text = Revision::getRevisionText( $row );
00452                         if( $text === false ) {
00453                                 // Failed to fetch data; possible ES errors?
00454                                 // Store a marker to fetch on-demand as a workaround...
00455                                 $entry = '!TOO BIG';
00456                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$row->page_title} ($code)" );
00457                         } else {
00458                                 $entry = ' ' . $text;
00459                         }
00460                         $cache[$row->page_title] = $entry;
00461                 }
00462 
00463                 $cache['VERSION'] = MSG_CACHE_VERSION;
00464                 wfProfileOut( __METHOD__ );
00465                 return $cache;
00466         }
00467 
00474         public function replace( $title, $text ) {
00475                 global $wgMaxMsgCacheEntrySize;
00476                 wfProfileIn( __METHOD__ );
00477 
00478                 if ( $this->mDisable ) {
00479                         wfProfileOut( __METHOD__ );
00480                         return;
00481                 }
00482 
00483                 list( $msg, $code ) = $this->figureMessage( $title );
00484 
00485                 $cacheKey = wfMemcKey( 'messages', $code );
00486                 $this->load( $code );
00487                 $this->lock( $cacheKey );
00488 
00489                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00490 
00491                 if ( $text === false ) {
00492                         # Article was deleted
00493                         $this->mCache[$code][$title] = '!NONEXISTENT';
00494                         $this->mMemc->delete( $titleKey );
00495                 } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
00496                         # Check for size
00497                         $this->mCache[$code][$title] = '!TOO BIG';
00498                         $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
00499                 } else {
00500                         $this->mCache[$code][$title] = ' ' . $text;
00501                         $this->mMemc->delete( $titleKey );
00502                 }
00503 
00504                 # Update caches
00505                 $this->saveToCaches( $this->mCache[$code], true, $code );
00506                 $this->unlock( $cacheKey );
00507 
00508                 // Also delete cached sidebar... just in case it is affected
00509                 $codes = array( $code );
00510                 if ( $code === 'en' ) {
00511                         // Delete all sidebars, like for example on action=purge on the
00512                         // sidebar messages
00513                         $codes = array_keys( Language::fetchLanguageNames() );
00514                 }
00515 
00516                 global $wgMemc;
00517                 foreach ( $codes as $code ) {
00518                         $sidebarKey = wfMemcKey( 'sidebar', $code );
00519                         $wgMemc->delete( $sidebarKey );
00520                 }
00521 
00522                 // Update the message in the message blob store
00523                 global $wgContLang;
00524                 MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
00525 
00526                 wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
00527 
00528                 wfProfileOut( __METHOD__ );
00529         }
00530 
00539         protected function saveToCaches( $cache, $memc = true, $code = false ) {
00540                 wfProfileIn( __METHOD__ );
00541                 global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
00542 
00543                 $cacheKey = wfMemcKey( 'messages', $code );
00544 
00545                 if ( $memc ) {
00546                         $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
00547                 } else {
00548                         $success = true;
00549                 }
00550 
00551                 # Save to local cache
00552                 if ( $wgUseLocalMessageCache ) {
00553                         $serialized = serialize( $cache );
00554                         $hash = md5( $serialized );
00555                         $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
00556                         if ( $wgLocalMessageCacheSerialized ) {
00557                                 $this->saveToLocal( $serialized, $hash, $code );
00558                         } else {
00559                                 $this->saveToScript( $cache, $hash, $code );
00560                         }
00561                 }
00562 
00563                 wfProfileOut( __METHOD__ );
00564                 return $success;
00565         }
00566 
00574         function lock( $key ) {
00575                 $lockKey = $key . ':lock';
00576                 for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
00577                         sleep( 1 );
00578                 }
00579 
00580                 return $i >= MSG_WAIT_TIMEOUT;
00581         }
00582 
00583         function unlock( $key ) {
00584                 $lockKey = $key . ':lock';
00585                 $this->mMemc->delete( $lockKey );
00586         }
00587 
00608         function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
00609                 global $wgLanguageCode, $wgContLang;
00610 
00611                 if ( is_int( $key ) ) {
00612                         // "Non-string key given" exception sometimes happens for numerical strings that become ints somewhere on their way here
00613                         $key = strval( $key );
00614                 }
00615 
00616                 if ( !is_string( $key ) ) {
00617                         throw new MWException( 'Non-string key given' );
00618                 }
00619 
00620                 if ( strval( $key ) === '' ) {
00621                         # Shortcut: the empty key is always missing
00622                         return false;
00623                 }
00624 
00625                 $lang = wfGetLangObj( $langcode );
00626                 if ( !$lang ) {
00627                         throw new MWException( "Bad lang code $langcode given" );
00628                 }
00629 
00630                 $langcode = $lang->getCode();
00631 
00632                 $message = false;
00633 
00634                 # Normalise title-case input (with some inlining)
00635                 $lckey = str_replace( ' ', '_', $key );
00636                 if ( ord( $key ) < 128 ) {
00637                         $lckey[0] = strtolower( $lckey[0] );
00638                         $uckey = ucfirst( $lckey );
00639                 } else {
00640                         $lckey = $wgContLang->lcfirst( $lckey );
00641                         $uckey = $wgContLang->ucfirst( $lckey );
00642                 }
00643 
00644                 # Try the MediaWiki namespace
00645                 if( !$this->mDisable && $useDB ) {
00646                         $title = $uckey;
00647                         if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00648                                 $title .= '/' . $langcode;
00649                         }
00650                         $message = $this->getMsgFromNamespace( $title, $langcode );
00651                 }
00652 
00653                 # Try the array in the language object
00654                 if ( $message === false ) {
00655                         $message = $lang->getMessage( $lckey );
00656                         if ( is_null( $message ) ) {
00657                                 $message = false;
00658                         }
00659                 }
00660 
00661                 # Try the array of another language
00662                 if( $message === false ) {
00663                         $parts = explode( '/', $lckey );
00664                         # We may get calls for things that are http-urls from sidebar
00665                         # Let's not load nonexistent languages for those
00666                         # They usually have more than one slash.
00667                         if ( count( $parts ) == 2 && $parts[1] !== '' ) {
00668                                 $message = Language::getMessageFor( $parts[0], $parts[1] );
00669                                 if ( is_null( $message ) ) {
00670                                         $message = false;
00671                                 }
00672                         }
00673                 }
00674 
00675                 # Is this a custom message? Try the default language in the db...
00676                 if( ( $message === false || $message === '-' ) &&
00677                         !$this->mDisable && $useDB &&
00678                         !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00679                         $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
00680                 }
00681 
00682                 # Final fallback
00683                 if( $message === false ) {
00684                         return false;
00685                 }
00686 
00687                 # Fix whitespace
00688                 $message = strtr( $message,
00689                         array(
00690                                 # Fix for trailing whitespace, removed by textarea
00691                                 '&#32;' => ' ',
00692                                 # Fix for NBSP, converted to space by firefox
00693                                 '&nbsp;' => "\xc2\xa0",
00694                                 '&#160;' => "\xc2\xa0",
00695                         ) );
00696 
00697                 return $message;
00698         }
00699 
00709         function getMsgFromNamespace( $title, $code ) {
00710                 $this->load( $code );
00711                 if ( isset( $this->mCache[$code][$title] ) ) {
00712                         $entry = $this->mCache[$code][$title];
00713                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00714                                 return substr( $entry, 1 );
00715                         } elseif ( $entry === '!NONEXISTENT' ) {
00716                                 return false;
00717                         } elseif( $entry === '!TOO BIG' ) {
00718                                 // Fall through and try invididual message cache below
00719                         }
00720                 } else {
00721                         // XXX: This is not cached in process cache, should it?
00722                         $message = false;
00723                         wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
00724                         if ( $message !== false ) {
00725                                 return $message;
00726                         }
00727 
00728                         return false;
00729                 }
00730 
00731                 # Try the individual message cache
00732                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00733                 $entry = $this->mMemc->get( $titleKey );
00734                 if ( $entry ) {
00735                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00736                                 $this->mCache[$code][$title] = $entry;
00737                                 return substr( $entry, 1 );
00738                         } elseif ( $entry === '!NONEXISTENT' ) {
00739                                 $this->mCache[$code][$title] = '!NONEXISTENT';
00740                                 return false;
00741                         } else {
00742                                 # Corrupt/obsolete entry, delete it
00743                                 $this->mMemc->delete( $titleKey );
00744                         }
00745                 }
00746 
00747                 # Try loading it from the database
00748                 $revision = Revision::newFromTitle(
00749                         Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
00750                 );
00751                 if ( $revision ) {
00752                         $content = $revision->getContent();
00753                         if ( !$content ) {
00754                                 // A possibly temporary loading failure.
00755                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
00756                                 $message = null; // no negative caching
00757                         } else {
00758                                 // XXX: Is this the right way to turn a Content object into a message?
00759                                 // NOTE: $content is typically either WikitextContent, JavaScriptContent or CssContent.
00760                                 //       MessageContent is *not* used for storing messages, it's only used for wrapping them when needed.
00761                                 $message = $content->getWikitextForTransclusion();
00762 
00763                                 if ( $message === false || $message === null ) {
00764                                         wfDebugLog( 'MessageCache', __METHOD__ . ": message content doesn't provide wikitext "
00765                                                                 . "(content model: " . $content->getContentHandler() . ")" );
00766 
00767                                         $message = false; // negative caching
00768                                 } else {
00769                                         $this->mCache[$code][$title] = ' ' . $message;
00770                                         $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
00771                                 }
00772                         }
00773                 } else {
00774                         $message = false; // negative caching
00775                 }
00776 
00777                 if ( $message === false ) { // negative caching
00778                         $this->mCache[$code][$title] = '!NONEXISTENT';
00779                         $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
00780                 }
00781 
00782                 return $message;
00783         }
00784 
00792         function transform( $message, $interface = false, $language = null, $title = null ) {
00793                 // Avoid creating parser if nothing to transform
00794                 if( strpos( $message, '{{' ) === false ) {
00795                         return $message;
00796                 }
00797 
00798                 if ( $this->mInParser ) {
00799                         return $message;
00800                 }
00801 
00802                 $parser = $this->getParser();
00803                 if ( $parser ) {
00804                         $popts = $this->getParserOptions();
00805                         $popts->setInterfaceMessage( $interface );
00806                         $popts->setTargetLanguage( $language );
00807 
00808                         $userlang = $popts->setUserLang( $language );
00809                         $this->mInParser = true;
00810                         $message = $parser->transformMsg( $message, $popts, $title );
00811                         $this->mInParser = false;
00812                         $popts->setUserLang( $userlang );
00813                 }
00814                 return $message;
00815         }
00816 
00820         function getParser() {
00821                 global $wgParser, $wgParserConf;
00822                 if ( !$this->mParser && isset( $wgParser ) ) {
00823                         # Do some initialisation so that we don't have to do it twice
00824                         $wgParser->firstCallInit();
00825                         # Clone it and store it
00826                         $class = $wgParserConf['class'];
00827                         if ( $class == 'Parser_DiffTest' ) {
00828                                 # Uncloneable
00829                                 $this->mParser = new $class( $wgParserConf );
00830                         } else {
00831                                 $this->mParser = clone $wgParser;
00832                         }
00833                 }
00834                 return $this->mParser;
00835         }
00836 
00845         public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null  ) {
00846                 if ( $this->mInParser ) {
00847                         return htmlspecialchars( $text );
00848                 }
00849 
00850                 $parser = $this->getParser();
00851                 $popts = $this->getParserOptions();
00852                 $popts->setInterfaceMessage( $interface );
00853                 $popts->setTargetLanguage( $language );
00854 
00855                 wfProfileIn( __METHOD__ );
00856                 if ( !$title || !$title instanceof Title ) {
00857                         global $wgTitle;
00858                         $title = $wgTitle;
00859                 }
00860                 // Sometimes $wgTitle isn't set either...
00861                 if ( !$title ) {
00862                         # It's not uncommon having a null $wgTitle in scripts. See r80898
00863                         # Create a ghost title in such case
00864                         $title = Title::newFromText( 'Dwimmerlaik' );
00865                 }
00866 
00867                 $this->mInParser = true;
00868                 $res = $parser->parse( $text, $title, $popts, $linestart );
00869                 $this->mInParser = false;
00870 
00871                 wfProfileOut( __METHOD__ );
00872                 return $res;
00873         }
00874 
00875         function disable() {
00876                 $this->mDisable = true;
00877         }
00878 
00879         function enable() {
00880                 $this->mDisable = false;
00881         }
00882 
00886         function clear() {
00887                 $langs = Language::fetchLanguageNames( null, 'mw' );
00888                 foreach ( array_keys( $langs ) as $code ) {
00889                         # Global cache
00890                         $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
00891                         # Invalidate all local caches
00892                         $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
00893                 }
00894                 $this->mLoadedLanguages = array();
00895         }
00896 
00901         public function figureMessage( $key ) {
00902                 global $wgLanguageCode;
00903                 $pieces = explode( '/', $key );
00904                 if( count( $pieces ) < 2 ) {
00905                         return array( $key, $wgLanguageCode );
00906                 }
00907 
00908                 $lang = array_pop( $pieces );
00909                 if( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
00910                         return array( $key, $wgLanguageCode );
00911                 }
00912 
00913                 $message = implode( '/', $pieces );
00914                 return array( $message, $lang );
00915         }
00916 
00925         public function getAllMessageKeys( $code ) {
00926                 global $wgContLang;
00927                 $this->load( $code );
00928                 if ( !isset( $this->mCache[$code] ) ) {
00929                         // Apparently load() failed
00930                         return null;
00931                 }
00932                 $cache = $this->mCache[$code]; // Copy the cache
00933                 unset( $cache['VERSION'] ); // Remove the VERSION key
00934                 $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys
00935                 // Keys may appear with a capital first letter. lcfirst them.
00936                 return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
00937         }
00938 }