MediaWiki  1.23.16
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  // Messages may take parameters that could come
151  // from malicious sources. As a precaution, disable
152  // the <html> parser tag when parsing messages.
153  $this->mParserOptions->setAllowUnsafeRawHtml( false );
154  }
155 
156  return $this->mParserOptions;
157  }
158 
166  function getLocalCache( $hash, $code ) {
167  global $wgCacheDirectory;
168 
169  $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
170 
171  # Check file existence
173  $file = fopen( $filename, 'r' );
175  if ( !$file ) {
176  return false; // No cache file
177  }
178 
179  // Check to see if the file has the hash specified
180  $localHash = fread( $file, 32 );
181  if ( $hash === $localHash ) {
182  // All good, get the rest of it
183  $serialized = '';
184  while ( !feof( $file ) ) {
185  $serialized .= fread( $file, 100000 );
186  }
187  fclose( $file );
188 
189  return unserialize( $serialized );
190  } else {
191  fclose( $file );
192 
193  return false; // Wrong hash
194  }
195  }
196 
200  function saveToLocal( $serialized, $hash, $code ) {
201  global $wgCacheDirectory;
202 
203  $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
204  wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
205 
207  $file = fopen( $filename, 'w' );
209 
210  if ( !$file ) {
211  wfDebug( "Unable to open local cache file for writing\n" );
212 
213  return;
214  }
215 
216  fwrite( $file, $hash . $serialized );
217  fclose( $file );
219  chmod( $filename, 0666 );
221  }
222 
243  function load( $code = false ) {
244  global $wgUseLocalMessageCache;
245 
246  if ( !is_string( $code ) ) {
247  # This isn't really nice, so at least make a note about it and try to
248  # fall back
249  wfDebug( __METHOD__ . " called without providing a language code\n" );
250  $code = 'en';
251  }
252 
253  # Don't do double loading...
254  if ( isset( $this->mLoadedLanguages[$code] ) ) {
255  return true;
256  }
257 
258  # 8 lines of code just to say (once) that message cache is disabled
259  if ( $this->mDisable ) {
260  static $shownDisabled = false;
261  if ( !$shownDisabled ) {
262  wfDebug( __METHOD__ . ": disabled\n" );
263  $shownDisabled = true;
264  }
265 
266  return true;
267  }
268 
269  # Loading code starts
270  wfProfileIn( __METHOD__ );
271  $success = false; # Keep track of success
272  $staleCache = false; # a cache array with expired data, or false if none has been loaded
273  $where = array(); # Debug info, delayed to avoid spamming debug log too much
274  $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
275 
276  # Local cache
277  # Hash of the contents is stored in memcache, to detect if local cache goes
278  # out of date (e.g. due to replace() on some other server)
279  if ( $wgUseLocalMessageCache ) {
280  wfProfileIn( __METHOD__ . '-fromlocal' );
281 
282  $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
283  if ( $hash ) {
284  $cache = $this->getLocalCache( $hash, $code );
285  if ( !$cache ) {
286  $where[] = 'local cache is empty or has the wrong hash';
287  } elseif ( $this->isCacheExpired( $cache ) ) {
288  $where[] = 'local cache is expired';
289  $staleCache = $cache;
290  } else {
291  $where[] = 'got from local cache';
292  $success = true;
293  $this->mCache[$code] = $cache;
294  }
295  }
296  wfProfileOut( __METHOD__ . '-fromlocal' );
297  }
298 
299  if ( !$success ) {
300  # Try the global cache. If it is empty, try to acquire a lock. If
301  # the lock can't be acquired, wait for the other thread to finish
302  # and then try the global cache a second time.
303  for ( $failedAttempts = 0; $failedAttempts < 2; $failedAttempts++ ) {
304  wfProfileIn( __METHOD__ . '-fromcache' );
305  $cache = $this->mMemc->get( $cacheKey );
306  if ( !$cache ) {
307  $where[] = 'global cache is empty';
308  } elseif ( $this->isCacheExpired( $cache ) ) {
309  $where[] = 'global cache is expired';
310  $staleCache = $cache;
311  } else {
312  $where[] = 'got from global cache';
313  $this->mCache[$code] = $cache;
314  $this->saveToCaches( $cache, 'local-only', $code );
315  $success = true;
316  }
317 
318  wfProfileOut( __METHOD__ . '-fromcache' );
319 
320  if ( $success ) {
321  # Done, no need to retry
322  break;
323  }
324 
325  # We need to call loadFromDB. Limit the concurrency to a single
326  # process. This prevents the site from going down when the cache
327  # expires.
328  $statusKey = wfMemcKey( 'messages', $code, 'status' );
329  $acquired = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
330  if ( $acquired ) {
331  # Unlock the status key if there is an exception
332  $that = $this;
333  $statusUnlocker = new ScopedCallback( function () use ( $that, $statusKey ) {
334  $that->mMemc->delete( $statusKey );
335  } );
336 
337  # Now let's regenerate
338  $where[] = 'loading from database';
339 
340  # Lock the cache to prevent conflicting writes
341  # If this lock fails, it doesn't really matter, it just means the
342  # write is potentially non-atomic, e.g. the results of a replace()
343  # may be discarded.
344  if ( $this->lock( $cacheKey ) ) {
345  $mainUnlocker = new ScopedCallback( function () use ( $that, $cacheKey ) {
346  $that->unlock( $cacheKey );
347  } );
348  } else {
349  $mainUnlocker = null;
350  $where[] = 'could not acquire main lock';
351  }
352 
353  $cache = $this->loadFromDB( $code );
354  $this->mCache[$code] = $cache;
355  $success = true;
356  $saveSuccess = $this->saveToCaches( $cache, 'all', $code );
357 
358  # Unlock
359  ScopedCallback::consume( $mainUnlocker );
360  ScopedCallback::consume( $statusUnlocker );
361 
362  if ( !$saveSuccess ) {
363  # Cache save has failed.
364  # There are two main scenarios where this could be a problem:
365  #
366  # - The cache is more than the maximum size (typically
367  # 1MB compressed).
368  #
369  # - Memcached has no space remaining in the relevant slab
370  # class. This is unlikely with recent versions of
371  # memcached.
372  #
373  # Either way, if there is a local cache, nothing bad will
374  # happen. If there is no local cache, disabling the message
375  # cache for all requests avoids incurring a loadFromDB()
376  # overhead on every request, and thus saves the wiki from
377  # complete downtime under moderate traffic conditions.
378  if ( !$wgUseLocalMessageCache ) {
379  $this->mMemc->set( $statusKey, 'error', 60 * 5 );
380  $where[] = 'could not save cache, disabled globally for 5 minutes';
381  } else {
382  $where[] = "could not save global cache";
383  }
384  }
385 
386  # Load from DB complete, no need to retry
387  break;
388  } elseif ( $staleCache ) {
389  # Use the stale cache while some other thread constructs the new one
390  $where[] = 'using stale cache';
391  $this->mCache[$code] = $staleCache;
392  $success = true;
393  break;
394  } elseif ( $failedAttempts > 0 ) {
395  # Already retried once, still failed, so don't do another lock/unlock cycle
396  # This case will typically be hit if memcached is down, or if
397  # loadFromDB() takes longer than MSG_WAIT_TIMEOUT
398  $where[] = "could not acquire status key.";
399  break;
400  } else {
401  $status = $this->mMemc->get( $statusKey );
402  if ( $status === 'error' ) {
403  # Disable cache
404  break;
405  } else {
406  # Wait for the other thread to finish, then retry
407  $where[] = 'waited for other thread to complete';
408  $this->lock( $cacheKey );
409  $this->unlock( $cacheKey );
410  }
411  }
412  }
413  }
414 
415  if ( !$success ) {
416  $where[] = 'loading FAILED - cache is disabled';
417  $this->mDisable = true;
418  $this->mCache = false;
419  # This used to throw an exception, but that led to nasty side effects like
420  # the whole wiki being instantly down if the memcached server died
421  } else {
422  # All good, just record the success
423  $this->mLoadedLanguages[$code] = true;
424  }
425  $info = implode( ', ', $where );
426  wfDebug( __METHOD__ . ": Loading $code... $info\n" );
427  wfProfileOut( __METHOD__ );
428 
429  return $success;
430  }
431 
440  function loadFromDB( $code ) {
441  wfProfileIn( __METHOD__ );
442  global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
443  $dbr = wfGetDB( DB_SLAVE );
444  $cache = array();
445 
446  # Common conditions
447  $conds = array(
448  'page_is_redirect' => 0,
449  'page_namespace' => NS_MEDIAWIKI,
450  );
451 
452  $mostused = array();
453  if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
454  if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
455  $this->load( $wgLanguageCode );
456  }
457  $mostused = array_keys( $this->mCache[$wgLanguageCode] );
458  foreach ( $mostused as $key => $value ) {
459  $mostused[$key] = "$value/$code";
460  }
461  }
462 
463  if ( count( $mostused ) ) {
464  $conds['page_title'] = $mostused;
465  } elseif ( $code !== $wgLanguageCode ) {
466  $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code );
467  } else {
468  # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
469  # other than language code.
470  $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
471  }
472 
473  # Conditions to fetch oversized pages to ignore them
474  $bigConds = $conds;
475  $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
476 
477  # Load titles for all oversized pages in the MediaWiki namespace
478  $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
479  foreach ( $res as $row ) {
480  $cache[$row->page_title] = '!TOO BIG';
481  }
482 
483  # Conditions to load the remaining pages with their contents
484  $smallConds = $conds;
485  $smallConds[] = 'page_latest=rev_id';
486  $smallConds[] = 'rev_text_id=old_id';
487  $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
488 
489  $res = $dbr->select(
490  array( 'page', 'revision', 'text' ),
491  array( 'page_title', 'old_text', 'old_flags' ),
492  $smallConds,
493  __METHOD__ . "($code)-small"
494  );
495 
496  foreach ( $res as $row ) {
497  $text = Revision::getRevisionText( $row );
498  if ( $text === false ) {
499  // Failed to fetch data; possible ES errors?
500  // Store a marker to fetch on-demand as a workaround...
501  $entry = '!TOO BIG';
502  wfDebugLog(
503  'MessageCache',
504  __METHOD__
505  . ": failed to load message page text for {$row->page_title} ($code)"
506  );
507  } else {
508  $entry = ' ' . $text;
509  }
510  $cache[$row->page_title] = $entry;
511  }
512 
513  $cache['VERSION'] = MSG_CACHE_VERSION;
514  $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
515  wfProfileOut( __METHOD__ );
516 
517  return $cache;
518  }
519 
526  public function replace( $title, $text ) {
527  global $wgMaxMsgCacheEntrySize;
528  wfProfileIn( __METHOD__ );
529 
530  if ( $this->mDisable ) {
531  wfProfileOut( __METHOD__ );
532 
533  return;
534  }
535 
536  list( $msg, $code ) = $this->figureMessage( $title );
537 
538  $cacheKey = wfMemcKey( 'messages', $code );
539  $this->load( $code );
540  $this->lock( $cacheKey );
541 
542  $titleKey = wfMemcKey( 'messages', 'individual', $title );
543 
544  if ( $text === false ) {
545  # Article was deleted
546  $this->mCache[$code][$title] = '!NONEXISTENT';
547  $this->mMemc->delete( $titleKey );
548  } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
549  # Check for size
550  $this->mCache[$code][$title] = '!TOO BIG';
551  $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
552  } else {
553  $this->mCache[$code][$title] = ' ' . $text;
554  $this->mMemc->delete( $titleKey );
555  }
556 
557  # Update caches
558  $this->saveToCaches( $this->mCache[$code], 'all', $code );
559  $this->unlock( $cacheKey );
560 
561  // Also delete cached sidebar... just in case it is affected
562  $codes = array( $code );
563  if ( $code === 'en' ) {
564  // Delete all sidebars, like for example on action=purge on the
565  // sidebar messages
566  $codes = array_keys( Language::fetchLanguageNames() );
567  }
568 
569  global $wgMemc;
570  foreach ( $codes as $code ) {
571  $sidebarKey = wfMemcKey( 'sidebar', $code );
572  $wgMemc->delete( $sidebarKey );
573  }
574 
575  // Update the message in the message blob store
577  MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
578 
579  wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
580 
581  wfProfileOut( __METHOD__ );
582  }
583 
590  protected function isCacheExpired( $cache ) {
591  if ( !isset( $cache['VERSION'] ) || !isset( $cache['EXPIRY'] ) ) {
592  return true;
593  }
594  if ( $cache['VERSION'] != MSG_CACHE_VERSION ) {
595  return true;
596  }
597  if ( wfTimestampNow() >= $cache['EXPIRY'] ) {
598  return true;
599  }
600 
601  return false;
602  }
603 
613  protected function saveToCaches( $cache, $dest, $code = false ) {
614  wfProfileIn( __METHOD__ );
615  global $wgUseLocalMessageCache;
616 
617  $cacheKey = wfMemcKey( 'messages', $code );
618 
619  if ( $dest === 'all' ) {
620  $success = $this->mMemc->set( $cacheKey, $cache );
621  } else {
622  $success = true;
623  }
624 
625  # Save to local cache
626  if ( $wgUseLocalMessageCache ) {
627  $serialized = serialize( $cache );
628  $hash = md5( $serialized );
629  $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash );
630  $this->saveToLocal( $serialized, $hash, $code );
631  }
632 
633  wfProfileOut( __METHOD__ );
634 
635  return $success;
636  }
637 
647  function lock( $key ) {
648  $lockKey = $key . ':lock';
649  $acquired = false;
650  $testDone = false;
651  for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$acquired; $i++ ) {
652  $acquired = $this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT );
653  if ( $acquired ) {
654  break;
655  }
656 
657  # Fail fast if memcached is totally down
658  if ( !$testDone ) {
659  $testDone = true;
660  if ( !$this->mMemc->set( wfMemcKey( 'test' ), 'test', 1 ) ) {
661  break;
662  }
663  }
664  sleep( 1 );
665  }
666 
667  return $acquired;
668  }
669 
670  function unlock( $key ) {
671  $lockKey = $key . ':lock';
672  $this->mMemc->delete( $lockKey );
673  }
674 
710  function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
712 
713  $section = new ProfileSection( __METHOD__ );
714 
715  if ( is_int( $key ) ) {
716  // Fix numerical strings that somehow become ints
717  // on their way here
718  $key = (string)$key;
719  } elseif ( !is_string( $key ) ) {
720  throw new MWException( 'Non-string key given' );
721  } elseif ( $key === '' ) {
722  // Shortcut: the empty key is always missing
723  return false;
724  }
725 
726  // For full keys, get the language code from the key
727  $pos = strrpos( $key, '/' );
728  if ( $isFullKey && $pos !== false ) {
729  $langcode = substr( $key, $pos + 1 );
730  $key = substr( $key, 0, $pos );
731  }
732 
733  // Normalise title-case input (with some inlining)
734  $lckey = strtr( $key, ' ', '_' );
735  if ( ord( $lckey ) < 128 ) {
736  $lckey[0] = strtolower( $lckey[0] );
737  } else {
738  $lckey = $wgContLang->lcfirst( $lckey );
739  }
740 
741  wfRunHooks( 'MessageCache::get', array( &$lckey ) );
742 
743  if ( ord( $lckey ) < 128 ) {
744  $uckey = ucfirst( $lckey );
745  } else {
746  $uckey = $wgContLang->ucfirst( $lckey );
747  }
748 
749  // Loop through each language in the fallback list until we find something useful
750  $lang = wfGetLangObj( $langcode );
751  $message = $this->getMessageFromFallbackChain(
752  $lang,
753  $lckey,
754  $uckey,
755  !$this->mDisable && $useDB
756  );
757 
758  // If we still have no message, maybe the key was in fact a full key so try that
759  if ( $message === false ) {
760  $parts = explode( '/', $lckey );
761  // We may get calls for things that are http-urls from sidebar
762  // Let's not load nonexistent languages for those
763  // They usually have more than one slash.
764  if ( count( $parts ) == 2 && $parts[1] !== '' ) {
765  $message = Language::getMessageFor( $parts[0], $parts[1] );
766  if ( $message === null ) {
767  $message = false;
768  }
769  }
770  }
771 
772  // Post-processing if the message exists
773  if ( $message !== false ) {
774  // Fix whitespace
775  $message = str_replace(
776  array(
777  # Fix for trailing whitespace, removed by textarea
778  '&#32;',
779  # Fix for NBSP, converted to space by firefox
780  '&nbsp;',
781  '&#160;',
782  ),
783  array(
784  ' ',
785  "\xc2\xa0",
786  "\xc2\xa0"
787  ),
788  $message
789  );
790  }
791 
792  return $message;
793  }
794 
808  protected function getMessageFromFallbackChain( $lang, $lckey, $uckey, $useDB ) {
809  global $wgLanguageCode, $wgContLang;
810 
811  $langcode = $lang->getCode();
812  $message = false;
813 
814  // First try the requested language.
815  if ( $useDB ) {
816  if ( $langcode === $wgLanguageCode ) {
817  // Messages created in the content language will not have the /lang extension
818  $message = $this->getMsgFromNamespace( $uckey, $langcode );
819  } else {
820  $message = $this->getMsgFromNamespace( "$uckey/$langcode", $langcode );
821  }
822  }
823 
824  if ( $message !== false ) {
825  return $message;
826  }
827 
828  // Check the CDB cache
829  $message = $lang->getMessage( $lckey );
830  if ( $message !== null ) {
831  return $message;
832  }
833 
834  list( $fallbackChain, $siteFallbackChain ) =
836 
837  // Next try checking the database for all of the fallback languages of the requested language.
838  if ( $useDB ) {
839  foreach ( $fallbackChain as $code ) {
840  if ( $code === $wgLanguageCode ) {
841  // Messages created in the content language will not have the /lang extension
842  $message = $this->getMsgFromNamespace( $uckey, $code );
843  } else {
844  $message = $this->getMsgFromNamespace( "$uckey/$code", $code );
845  }
846 
847  if ( $message !== false ) {
848  // Found the message.
849  return $message;
850  }
851  }
852  }
853 
854  // Now try checking the site language.
855  if ( $useDB ) {
856  $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
857  if ( $message !== false ) {
858  return $message;
859  }
860  }
861 
862  $message = $wgContLang->getMessage( $lckey );
863  if ( $message !== null ) {
864  return $message;
865  }
866 
867  // Finally try the DB for the site language's fallbacks.
868  if ( $useDB ) {
869  foreach ( $siteFallbackChain as $code ) {
870  $message = $this->getMsgFromNamespace( "$uckey/$code", $code );
871  if ( $message === false && $code === $wgLanguageCode ) {
872  // Messages created in the content language will not have the /lang extension
873  $message = $this->getMsgFromNamespace( $uckey, $code );
874  }
875 
876  if ( $message !== false ) {
877  // Found the message.
878  return $message;
879  }
880  }
881  }
882 
883  return false;
884  }
885 
898  function getMsgFromNamespace( $title, $code ) {
899  $this->load( $code );
900  if ( isset( $this->mCache[$code][$title] ) ) {
901  $entry = $this->mCache[$code][$title];
902  if ( substr( $entry, 0, 1 ) === ' ' ) {
903  // The message exists, so make sure a string
904  // is returned.
905  return (string)substr( $entry, 1 );
906  } elseif ( $entry === '!NONEXISTENT' ) {
907  return false;
908  } elseif ( $entry === '!TOO BIG' ) {
909  // Fall through and try invididual message cache below
910  }
911  } else {
912  // XXX: This is not cached in process cache, should it?
913  $message = false;
914  wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
915  if ( $message !== false ) {
916  return $message;
917  }
918 
919  return false;
920  }
921 
922  # Try the individual message cache
923  $titleKey = wfMemcKey( 'messages', 'individual', $title );
924  $entry = $this->mMemc->get( $titleKey );
925  if ( $entry ) {
926  if ( substr( $entry, 0, 1 ) === ' ' ) {
927  $this->mCache[$code][$title] = $entry;
928 
929  // The message exists, so make sure a string
930  // is returned.
931  return (string)substr( $entry, 1 );
932  } elseif ( $entry === '!NONEXISTENT' ) {
933  $this->mCache[$code][$title] = '!NONEXISTENT';
934 
935  return false;
936  } else {
937  # Corrupt/obsolete entry, delete it
938  $this->mMemc->delete( $titleKey );
939  }
940  }
941 
942  # Try loading it from the database
943  $revision = Revision::newFromTitle(
945  );
946  if ( $revision ) {
947  $content = $revision->getContent();
948  if ( !$content ) {
949  // A possibly temporary loading failure.
950  wfDebugLog(
951  'MessageCache',
952  __METHOD__ . ": failed to load message page text for {$title} ($code)"
953  );
954  $message = null; // no negative caching
955  } else {
956  // XXX: Is this the right way to turn a Content object into a message?
957  // NOTE: $content is typically either WikitextContent, JavaScriptContent or
958  // CssContent. MessageContent is *not* used for storing messages, it's
959  // only used for wrapping them when needed.
960  $message = $content->getWikitextForTransclusion();
961 
962  if ( $message === false || $message === null ) {
963  wfDebugLog(
964  'MessageCache',
965  __METHOD__ . ": message content doesn't provide wikitext "
966  . "(content model: " . $content->getContentHandler() . ")"
967  );
968 
969  $message = false; // negative caching
970  } else {
971  $this->mCache[$code][$title] = ' ' . $message;
972  $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
973  }
974  }
975  } else {
976  $message = false; // negative caching
977  }
978 
979  if ( $message === false ) { // negative caching
980  $this->mCache[$code][$title] = '!NONEXISTENT';
981  $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
982  }
983 
984  return $message;
985  }
986 
994  function transform( $message, $interface = false, $language = null, $title = null ) {
995  // Avoid creating parser if nothing to transform
996  if ( strpos( $message, '{{' ) === false ) {
997  return $message;
998  }
999 
1000  if ( $this->mInParser ) {
1001  return $message;
1002  }
1003 
1004  $parser = $this->getParser();
1005  if ( $parser ) {
1006  $popts = $this->getParserOptions();
1007  $popts->setInterfaceMessage( $interface );
1008  $popts->setTargetLanguage( $language );
1009 
1010  $userlang = $popts->setUserLang( $language );
1011  $this->mInParser = true;
1012  $message = $parser->transformMsg( $message, $popts, $title );
1013  $this->mInParser = false;
1014  $popts->setUserLang( $userlang );
1015  }
1016 
1017  return $message;
1018  }
1019 
1023  function getParser() {
1024  global $wgParser, $wgParserConf;
1025  if ( !$this->mParser && isset( $wgParser ) ) {
1026  # Do some initialisation so that we don't have to do it twice
1027  $wgParser->firstCallInit();
1028  # Clone it and store it
1029  $class = $wgParserConf['class'];
1030  if ( $class == 'Parser_DiffTest' ) {
1031  # Uncloneable
1032  $this->mParser = new $class( $wgParserConf );
1033  } else {
1034  $this->mParser = clone $wgParser;
1035  }
1036  }
1037 
1038  return $this->mParser;
1039  }
1040 
1049  public function parse( $text, $title = null, $linestart = true,
1050  $interface = false, $language = null
1051  ) {
1052  if ( $this->mInParser ) {
1053  return htmlspecialchars( $text );
1054  }
1055 
1056  $parser = $this->getParser();
1057  $popts = $this->getParserOptions();
1058  $popts->setInterfaceMessage( $interface );
1059  $popts->setTargetLanguage( $language );
1060 
1061  wfProfileIn( __METHOD__ );
1062  if ( !$title || !$title instanceof Title ) {
1063  global $wgTitle;
1064  $title = $wgTitle;
1065  }
1066  // Sometimes $wgTitle isn't set either...
1067  if ( !$title ) {
1068  # It's not uncommon having a null $wgTitle in scripts. See r80898
1069  # Create a ghost title in such case
1070  $title = Title::newFromText( 'Dwimmerlaik' );
1071  }
1072 
1073  $this->mInParser = true;
1074  $res = $parser->parse( $text, $title, $popts, $linestart );
1075  $this->mInParser = false;
1076 
1077  wfProfileOut( __METHOD__ );
1078 
1079  return $res;
1080  }
1081 
1082  function disable() {
1083  $this->mDisable = true;
1084  }
1085 
1086  function enable() {
1087  $this->mDisable = false;
1088  }
1089 
1093  function clear() {
1094  $langs = Language::fetchLanguageNames( null, 'mw' );
1095  foreach ( array_keys( $langs ) as $code ) {
1096  # Global cache
1097  $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
1098  # Invalidate all local caches
1099  $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
1100  }
1101  $this->mLoadedLanguages = array();
1102  }
1103 
1108  public function figureMessage( $key ) {
1109  global $wgLanguageCode;
1110  $pieces = explode( '/', $key );
1111  if ( count( $pieces ) < 2 ) {
1112  return array( $key, $wgLanguageCode );
1113  }
1114 
1115  $lang = array_pop( $pieces );
1116  if ( !Language::fetchLanguageName( $lang, null, 'mw' ) ) {
1117  return array( $key, $wgLanguageCode );
1118  }
1119 
1120  $message = implode( '/', $pieces );
1121 
1122  return array( $message, $lang );
1123  }
1124 
1133  public function getAllMessageKeys( $code ) {
1135  $this->load( $code );
1136  if ( !isset( $this->mCache[$code] ) ) {
1137  // Apparently load() failed
1138  return null;
1139  }
1140  // Remove administrative keys
1141  $cache = $this->mCache[$code];
1142  unset( $cache['VERSION'] );
1143  unset( $cache['EXPIRY'] );
1144  // Remove any !NONEXISTENT keys
1145  $cache = array_diff( $cache, array( '!NONEXISTENT' ) );
1146 
1147  // Keys may appear with a capital first letter. lcfirst them.
1148  return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
1149  }
1150 }
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:991
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:805
MessageCache\unlock
unlock( $key)
Definition: MessageCache.php:667
MessageCache\parse
parse( $text, $title=null, $linestart=true, $interface=false, $language=null)
Definition: MessageCache.php:1046
wfMkdirParents
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
Definition: GlobalFunctions.php:2637
MessageCache\loadFromDB
loadFromDB( $code)
Loads cacheable messages from the database.
Definition: MessageCache.php:437
MessageCache\enable
enable()
Definition: MessageCache.php:1083
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:3714
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:2530
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:1087
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
wfSuppressWarnings
wfSuppressWarnings( $end=false)
Reference-counted warning suppression.
Definition: GlobalFunctions.php:2434
$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:4013
$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:197
$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:3635
MessageCache\getMsgFromNamespace
getMsgFromNamespace( $title, $code)
Get a message from the MediaWiki namespace, with caching.
Definition: MessageCache.php:895
MessageCache\saveToCaches
saveToCaches( $cache, $dest, $code=false)
Shortcut to update caches.
Definition: MessageCache.php:610
wfRestoreWarnings
wfRestoreWarnings()
Restore error level to previous value.
Definition: GlobalFunctions.php:2464
MessageCache\lock
lock( $key)
Represents a write lock on the messages key.
Definition: MessageCache.php:644
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:4209
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:1961
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:4066
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:163
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:1105
wfGetLangObj
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
Definition: GlobalFunctions.php:1399
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:523
MessageCache\load
load( $code=false)
Loads messages from caches or from database in this order: (1) local message cache (if $wgUseLocalMes...
Definition: MessageCache.php:240
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:2561
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:2478
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:980
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:3668
$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:1090
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:941
$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:2708
MessageCache\getParser
getParser()
Definition: MessageCache.php:1020
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
wfGetMessageCacheStorage
wfGetMessageCacheStorage()
Get the cache object used by the message cache.
Definition: GlobalFunctions.php:4032
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:587
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:4166
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:587
ParserOptions\setEditSection
setEditSection( $x)
Definition: ParserOptions.php:315
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:1079
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:1130
MessageCache\destroyInstance
static destroyInstance()
Destroy the singleton instance.
Definition: MessageCache.php:119