73b1b43ac501cb90620e2c845e21add9c74cc66d
[lhc/web/wiklou.git] / includes / MessageCache.php
1 <?php
2 /**
3 * @file
4 * @ingroup Cache
5 */
6
7 /**
8 *
9 */
10 define( 'MSG_LOAD_TIMEOUT', 60);
11 define( 'MSG_LOCK_TIMEOUT', 10);
12 define( 'MSG_WAIT_TIMEOUT', 10);
13 define( 'MSG_CACHE_VERSION', 1 );
14
15 /**
16 * Message cache
17 * Performs various MediaWiki namespace-related functions
18 * @ingroup Cache
19 */
20 class MessageCache {
21 /**
22 * Process local cache of loaded messages that are defined in
23 * MediaWiki namespace. First array level is a language code,
24 * second level is message key and the values are either message
25 * content prefixed with space, or !NONEXISTENT for negative
26 * caching.
27 */
28 protected $mCache;
29
30 // Should mean that database cannot be used, but check
31 protected $mDisable;
32
33 /// Lifetime for cache, used by object caching
34 protected $mExpiry;
35
36 /**
37 * Message cache has it's own parser which it uses to transform
38 * messages.
39 */
40 protected $mParserOptions, $mParser;
41
42 /// Variable for tracking which variables are already loaded
43 protected $mLoadedLanguages = array();
44
45 /**
46 * Used for automatic detection of most used messages.
47 */
48 protected $mRequestedMessages = array();
49
50 /**
51 * How long the message request counts are stored. Longer period gives
52 * better sample, but also takes longer to adapt changes. The counts
53 * are aggregrated per day, regardless of the value of this variable.
54 */
55 protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
56
57 /**
58 * Filter the tail of less used messages that are requested more seldom
59 * than this factor times the number of request of most requested message.
60 * These messages are not loaded in the default set, but are still cached
61 * individually on demand with the normal cache expiry time.
62 */
63 protected static $mAdaptiveInclusionThreshold = 0.05;
64
65 /**
66 * Singleton instance
67 */
68 private static $instance;
69
70 /**
71 * Get the signleton instance of this class
72 *
73 * @since 1.18
74 * @return MessageCache object
75 */
76 public static function singleton() {
77 if ( is_null( self::$instance ) ) {
78 global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
79 self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
80 }
81 return self::$instance;
82 }
83
84 /**
85 * Destroy the signleton instance
86 *
87 * @since 1.18
88 */
89 public static function destroyInstance() {
90 self::$instance = null;
91 }
92
93 function __construct( $memCached, $useDB, $expiry ) {
94 if ( !$memCached ) {
95 $memCached = wfGetCache( CACHE_NONE );
96 }
97
98 $this->mMemc = $memCached;
99 $this->mDisable = !$useDB;
100 $this->mExpiry = $expiry;
101 }
102
103
104 /**
105 * ParserOptions is lazy initialised.
106 */
107 function getParserOptions() {
108 if ( !$this->mParserOptions ) {
109 $this->mParserOptions = new ParserOptions;
110 }
111 return $this->mParserOptions;
112 }
113
114 /**
115 * Try to load the cache from a local file.
116 * Actual format of the file depends on the $wgLocalMessageCacheSerialized
117 * setting.
118 *
119 * @param $hash String: the hash of contents, to check validity.
120 * @param $code Mixed: Optional language code, see documenation of load().
121 * @return false on failure.
122 */
123 function loadFromLocal( $hash, $code ) {
124 global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
125
126 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
127
128 # Check file existence
129 wfSuppressWarnings();
130 $file = fopen( $filename, 'r' );
131 wfRestoreWarnings();
132 if ( !$file ) {
133 return false; // No cache file
134 }
135
136 if ( $wgLocalMessageCacheSerialized ) {
137 // Check to see if the file has the hash specified
138 $localHash = fread( $file, 32 );
139 if ( $hash === $localHash ) {
140 // All good, get the rest of it
141 $serialized = '';
142 while ( !feof( $file ) ) {
143 $serialized .= fread( $file, 100000 );
144 }
145 fclose( $file );
146 return $this->setCache( unserialize( $serialized ), $code );
147 } else {
148 fclose( $file );
149 return false; // Wrong hash
150 }
151 } else {
152 $localHash=substr(fread($file,40),8);
153 fclose($file);
154 if ($hash!=$localHash) {
155 return false; // Wrong hash
156 }
157
158 # Require overwrites the member variable or just shadows it?
159 require( $filename );
160 return $this->setCache( $this->mCache, $code );
161 }
162 }
163
164 /**
165 * Save the cache to a local file.
166 */
167 function saveToLocal( $serialized, $hash, $code ) {
168 global $wgCacheDirectory;
169
170 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
171 wfMkdirParents( $wgCacheDirectory ); // might fail
172
173 wfSuppressWarnings();
174 $file = fopen( $filename, 'w' );
175 wfRestoreWarnings();
176
177 if ( !$file ) {
178 wfDebug( "Unable to open local cache file for writing\n" );
179 return;
180 }
181
182 fwrite( $file, $hash . $serialized );
183 fclose( $file );
184 @chmod( $filename, 0666 );
185 }
186
187 function saveToScript( $array, $hash, $code ) {
188 global $wgCacheDirectory;
189
190 $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
191 $tempFilename = $filename . '.tmp';
192 wfMkdirParents( $wgCacheDirectory ); // might fail
193
194 wfSuppressWarnings();
195 $file = fopen( $tempFilename, 'w');
196 wfRestoreWarnings();
197
198 if ( !$file ) {
199 wfDebug( "Unable to open local cache file for writing\n" );
200 return;
201 }
202
203 fwrite($file,"<?php\n//$hash\n\n \$this->mCache = array(");
204
205 foreach ( $array as $key => $message ) {
206 $key = $this->escapeForScript($key);
207 $message = $this->escapeForScript($message);
208 fwrite($file, "'$key' => '$message',\n");
209 }
210
211 fwrite($file,");\n?>");
212 fclose($file);
213 rename($tempFilename, $filename);
214 }
215
216 function escapeForScript($string) {
217 $string = str_replace( '\\', '\\\\', $string );
218 $string = str_replace( '\'', '\\\'', $string );
219 return $string;
220 }
221
222 /**
223 * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
224 */
225 function setCache( $cache, $code ) {
226 if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
227 $this->mCache[$code] = $cache;
228 return true;
229 } else {
230 return false;
231 }
232 }
233
234 /**
235 * Loads messages from caches or from database in this order:
236 * (1) local message cache (if $wgUseLocalMessageCache is enabled)
237 * (2) memcached
238 * (3) from the database.
239 *
240 * When succesfully loading from (2) or (3), all higher level caches are
241 * updated for the newest version.
242 *
243 * Nothing is loaded if member variable mDisable is true, either manually
244 * set by calling code or if message loading fails (is this possible?).
245 *
246 * Returns true if cache is already populated or it was succesfully populated,
247 * or false if populating empty cache fails. Also returns true if MessageCache
248 * is disabled.
249 *
250 * @param $code String: language to which load messages
251 */
252 function load( $code = false ) {
253 global $wgUseLocalMessageCache;
254
255 if( !is_string( $code ) ) {
256 # This isn't really nice, so at least make a note about it and try to
257 # fall back
258 wfDebug( __METHOD__ . " called without providing a language code\n" );
259 $code = 'en';
260 }
261
262 # Don't do double loading...
263 if ( isset($this->mLoadedLanguages[$code]) ) return true;
264
265 # 8 lines of code just to say (once) that message cache is disabled
266 if ( $this->mDisable ) {
267 static $shownDisabled = false;
268 if ( !$shownDisabled ) {
269 wfDebug( __METHOD__ . ": disabled\n" );
270 $shownDisabled = true;
271 }
272 return true;
273 }
274
275 # Loading code starts
276 wfProfileIn( __METHOD__ );
277 $success = false; # Keep track of success
278 $where = array(); # Debug info, delayed to avoid spamming debug log too much
279 $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
280
281
282 # (1) local cache
283 # Hash of the contents is stored in memcache, to detect if local cache goes
284 # out of date (due to update in other thread?)
285 if ( $wgUseLocalMessageCache ) {
286 wfProfileIn( __METHOD__ . '-fromlocal' );
287
288 $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
289 if ( $hash ) {
290 $success = $this->loadFromLocal( $hash, $code );
291 if ( $success ) $where[] = 'got from local cache';
292 }
293 wfProfileOut( __METHOD__ . '-fromlocal' );
294 }
295
296 # (2) memcache
297 # Fails if nothing in cache, or in the wrong version.
298 if ( !$success ) {
299 wfProfileIn( __METHOD__ . '-fromcache' );
300 $cache = $this->mMemc->get( $cacheKey );
301 $success = $this->setCache( $cache, $code );
302 if ( $success ) {
303 $where[] = 'got from global cache';
304 $this->saveToCaches( $cache, false, $code );
305 }
306 wfProfileOut( __METHOD__ . '-fromcache' );
307 }
308
309
310 # (3)
311 # Nothing in caches... so we need create one and store it in caches
312 if ( !$success ) {
313 $where[] = 'cache is empty';
314 $where[] = 'loading from database';
315
316 $this->lock($cacheKey);
317
318 # Limit the concurrency of loadFromDB to a single process
319 # This prevents the site from going down when the cache expires
320 $statusKey = wfMemcKey( 'messages', $code, 'status' );
321 $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
322 if ( $success ) {
323 $cache = $this->loadFromDB( $code );
324 $success = $this->setCache( $cache, $code );
325 }
326 if ( $success ) {
327 $success = $this->saveToCaches( $cache, true, $code );
328 if ( $success ) {
329 $this->mMemc->delete( $statusKey );
330 } else {
331 $this->mMemc->set( $statusKey, 'error', 60*5 );
332 wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
333 }
334 }
335 $this->unlock($cacheKey);
336 }
337
338 if ( !$success ) {
339 # Bad luck... this should not happen
340 $where[] = 'loading FAILED - cache is disabled';
341 $info = implode( ', ', $where );
342 wfDebug( __METHOD__ . ": Loading $code... $info\n" );
343 $this->mDisable = true;
344 $this->mCache = false;
345 } else {
346 # All good, just record the success
347 $info = implode( ', ', $where );
348 wfDebug( __METHOD__ . ": Loading $code... $info\n" );
349 $this->mLoadedLanguages[$code] = true;
350 }
351 wfProfileOut( __METHOD__ );
352 return $success;
353 }
354
355 /**
356 * Loads cacheable messages from the database. Messages bigger than
357 * $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
358 * on-demand from the database later.
359 *
360 * @param $code \string Language code.
361 * @return \array Loaded messages for storing in caches.
362 */
363 function loadFromDB( $code ) {
364 wfProfileIn( __METHOD__ );
365 global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
366 $dbr = wfGetDB( DB_SLAVE );
367 $cache = array();
368
369 # Common conditions
370 $conds = array(
371 'page_is_redirect' => 0,
372 'page_namespace' => NS_MEDIAWIKI,
373 );
374
375 $mostused = array();
376 if ( $wgAdaptiveMessageCache ) {
377 $mostused = $this->getMostUsedMessages();
378 if ( $code !== $wgLanguageCode ) {
379 foreach ( $mostused as $key => $value ) $mostused[$key] = "$value/$code";
380 }
381 }
382
383 if ( count( $mostused ) ) {
384 $conds['page_title'] = $mostused;
385 } elseif ( $code !== $wgLanguageCode ) {
386 $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
387 } else {
388 # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
389 # other than language code.
390 $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
391 }
392
393 # Conditions to fetch oversized pages to ignore them
394 $bigConds = $conds;
395 $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
396
397 # Load titles for all oversized pages in the MediaWiki namespace
398 $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
399 foreach ( $res as $row ) {
400 $cache[$row->page_title] = '!TOO BIG';
401 }
402
403 # Conditions to load the remaining pages with their contents
404 $smallConds = $conds;
405 $smallConds[] = 'page_latest=rev_id';
406 $smallConds[] = 'rev_text_id=old_id';
407 $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
408
409 $res = $dbr->select( array( 'page', 'revision', 'text' ),
410 array( 'page_title', 'old_text', 'old_flags' ),
411 $smallConds, __METHOD__ . "($code)-small" );
412
413 foreach ( $res as $row ) {
414 $cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
415 }
416
417 foreach ( $mostused as $key ) {
418 if ( !isset( $cache[$key] ) ) {
419 $cache[$key] = '!NONEXISTENT';
420 }
421 }
422
423 $cache['VERSION'] = MSG_CACHE_VERSION;
424 wfProfileOut( __METHOD__ );
425 return $cache;
426 }
427
428 /**
429 * Updates cache as necessary when message page is changed
430 *
431 * @param $title String: name of the page changed.
432 * @param $text Mixed: new contents of the page.
433 */
434 public function replace( $title, $text ) {
435 global $wgMaxMsgCacheEntrySize;
436 wfProfileIn( __METHOD__ );
437
438 if ( $this->mDisable ) {
439 return;
440 }
441
442 list( $msg, $code ) = $this->figureMessage( $title );
443
444 $cacheKey = wfMemcKey( 'messages', $code );
445 $this->load( $code );
446 $this->lock( $cacheKey );
447
448 $titleKey = wfMemcKey( 'messages', 'individual', $title );
449
450 if ( $text === false ) {
451 # Article was deleted
452 $this->mCache[$code][$title] = '!NONEXISTENT';
453 $this->mMemc->delete( $titleKey );
454
455 } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
456 # Check for size
457 $this->mCache[$code][$title] = '!TOO BIG';
458 $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
459
460 } else {
461 $this->mCache[$code][$title] = ' ' . $text;
462 $this->mMemc->delete( $titleKey );
463 }
464
465 # Update caches
466 $this->saveToCaches( $this->mCache[$code], true, $code );
467 $this->unlock( $cacheKey );
468
469 // Also delete cached sidebar... just in case it is affected
470 $codes = array( $code );
471 if ( $code === 'en' ) {
472 // Delete all sidebars, like for example on action=purge on the
473 // sidebar messages
474 $codes = array_keys( Language::getLanguageNames() );
475 }
476
477 global $parserMemc;
478 foreach ( $codes as $code ) {
479 $sidebarKey = wfMemcKey( 'sidebar', $code );
480 $parserMemc->delete( $sidebarKey );
481 }
482
483 // Update the message in the message blob store
484 global $wgContLang;
485 MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
486
487 wfRunHooks( "MessageCacheReplace", array( $title, $text ) );
488
489 wfProfileOut( __METHOD__ );
490 }
491
492 /**
493 * Shortcut to update caches.
494 *
495 * @param $cache Array: cached messages with a version.
496 * @param $memc Bool: Wether to update or not memcache.
497 * @param $code String: Language code.
498 * @return False on somekind of error.
499 */
500 protected function saveToCaches( $cache, $memc = true, $code = false ) {
501 wfProfileIn( __METHOD__ );
502 global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
503
504 $cacheKey = wfMemcKey( 'messages', $code );
505
506 if ( $memc ) {
507 $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
508 } else {
509 $success = true;
510 }
511
512 # Save to local cache
513 if ( $wgUseLocalMessageCache ) {
514 $serialized = serialize( $cache );
515 $hash = md5( $serialized );
516 $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
517 if ($wgLocalMessageCacheSerialized) {
518 $this->saveToLocal( $serialized, $hash, $code );
519 } else {
520 $this->saveToScript( $cache, $hash, $code );
521 }
522 }
523
524 wfProfileOut( __METHOD__ );
525 return $success;
526 }
527
528 /**
529 * Represents a write lock on the messages key
530 *
531 * @return Boolean: success
532 */
533 function lock($key) {
534 $lockKey = $key . ':lock';
535 for ($i=0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
536 sleep(1);
537 }
538
539 return $i >= MSG_WAIT_TIMEOUT;
540 }
541
542 function unlock($key) {
543 $lockKey = $key . ':lock';
544 $this->mMemc->delete( $lockKey );
545 }
546
547 /**
548 * Get a message from either the content language or the user language.
549 *
550 * @param $key String: the message cache key
551 * @param $useDB Boolean: get the message from the DB, false to use only
552 * the localisation
553 * @param $langcode String: code of the language to get the message for, if
554 * it is a valid code create a language for that language,
555 * if it is a string but not a valid code then make a basic
556 * language object, if it is a false boolean then use the
557 * current users language (as a fallback for the old
558 * parameter functionality), or if it is a true boolean
559 * then use the wikis content language (also as a
560 * fallback).
561 * @param $isFullKey Boolean: specifies whether $key is a two part key
562 * "msg/lang".
563 */
564 function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
565 global $wgLanguageCode, $wgContLang;
566
567 if ( !is_string( $key ) ) {
568 throw new MWException( "Non-string key given" );
569 }
570
571 if ( strval( $key ) === '' ) {
572 # Shortcut: the empty key is always missing
573 return false;
574 }
575
576 $lang = wfGetLangObj( $langcode );
577 if ( !$lang ) {
578 throw new MWException( "Bad lang code $langcode given" );
579 }
580
581 // Don't change getPreferredVariant() to getCode() / mCode, for
582 // more details, see the comment in Language::getMessage().
583 $langcode = $lang->getPreferredVariant();
584
585 $message = false;
586
587 # Normalise title-case input (with some inlining)
588 $lckey = str_replace( ' ', '_', $key );
589 if ( ord( $key ) < 128 ) {
590 $lckey[0] = strtolower( $lckey[0] );
591 $uckey = ucfirst( $lckey );
592 } else {
593 $lckey = $wgContLang->lcfirst( $lckey );
594 $uckey = $wgContLang->ucfirst( $lckey );
595 }
596
597 /* Record each message request, but only once per request.
598 * This information is not used unless $wgAdaptiveMessageCache
599 * is enabled. */
600 $this->mRequestedMessages[$uckey] = true;
601
602 # Try the MediaWiki namespace
603 if( !$this->mDisable && $useDB ) {
604 $title = $uckey;
605 if(!$isFullKey && ( $langcode != $wgLanguageCode ) ) {
606 $title .= '/' . $langcode;
607 }
608 $message = $this->getMsgFromNamespace( $title, $langcode );
609 }
610
611 # Try the array in the language object
612 if ( $message === false ) {
613 $message = $lang->getMessage( $lckey );
614 if ( is_null( $message ) ) {
615 $message = false;
616 }
617 }
618
619 # Try the array of another language
620 if( $message === false ) {
621 $parts = explode( '/', $lckey );
622 # We may get calls for things that are http-urls from sidebar
623 # Let's not load nonexistent languages for those
624 # They usually have more than one slash.
625 if ( count( $parts ) == 2 && $parts[1] !== '' ) {
626 $message = Language::getMessageFor( $parts[0], $parts[1] );
627 if ( is_null( $message ) ) {
628 $message = false;
629 }
630 }
631 }
632
633 # Is this a custom message? Try the default language in the db...
634 if( ($message === false || $message === '-' ) &&
635 !$this->mDisable && $useDB &&
636 !$isFullKey && ($langcode != $wgLanguageCode) ) {
637 $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
638 }
639
640 # Final fallback
641 if( $message === false ) {
642 return false;
643 }
644
645 # Fix whitespace
646 $message = strtr( $message,
647 array(
648 # Fix for trailing whitespace, removed by textarea
649 '&#32;' => ' ',
650 # Fix for NBSP, converted to space by firefox
651 '&nbsp;' => "\xc2\xa0",
652 '&#160;' => "\xc2\xa0",
653 ) );
654
655 return $message;
656 }
657
658 /**
659 * Get a message from the MediaWiki namespace, with caching. The key must
660 * first be converted to two-part lang/msg form if necessary.
661 *
662 * @param $title String: Message cache key with initial uppercase letter.
663 * @param $code String: code denoting the language to try.
664 */
665 function getMsgFromNamespace( $title, $code ) {
666 global $wgAdaptiveMessageCache;
667
668 $this->load( $code );
669 if ( isset( $this->mCache[$code][$title] ) ) {
670 $entry = $this->mCache[$code][$title];
671 if ( substr( $entry, 0, 1 ) === ' ' ) {
672 return substr( $entry, 1 );
673 } elseif ( $entry === '!NONEXISTENT' ) {
674 return false;
675 } elseif( $entry === '!TOO BIG' ) {
676 // Fall through and try invididual message cache below
677 }
678 } else {
679 // XXX: This is not cached in process cache, should it?
680 $message = false;
681 wfRunHooks('MessagesPreLoad', array( $title, &$message ) );
682 if ( $message !== false ) {
683 return $message;
684 }
685
686 /* If message cache is in normal mode, it is guaranteed
687 * (except bugs) that there is always entry (or placeholder)
688 * in the cache if message exists. Thus we can do minor
689 * performance improvement and return false early.
690 */
691 if ( !$wgAdaptiveMessageCache ) {
692 return false;
693 }
694 }
695
696 # Try the individual message cache
697 $titleKey = wfMemcKey( 'messages', 'individual', $title );
698 $entry = $this->mMemc->get( $titleKey );
699 if ( $entry ) {
700 if ( substr( $entry, 0, 1 ) === ' ' ) {
701 $this->mCache[$code][$title] = $entry;
702 return substr( $entry, 1 );
703 } elseif ( $entry === '!NONEXISTENT' ) {
704 $this->mCache[$code][$title] = '!NONEXISTENT';
705 return false;
706 } else {
707 # Corrupt/obsolete entry, delete it
708 $this->mMemc->delete( $titleKey );
709 }
710 }
711
712 # Try loading it from the database
713 $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
714 if ( $revision ) {
715 $message = $revision->getText();
716 $this->mCache[$code][$title] = ' ' . $message;
717 $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
718 } else {
719 $message = false;
720 $this->mCache[$code][$title] = '!NONEXISTENT';
721 $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
722 }
723
724 return $message;
725 }
726
727 function transform( $message, $interface = false, $language = null ) {
728 // Avoid creating parser if nothing to transform
729 if( strpos( $message, '{{' ) === false ) {
730 return $message;
731 }
732
733 global $wgParser, $wgParserConf;
734 if ( !$this->mParser && isset( $wgParser ) ) {
735 # Do some initialisation so that we don't have to do it twice
736 $wgParser->firstCallInit();
737 # Clone it and store it
738 $class = $wgParserConf['class'];
739 if ( $class == 'Parser_DiffTest' ) {
740 # Uncloneable
741 $this->mParser = new $class( $wgParserConf );
742 } else {
743 $this->mParser = clone $wgParser;
744 }
745 #wfDebug( __METHOD__ . ": following contents triggered transform: $message\n" );
746 }
747 if ( $this->mParser ) {
748 $popts = $this->getParserOptions();
749 $popts->setInterfaceMessage( $interface );
750 $popts->setTargetLanguage( $language );
751 $popts->setUserLang( $language );
752 $message = $this->mParser->transformMsg( $message, $popts );
753 }
754 return $message;
755 }
756
757 function disable() { $this->mDisable = true; }
758 function enable() { $this->mDisable = false; }
759
760 /**
761 * Clear all stored messages. Mainly used after a mass rebuild.
762 */
763 function clear() {
764 $langs = Language::getLanguageNames( false );
765 foreach ( array_keys($langs) as $code ) {
766 # Global cache
767 $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
768 # Invalidate all local caches
769 $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
770 }
771 $this->mLoadedLanguages = array();
772 }
773
774 public function figureMessage( $key ) {
775 global $wgLanguageCode;
776 $pieces = explode( '/', $key );
777 if( count( $pieces ) < 2 )
778 return array( $key, $wgLanguageCode );
779
780 $lang = array_pop( $pieces );
781 $validCodes = Language::getLanguageNames();
782 if( !array_key_exists( $lang, $validCodes ) )
783 return array( $key, $wgLanguageCode );
784
785 $message = implode( '/', $pieces );
786 return array( $message, $lang );
787 }
788
789 public static function logMessages() {
790 global $wgAdaptiveMessageCache;
791 if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
792 return;
793 }
794
795 $cachekey = wfMemckey( 'message-profiling' );
796 $cache = wfGetCache( CACHE_DB );
797 $data = $cache->get( $cachekey );
798
799 if ( !$data ) $data = array();
800
801 $age = self::$mAdaptiveDataAge;
802 $filterDate = substr( wfTimestamp( TS_MW, time()-$age ), 0, 8 );
803 foreach ( array_keys( $data ) as $key ) {
804 if ( $key < $filterDate ) unset( $data[$key] );
805 }
806
807 $index = substr( wfTimestampNow(), 0, 8 );
808 if ( !isset( $data[$index] ) ) $data[$index] = array();
809
810 foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
811 if ( !isset( $data[$index][$message] ) ) $data[$index][$message] = 0;
812 $data[$index][$message]++;
813 }
814
815 $cache->set( $cachekey, $data );
816 }
817
818 public function getMostUsedMessages() {
819 $cachekey = wfMemckey( 'message-profiling' );
820 $cache = wfGetCache( CACHE_DB );
821 $data = $cache->get( $cachekey );
822 if ( !$data ) return array();
823
824 $list = array();
825
826 foreach( $data as $messages ) {
827 foreach( $messages as $message => $count ) {
828 $key = $message;
829 if ( !isset( $list[$key] ) ) $list[$key] = 0;
830 $list[$key] += $count;
831 }
832 }
833
834 $max = max( $list );
835 foreach ( $list as $message => $count ) {
836 if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
837 unset( $list[$message] );
838 }
839 }
840
841 return array_keys( $list );
842 }
843
844 }