MediaWiki  master
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 
00263         function load( $code = false ) {
00264                 global $wgUseLocalMessageCache;
00265 
00266                 if( !is_string( $code ) ) {
00267                         # This isn't really nice, so at least make a note about it and try to
00268                         # fall back
00269                         wfDebug( __METHOD__ . " called without providing a language code\n" );
00270                         $code = 'en';
00271                 }
00272 
00273                 # Don't do double loading...
00274                 if ( isset( $this->mLoadedLanguages[$code] ) ) {
00275                         return true;
00276                 }
00277 
00278                 # 8 lines of code just to say (once) that message cache is disabled
00279                 if ( $this->mDisable ) {
00280                         static $shownDisabled = false;
00281                         if ( !$shownDisabled ) {
00282                                 wfDebug( __METHOD__ . ": disabled\n" );
00283                                 $shownDisabled = true;
00284                         }
00285                         return true;
00286                 }
00287 
00288                 # Loading code starts
00289                 wfProfileIn( __METHOD__ );
00290                 $success = false; # Keep track of success
00291                 $where = array(); # Debug info, delayed to avoid spamming debug log too much
00292                 $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
00293 
00294                 # (1) local cache
00295                 # Hash of the contents is stored in memcache, to detect if local cache goes
00296                 # out of date (due to update in other thread?)
00297                 if ( $wgUseLocalMessageCache ) {
00298                         wfProfileIn( __METHOD__ . '-fromlocal' );
00299 
00300                         $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
00301                         if ( $hash ) {
00302                                 $success = $this->loadFromLocal( $hash, $code );
00303                                 if ( $success ) $where[] = 'got from local cache';
00304                         }
00305                         wfProfileOut( __METHOD__ . '-fromlocal' );
00306                 }
00307 
00308                 # (2) memcache
00309                 # Fails if nothing in cache, or in the wrong version.
00310                 if ( !$success ) {
00311                         wfProfileIn( __METHOD__ . '-fromcache' );
00312                         $cache = $this->mMemc->get( $cacheKey );
00313                         $success = $this->setCache( $cache, $code );
00314                         if ( $success ) {
00315                                 $where[] = 'got from global cache';
00316                                 $this->saveToCaches( $cache, false, $code );
00317                         }
00318                         wfProfileOut( __METHOD__ . '-fromcache' );
00319                 }
00320 
00321                 # (3)
00322                 # Nothing in caches... so we need create one and store it in caches
00323                 if ( !$success ) {
00324                         $where[] = 'cache is empty';
00325                         $where[] = 'loading from database';
00326 
00327                         $this->lock( $cacheKey );
00328 
00329                         # Limit the concurrency of loadFromDB to a single process
00330                         # This prevents the site from going down when the cache expires
00331                         $statusKey = wfMemcKey( 'messages', $code, 'status' );
00332                         $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
00333                         if ( $success ) {
00334                                 $cache = $this->loadFromDB( $code );
00335                                 $success = $this->setCache( $cache, $code );
00336                         }
00337                         if ( $success ) {
00338                                 $success = $this->saveToCaches( $cache, true, $code );
00339                                 if ( $success ) {
00340                                         $this->mMemc->delete( $statusKey );
00341                                 } else {
00342                                         $this->mMemc->set( $statusKey, 'error', 60 * 5 );
00343                                         wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
00344                                 }
00345                         }
00346                         $this->unlock($cacheKey);
00347                 }
00348 
00349                 if ( !$success ) {
00350                         # Bad luck... this should not happen
00351                         $where[] = 'loading FAILED - cache is disabled';
00352                         $info = implode( ', ', $where );
00353                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
00354                         $this->mDisable = true;
00355                         $this->mCache = false;
00356                 } else {
00357                         # All good, just record the success
00358                         $info = implode( ', ', $where );
00359                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
00360                         $this->mLoadedLanguages[$code] = true;
00361                 }
00362                 wfProfileOut( __METHOD__ );
00363                 return $success;
00364         }
00365 
00374         function loadFromDB( $code ) {
00375                 wfProfileIn( __METHOD__ );
00376                 global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
00377                 $dbr = wfGetDB( DB_SLAVE );
00378                 $cache = array();
00379 
00380                 # Common conditions
00381                 $conds = array(
00382                         'page_is_redirect' => 0,
00383                         'page_namespace' => NS_MEDIAWIKI,
00384                 );
00385 
00386                 $mostused = array();
00387                 if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
00388                         if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
00389                                 $this->load( $wgLanguageCode );
00390                         }
00391                         $mostused = array_keys( $this->mCache[$wgLanguageCode] );
00392                         foreach ( $mostused as $key => $value ) {
00393                                 $mostused[$key] = "$value/$code";
00394                         }
00395                 }
00396 
00397                 if ( count( $mostused ) ) {
00398                         $conds['page_title'] = $mostused;
00399                 } elseif ( $code !== $wgLanguageCode ) {
00400                         $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code );
00401                 } else {
00402                         # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
00403                         # other than language code.
00404                         $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
00405                 }
00406 
00407                 # Conditions to fetch oversized pages to ignore them
00408                 $bigConds = $conds;
00409                 $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
00410 
00411                 # Load titles for all oversized pages in the MediaWiki namespace
00412                 $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
00413                 foreach ( $res as $row ) {
00414                         $cache[$row->page_title] = '!TOO BIG';
00415                 }
00416 
00417                 # Conditions to load the remaining pages with their contents
00418                 $smallConds = $conds;
00419                 $smallConds[] = 'page_latest=rev_id';
00420                 $smallConds[] = 'rev_text_id=old_id';
00421                 $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
00422 
00423                 $res = $dbr->select(
00424                         array( 'page', 'revision', 'text' ),
00425                         array( 'page_title', 'old_text', 'old_flags' ),
00426                         $smallConds,
00427                         __METHOD__ . "($code)-small"
00428                 );
00429 
00430                 foreach ( $res as $row ) {
00431                         $text = Revision::getRevisionText( $row );
00432                         if( $text === false ) {
00433                                 // Failed to fetch data; possible ES errors?
00434                                 // Store a marker to fetch on-demand as a workaround...
00435                                 $entry = '!TOO BIG';
00436                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$row->page_title} ($code)" );
00437                         } else {
00438                                 $entry = ' ' . $text;
00439                         }
00440                         $cache[$row->page_title] = $entry;
00441                 }
00442 
00443                 $cache['VERSION'] = MSG_CACHE_VERSION;
00444                 wfProfileOut( __METHOD__ );
00445                 return $cache;
00446         }
00447 
00454         public function replace( $title, $text ) {
00455                 global $wgMaxMsgCacheEntrySize;
00456                 wfProfileIn( __METHOD__ );
00457 
00458                 if ( $this->mDisable ) {
00459                         wfProfileOut( __METHOD__ );
00460                         return;
00461                 }
00462 
00463                 list( $msg, $code ) = $this->figureMessage( $title );
00464 
00465                 $cacheKey = wfMemcKey( 'messages', $code );
00466                 $this->load( $code );
00467                 $this->lock( $cacheKey );
00468 
00469                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00470 
00471                 if ( $text === false ) {
00472                         # Article was deleted
00473                         $this->mCache[$code][$title] = '!NONEXISTENT';
00474                         $this->mMemc->delete( $titleKey );
00475                 } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
00476                         # Check for size
00477                         $this->mCache[$code][$title] = '!TOO BIG';
00478                         $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
00479                 } else {
00480                         $this->mCache[$code][$title] = ' ' . $text;
00481                         $this->mMemc->delete( $titleKey );
00482                 }
00483 
00484                 # Update caches
00485                 $this->saveToCaches( $this->mCache[$code], true, $code );
00486                 $this->unlock( $cacheKey );
00487 
00488                 // Also delete cached sidebar... just in case it is affected
00489                 $codes = array( $code );
00490                 if ( $code === 'en'  ) {
00491                         // Delete all sidebars, like for example on action=purge on the
00492                         // sidebar messages
00493                         $codes = array_keys( Language::fetchLanguageNames() );
00494                 }
00495 
00496                 global $wgMemc;
00497                 foreach ( $codes as $code ) {
00498                         $sidebarKey = wfMemcKey( 'sidebar', $code );
00499                         $wgMemc->delete( $sidebarKey );
00500                 }
00501 
00502                 // Update the message in the message blob store
00503                 global $wgContLang;
00504                 MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
00505 
00506                 wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
00507 
00508                 wfProfileOut( __METHOD__ );
00509         }
00510 
00519         protected function saveToCaches( $cache, $memc = true, $code = false ) {
00520                 wfProfileIn( __METHOD__ );
00521                 global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
00522 
00523                 $cacheKey = wfMemcKey( 'messages', $code );
00524 
00525                 if ( $memc ) {
00526                         $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
00527                 } else {
00528                         $success = true;
00529                 }
00530 
00531                 # Save to local cache
00532                 if ( $wgUseLocalMessageCache ) {
00533                         $serialized = serialize( $cache );
00534                         $hash = md5( $serialized );
00535                         $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
00536                         if ($wgLocalMessageCacheSerialized) {
00537                                 $this->saveToLocal( $serialized, $hash, $code );
00538                         } else {
00539                                 $this->saveToScript( $cache, $hash, $code );
00540                         }
00541                 }
00542 
00543                 wfProfileOut( __METHOD__ );
00544                 return $success;
00545         }
00546 
00554         function lock( $key ) {
00555                 $lockKey = $key . ':lock';
00556                 for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
00557                         sleep( 1 );
00558                 }
00559 
00560                 return $i >= MSG_WAIT_TIMEOUT;
00561         }
00562 
00563         function unlock( $key ) {
00564                 $lockKey = $key . ':lock';
00565                 $this->mMemc->delete( $lockKey );
00566         }
00567 
00588         function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
00589                 global $wgLanguageCode, $wgContLang;
00590 
00591                 if ( is_int( $key ) ) {
00592                         // "Non-string key given" exception sometimes happens for numerical strings that become ints somewhere on their way here
00593                         $key = strval( $key );
00594                 }
00595 
00596                 if ( !is_string( $key ) ) {
00597                         throw new MWException( 'Non-string key given' );
00598                 }
00599 
00600                 if ( strval( $key ) === '' ) {
00601                         # Shortcut: the empty key is always missing
00602                         return false;
00603                 }
00604 
00605                 $lang = wfGetLangObj( $langcode );
00606                 if ( !$lang ) {
00607                         throw new MWException( "Bad lang code $langcode given" );
00608                 }
00609 
00610                 $langcode = $lang->getCode();
00611 
00612                 $message = false;
00613 
00614                 # Normalise title-case input (with some inlining)
00615                 $lckey = str_replace( ' ', '_', $key );
00616                 if ( ord( $key ) < 128 ) {
00617                         $lckey[0] = strtolower( $lckey[0] );
00618                         $uckey = ucfirst( $lckey );
00619                 } else {
00620                         $lckey = $wgContLang->lcfirst( $lckey );
00621                         $uckey = $wgContLang->ucfirst( $lckey );
00622                 }
00623 
00624                 # Try the MediaWiki namespace
00625                 if( !$this->mDisable && $useDB ) {
00626                         $title = $uckey;
00627                         if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00628                                 $title .= '/' . $langcode;
00629                         }
00630                         $message = $this->getMsgFromNamespace( $title, $langcode );
00631                 }
00632 
00633                 # Try the array in the language object
00634                 if ( $message === false ) {
00635                         $message = $lang->getMessage( $lckey );
00636                         if ( is_null( $message ) ) {
00637                                 $message = false;
00638                         }
00639                 }
00640 
00641                 # Try the array of another language
00642                 if( $message === false ) {
00643                         $parts = explode( '/', $lckey );
00644                         # We may get calls for things that are http-urls from sidebar
00645                         # Let's not load nonexistent languages for those
00646                         # They usually have more than one slash.
00647                         if ( count( $parts ) == 2 && $parts[1] !== '' ) {
00648                                 $message = Language::getMessageFor( $parts[0], $parts[1] );
00649                                 if ( is_null( $message ) ) {
00650                                         $message = false;
00651                                 }
00652                         }
00653                 }
00654 
00655                 # Is this a custom message? Try the default language in the db...
00656                 if( ( $message === false || $message === '-' ) &&
00657                         !$this->mDisable && $useDB &&
00658                         !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00659                         $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
00660                 }
00661 
00662                 # Final fallback
00663                 if( $message === false ) {
00664                         return false;
00665                 }
00666 
00667                 # Fix whitespace
00668                 $message = strtr( $message,
00669                         array(
00670                                 # Fix for trailing whitespace, removed by textarea
00671                                 '&#32;' => ' ',
00672                                 # Fix for NBSP, converted to space by firefox
00673                                 '&nbsp;' => "\xc2\xa0",
00674                                 '&#160;' => "\xc2\xa0",
00675                         ) );
00676 
00677                 return $message;
00678         }
00679 
00689         function getMsgFromNamespace( $title, $code ) {
00690                 $this->load( $code );
00691                 if ( isset( $this->mCache[$code][$title] ) ) {
00692                         $entry = $this->mCache[$code][$title];
00693                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00694                                 return substr( $entry, 1 );
00695                         } elseif ( $entry === '!NONEXISTENT' ) {
00696                                 return false;
00697                         } elseif( $entry === '!TOO BIG' ) {
00698                                 // Fall through and try invididual message cache below
00699                         }
00700                 } else {
00701                         // XXX: This is not cached in process cache, should it?
00702                         $message = false;
00703                         wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
00704                         if ( $message !== false ) {
00705                                 return $message;
00706                         }
00707 
00708                         return false;
00709                 }
00710 
00711                 # Try the individual message cache
00712                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00713                 $entry = $this->mMemc->get( $titleKey );
00714                 if ( $entry ) {
00715                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00716                                 $this->mCache[$code][$title] = $entry;
00717                                 return substr( $entry, 1 );
00718                         } elseif ( $entry === '!NONEXISTENT' ) {
00719                                 $this->mCache[$code][$title] = '!NONEXISTENT';
00720                                 return false;
00721                         } else {
00722                                 # Corrupt/obsolete entry, delete it
00723                                 $this->mMemc->delete( $titleKey );
00724                         }
00725                 }
00726 
00727                 # Try loading it from the database
00728                 $revision = Revision::newFromTitle(
00729                         Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
00730                 );
00731                 if ( $revision ) {
00732                         $content = $revision->getContent();
00733                         if ( !$content ) {
00734                                 // A possibly temporary loading failure.
00735                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
00736                                 $message = null; // no negative caching
00737                         } else {
00738                                 // XXX: Is this the right way to turn a Content object into a message?
00739                                 // NOTE: $content is typically either WikitextContent, JavaScriptContent or CssContent.
00740                                 //       MessageContent is *not* used for storing messages, it's only used for wrapping them when needed.
00741                                 $message = $content->getWikitextForTransclusion();
00742 
00743                                 if ( $message === false || $message === null ) {
00744                                         wfDebugLog( 'MessageCache', __METHOD__ . ": message content doesn't provide wikitext "
00745                                                                 . "(content model: " . $content->getContentHandler() . ")" );
00746 
00747                                         $message = false; // negative caching
00748                                 } else {
00749                                         $this->mCache[$code][$title] = ' ' . $message;
00750                                         $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
00751                                 }
00752                         }
00753                 } else {
00754                         $message = false; // negative caching
00755                 }
00756 
00757                 if ( $message === false ) { // negative caching
00758                         $this->mCache[$code][$title] = '!NONEXISTENT';
00759                         $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
00760                 }
00761 
00762                 return $message;
00763         }
00764 
00772         function transform( $message, $interface = false, $language = null, $title = null ) {
00773                 // Avoid creating parser if nothing to transform
00774                 if( strpos( $message, '{{' ) === false ) {
00775                         return $message;
00776                 }
00777 
00778                 if ( $this->mInParser ) {
00779                         return $message;
00780                 }
00781 
00782                 $parser = $this->getParser();
00783                 if ( $parser ) {
00784                         $popts = $this->getParserOptions();
00785                         $popts->setInterfaceMessage( $interface );
00786                         $popts->setTargetLanguage( $language );
00787 
00788                         $userlang = $popts->setUserLang( $language );
00789                         $this->mInParser = true;
00790                         $message = $parser->transformMsg( $message, $popts, $title );
00791                         $this->mInParser = false;
00792                         $popts->setUserLang( $userlang );
00793                 }
00794                 return $message;
00795         }
00796 
00800         function getParser() {
00801                 global $wgParser, $wgParserConf;
00802                 if ( !$this->mParser && isset( $wgParser ) ) {
00803                         # Do some initialisation so that we don't have to do it twice
00804                         $wgParser->firstCallInit();
00805                         # Clone it and store it
00806                         $class = $wgParserConf['class'];
00807                         if ( $class == 'Parser_DiffTest' ) {
00808                                 # Uncloneable
00809                                 $this->mParser = new $class( $wgParserConf );
00810                         } else {
00811                                 $this->mParser = clone $wgParser;
00812                         }
00813                 }
00814                 return $this->mParser;
00815         }
00816 
00825         public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null  ) {
00826                 if ( $this->mInParser ) {
00827                         return htmlspecialchars( $text );
00828                 }
00829 
00830                 $parser = $this->getParser();
00831                 $popts = $this->getParserOptions();
00832                 $popts->setInterfaceMessage( $interface );
00833                 $popts->setTargetLanguage( $language );
00834 
00835                 wfProfileIn( __METHOD__ );
00836                 if ( !$title || !$title instanceof Title ) {
00837                         global $wgTitle;
00838                         $title = $wgTitle;
00839                 }
00840                 // Sometimes $wgTitle isn't set either...
00841                 if ( !$title ) {
00842                         # It's not uncommon having a null $wgTitle in scripts. See r80898
00843                         # Create a ghost title in such case
00844                         $title = Title::newFromText( 'Dwimmerlaik' );
00845                 }
00846 
00847                 $this->mInParser = true;
00848                 $res = $parser->parse( $text, $title, $popts, $linestart );
00849                 $this->mInParser = false;
00850 
00851                 wfProfileOut( __METHOD__ );
00852                 return $res;
00853         }
00854 
00855         function disable() {
00856                 $this->mDisable = true;
00857         }
00858 
00859         function enable() {
00860                 $this->mDisable = false;
00861         }
00862 
00866         function clear() {
00867                 $langs = Language::fetchLanguageNames( null, 'mw' );
00868                 foreach ( array_keys( $langs ) as $code ) {
00869                         # Global cache
00870                         $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
00871                         # Invalidate all local caches
00872                         $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
00873                 }
00874                 $this->mLoadedLanguages = array();
00875         }
00876 
00881         public function figureMessage( $key ) {
00882                 global $wgLanguageCode;
00883                 $pieces = explode( '/', $key );
00884                 if( count( $pieces ) < 2 ) {
00885                         return array( $key, $wgLanguageCode );
00886                 }
00887 
00888                 $lang = array_pop( $pieces );
00889                 if( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
00890                         return array( $key, $wgLanguageCode );
00891                 }
00892 
00893                 $message = implode( '/', $pieces );
00894                 return array( $message, $lang );
00895         }
00896 
00905         public function getAllMessageKeys( $code ) {
00906                 global $wgContLang;
00907                 $this->load( $code );
00908                 if ( !isset( $this->mCache[$code] ) ) {
00909                         // Apparently load() failed
00910                         return null;
00911                 }
00912                 $cache = $this->mCache[$code]; // Copy the cache
00913                 unset( $cache['VERSION'] ); // Remove the VERSION key
00914                 $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys
00915                 // Keys may appear with a capital first letter. lcfirst them.
00916                 return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
00917         }
00918 }