MediaWiki  REL1_19
MessageCache.php
Go to the documentation of this file.
00001 <?php
00010 define( 'MSG_LOAD_TIMEOUT', 60 );
00011 define( 'MSG_LOCK_TIMEOUT', 10 );
00012 define( 'MSG_WAIT_TIMEOUT', 10 );
00013 define( 'MSG_CACHE_VERSION', 1 );
00014 
00020 class MessageCache {
00028         protected $mCache;
00029 
00030         // Should  mean that database cannot be used, but check
00031         protected $mDisable;
00032 
00034         protected $mExpiry;
00035 
00040         protected $mParserOptions, $mParser;
00041 
00043         protected $mLoadedLanguages = array();
00044 
00048         protected $mRequestedMessages = array();
00049 
00055         protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
00056 
00063         protected static $mAdaptiveInclusionThreshold = 0.05;
00064 
00070         private static $instance;
00071 
00075         protected $mInParser = false;
00076 
00083         public static function singleton() {
00084                 if ( is_null( self::$instance ) ) {
00085                         global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
00086                         self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
00087                 }
00088                 return self::$instance;
00089         }
00090 
00096         public static function destroyInstance() {
00097                 self::$instance = null;
00098         }
00099 
00100         function __construct( $memCached, $useDB, $expiry ) {
00101                 if ( !$memCached ) {
00102                         $memCached = wfGetCache( CACHE_NONE );
00103                 }
00104 
00105                 $this->mMemc = $memCached;
00106                 $this->mDisable = !$useDB;
00107                 $this->mExpiry = $expiry;
00108         }
00109 
00115         function getParserOptions() {
00116                 if ( !$this->mParserOptions ) {
00117                         $this->mParserOptions = new ParserOptions;
00118                 }
00119                 return $this->mParserOptions;
00120         }
00121 
00131         function loadFromLocal( $hash, $code ) {
00132                 global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
00133 
00134                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00135 
00136                 # Check file existence
00137                 wfSuppressWarnings();
00138                 $file = fopen( $filename, 'r' );
00139                 wfRestoreWarnings();
00140                 if ( !$file ) {
00141                         return false; // No cache file
00142                 }
00143 
00144                 if ( $wgLocalMessageCacheSerialized ) {
00145                         // Check to see if the file has the hash specified
00146                         $localHash = fread( $file, 32 );
00147                         if ( $hash === $localHash ) {
00148                                 // All good, get the rest of it
00149                                 $serialized = '';
00150                                 while ( !feof( $file ) ) {
00151                                         $serialized .= fread( $file, 100000 );
00152                                 }
00153                                 fclose( $file );
00154                                 return $this->setCache( unserialize( $serialized ), $code );
00155                         } else {
00156                                 fclose( $file );
00157                                 return false; // Wrong hash
00158                         }
00159                 } else {
00160                         $localHash = substr( fread( $file, 40 ), 8 );
00161                         fclose( $file );
00162                         if ( $hash != $localHash ) {
00163                                 return false; // Wrong hash
00164                         }
00165 
00166                         # Require overwrites the member variable or just shadows it?
00167                         require( $filename );
00168                         return $this->setCache( $this->mCache, $code );
00169                 }
00170         }
00171 
00175         function saveToLocal( $serialized, $hash, $code ) {
00176                 global $wgCacheDirectory;
00177 
00178                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00179                 wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
00180 
00181                 wfSuppressWarnings();
00182                 $file = fopen( $filename, 'w' );
00183                 wfRestoreWarnings();
00184 
00185                 if ( !$file ) {
00186                         wfDebug( "Unable to open local cache file for writing\n" );
00187                         return;
00188                 }
00189 
00190                 fwrite( $file, $hash . $serialized );
00191                 fclose( $file );
00192                 wfSuppressWarnings();
00193                 chmod( $filename, 0666 );
00194                 wfRestoreWarnings();
00195         }
00196 
00197         function saveToScript( $array, $hash, $code ) {
00198                 global $wgCacheDirectory;
00199 
00200                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00201                 $tempFilename = $filename . '.tmp';
00202                 wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
00203 
00204                 wfSuppressWarnings();
00205                 $file = fopen( $tempFilename, 'w' );
00206                 wfRestoreWarnings();
00207 
00208                 if ( !$file ) {
00209                         wfDebug( "Unable to open local cache file for writing\n" );
00210                         return;
00211                 }
00212 
00213                 fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
00214 
00215                 foreach ( $array as $key => $message ) {
00216                         $key = $this->escapeForScript( $key );
00217                         $message = $this->escapeForScript( $message );
00218                         fwrite( $file, "'$key' => '$message',\n" );
00219                 }
00220 
00221                 fwrite( $file, ");\n?>" );
00222                 fclose( $file);
00223                 rename( $tempFilename, $filename );
00224         }
00225 
00226         function escapeForScript( $string ) {
00227                 $string = str_replace( '\\', '\\\\', $string );
00228                 $string = str_replace( '\'', '\\\'', $string );
00229                 return $string;
00230         }
00231 
00237         function setCache( $cache, $code ) {
00238                 if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
00239                         $this->mCache[$code] = $cache;
00240                         return true;
00241                 } else {
00242                         return false;
00243                 }
00244         }
00245 
00264         function load( $code = false ) {
00265                 global $wgUseLocalMessageCache;
00266 
00267                 if( !is_string( $code ) ) {
00268                         # This isn't really nice, so at least make a note about it and try to
00269                         # fall back
00270                         wfDebug( __METHOD__ . " called without providing a language code\n" );
00271                         $code = 'en';
00272                 }
00273 
00274                 # Don't do double loading...
00275                 if ( isset( $this->mLoadedLanguages[$code] ) ) {
00276                         return true;
00277                 }
00278 
00279                 # 8 lines of code just to say (once) that message cache is disabled
00280                 if ( $this->mDisable ) {
00281                         static $shownDisabled = false;
00282                         if ( !$shownDisabled ) {
00283                                 wfDebug( __METHOD__ . ": disabled\n" );
00284                                 $shownDisabled = true;
00285                         }
00286                         return true;
00287                 }
00288 
00289                 # Loading code starts
00290                 wfProfileIn( __METHOD__ );
00291                 $success = false; # Keep track of success
00292                 $where = array(); # Debug info, delayed to avoid spamming debug log too much
00293                 $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
00294 
00295                 # (1) local cache
00296                 # Hash of the contents is stored in memcache, to detect if local cache goes
00297                 # out of date (due to update in other thread?)
00298                 if ( $wgUseLocalMessageCache ) {
00299                         wfProfileIn( __METHOD__ . '-fromlocal' );
00300 
00301                         $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
00302                         if ( $hash ) {
00303                                 $success = $this->loadFromLocal( $hash, $code );
00304                                 if ( $success ) $where[] = 'got from local cache';
00305                         }
00306                         wfProfileOut( __METHOD__ . '-fromlocal' );
00307                 }
00308 
00309                 # (2) memcache
00310                 # Fails if nothing in cache, or in the wrong version.
00311                 if ( !$success ) {
00312                         wfProfileIn( __METHOD__ . '-fromcache' );
00313                         $cache = $this->mMemc->get( $cacheKey );
00314                         $success = $this->setCache( $cache, $code );
00315                         if ( $success ) {
00316                                 $where[] = 'got from global cache';
00317                                 $this->saveToCaches( $cache, false, $code );
00318                         }
00319                         wfProfileOut( __METHOD__ . '-fromcache' );
00320                 }
00321 
00322                 # (3)
00323                 # Nothing in caches... so we need create one and store it in caches
00324                 if ( !$success ) {
00325                         $where[] = 'cache is empty';
00326                         $where[] = 'loading from database';
00327 
00328                         $this->lock( $cacheKey );
00329 
00330                         # Limit the concurrency of loadFromDB to a single process
00331                         # This prevents the site from going down when the cache expires
00332                         $statusKey = wfMemcKey( 'messages', $code, 'status' );
00333                         $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
00334                         if ( $success ) {
00335                                 $cache = $this->loadFromDB( $code );
00336                                 $success = $this->setCache( $cache, $code );
00337                         }
00338                         if ( $success ) {
00339                                 $success = $this->saveToCaches( $cache, true, $code );
00340                                 if ( $success ) {
00341                                         $this->mMemc->delete( $statusKey );
00342                                 } else {
00343                                         $this->mMemc->set( $statusKey, 'error', 60 * 5 );
00344                                         wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
00345                                 }
00346                         }
00347                         $this->unlock($cacheKey);
00348                 }
00349 
00350                 if ( !$success ) {
00351                         # Bad luck... this should not happen
00352                         $where[] = 'loading FAILED - cache is disabled';
00353                         $info = implode( ', ', $where );
00354                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
00355                         $this->mDisable = true;
00356                         $this->mCache = false;
00357                 } else {
00358                         # All good, just record the success
00359                         $info = implode( ', ', $where );
00360                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
00361                         $this->mLoadedLanguages[$code] = true;
00362                 }
00363                 wfProfileOut( __METHOD__ );
00364                 return $success;
00365         }
00366 
00375         function loadFromDB( $code ) {
00376                 wfProfileIn( __METHOD__ );
00377                 global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
00378                 $dbr = wfGetDB( DB_SLAVE );
00379                 $cache = array();
00380 
00381                 # Common conditions
00382                 $conds = array(
00383                         'page_is_redirect' => 0,
00384                         'page_namespace' => NS_MEDIAWIKI,
00385                 );
00386 
00387                 $mostused = array();
00388                 if ( $wgAdaptiveMessageCache ) {
00389                         $mostused = $this->getMostUsedMessages();
00390                         if ( $code !== $wgLanguageCode ) {
00391                                 foreach ( $mostused as $key => $value ) {
00392                                         $mostused[$key] = "$value/$code";
00393                                 }
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                 foreach ( $mostused as $key ) {
00444                         if ( !isset( $cache[$key] ) ) {
00445                                 $cache[$key] = '!NONEXISTENT';
00446                         }
00447                 }
00448 
00449                 $cache['VERSION'] = MSG_CACHE_VERSION;
00450                 wfProfileOut( __METHOD__ );
00451                 return $cache;
00452         }
00453 
00460         public function replace( $title, $text ) {
00461                 global $wgMaxMsgCacheEntrySize;
00462                 wfProfileIn( __METHOD__ );
00463 
00464                 if ( $this->mDisable ) {
00465                         wfProfileOut( __METHOD__ );
00466                         return;
00467                 }
00468 
00469                 list( $msg, $code ) = $this->figureMessage( $title );
00470 
00471                 $cacheKey = wfMemcKey( 'messages', $code );
00472                 $this->load( $code );
00473                 $this->lock( $cacheKey );
00474 
00475                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00476 
00477                 if ( $text === false ) {
00478                         # Article was deleted
00479                         $this->mCache[$code][$title] = '!NONEXISTENT';
00480                         $this->mMemc->delete( $titleKey );
00481                 } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
00482                         # Check for size
00483                         $this->mCache[$code][$title] = '!TOO BIG';
00484                         $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
00485                 } else {
00486                         $this->mCache[$code][$title] = ' ' . $text;
00487                         $this->mMemc->delete( $titleKey );
00488                 }
00489 
00490                 # Update caches
00491                 $this->saveToCaches( $this->mCache[$code], true, $code );
00492                 $this->unlock( $cacheKey );
00493 
00494                 // Also delete cached sidebar... just in case it is affected
00495                 $codes = array( $code );
00496                 if ( $code === 'en'  ) {
00497                         // Delete all sidebars, like for example on action=purge on the
00498                         // sidebar messages
00499                         $codes = array_keys( Language::getLanguageNames() );
00500                 }
00501 
00502                 global $wgMemc;
00503                 foreach ( $codes as $code ) {
00504                         $sidebarKey = wfMemcKey( 'sidebar', $code );
00505                         $wgMemc->delete( $sidebarKey );
00506                 }
00507 
00508                 // Update the message in the message blob store
00509                 global $wgContLang;
00510                 MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
00511 
00512                 wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
00513 
00514                 wfProfileOut( __METHOD__ );
00515         }
00516 
00525         protected function saveToCaches( $cache, $memc = true, $code = false ) {
00526                 wfProfileIn( __METHOD__ );
00527                 global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
00528 
00529                 $cacheKey = wfMemcKey( 'messages', $code );
00530 
00531                 if ( $memc ) {
00532                         $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
00533                 } else {
00534                         $success = true;
00535                 }
00536 
00537                 # Save to local cache
00538                 if ( $wgUseLocalMessageCache ) {
00539                         $serialized = serialize( $cache );
00540                         $hash = md5( $serialized );
00541                         $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
00542                         if ($wgLocalMessageCacheSerialized) {
00543                                 $this->saveToLocal( $serialized, $hash, $code );
00544                         } else {
00545                                 $this->saveToScript( $cache, $hash, $code );
00546                         }
00547                 }
00548 
00549                 wfProfileOut( __METHOD__ );
00550                 return $success;
00551         }
00552 
00560         function lock( $key ) {
00561                 $lockKey = $key . ':lock';
00562                 for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
00563                         sleep( 1 );
00564                 }
00565 
00566                 return $i >= MSG_WAIT_TIMEOUT;
00567         }
00568 
00569         function unlock( $key ) {
00570                 $lockKey = $key . ':lock';
00571                 $this->mMemc->delete( $lockKey );
00572         }
00573 
00593         function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
00594                 global $wgLanguageCode, $wgContLang;
00595 
00596                 if ( is_int( $key ) ) {
00597                         // "Non-string key given" exception sometimes happens for numerical strings that become ints somewhere on their way here
00598                         $key = strval( $key );
00599                 }
00600 
00601                 if ( !is_string( $key ) ) {
00602                         throw new MWException( 'Non-string key given' );
00603                 }
00604 
00605                 if ( strval( $key ) === '' ) {
00606                         # Shortcut: the empty key is always missing
00607                         return false;
00608                 }
00609 
00610                 $lang = wfGetLangObj( $langcode );
00611                 if ( !$lang ) {
00612                         throw new MWException( "Bad lang code $langcode given" );
00613                 }
00614 
00615                 $langcode = $lang->getCode();
00616 
00617                 $message = false;
00618 
00619                 # Normalise title-case input (with some inlining)
00620                 $lckey = str_replace( ' ', '_', $key );
00621                 if ( ord( $key ) < 128 ) {
00622                         $lckey[0] = strtolower( $lckey[0] );
00623                         $uckey = ucfirst( $lckey );
00624                 } else {
00625                         $lckey = $wgContLang->lcfirst( $lckey );
00626                         $uckey = $wgContLang->ucfirst( $lckey );
00627                 }
00628 
00634                 $this->mRequestedMessages[$uckey] = true;
00635 
00636                 # Try the MediaWiki namespace
00637                 if( !$this->mDisable && $useDB ) {
00638                         $title = $uckey;
00639                         if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00640                                 $title .= '/' . $langcode;
00641                         }
00642                         $message = $this->getMsgFromNamespace( $title, $langcode );
00643                 }
00644 
00645                 # Try the array in the language object
00646                 if ( $message === false ) {
00647                         $message = $lang->getMessage( $lckey );
00648                         if ( is_null( $message ) ) {
00649                                 $message = false;
00650                         }
00651                 }
00652 
00653                 # Try the array of another language
00654                 if( $message === false ) {
00655                         $parts = explode( '/', $lckey );
00656                         # We may get calls for things that are http-urls from sidebar
00657                         # Let's not load nonexistent languages for those
00658                         # They usually have more than one slash.
00659                         if ( count( $parts ) == 2 && $parts[1] !== '' ) {
00660                                 $message = Language::getMessageFor( $parts[0], $parts[1] );
00661                                 if ( is_null( $message ) ) {
00662                                         $message = false;
00663                                 }
00664                         }
00665                 }
00666 
00667                 # Is this a custom message? Try the default language in the db...
00668                 if( ( $message === false || $message === '-' ) &&
00669                         !$this->mDisable && $useDB &&
00670                         !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00671                         $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
00672                 }
00673 
00674                 # Final fallback
00675                 if( $message === false ) {
00676                         return false;
00677                 }
00678 
00679                 # Fix whitespace
00680                 $message = strtr( $message,
00681                         array(
00682                                 # Fix for trailing whitespace, removed by textarea
00683                                 '&#32;' => ' ',
00684                                 # Fix for NBSP, converted to space by firefox
00685                                 '&nbsp;' => "\xc2\xa0",
00686                                 '&#160;' => "\xc2\xa0",
00687                         ) );
00688 
00689                 return $message;
00690         }
00691 
00701         function getMsgFromNamespace( $title, $code ) {
00702                 global $wgAdaptiveMessageCache;
00703 
00704                 $this->load( $code );
00705                 if ( isset( $this->mCache[$code][$title] ) ) {
00706                         $entry = $this->mCache[$code][$title];
00707                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00708                                 return substr( $entry, 1 );
00709                         } elseif ( $entry === '!NONEXISTENT' ) {
00710                                 return false;
00711                         } elseif( $entry === '!TOO BIG' ) {
00712                                 // Fall through and try invididual message cache below
00713                         }
00714                 } else {
00715                         // XXX: This is not cached in process cache, should it?
00716                         $message = false;
00717                         wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
00718                         if ( $message !== false ) {
00719                                 return $message;
00720                         }
00721 
00728                         if ( !$wgAdaptiveMessageCache ) {
00729                                 return false;
00730                         }
00731                 }
00732 
00733                 # Try the individual message cache
00734                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00735                 $entry = $this->mMemc->get( $titleKey );
00736                 if ( $entry ) {
00737                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00738                                 $this->mCache[$code][$title] = $entry;
00739                                 return substr( $entry, 1 );
00740                         } elseif ( $entry === '!NONEXISTENT' ) {
00741                                 $this->mCache[$code][$title] = '!NONEXISTENT';
00742                                 return false;
00743                         } else {
00744                                 # Corrupt/obsolete entry, delete it
00745                                 $this->mMemc->delete( $titleKey );
00746                         }
00747                 }
00748 
00749                 # Try loading it from the database
00750                 $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
00751                 if ( $revision ) {
00752                         $message = $revision->getText();
00753                         if ($message === false) {
00754                                 // A possibly temporary loading failure.
00755                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title->getDbKey()} ($code)" );
00756                         } else {
00757                                 $this->mCache[$code][$title] = ' ' . $message;
00758                                 $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
00759                         }
00760                 } else {
00761                         $message = false;
00762                         $this->mCache[$code][$title] = '!NONEXISTENT';
00763                         $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
00764                 }
00765 
00766                 return $message;
00767         }
00768 
00776         function transform( $message, $interface = false, $language = null, $title = null ) {
00777                 // Avoid creating parser if nothing to transform
00778                 if( strpos( $message, '{{' ) === false ) {
00779                         return $message;
00780                 }
00781 
00782                 if ( $this->mInParser ) {
00783                         return $message;
00784                 }
00785 
00786                 $parser = $this->getParser();
00787                 if ( $parser ) {
00788                         $popts = $this->getParserOptions();
00789                         $popts->setInterfaceMessage( $interface );
00790                         $popts->setTargetLanguage( $language );
00791 
00792                         $userlang = $popts->setUserLang( $language );
00793                         $this->mInParser = true;
00794                         $message = $parser->transformMsg( $message, $popts, $title );
00795                         $this->mInParser = false;
00796                         $popts->setUserLang( $userlang );
00797                 }
00798                 return $message;
00799         }
00800 
00804         function getParser() {
00805                 global $wgParser, $wgParserConf;
00806                 if ( !$this->mParser && isset( $wgParser ) ) {
00807                         # Do some initialisation so that we don't have to do it twice
00808                         $wgParser->firstCallInit();
00809                         # Clone it and store it
00810                         $class = $wgParserConf['class'];
00811                         if ( $class == 'Parser_DiffTest' ) {
00812                                 # Uncloneable
00813                                 $this->mParser = new $class( $wgParserConf );
00814                         } else {
00815                                 $this->mParser = clone $wgParser;
00816                         }
00817                 }
00818                 return $this->mParser;
00819         }
00820 
00829         public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null  ) {
00830                 if ( $this->mInParser ) {
00831                         return htmlspecialchars( $text );
00832                 }
00833 
00834                 $parser = $this->getParser();
00835                 $popts = $this->getParserOptions();
00836                 $popts->setInterfaceMessage( $interface );
00837                 $popts->setTargetLanguage( $language );
00838 
00839                 wfProfileIn( __METHOD__ );
00840                 if ( !$title || !$title instanceof Title ) {
00841                         global $wgTitle;
00842                         $title = $wgTitle;
00843                 }
00844                 // Sometimes $wgTitle isn't set either...
00845                 if ( !$title ) {
00846                         # It's not uncommon having a null $wgTitle in scripts. See r80898
00847                         # Create a ghost title in such case
00848                         $title = Title::newFromText( 'Dwimmerlaik' );
00849                 }
00850 
00851                 $this->mInParser = true;
00852                 $res = $parser->parse( $text, $title, $popts, $linestart );
00853                 $this->mInParser = false;
00854 
00855                 wfProfileOut( __METHOD__ );
00856                 return $res;
00857         }
00858 
00859         function disable() {
00860                 $this->mDisable = true;
00861         }
00862 
00863         function enable() {
00864                 $this->mDisable = false;
00865         }
00866 
00870         function clear() {
00871                 $langs = Language::getLanguageNames( false );
00872                 foreach ( array_keys($langs) as $code ) {
00873                         # Global cache
00874                         $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
00875                         # Invalidate all local caches
00876                         $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
00877                 }
00878                 $this->mLoadedLanguages = array();
00879         }
00880 
00885         public function figureMessage( $key ) {
00886                 global $wgLanguageCode;
00887                 $pieces = explode( '/', $key );
00888                 if( count( $pieces ) < 2 ) {
00889                         return array( $key, $wgLanguageCode );
00890                 }
00891 
00892                 $lang = array_pop( $pieces );
00893                 $validCodes = Language::getLanguageNames();
00894                 if( !array_key_exists( $lang, $validCodes ) ) {
00895                         return array( $key, $wgLanguageCode );
00896                 }
00897 
00898                 $message = implode( '/', $pieces );
00899                 return array( $message, $lang );
00900         }
00901 
00902         public static function logMessages() {
00903                 wfProfileIn( __METHOD__ );
00904                 global $wgAdaptiveMessageCache;
00905                 if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
00906                         wfProfileOut( __METHOD__ );
00907                         return;
00908                 }
00909 
00910                 $cachekey = wfMemckey( 'message-profiling' );
00911                 $cache = wfGetCache( CACHE_DB );
00912                 $data = $cache->get( $cachekey );
00913 
00914                 if ( !$data ) {
00915                         $data = array();
00916                 }
00917 
00918                 $age = self::$mAdaptiveDataAge;
00919                 $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
00920                 foreach ( array_keys( $data ) as $key ) {
00921                         if ( $key < $filterDate ) {
00922                                 unset( $data[$key] );
00923                         }
00924                 }
00925 
00926                 $index = substr( wfTimestampNow(), 0, 8 );
00927                 if ( !isset( $data[$index] ) ) {
00928                         $data[$index] = array();
00929                 }
00930 
00931                 foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
00932                         if ( !isset( $data[$index][$message] ) ) {
00933                                 $data[$index][$message] = 0;
00934                         }
00935                         $data[$index][$message]++;
00936                 }
00937 
00938                 $cache->set( $cachekey, $data );
00939                 wfProfileOut( __METHOD__ );
00940         }
00941 
00945         public function getMostUsedMessages() {
00946                 wfProfileIn( __METHOD__ );
00947                 $cachekey = wfMemcKey( 'message-profiling' );
00948                 $cache = wfGetCache( CACHE_DB );
00949                 $data = $cache->get( $cachekey );
00950                 if ( !$data ) {
00951                         wfProfileOut( __METHOD__ );
00952                         return array();
00953                 }
00954 
00955                 $list = array();
00956 
00957                 foreach( $data as $messages ) {
00958                         foreach( $messages as $message => $count ) {
00959                                 $key = $message;
00960                                 if ( !isset( $list[$key] ) ) {
00961                                         $list[$key] = 0;
00962                                 }
00963                                 $list[$key] += $count;
00964                         }
00965                 }
00966 
00967                 $max = max( $list );
00968                 foreach ( $list as $message => $count ) {
00969                         if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
00970                                 unset( $list[$message] );
00971                         }
00972                 }
00973 
00974                 wfProfileOut( __METHOD__ );
00975                 return array_keys( $list );
00976         }
00977 
00986         public function getAllMessageKeys( $code ) {
00987                 global $wgContLang;
00988                 $this->load( $code );
00989                 if ( !isset( $this->mCache[$code] ) ) {
00990                         // Apparently load() failed
00991                         return null;
00992                 }
00993                 $cache = $this->mCache[$code]; // Copy the cache
00994                 unset( $cache['VERSION'] ); // Remove the VERSION key
00995                 $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys
00996                 // Keys may appear with a capital first letter. lcfirst them.
00997                 return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
00998         }
00999 }