MediaWiki  1.23.2
MessageCache.php
Go to the documentation of this file.
1 <?php
28 define( 'MSG_CACHE_VERSION', 1 );
29 
34 define( 'MSG_LOAD_TIMEOUT', 60 );
35 
40 define( 'MSG_LOCK_TIMEOUT', 30 );
45 define( 'MSG_WAIT_TIMEOUT', 30 );
46 
52 class MessageCache {
60  protected $mCache;
61 
66  protected $mDisable;
67 
72  protected $mExpiry;
73 
78  protected $mParserOptions, $mParser;
79 
84  protected $mLoadedLanguages = array();
85 
91  private static $instance;
92 
96  protected $mInParser = false;
97 
104  public static function singleton() {
105  if ( is_null( self::$instance ) ) {
106  global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
107  self::$instance = new self(
109  $wgUseDatabaseMessages,
110  $wgMsgCacheExpiry
111  );
112  }
113 
114  return self::$instance;
115  }
116 
122  public static function destroyInstance() {
123  self::$instance = null;
124  }
125 
131  function __construct( $memCached, $useDB, $expiry ) {
132  if ( !$memCached ) {
133  $memCached = wfGetCache( CACHE_NONE );
134  }
135 
136  $this->mMemc = $memCached;
137  $this->mDisable = !$useDB;
138  $this->mExpiry = $expiry;
139  }
140 
146  function getParserOptions() {
147  if ( !$this->mParserOptions ) {
148  $this->mParserOptions = new ParserOptions;
149  $this->mParserOptions->setEditSection( false );
150  }
151 
152  return $this->mParserOptions;
153  }
154 
162  function getLocalCache( $hash, $code ) {
163  global $wgCacheDirectory;
164 
165  $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
166 
167  # Check file existence
169  $file = fopen( $filename, 'r' );
171  if ( !$file ) {
172  return false; // No cache file
173  }
174 
175  // Check to see if the file has the hash specified
176  $localHash = fread( $file, 32 );
177  if ( $hash === $localHash ) {
178  // All good, get the rest of it
179  $serialized = '';
180  while ( !feof( $file ) ) {
181  $serialized .= fread( $file, 100000 );
182  }
183  fclose( $file );
184 
185  return unserialize( $serialized );
186  } else {
187  fclose( $file );
188 
189  return false; // Wrong hash
190  }
191  }
192 
196  function saveToLocal( $serialized, $hash, $code ) {
197  global $wgCacheDirectory;
198 
199  $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
200  wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
201 
203  $file = fopen( $filename, 'w' );
205 
206  if ( !$file ) {
207  wfDebug( "Unable to open local cache file for writing\n" );
208 
209  return;
210  }
211 
212  fwrite( $file, $hash . $serialized );
213  fclose( $file );
215  chmod( $filename, 0666 );
217  }
218 
239  function load( $code = false ) {
240  global $wgUseLocalMessageCache;
241 
242  if ( !is_string( $code ) ) {
243  # This isn't really nice, so at least make a note about it and try to
244  # fall back
245  wfDebug( __METHOD__ . " called without providing a language code\n" );
246  $code = 'en';
247  }
248 
249  # Don't do double loading...
250  if ( isset( $this->mLoadedLanguages[$code] ) ) {
251  return true;
252  }
253 
254  # 8 lines of code just to say (once) that message cache is disabled
255  if ( $this->mDisable ) {
256  static $shownDisabled = false;
257  if ( !$shownDisabled ) {
258  wfDebug( __METHOD__ . ": disabled\n" );
259  $shownDisabled = true;
260  }
261 
262  return true;
263  }
264 
265  # Loading code starts
266  wfProfileIn( __METHOD__ );
267  $success = false; # Keep track of success
268  $staleCache = false; # a cache array with expired data, or false if none has been loaded
269  $where = array(); # Debug info, delayed to avoid spamming debug log too much
270  $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
271 
272  # Local cache
273  # Hash of the contents is stored in memcache, to detect if local cache goes
274  # out of date (e.g. due to replace() on some other server)
275  if ( $wgUseLocalMessageCache ) {
276  wfProfileIn( __METHOD__ . '-fromlocal' );
277 
278  $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
279  if ( $hash ) {
280  $cache = $this->getLocalCache( $hash, $code );
281  if ( !$cache ) {
282  $where[] = 'local cache is empty or has the wrong hash';
283  } elseif ( $this->isCacheExpired( $cache ) ) {
284  $where[] = 'local cache is expired';
285  $staleCache = $cache;
286  } else {
287  $where[] = 'got from local cache';
288  $success = true;
289  $this->mCache[$code] = $cache;
290  }
291  }
292  wfProfileOut( __METHOD__ . '-fromlocal' );
293  }
294 
295  if ( !$success ) {
296  # Try the global cache. If it is empty, try to acquire a lock. If
297  # the lock can't be acquired, wait for the other thread to finish
298  # and then try the global cache a second time.
299  for ( $failedAttempts = 0; $failedAttempts < 2; $failedAttempts++ ) {
300  wfProfileIn( __METHOD__ . '-fromcache' );
301  $cache = $this->mMemc->get( $cacheKey );
302  if ( !$cache ) {
303  $where[] = 'global cache is empty';
304  } elseif ( $this->isCacheExpired( $cache ) ) {
305  $where[] = 'global cache is expired';
306  $staleCache = $cache;
307  } else {
308  $where[] = 'got from global cache';
309  $this->mCache[$code] = $cache;
310  $this->saveToCaches( $cache, 'local-only', $code );
311  $success = true;
312  }
313 
314  wfProfileOut( __METHOD__ . '-fromcache' );
315 
316  if ( $success ) {
317  # Done, no need to retry
318  break;
319  }
320 
321  # We need to call loadFromDB. Limit the concurrency to a single
322  # process. This prevents the site from going down when the cache
323  # expires.
324  $statusKey = wfMemcKey( 'messages', $code, 'status' );
325  $acquired = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
326  if ( $acquired ) {
327  # Unlock the status key if there is an exception
328  $that = $this;
329  $statusUnlocker = new ScopedCallback( function () use ( $that, $statusKey ) {
330  $that->mMemc->delete( $statusKey );
331  } );
332 
333  # Now let's regenerate
334  $where[] = 'loading from database';
335 
336  # Lock the cache to prevent conflicting writes
337  # If this lock fails, it doesn't really matter, it just means the
338  # write is potentially non-atomic, e.g. the results of a replace()
339  # may be discarded.
340  if ( $this->lock( $cacheKey ) ) {
341  $mainUnlocker = new ScopedCallback( function () use ( $that, $cacheKey ) {
342  $that->unlock( $cacheKey );
343  } );
344  } else {
345  $mainUnlocker = null;
346  $where[] = 'could not acquire main lock';
347  }
348 
349  $cache = $this->loadFromDB( $code );
350  $this->mCache[$code] = $cache;
351  $success = true;
352  $saveSuccess = $this->saveToCaches( $cache, 'all', $code );
353 
354  # Unlock
355  ScopedCallback::consume( $mainUnlocker );
356  ScopedCallback::consume( $statusUnlocker );
357 
358  if ( !$saveSuccess ) {
359  # Cache save has failed.
360  # There are two main scenarios where this could be a problem:
361  #
362  # - The cache is more than the maximum size (typically
363  # 1MB compressed).
364  #
365  # - Memcached has no space remaining in the relevant slab
366  # class. This is unlikely with recent versions of
367  # memcached.
368  #
369  # Either way, if there is a local cache, nothing bad will
370  # happen. If there is no local cache, disabling the message
371  # cache for all requests avoids incurring a loadFromDB()
372  # overhead on every request, and thus saves the wiki from
373  # complete downtime under moderate traffic conditions.
374  if ( !$wgUseLocalMessageCache ) {
375  $this->mMemc->set( $statusKey, 'error', 60 * 5 );
376  $where[] = 'could not save cache, disabled globally for 5 minutes';
377  } else {
378  $where[] = "could not save global cache";
379  }
380  }
381 
382  # Load from DB complete, no need to retry
383  break;
384  } elseif ( $staleCache ) {
385  # Use the stale cache while some other thread constructs the new one
386  $where[] = 'using stale cache';
387  $this->mCache[$code] = $staleCache;
388  $success = true;
389  break;
390  } elseif ( $failedAttempts > 0 ) {
391  # Already retried once, still failed, so don't do another lock/unlock cycle
392  # This case will typically be hit if memcached is down, or if
393  # loadFromDB() takes longer than MSG_WAIT_TIMEOUT
394  $where[] = "could not acquire status key.";
395  break;
396  } else {
397  $status = $this->mMemc->get( $statusKey );
398  if ( $status === 'error' ) {
399  # Disable cache
400  break;
401  } else {
402  # Wait for the other thread to finish, then retry
403  $where[] = 'waited for other thread to complete';
404  $this->lock( $cacheKey );
405  $this->unlock( $cacheKey );
406  }
407  }
408  }
409  }
410 
411  if ( !$success ) {
412  $where[] = 'loading FAILED - cache is disabled';
413  $this->mDisable = true;
414  $this->mCache = false;
415  # This used to throw an exception, but that led to nasty side effects like
416  # the whole wiki being instantly down if the memcached server died
417  } else {
418  # All good, just record the success
419  $this->mLoadedLanguages[$code] = true;
420  }
421  $info = implode( ', ', $where );
422  wfDebug( __METHOD__ . ": Loading $code... $info\n" );
423  wfProfileOut( __METHOD__ );
424 
425  return $success;
426  }
427 
436  function loadFromDB( $code ) {
437  wfProfileIn( __METHOD__ );
438  global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
439  $dbr = wfGetDB( DB_SLAVE );
440  $cache = array();
441 
442  # Common conditions
443  $conds = array(
444  'page_is_redirect' => 0,
445  'page_namespace' => NS_MEDIAWIKI,
446  );
447 
448  $mostused = array();
449  if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
450  if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
451  $this->load( $wgLanguageCode );
452  }
453  $mostused = array_keys( $this->mCache[$wgLanguageCode] );
454  foreach ( $mostused as $key => $value ) {
455  $mostused[$key] = "$value/$code";
456  }
457  }
458 
459  if ( count( $mostused ) ) {
460  $conds['page_title'] = $mostused;
461  } elseif ( $code !== $wgLanguageCode ) {
462  $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code );
463  } else {
464  # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
465  # other than language code.
466  $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
467  }
468 
469  # Conditions to fetch oversized pages to ignore them
470  $bigConds = $conds;
471  $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
472 
473  # Load titles for all oversized pages in the MediaWiki namespace
474  $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
475  foreach ( $res as $row ) {
476  $cache[$row->page_title] = '!TOO BIG';
477  }
478 
479  # Conditions to load the remaining pages with their contents
480  $smallConds = $conds;
481  $smallConds[] = 'page_latest=rev_id';
482  $smallConds[] = 'rev_text_id=old_id';
483  $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
484 
485  $res = $dbr->select(
486  array( 'page', 'revision', 'text' ),
487  array( 'page_title', 'old_text', 'old_flags' ),
488  $smallConds,
489  __METHOD__ . "($code)-small"
490  );
491 
492  foreach ( $res as $row ) {
493  $text = Revision::getRevisionText( $row );
494  if ( $text === false ) {
495  // Failed to fetch data; possible ES errors?
496  // Store a marker to fetch on-demand as a workaround...
497  $entry = '!TOO BIG';
498  wfDebugLog(
499  'MessageCache',
500  __METHOD__
501  . ": failed to load message page text for {$row->page_title} ($code)"
502  );
503  } else {
504  $entry = ' ' . $text;
505  }
506  $cache[$row->page_title] = $entry;
507  }
508 
509  $cache['VERSION'] = MSG_CACHE_VERSION;
510  $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
511  wfProfileOut( __METHOD__ );
512 
513  return $cache;
514  }
515 
522  public function replace( $title, $text ) {
523  global $wgMaxMsgCacheEntrySize;
524  wfProfileIn( __METHOD__ );
525 
526  if ( $this->mDisable ) {
527  wfProfileOut( __METHOD__ );
528 
529  return;
530  }
531 
532  list( $msg, $code ) = $this->figureMessage( $title );
533 
534  $cacheKey = wfMemcKey( 'messages', $code );
535  $this->load( $code );
536  $this->lock( $cacheKey );
537 
538  $titleKey = wfMemcKey( 'messages', 'individual', $title );
539 
540  if ( $text === false ) {
541  # Article was deleted
542  $this->mCache[$code][$title] = '!NONEXISTENT';
543  $this->mMemc->delete( $titleKey );
544  } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
545  # Check for size
546  $this->mCache[$code][$title] = '!TOO BIG';
547  $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
548  } else {
549  $this->mCache[$code][$title] = ' ' . $text;
550  $this->mMemc->delete( $titleKey );
551  }
552 
553  # Update caches
554  $this->saveToCaches( $this->mCache[$code], 'all', $code );
555  $this->unlock( $cacheKey );
556 
557  // Also delete cached sidebar... just in case it is affected
558  $codes = array( $code );
559  if ( $code === 'en' ) {
560  // Delete all sidebars, like for example on action=purge on the
561  // sidebar messages
562  $codes = array_keys( Language::fetchLanguageNames() );
563  }
564 
565  global $wgMemc;
566  foreach ( $codes as $code ) {
567  $sidebarKey = wfMemcKey( 'sidebar', $code );
568  $wgMemc->delete( $sidebarKey );
569  }
570 
571  // Update the message in the message blob store
573  MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
574 
575  wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
576 
577  wfProfileOut( __METHOD__ );
578  }
579 
586  protected function isCacheExpired( $cache ) {
587  if ( !isset( $cache['VERSION'] ) || !isset( $cache['EXPIRY'] ) ) {
588  return true;
589  }
590  if ( $cache['VERSION'] != MSG_CACHE_VERSION ) {
591  return true;
592  }
593  if ( wfTimestampNow() >= $cache['EXPIRY'] ) {
594  return true;
595  }
596 
597  return false;
598  }
599 
609  protected function saveToCaches( $cache, $dest, $code = false ) {
610  wfProfileIn( __METHOD__ );
611  global $wgUseLocalMessageCache;
612 
613  $cacheKey = wfMemcKey( 'messages', $code );
614 
615  if ( $dest === 'all' ) {
616  $success = $this->mMemc->set( $cacheKey, $cache );
617  } else {
618  $success = true;
619  }
620 
621  # Save to local cache
622  if ( $wgUseLocalMessageCache ) {
623  $serialized = serialize( $cache );
624  $hash = md5( $serialized );
625  $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash );
626  $this->saveToLocal( $serialized, $hash, $code );
627  }
628 
629  wfProfileOut( __METHOD__ );
630 
631  return $success;
632  }
633 
643  function lock( $key ) {
644  $lockKey = $key . ':lock';
645  $acquired = false;
646  $testDone = false;
647  for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$acquired; $i++ ) {
648  $acquired = $this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT );
649  if ( $acquired ) {
650  break;
651  }
652 
653  # Fail fast if memcached is totally down
654  if ( !$testDone ) {
655  $testDone = true;
656  if ( !$this->mMemc->set( wfMemcKey( 'test' ), 'test', 1 ) ) {
657  break;
658  }
659  }
660  sleep( 1 );
661  }
662 
663  return $acquired;
664  }
665 
666  function unlock( $key ) {
667  $lockKey = $key . ':lock';
668  $this->mMemc->delete( $lockKey );
669  }
670 
706  function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
708 
709  $section = new ProfileSection( __METHOD__ );
710 
711  if ( is_int( $key ) ) {
712  // Fix numerical strings that somehow become ints
713  // on their way here
714  $key = (string)$key;
715  } elseif ( !is_string( $key ) ) {
716  throw new MWException( 'Non-string key given' );
717  } elseif ( $key === '' ) {
718  // Shortcut: the empty key is always missing
719  return false;
720  }
721 
722  // For full keys, get the language code from the key
723  $pos = strrpos( $key, '/' );
724  if ( $isFullKey && $pos !== false ) {
725  $langcode = substr( $key, $pos + 1 );
726  $key = substr( $key, 0, $pos );
727  }
728 
729  // Normalise title-case input (with some inlining)
730  $lckey = strtr( $key, ' ', '_' );
731  if ( ord( $lckey ) < 128 ) {
732  $lckey[0] = strtolower( $lckey[0] );
733  } else {
734  $lckey = $wgContLang->lcfirst( $lckey );
735  }
736 
737  wfRunHooks( 'MessageCache::get', array( &$lckey ) );
738 
739  if ( ord( $lckey ) < 128 ) {
740  $uckey = ucfirst( $lckey );
741  } else {
742  $uckey = $wgContLang->ucfirst( $lckey );
743  }
744 
745  // Loop through each language in the fallback list until we find something useful
746  $lang = wfGetLangObj( $langcode );
747  $message = $this->getMessageFromFallbackChain(
748  $lang,
749  $lckey,
750  $uckey,
751  !$this->mDisable && $useDB
752  );
753 
754  // If we still have no message, maybe the key was in fact a full key so try that
755  if ( $message === false ) {
756  $parts = explode( '/', $lckey );
757  // We may get calls for things that are http-urls from sidebar
758  // Let's not load nonexistent languages for those
759  // They usually have more than one slash.
760  if ( count( $parts ) == 2 && $parts[1] !== '' ) {
761  $message = Language::getMessageFor( $parts[0], $parts[1] );
762  if ( $message === null ) {
763  $message = false;
764  }
765  }
766  }
767 
768  // Post-processing if the message exists
769  if ( $message !== false ) {
770  // Fix whitespace
771  $message = str_replace(
772  array(
773  # Fix for trailing whitespace, removed by textarea
774  '&#32;',
775  # Fix for NBSP, converted to space by firefox
776  '&nbsp;',
777  '&#160;',
778  ),
779  array(
780  ' ',
781  "\xc2\xa0",
782  "\xc2\xa0"
783  ),
784  $message
785  );
786  }
787 
788  return $message;
789  }
790 
804  protected function getMessageFromFallbackChain( $lang, $lckey, $uckey, $useDB ) {
805  global $wgLanguageCode, $wgContLang;
806 
807  $langcode = $lang->getCode();
808  $message = false;
809 
810  // First try the requested language.
811  if ( $useDB ) {
812  if ( $langcode === $wgLanguageCode ) {
813  // Messages created in the content language will not have the /lang extension
814  $message = $this->getMsgFromNamespace( $uckey, $langcode );
815  } else {
816  $message = $this->getMsgFromNamespace( "$uckey/$langcode", $langcode );
817  }
818  }
819 
820  if ( $message !== false ) {
821  return $message;
822  }
823 
824  // Check the CDB cache
825  $message = $lang->getMessage( $lckey );
826  if ( $message !== null ) {
827  return $message;
828  }
829 
830  list( $fallbackChain, $siteFallbackChain ) =
832 
833  // Next try checking the database for all of the fallback languages of the requested language.
834  if ( $useDB ) {
835  foreach ( $fallbackChain as $code ) {
836  if ( $code === $wgLanguageCode ) {
837  // Messages created in the content language will not have the /lang extension
838  $message = $this->getMsgFromNamespace( $uckey, $code );
839  } else {
840  $message = $this->getMsgFromNamespace( "$uckey/$code", $code );
841  }
842 
843  if ( $message !== false ) {
844  // Found the message.
845  return $message;
846  }
847  }
848  }
849 
850  // Now try checking the site language.
851  if ( $useDB ) {
852  $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
853  if ( $message !== false ) {
854  return $message;
855  }
856  }
857 
858  $message = $wgContLang->getMessage( $lckey );
859  if ( $message !== null ) {
860  return $message;
861  }
862 
863  // Finally try the DB for the site language's fallbacks.
864  if ( $useDB ) {
865  foreach ( $siteFallbackChain as $code ) {
866  $message = $this->getMsgFromNamespace( "$uckey/$code", $code );
867  if ( $message === false && $code === $wgLanguageCode ) {
868  // Messages created in the content language will not have the /lang extension
869  $message = $this->getMsgFromNamespace( $uckey, $code );
870  }
871 
872  if ( $message !== false ) {
873  // Found the message.
874  return $message;
875  }
876  }
877  }
878 
879  return false;
880  }
881 
894  function getMsgFromNamespace( $title, $code ) {
895  $this->load( $code );
896  if ( isset( $this->mCache[$code][$title] ) ) {
897  $entry = $this->mCache[$code][$title];
898  if ( substr( $entry, 0, 1 ) === ' ' ) {
899  // The message exists, so make sure a string
900  // is returned.
901  return (string)substr( $entry, 1 );
902  } elseif ( $entry === '!NONEXISTENT' ) {
903  return false;
904  } elseif ( $entry === '!TOO BIG' ) {
905  // Fall through and try invididual message cache below
906  }
907  } else {
908  // XXX: This is not cached in process cache, should it?
909  $message = false;
910  wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
911  if ( $message !== false ) {
912  return $message;
913  }
914 
915  return false;
916  }
917 
918  # Try the individual message cache
919  $titleKey = wfMemcKey( 'messages', 'individual', $title );
920  $entry = $this->mMemc->get( $titleKey );
921  if ( $entry ) {
922  if ( substr( $entry, 0, 1 ) === ' ' ) {
923  $this->mCache[$code][$title] = $entry;
924 
925  // The message exists, so make sure a string
926  // is returned.
927  return (string)substr( $entry, 1 );
928  } elseif ( $entry === '!NONEXISTENT' ) {
929  $this->mCache[$code][$title] = '!NONEXISTENT';
930 
931  return false;
932  } else {
933  # Corrupt/obsolete entry, delete it
934  $this->mMemc->delete( $titleKey );
935  }
936  }
937 
938  # Try loading it from the database
939  $revision = Revision::newFromTitle(
941  );
942  if ( $revision ) {
943  $content = $revision->getContent();
944  if ( !$content ) {
945  // A possibly temporary loading failure.
946  wfDebugLog(
947  'MessageCache',
948  __METHOD__ . ": failed to load message page text for {$title} ($code)"
949  );
950  $message = null; // no negative caching
951  } else {
952  // XXX: Is this the right way to turn a Content object into a message?
953  // NOTE: $content is typically either WikitextContent, JavaScriptContent or
954  // CssContent. MessageContent is *not* used for storing messages, it's
955  // only used for wrapping them when needed.
956  $message = $content->getWikitextForTransclusion();
957 
958  if ( $message === false || $message === null ) {
959  wfDebugLog(
960  'MessageCache',
961  __METHOD__ . ": message content doesn't provide wikitext "
962  . "(content model: " . $content->getContentHandler() . ")"
963  );
964 
965  $message = false; // negative caching
966  } else {
967  $this->mCache[$code][$title] = ' ' . $message;
968  $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
969  }
970  }
971  } else {
972  $message = false; // negative caching
973  }
974 
975  if ( $message === false ) { // negative caching
976  $this->mCache[$code][$title] = '!NONEXISTENT';
977  $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
978  }
979 
980  return $message;
981  }
982 
990  function transform( $message, $interface = false, $language = null, $title = null ) {
991  // Avoid creating parser if nothing to transform
992  if ( strpos( $message, '{{' ) === false ) {
993  return $message;
994  }
995 
996  if ( $this->mInParser ) {
997  return $message;
998  }
999 
1000  $parser = $this->getParser();
1001  if ( $parser ) {
1002  $popts = $this->getParserOptions();
1003  $popts->setInterfaceMessage( $interface );
1004  $popts->setTargetLanguage( $language );
1005 
1006  $userlang = $popts->setUserLang( $language );
1007  $this->mInParser = true;
1008  $message = $parser->transformMsg( $message, $popts, $title );
1009  $this->mInParser = false;
1010  $popts->setUserLang( $userlang );
1011  }
1012 
1013  return $message;
1014  }
1015 
1019  function getParser() {
1020  global $wgParser, $wgParserConf;
1021  if ( !$this->mParser && isset( $wgParser ) ) {
1022  # Do some initialisation so that we don't have to do it twice
1023  $wgParser->firstCallInit();
1024  # Clone it and store it
1025  $class = $wgParserConf['class'];
1026  if ( $class == 'Parser_DiffTest' ) {
1027  # Uncloneable
1028  $this->mParser = new $class( $wgParserConf );
1029  } else {
1030  $this->mParser = clone $wgParser;
1031  }
1032  }
1033 
1034  return $this->mParser;
1035  }
1036 
1045  public function parse( $text, $title = null, $linestart = true,
1046  $interface = false, $language = null
1047  ) {
1048  if ( $this->mInParser ) {
1049  return htmlspecialchars( $text );
1050  }
1051 
1052  $parser = $this->getParser();
1053  $popts = $this->getParserOptions();
1054  $popts->setInterfaceMessage( $interface );
1055  $popts->setTargetLanguage( $language );
1056 
1057  wfProfileIn( __METHOD__ );
1058  if ( !$title || !$title instanceof Title ) {
1059  global $wgTitle;
1060  $title = $wgTitle;
1061  }
1062  // Sometimes $wgTitle isn't set either...
1063  if ( !$title ) {
1064  # It's not uncommon having a null $wgTitle in scripts. See r80898
1065  # Create a ghost title in such case
1066  $title = Title::newFromText( 'Dwimmerlaik' );
1067  }
1068 
1069  $this->mInParser = true;
1070  $res = $parser->parse( $text, $title, $popts, $linestart );
1071  $this->mInParser = false;
1072 
1073  wfProfileOut( __METHOD__ );
1074 
1075  return $res;
1076  }
1077 
1078  function disable() {
1079  $this->mDisable = true;
1080  }
1081 
1082  function enable() {
1083  $this->mDisable = false;
1084  }
1085 
1089  function clear() {
1090  $langs = Language::fetchLanguageNames( null, 'mw' );
1091  foreach ( array_keys( $langs ) as $code ) {
1092  # Global cache
1093  $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
1094  # Invalidate all local caches
1095  $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
1096  }
1097  $this->mLoadedLanguages = array();
1098  }
1099 
1104  public function figureMessage( $key ) {
1105  global $wgLanguageCode;
1106  $pieces = explode( '/', $key );
1107  if ( count( $pieces ) < 2 ) {
1108  return array( $key, $wgLanguageCode );
1109  }
1110 
1111  $lang = array_pop( $pieces );
1112  if ( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
1113  return array( $key, $wgLanguageCode );
1114  }
1115 
1116  $message = implode( '/', $pieces );
1117 
1118  return array( $message, $lang );
1119  }
1120 
1129  public function getAllMessageKeys( $code ) {
1131  $this->load( $code );
1132  if ( !isset( $this->mCache[$code] ) ) {
1133  // Apparently load() failed
1134  return null;
1135  }
1136  // Remove administrative keys
1137  $cache = $this->mCache[$code];
1138  unset( $cache['VERSION'] );
1139  unset( $cache['EXPIRY'] );
1140  // Remove any !NONEXISTENT keys
1141  $cache = array_diff( $cache, array( '!NONEXISTENT' ) );
1142 
1143  // Keys may appear with a capital first letter. lcfirst them.
1144  return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
1145  }
1146 }
MSG_WAIT_TIMEOUT
const MSG_WAIT_TIMEOUT
Number of times we will try to acquire a lock from Memcached.
Definition: MessageCache.php:45
MessageCache\$mParserOptions
$mParserOptions
Message cache has it's own parser which it uses to transform messages.
Definition: MessageCache.php:77
MessageCache\transform
transform( $message, $interface=false, $language=null, $title=null)
Definition: MessageCache.php:987
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:31
Title\makeTitle
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:398
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:189
MessageCache\$mLoadedLanguages
array $mLoadedLanguages
Variable for tracking which variables are already loaded $mLoadedLanguages.
Definition: MessageCache.php:82
data
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of data
Definition: hooks.txt:6
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
MessageCache\getMessageFromFallbackChain
getMessageFromFallbackChain( $lang, $lckey, $uckey, $useDB)
Given a language, try and fetch a message from that language, then the fallbacks of that language,...
Definition: MessageCache.php:801
MessageCache\unlock
unlock( $key)
Definition: MessageCache.php:663
MessageCache\parse
parse( $text, $title=null, $linestart=true, $interface=false, $language=null)
Definition: MessageCache.php:1042
wfMkdirParents
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
Definition: GlobalFunctions.php:2590
MessageCache\loadFromDB
loadFromDB( $code)
Loads cacheable messages from the database.
Definition: MessageCache.php:433
MessageCache\enable
enable()
Definition: MessageCache.php:1079
MessageCache\$mCache
$mCache
Process local cache of loaded messages that are defined in MediaWiki namespace.
Definition: MessageCache.php:60
$wgMemc
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest to get request data $wgMemc
Definition: globals.txt:25
wfGetDB
& wfGetDB( $db, $groups=array(), $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3650
MessageCache\$mParser
$mParser
Definition: MessageCache.php:77
CACHE_NONE
const CACHE_NONE
Definition: Defines.php:112
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2483
MessageBlobStore\updateMessage
static updateMessage( $key)
Update a single message in all message blobs it occurs in.
Definition: MessageBlobStore.php:200
MessageCache\$mInParser
bool $mInParser
$mInParser
Definition: MessageCache.php:93
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all')
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1040
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
wfSuppressWarnings
wfSuppressWarnings( $end=false)
Reference-counted warning suppression.
Definition: GlobalFunctions.php:2387
$serialized
foreach( $res as $row) $serialized
Definition: testCompression.php:77
Revision\getRevisionText
static getRevisionText( $row, $prefix='old_', $wiki=false)
Get revision text associated with an old or archive row $row is usually an object from wfFetchRow(),...
Definition: Revision.php:1212
wfGetCache
wfGetCache( $inputType)
Get a cache object.
Definition: GlobalFunctions.php:3948
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
messages
namespace and then decline to actually register it RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. Handler functions that modify $result should generally return false to further attempts at conversion. 'ContribsPager you ll need to handle error messages
Definition: hooks.txt:896
IDBAccessObject\READ_LATEST
const READ_LATEST
Definition: IDBAccessObject.php:49
cache
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:52
MessageCache\saveToLocal
saveToLocal( $serialized, $hash, $code)
Save the cache to a local file.
Definition: MessageCache.php:193
$dbr
$dbr
Definition: testCompression.php:48
$success
$success
Definition: Utf8Test.php:91
ProfileSection
Class for handling function-scope profiling.
Definition: Profiler.php:60
MessageCache\$instance
static $instance
Singleton instance.
Definition: MessageCache.php:89
MWException
MediaWiki exception.
Definition: MWException.php:26
wfMemcKey
wfMemcKey()
Get a cache key.
Definition: GlobalFunctions.php:3571
MessageCache\getMsgFromNamespace
getMsgFromNamespace( $title, $code)
Get a message from the MediaWiki namespace, with caching.
Definition: MessageCache.php:891
MessageCache\saveToCaches
saveToCaches( $cache, $dest, $code=false)
Shortcut to update caches.
Definition: MessageCache.php:606
wfRestoreWarnings
wfRestoreWarnings()
Restore error level to previous value.
Definition: GlobalFunctions.php:2417
MessageCache\lock
lock( $key)
Represents a write lock on the messages key.
Definition: MessageCache.php:640
Language\fetchLanguageNames
static fetchLanguageNames( $inLanguage=null, $include='mw')
Get an array of language names, indexed by code.
Definition: Language.php:875
Language\getMessageFor
static getMessageFor( $key, $code)
Get a message for a given language.
Definition: Language.php:4204
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:1956
wfProfileOut
wfProfileOut( $functionname='missing')
Stop profiling of a function.
Definition: Profiler.php:46
wfRunHooks
wfRunHooks( $event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in $wgHooks.
Definition: GlobalFunctions.php:4001
MSG_CACHE_VERSION
const MSG_CACHE_VERSION
MediaWiki message cache structure version.
Definition: MessageCache.php:28
MessageCache\getLocalCache
getLocalCache( $hash, $code)
Try to load the cache from a local file.
Definition: MessageCache.php:159
MSG_LOCK_TIMEOUT
const MSG_LOCK_TIMEOUT
Memcached timeout when locking a key for a writing operation.
Definition: MessageCache.php:40
MessageCache\figureMessage
figureMessage( $key)
Definition: MessageCache.php:1101
wfGetLangObj
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
Definition: GlobalFunctions.php:1352
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
MessageCache\replace
replace( $title, $text)
Updates cache as necessary when message page is changed.
Definition: MessageCache.php:519
MessageCache\load
load( $code=false)
Loads messages from caches or from database in this order: (1) local message cache (if $wgUseLocalMes...
Definition: MessageCache.php:236
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2514
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
MessageCache\singleton
static singleton()
Get the signleton instance of this class.
Definition: MessageCache.php:101
ScopedCallback
Class for asserting that a callback happens when an dummy object leaves scope.
Definition: ScopedCallback.php:28
$section
$section
Definition: Utf8Test.php:88
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: GlobalFunctions.php:2431
Revision\newFromTitle
static newFromTitle( $title, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given title.
Definition: Revision.php:106
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:933
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:3604
$title
presenting them properly to the user as errors is done by the caller $title
Definition: hooks.txt:1324
$value
$value
Definition: styleTest.css.php:45
too
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next that s OK too
Definition: design.txt:79
MessageCache\clear
clear()
Clear all stored messages.
Definition: MessageCache.php:1086
ScopedCallback\consume
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
Definition: ScopedCallback.php:48
Language\fetchLanguageName
static fetchLanguageName( $code, $inLanguage=null, $include='all')
Definition: Language.php:936
$hash
return false to override stock group addition can be modified try getUserPermissionsErrors userCan checks are continued by internal code can override on output return false to not delete it return false to override the default password checks & $hash
Definition: hooks.txt:2697
MessageCache\getParser
getParser()
Definition: MessageCache.php:1016
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
wfGetMessageCacheStorage
wfGetMessageCacheStorage()
Get the cache object used by the message cache.
Definition: GlobalFunctions.php:3967
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:55
Title
Represents a title within MediaWiki.
Definition: Title.php:35
MessageCache\$mDisable
bool $mDisable
Should mean that database cannot be used, but check $mDisable.
Definition: MessageCache.php:65
$cache
$cache
Definition: mcc.php:32
$wgParser
$wgParser
Definition: Setup.php:567
MessageCache\__construct
__construct( $memCached, $useDB, $expiry)
Definition: MessageCache.php:128
in
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning in
Definition: maintenance.txt:1
Language\getFallbacksIncludingSiteLanguage
static getFallbacksIncludingSiteLanguage( $code)
Get the ordered list of fallback languages, ending with the fallback language chain for the site lang...
Definition: Language.php:4161
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
MessageCache\getParserOptions
getParserOptions()
ParserOptions is lazy initialised.
Definition: MessageCache.php:143
MessageCache\isCacheExpired
isCacheExpired( $cache)
Is the given cache array expired due to time passing or a version change?
Definition: MessageCache.php:583
ParserOptions\setEditSection
setEditSection( $x)
Definition: ParserOptions.php:300
MessageCache\$mExpiry
$mExpiry
Lifetime for cache, used by object caching.
Definition: MessageCache.php:71
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:87
MessageCache\disable
disable()
Definition: MessageCache.php:1075
MessageCache
Message cache Performs various MediaWiki namespace-related functions.
Definition: MessageCache.php:52
$res
$res
Definition: database.txt:21
MSG_LOAD_TIMEOUT
const MSG_LOAD_TIMEOUT
Memcached timeout when loading a key.
Definition: MessageCache.php:34
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(! $wgEnableAPI) $wgTitle
Definition: api.php:63
MessageCache\getAllMessageKeys
getAllMessageKeys( $code)
Get all message keys stored in the message cache for a given language.
Definition: MessageCache.php:1126
MessageCache\destroyInstance
static destroyInstance()
Destroy the singleton instance.
Definition: MessageCache.php:119