MediaWiki  REL1_20
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 
00065         protected $mRequestedMessages = array();
00066 
00072         protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
00073 
00080         protected static $mAdaptiveInclusionThreshold = 0.05;
00081 
00087         private static $instance;
00088 
00092         protected $mInParser = false;
00093 
00100         public static function singleton() {
00101                 if ( is_null( self::$instance ) ) {
00102                         global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
00103                         self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
00104                 }
00105                 return self::$instance;
00106         }
00107 
00113         public static function destroyInstance() {
00114                 self::$instance = null;
00115         }
00116 
00117         function __construct( $memCached, $useDB, $expiry ) {
00118                 if ( !$memCached ) {
00119                         $memCached = wfGetCache( CACHE_NONE );
00120                 }
00121 
00122                 $this->mMemc = $memCached;
00123                 $this->mDisable = !$useDB;
00124                 $this->mExpiry = $expiry;
00125         }
00126 
00132         function getParserOptions() {
00133                 if ( !$this->mParserOptions ) {
00134                         $this->mParserOptions = new ParserOptions;
00135                         $this->mParserOptions->setEditSection( false );
00136                 }
00137                 return $this->mParserOptions;
00138         }
00139 
00149         function loadFromLocal( $hash, $code ) {
00150                 global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
00151 
00152                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00153 
00154                 # Check file existence
00155                 wfSuppressWarnings();
00156                 $file = fopen( $filename, 'r' );
00157                 wfRestoreWarnings();
00158                 if ( !$file ) {
00159                         return false; // No cache file
00160                 }
00161 
00162                 if ( $wgLocalMessageCacheSerialized ) {
00163                         // Check to see if the file has the hash specified
00164                         $localHash = fread( $file, 32 );
00165                         if ( $hash === $localHash ) {
00166                                 // All good, get the rest of it
00167                                 $serialized = '';
00168                                 while ( !feof( $file ) ) {
00169                                         $serialized .= fread( $file, 100000 );
00170                                 }
00171                                 fclose( $file );
00172                                 return $this->setCache( unserialize( $serialized ), $code );
00173                         } else {
00174                                 fclose( $file );
00175                                 return false; // Wrong hash
00176                         }
00177                 } else {
00178                         $localHash = substr( fread( $file, 40 ), 8 );
00179                         fclose( $file );
00180                         if ( $hash != $localHash ) {
00181                                 return false; // Wrong hash
00182                         }
00183 
00184                         # Require overwrites the member variable or just shadows it?
00185                         require( $filename );
00186                         return $this->setCache( $this->mCache, $code );
00187                 }
00188         }
00189 
00193         function saveToLocal( $serialized, $hash, $code ) {
00194                 global $wgCacheDirectory;
00195 
00196                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00197                 wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
00198 
00199                 wfSuppressWarnings();
00200                 $file = fopen( $filename, 'w' );
00201                 wfRestoreWarnings();
00202 
00203                 if ( !$file ) {
00204                         wfDebug( "Unable to open local cache file for writing\n" );
00205                         return;
00206                 }
00207 
00208                 fwrite( $file, $hash . $serialized );
00209                 fclose( $file );
00210                 wfSuppressWarnings();
00211                 chmod( $filename, 0666 );
00212                 wfRestoreWarnings();
00213         }
00214 
00215         function saveToScript( $array, $hash, $code ) {
00216                 global $wgCacheDirectory;
00217 
00218                 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
00219                 $tempFilename = $filename . '.tmp';
00220                 wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
00221 
00222                 wfSuppressWarnings();
00223                 $file = fopen( $tempFilename, 'w' );
00224                 wfRestoreWarnings();
00225 
00226                 if ( !$file ) {
00227                         wfDebug( "Unable to open local cache file for writing\n" );
00228                         return;
00229                 }
00230 
00231                 fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
00232 
00233                 foreach ( $array as $key => $message ) {
00234                         $key = $this->escapeForScript( $key );
00235                         $message = $this->escapeForScript( $message );
00236                         fwrite( $file, "'$key' => '$message',\n" );
00237                 }
00238 
00239                 fwrite( $file, ");\n?>" );
00240                 fclose( $file);
00241                 rename( $tempFilename, $filename );
00242         }
00243 
00244         function escapeForScript( $string ) {
00245                 $string = str_replace( '\\', '\\\\', $string );
00246                 $string = str_replace( '\'', '\\\'', $string );
00247                 return $string;
00248         }
00249 
00255         function setCache( $cache, $code ) {
00256                 if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
00257                         $this->mCache[$code] = $cache;
00258                         return true;
00259                 } else {
00260                         return false;
00261                 }
00262         }
00263 
00283         function load( $code = false ) {
00284                 global $wgUseLocalMessageCache;
00285 
00286                 if( !is_string( $code ) ) {
00287                         # This isn't really nice, so at least make a note about it and try to
00288                         # fall back
00289                         wfDebug( __METHOD__ . " called without providing a language code\n" );
00290                         $code = 'en';
00291                 }
00292 
00293                 # Don't do double loading...
00294                 if ( isset( $this->mLoadedLanguages[$code] ) ) {
00295                         return true;
00296                 }
00297 
00298                 # 8 lines of code just to say (once) that message cache is disabled
00299                 if ( $this->mDisable ) {
00300                         static $shownDisabled = false;
00301                         if ( !$shownDisabled ) {
00302                                 wfDebug( __METHOD__ . ": disabled\n" );
00303                                 $shownDisabled = true;
00304                         }
00305                         return true;
00306                 }
00307 
00308                 # Loading code starts
00309                 wfProfileIn( __METHOD__ );
00310                 $success = false; # Keep track of success
00311                 $where = array(); # Debug info, delayed to avoid spamming debug log too much
00312                 $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
00313 
00314                 # (1) local cache
00315                 # Hash of the contents is stored in memcache, to detect if local cache goes
00316                 # out of date (due to update in other thread?)
00317                 if ( $wgUseLocalMessageCache ) {
00318                         wfProfileIn( __METHOD__ . '-fromlocal' );
00319 
00320                         $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
00321                         if ( $hash ) {
00322                                 $success = $this->loadFromLocal( $hash, $code );
00323                                 if ( $success ) $where[] = 'got from local cache';
00324                         }
00325                         wfProfileOut( __METHOD__ . '-fromlocal' );
00326                 }
00327 
00328                 # (2) memcache
00329                 # Fails if nothing in cache, or in the wrong version.
00330                 if ( !$success ) {
00331                         wfProfileIn( __METHOD__ . '-fromcache' );
00332                         $cache = $this->mMemc->get( $cacheKey );
00333                         $success = $this->setCache( $cache, $code );
00334                         if ( $success ) {
00335                                 $where[] = 'got from global cache';
00336                                 $this->saveToCaches( $cache, false, $code );
00337                         }
00338                         wfProfileOut( __METHOD__ . '-fromcache' );
00339                 }
00340 
00341                 # (3)
00342                 # Nothing in caches... so we need create one and store it in caches
00343                 if ( !$success ) {
00344                         $where[] = 'cache is empty';
00345                         $where[] = 'loading from database';
00346 
00347                         $this->lock( $cacheKey );
00348 
00349                         # Limit the concurrency of loadFromDB to a single process
00350                         # This prevents the site from going down when the cache expires
00351                         $statusKey = wfMemcKey( 'messages', $code, 'status' );
00352                         $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
00353                         if ( $success ) {
00354                                 $cache = $this->loadFromDB( $code );
00355                                 $success = $this->setCache( $cache, $code );
00356                         }
00357                         if ( $success ) {
00358                                 $success = $this->saveToCaches( $cache, true, $code );
00359                                 if ( $success ) {
00360                                         $this->mMemc->delete( $statusKey );
00361                                 } else {
00362                                         $this->mMemc->set( $statusKey, 'error', 60 * 5 );
00363                                         wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
00364                                 }
00365                         }
00366                         $this->unlock($cacheKey);
00367                 }
00368 
00369                 if ( !$success ) {
00370                         # Bad luck... this should not happen
00371                         $where[] = 'loading FAILED - cache is disabled';
00372                         $info = implode( ', ', $where );
00373                         wfDebug( __METHOD__ . ": Loading $code... $info\n" );
00374                         $this->mDisable = true;
00375                         $this->mCache = false;
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 ) {
00408                         $mostused = $this->getMostUsedMessages();
00409                         if ( $code !== $wgLanguageCode ) {
00410                                 foreach ( $mostused as $key => $value ) {
00411                                         $mostused[$key] = "$value/$code";
00412                                 }
00413                         }
00414                 }
00415 
00416                 if ( count( $mostused ) ) {
00417                         $conds['page_title'] = $mostused;
00418                 } elseif ( $code !== $wgLanguageCode ) {
00419                         $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
00420                 } else {
00421                         # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
00422                         # other than language code.
00423                         $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
00424                 }
00425 
00426                 # Conditions to fetch oversized pages to ignore them
00427                 $bigConds = $conds;
00428                 $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
00429 
00430                 # Load titles for all oversized pages in the MediaWiki namespace
00431                 $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
00432                 foreach ( $res as $row ) {
00433                         $cache[$row->page_title] = '!TOO BIG';
00434                 }
00435 
00436                 # Conditions to load the remaining pages with their contents
00437                 $smallConds = $conds;
00438                 $smallConds[] = 'page_latest=rev_id';
00439                 $smallConds[] = 'rev_text_id=old_id';
00440                 $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
00441 
00442                 $res = $dbr->select(
00443                         array( 'page', 'revision', 'text' ),
00444                         array( 'page_title', 'old_text', 'old_flags' ),
00445                         $smallConds,
00446                         __METHOD__ . "($code)-small"
00447                 );
00448 
00449                 foreach ( $res as $row ) {
00450                         $text = Revision::getRevisionText( $row );
00451                         if( $text === false ) {
00452                                 // Failed to fetch data; possible ES errors?
00453                                 // Store a marker to fetch on-demand as a workaround...
00454                                 $entry = '!TOO BIG';
00455                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$row->page_title} ($code)" );
00456                         } else {
00457                                 $entry = ' ' . $text;
00458                         }
00459                         $cache[$row->page_title] = $entry;
00460                 }
00461 
00462                 foreach ( $mostused as $key ) {
00463                         if ( !isset( $cache[$key] ) ) {
00464                                 $cache[$key] = '!NONEXISTENT';
00465                         }
00466                 }
00467 
00468                 $cache['VERSION'] = MSG_CACHE_VERSION;
00469                 wfProfileOut( __METHOD__ );
00470                 return $cache;
00471         }
00472 
00479         public function replace( $title, $text ) {
00480                 global $wgMaxMsgCacheEntrySize;
00481                 wfProfileIn( __METHOD__ );
00482 
00483                 if ( $this->mDisable ) {
00484                         wfProfileOut( __METHOD__ );
00485                         return;
00486                 }
00487 
00488                 list( $msg, $code ) = $this->figureMessage( $title );
00489 
00490                 $cacheKey = wfMemcKey( 'messages', $code );
00491                 $this->load( $code );
00492                 $this->lock( $cacheKey );
00493 
00494                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00495 
00496                 if ( $text === false ) {
00497                         # Article was deleted
00498                         $this->mCache[$code][$title] = '!NONEXISTENT';
00499                         $this->mMemc->delete( $titleKey );
00500                 } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
00501                         # Check for size
00502                         $this->mCache[$code][$title] = '!TOO BIG';
00503                         $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
00504                 } else {
00505                         $this->mCache[$code][$title] = ' ' . $text;
00506                         $this->mMemc->delete( $titleKey );
00507                 }
00508 
00509                 # Update caches
00510                 $this->saveToCaches( $this->mCache[$code], true, $code );
00511                 $this->unlock( $cacheKey );
00512 
00513                 // Also delete cached sidebar... just in case it is affected
00514                 $codes = array( $code );
00515                 if ( $code === 'en'  ) {
00516                         // Delete all sidebars, like for example on action=purge on the
00517                         // sidebar messages
00518                         $codes = array_keys( Language::fetchLanguageNames() );
00519                 }
00520 
00521                 global $wgMemc;
00522                 foreach ( $codes as $code ) {
00523                         $sidebarKey = wfMemcKey( 'sidebar', $code );
00524                         $wgMemc->delete( $sidebarKey );
00525                 }
00526 
00527                 // Update the message in the message blob store
00528                 global $wgContLang;
00529                 MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
00530 
00531                 wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
00532 
00533                 wfProfileOut( __METHOD__ );
00534         }
00535 
00544         protected function saveToCaches( $cache, $memc = true, $code = false ) {
00545                 wfProfileIn( __METHOD__ );
00546                 global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
00547 
00548                 $cacheKey = wfMemcKey( 'messages', $code );
00549 
00550                 if ( $memc ) {
00551                         $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
00552                 } else {
00553                         $success = true;
00554                 }
00555 
00556                 # Save to local cache
00557                 if ( $wgUseLocalMessageCache ) {
00558                         $serialized = serialize( $cache );
00559                         $hash = md5( $serialized );
00560                         $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
00561                         if ($wgLocalMessageCacheSerialized) {
00562                                 $this->saveToLocal( $serialized, $hash, $code );
00563                         } else {
00564                                 $this->saveToScript( $cache, $hash, $code );
00565                         }
00566                 }
00567 
00568                 wfProfileOut( __METHOD__ );
00569                 return $success;
00570         }
00571 
00579         function lock( $key ) {
00580                 $lockKey = $key . ':lock';
00581                 for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
00582                         sleep( 1 );
00583                 }
00584 
00585                 return $i >= MSG_WAIT_TIMEOUT;
00586         }
00587 
00588         function unlock( $key ) {
00589                 $lockKey = $key . ':lock';
00590                 $this->mMemc->delete( $lockKey );
00591         }
00592 
00612         function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
00613                 global $wgLanguageCode, $wgContLang;
00614 
00615                 if ( is_int( $key ) ) {
00616                         // "Non-string key given" exception sometimes happens for numerical strings that become ints somewhere on their way here
00617                         $key = strval( $key );
00618                 }
00619 
00620                 if ( !is_string( $key ) ) {
00621                         throw new MWException( 'Non-string key given' );
00622                 }
00623 
00624                 if ( strval( $key ) === '' ) {
00625                         # Shortcut: the empty key is always missing
00626                         return false;
00627                 }
00628 
00629                 $lang = wfGetLangObj( $langcode );
00630                 if ( !$lang ) {
00631                         throw new MWException( "Bad lang code $langcode given" );
00632                 }
00633 
00634                 $langcode = $lang->getCode();
00635 
00636                 $message = false;
00637 
00638                 # Normalise title-case input (with some inlining)
00639                 $lckey = str_replace( ' ', '_', $key );
00640                 if ( ord( $key ) < 128 ) {
00641                         $lckey[0] = strtolower( $lckey[0] );
00642                         $uckey = ucfirst( $lckey );
00643                 } else {
00644                         $lckey = $wgContLang->lcfirst( $lckey );
00645                         $uckey = $wgContLang->ucfirst( $lckey );
00646                 }
00647 
00653                 $this->mRequestedMessages[$uckey] = true;
00654 
00655                 # Try the MediaWiki namespace
00656                 if( !$this->mDisable && $useDB ) {
00657                         $title = $uckey;
00658                         if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00659                                 $title .= '/' . $langcode;
00660                         }
00661                         $message = $this->getMsgFromNamespace( $title, $langcode );
00662                 }
00663 
00664                 # Try the array in the language object
00665                 if ( $message === false ) {
00666                         $message = $lang->getMessage( $lckey );
00667                         if ( is_null( $message ) ) {
00668                                 $message = false;
00669                         }
00670                 }
00671 
00672                 # Try the array of another language
00673                 if( $message === false ) {
00674                         $parts = explode( '/', $lckey );
00675                         # We may get calls for things that are http-urls from sidebar
00676                         # Let's not load nonexistent languages for those
00677                         # They usually have more than one slash.
00678                         if ( count( $parts ) == 2 && $parts[1] !== '' ) {
00679                                 $message = Language::getMessageFor( $parts[0], $parts[1] );
00680                                 if ( is_null( $message ) ) {
00681                                         $message = false;
00682                                 }
00683                         }
00684                 }
00685 
00686                 # Is this a custom message? Try the default language in the db...
00687                 if( ( $message === false || $message === '-' ) &&
00688                         !$this->mDisable && $useDB &&
00689                         !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
00690                         $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
00691                 }
00692 
00693                 # Final fallback
00694                 if( $message === false ) {
00695                         return false;
00696                 }
00697 
00698                 # Fix whitespace
00699                 $message = strtr( $message,
00700                         array(
00701                                 # Fix for trailing whitespace, removed by textarea
00702                                 '&#32;' => ' ',
00703                                 # Fix for NBSP, converted to space by firefox
00704                                 '&nbsp;' => "\xc2\xa0",
00705                                 '&#160;' => "\xc2\xa0",
00706                         ) );
00707 
00708                 return $message;
00709         }
00710 
00720         function getMsgFromNamespace( $title, $code ) {
00721                 global $wgAdaptiveMessageCache;
00722 
00723                 $this->load( $code );
00724                 if ( isset( $this->mCache[$code][$title] ) ) {
00725                         $entry = $this->mCache[$code][$title];
00726                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00727                                 return substr( $entry, 1 );
00728                         } elseif ( $entry === '!NONEXISTENT' ) {
00729                                 return false;
00730                         } elseif( $entry === '!TOO BIG' ) {
00731                                 // Fall through and try invididual message cache below
00732                         }
00733                 } else {
00734                         // XXX: This is not cached in process cache, should it?
00735                         $message = false;
00736                         wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
00737                         if ( $message !== false ) {
00738                                 return $message;
00739                         }
00740 
00747                         if ( !$wgAdaptiveMessageCache ) {
00748                                 return false;
00749                         }
00750                 }
00751 
00752                 # Try the individual message cache
00753                 $titleKey = wfMemcKey( 'messages', 'individual', $title );
00754                 $entry = $this->mMemc->get( $titleKey );
00755                 if ( $entry ) {
00756                         if ( substr( $entry, 0, 1 ) === ' ' ) {
00757                                 $this->mCache[$code][$title] = $entry;
00758                                 return substr( $entry, 1 );
00759                         } elseif ( $entry === '!NONEXISTENT' ) {
00760                                 $this->mCache[$code][$title] = '!NONEXISTENT';
00761                                 return false;
00762                         } else {
00763                                 # Corrupt/obsolete entry, delete it
00764                                 $this->mMemc->delete( $titleKey );
00765                         }
00766                 }
00767 
00768                 # Try loading it from the database
00769                 $revision = Revision::newFromTitle(
00770                         Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
00771                 );
00772                 if ( $revision ) {
00773                         $message = $revision->getText();
00774                         if ($message === false) {
00775                                 // A possibly temporary loading failure.
00776                                 wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
00777                         } else {
00778                                 $this->mCache[$code][$title] = ' ' . $message;
00779                                 $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
00780                         }
00781                 } else {
00782                         $message = false;
00783                         $this->mCache[$code][$title] = '!NONEXISTENT';
00784                         $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
00785                 }
00786 
00787                 return $message;
00788         }
00789 
00797         function transform( $message, $interface = false, $language = null, $title = null ) {
00798                 // Avoid creating parser if nothing to transform
00799                 if( strpos( $message, '{{' ) === false ) {
00800                         return $message;
00801                 }
00802 
00803                 if ( $this->mInParser ) {
00804                         return $message;
00805                 }
00806 
00807                 $parser = $this->getParser();
00808                 if ( $parser ) {
00809                         $popts = $this->getParserOptions();
00810                         $popts->setInterfaceMessage( $interface );
00811                         $popts->setTargetLanguage( $language );
00812 
00813                         $userlang = $popts->setUserLang( $language );
00814                         $this->mInParser = true;
00815                         $message = $parser->transformMsg( $message, $popts, $title );
00816                         $this->mInParser = false;
00817                         $popts->setUserLang( $userlang );
00818                 }
00819                 return $message;
00820         }
00821 
00825         function getParser() {
00826                 global $wgParser, $wgParserConf;
00827                 if ( !$this->mParser && isset( $wgParser ) ) {
00828                         # Do some initialisation so that we don't have to do it twice
00829                         $wgParser->firstCallInit();
00830                         # Clone it and store it
00831                         $class = $wgParserConf['class'];
00832                         if ( $class == 'Parser_DiffTest' ) {
00833                                 # Uncloneable
00834                                 $this->mParser = new $class( $wgParserConf );
00835                         } else {
00836                                 $this->mParser = clone $wgParser;
00837                         }
00838                 }
00839                 return $this->mParser;
00840         }
00841 
00850         public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null  ) {
00851                 if ( $this->mInParser ) {
00852                         return htmlspecialchars( $text );
00853                 }
00854 
00855                 $parser = $this->getParser();
00856                 $popts = $this->getParserOptions();
00857                 $popts->setInterfaceMessage( $interface );
00858                 $popts->setTargetLanguage( $language );
00859 
00860                 wfProfileIn( __METHOD__ );
00861                 if ( !$title || !$title instanceof Title ) {
00862                         global $wgTitle;
00863                         $title = $wgTitle;
00864                 }
00865                 // Sometimes $wgTitle isn't set either...
00866                 if ( !$title ) {
00867                         # It's not uncommon having a null $wgTitle in scripts. See r80898
00868                         # Create a ghost title in such case
00869                         $title = Title::newFromText( 'Dwimmerlaik' );
00870                 }
00871 
00872                 $this->mInParser = true;
00873                 $res = $parser->parse( $text, $title, $popts, $linestart );
00874                 $this->mInParser = false;
00875 
00876                 wfProfileOut( __METHOD__ );
00877                 return $res;
00878         }
00879 
00880         function disable() {
00881                 $this->mDisable = true;
00882         }
00883 
00884         function enable() {
00885                 $this->mDisable = false;
00886         }
00887 
00891         function clear() {
00892                 $langs = Language::fetchLanguageNames( null, 'mw' );
00893                 foreach ( array_keys($langs) as $code ) {
00894                         # Global cache
00895                         $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
00896                         # Invalidate all local caches
00897                         $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
00898                 }
00899                 $this->mLoadedLanguages = array();
00900         }
00901 
00906         public function figureMessage( $key ) {
00907                 global $wgLanguageCode;
00908                 $pieces = explode( '/', $key );
00909                 if( count( $pieces ) < 2 ) {
00910                         return array( $key, $wgLanguageCode );
00911                 }
00912 
00913                 $lang = array_pop( $pieces );
00914                 if( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
00915                         return array( $key, $wgLanguageCode );
00916                 }
00917 
00918                 $message = implode( '/', $pieces );
00919                 return array( $message, $lang );
00920         }
00921 
00922         public static function logMessages() {
00923                 wfProfileIn( __METHOD__ );
00924                 global $wgAdaptiveMessageCache;
00925                 if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
00926                         wfProfileOut( __METHOD__ );
00927                         return;
00928                 }
00929 
00930                 $cachekey = wfMemckey( 'message-profiling' );
00931                 $cache = wfGetCache( CACHE_DB );
00932                 $data = $cache->get( $cachekey );
00933 
00934                 if ( !$data ) {
00935                         $data = array();
00936                 }
00937 
00938                 $age = self::$mAdaptiveDataAge;
00939                 $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
00940                 foreach ( array_keys( $data ) as $key ) {
00941                         if ( $key < $filterDate ) {
00942                                 unset( $data[$key] );
00943                         }
00944                 }
00945 
00946                 $index = substr( wfTimestampNow(), 0, 8 );
00947                 if ( !isset( $data[$index] ) ) {
00948                         $data[$index] = array();
00949                 }
00950 
00951                 foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
00952                         if ( !isset( $data[$index][$message] ) ) {
00953                                 $data[$index][$message] = 0;
00954                         }
00955                         $data[$index][$message]++;
00956                 }
00957 
00958                 $cache->set( $cachekey, $data );
00959                 wfProfileOut( __METHOD__ );
00960         }
00961 
00965         public function getMostUsedMessages() {
00966                 wfProfileIn( __METHOD__ );
00967                 $cachekey = wfMemcKey( 'message-profiling' );
00968                 $cache = wfGetCache( CACHE_DB );
00969                 $data = $cache->get( $cachekey );
00970                 if ( !$data ) {
00971                         wfProfileOut( __METHOD__ );
00972                         return array();
00973                 }
00974 
00975                 $list = array();
00976 
00977                 foreach( $data as $messages ) {
00978                         foreach( $messages as $message => $count ) {
00979                                 $key = $message;
00980                                 if ( !isset( $list[$key] ) ) {
00981                                         $list[$key] = 0;
00982                                 }
00983                                 $list[$key] += $count;
00984                         }
00985                 }
00986 
00987                 $max = max( $list );
00988                 foreach ( $list as $message => $count ) {
00989                         if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
00990                                 unset( $list[$message] );
00991                         }
00992                 }
00993 
00994                 wfProfileOut( __METHOD__ );
00995                 return array_keys( $list );
00996         }
00997 
01006         public function getAllMessageKeys( $code ) {
01007                 global $wgContLang;
01008                 $this->load( $code );
01009                 if ( !isset( $this->mCache[$code] ) ) {
01010                         // Apparently load() failed
01011                         return null;
01012                 }
01013                 $cache = $this->mCache[$code]; // Copy the cache
01014                 unset( $cache['VERSION'] ); // Remove the VERSION key
01015                 $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys
01016                 // Keys may appear with a capital first letter. lcfirst them.
01017                 return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
01018         }
01019 }