]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/MessageCache.php
MediaWiki 1.5.8 (initial commit)
[autoinstallsdev/mediawiki.git] / includes / MessageCache.php
1 <?php
2 /**
3  *
4  * @package MediaWiki
5  * @subpackage Cache
6  */
7
8 /** */
9 require_once( 'Revision.php' );
10
11 /**
12  *
13  */
14 define( 'MSG_LOAD_TIMEOUT', 60);
15 define( 'MSG_LOCK_TIMEOUT', 10);
16 define( 'MSG_WAIT_TIMEOUT', 10);
17
18 /**
19  * Message cache
20  * Performs various useful MediaWiki namespace-related functions
21  *
22  * @package MediaWiki
23  */
24 class MessageCache
25 {
26         var $mCache, $mUseCache, $mDisable, $mExpiry;
27         var $mMemcKey, $mKeys, $mParserOptions, $mParser;
28         var $mExtensionMessages = array();
29         var $mInitialised = false;
30         var $mDeferred = true;
31
32         function initialise( &$memCached, $useDB, $expiry, $memcPrefix) {
33                 $fname = 'MessageCache::initialise';
34                 wfProfileIn( $fname );
35
36                 $this->mUseCache = !is_null( $memCached );
37                 $this->mMemc = &$memCached;
38                 $this->mDisable = !$useDB;
39                 $this->mExpiry = $expiry;
40                 $this->mDisableTransform = false;
41                 $this->mMemcKey = $memcPrefix.':messages';
42                 $this->mKeys = false; # initialised on demand
43                 $this->mInitialised = true;
44
45                 wfProfileIn( $fname.'-parseropt' );
46                 $this->mParserOptions = ParserOptions::newFromUser( $u=NULL );
47                 wfProfileOut( $fname.'-parseropt' );
48                 wfProfileIn( $fname.'-parser' );
49                 $this->mParser = new Parser;
50                 wfProfileOut( $fname.'-parser' );
51
52                 # When we first get asked for a message,
53                 # then we'll fill up the cache. If we
54                 # can return a cache hit, this saves
55                 # some extra milliseconds
56                 $this->mDeferred = true;
57
58                 wfProfileOut( $fname );
59         }
60
61         /**
62          * Loads messages either from memcached or the database, if not disabled
63          * On error, quietly switches to a fallback mode
64          * Returns false for a reportable error, true otherwise
65          */
66         function load() {
67                 global $wgAllMessagesEn;
68
69                 if ( $this->mDisable ) {
70                         static $shownDisabled = false;
71                         if ( !$shownDisabled ) {
72                                 wfDebug( "MessageCache::load(): disabled\n" );
73                                 $shownDisabled = true;
74                         }
75                         return true;
76                 }
77                 $fname = 'MessageCache::load';
78                 wfProfileIn( $fname );
79                 $success = true;
80
81                 if ( $this->mUseCache ) {
82                         wfProfileIn( $fname.'-fromcache' );
83                         $this->mCache = $this->mMemc->get( $this->mMemcKey );
84                         wfProfileOut( $fname.'-fromcache' );
85
86                         # If there's nothing in memcached, load all the messages from the database
87                         if ( !$this->mCache ) {
88                                 wfDebug( "MessageCache::load(): loading all messages\n" );
89                                 $this->lock();
90                                 # Other threads don't need to load the messages if another thread is doing it.
91                                 $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT );
92                                 if ( $success ) {
93                                         wfProfileIn( $fname.'-load' );
94                                         $this->loadFromDB();
95                                         wfProfileOut( $fname.'-load' );
96                                         # Save in memcached
97                                         # Keep trying if it fails, this is kind of important
98                                         wfProfileIn( $fname.'-save' );
99                                         for ($i=0; $i<20 &&
100                                                    !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
101                                              $i++ ) {
102                                                 usleep(mt_rand(500000,1500000));
103                                         }
104                                         wfProfileOut( $fname.'-save' );
105                                         if ( $i == 20 ) {
106                                                 $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 );
107                                                 wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
108                                         }
109                                 }
110                                 $this->unlock();
111                         }
112
113                         if ( !is_array( $this->mCache ) ) {
114                                 wfDebug( "MessageCache::load(): individual message mode\n" );
115                                 # If it is 'loading' or 'error', switch to individual message mode, otherwise disable
116                                 # Causing too much DB load, disabling -- TS
117                                 $this->mDisable = true;
118                                 /*
119                                 if ( $this->mCache == "loading" ) {
120                                         $this->mUseCache = false;
121                                 } elseif ( $this->mCache == "error" ) {
122                                         $this->mUseCache = false;
123                                         $success = false;
124                                 } else {
125                                         $this->mDisable = true;
126                                         $success = false;
127                                 }*/
128                                 $this->mCache = false;
129                         }
130                 }
131                 wfProfileOut( $fname );
132                 $this->mDeferred = false;
133                 return $success;
134         }
135
136         /**
137          * Loads all or main part of cacheable messages from the database
138          */
139         function loadFromDB() {
140                 $fname = 'MessageCache::loadFromDB';
141                 $dbr =& wfGetDB( DB_SLAVE );
142                 $conditions = array( 'page_is_redirect' => 0,
143                                         'page_namespace' => NS_MEDIAWIKI);
144                 $res = $dbr->select( array( 'page', 'revision', 'text' ),
145                         array( 'page_title', 'old_text', 'old_flags' ),
146                         'page_is_redirect=0 AND page_namespace='.NS_MEDIAWIKI.' AND page_latest=rev_id AND rev_text_id=old_id',
147                         $fname
148                 );
149
150                 $this->mCache = array();
151                 for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) {
152                         $this->mCache[$row->page_title] = Revision::getRevisionText( $row );
153                 }
154
155                 $dbr->freeResult( $res );
156                 /*
157                 # FIXME: This is too slow currently.
158                 # We need to bulk-fetch revisions, but in a portable way...
159                 $resultSet = Revision::fetchFromConds( $dbr, array(
160                         'page_namespace'   => NS_MEDIAWIKI,
161                         'page_is_redirect' => 0,
162                         'page_id=rev_page' ) );
163                 while( $row = $resultSet->fetchObject() ) {
164                         $revision = new Revision( $row );
165                         $title = $revision->getTitle();
166                         $this->mCache[$title->getDBkey()] = $revision->getText();
167                 }
168                 $resultSet->free();
169                 */
170         }
171
172         /**
173          * Not really needed anymore
174          */
175         function getKeys() {
176                 global $wgAllMessagesEn, $wgContLang;
177                 if ( !$this->mKeys ) {
178                         $this->mKeys = array();
179                         foreach ( $wgAllMessagesEn as $key => $value ) {
180                                 $title = $wgContLang->ucfirst( $key );
181                                 array_push( $this->mKeys, $title );
182                         }
183                 }
184                 return $this->mKeys;
185         }
186
187         /**
188          * @deprecated
189          */
190         function isCacheable( $key ) {
191                 return true;
192         }
193
194         function replace( $title, $text ) {
195                 $this->lock();
196                 $this->load();
197                 if ( is_array( $this->mCache ) ) {
198                         $this->mCache[$title] = $text;
199                         $this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
200                 }
201                 $this->unlock();
202         }
203
204         /**
205          * Returns success
206          * Represents a write lock on the messages key
207          */
208         function lock() {
209                 if ( !$this->mUseCache ) {
210                         return true;
211                 }
212
213                 $lockKey = $this->mMemcKey . 'lock';
214                 for ($i=0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
215                         sleep(1);
216                 }
217
218                 return $i >= MSG_WAIT_TIMEOUT;
219         }
220
221         function unlock() {
222                 if ( !$this->mUseCache ) {
223                         return;
224                 }
225
226                 $lockKey = $this->mMemcKey . 'lock';
227                 $this->mMemc->delete( $lockKey );
228         }
229
230         function get( $key, $useDB, $forcontent=true, $isfullkey = false ) {
231                 global $wgContLanguageCode;
232                 if( $forcontent ) {
233                         global $wgContLang;
234                         $lang =& $wgContLang;
235                         $langcode = $wgContLanguageCode;
236                 } else {
237                         global $wgLang, $wgLanguageCode;
238                         $lang =& $wgLang;
239                         $langcode = $wgLanguageCode;
240                 }
241                 # If uninitialised, someone is trying to call this halfway through Setup.php
242                 if( !$this->mInitialised ) {
243                         return '&lt;' . htmlspecialchars($key) . '&gt;';
244                 }
245                 # If cache initialization was deferred, start it now.
246                 if( $this->mDeferred ) {
247                         $this->load();
248                 }
249
250                 $message = false;
251                 if( !$this->mDisable && $useDB ) {
252                         $title = $lang->ucfirst( $key );
253                         if(!$isfullkey && ($langcode != $wgContLanguageCode) ) {
254                                 $title .= '/' . $langcode;
255                         }
256                         $message = $this->getFromCache( $title );
257                 }
258                 # Try the extension array
259                 if( !$message ) {
260                         $message = @$this->mExtensionMessages[$key];
261                 }
262
263                 # Try the array in the language object
264                 if( !$message ) {
265                         wfSuppressWarnings();
266                         $message = $lang->getMessage( $key );
267                         wfRestoreWarnings();
268                 }
269
270                 # Try the English array
271                 if( !$message && $langcode != 'en' ) {
272                         wfSuppressWarnings();
273                         $message = Language::getMessage( $key );
274                         wfRestoreWarnings();
275                 }
276
277                 # Is this a custom message? Try the default language in the db...
278                 if( !$message &&
279                         !$this->mDisable && $useDB &&
280                         !$isfullkey && ($langcode != $wgContLanguageCode) ) {
281                         $message = $this->getFromCache( $lang->ucfirst( $key ) );
282                 }
283
284                 # Final fallback
285                 if( !$message ) {
286                         return '&lt;' . htmlspecialchars($key) . '&gt;';
287                 }
288
289                 # Replace brace tags
290                 $message = $this->transform( $message );
291                 return $message;
292         }
293
294         function getFromCache( $title ) {
295                 $message = false;
296
297                 # Try the cache
298                 if( $this->mUseCache && is_array( $this->mCache ) && array_key_exists( $title, $this->mCache ) ) {
299                         $message = $this->mCache[$title];
300                 }
301
302                 if ( !$message && $this->mUseCache ) {
303                         $message = $this->mMemc->get( $this->mMemcKey . ':' . $title );
304                         if( $message ) {
305                                 $this->mCache[$title] = $message;
306                         }
307                 }
308
309                 # Call message Hooks, in case they are defined
310                 wfRunHooks('MessagesPreLoad',array($title,&$message));
311
312                 # If it wasn't in the cache, load each message from the DB individually
313                 if ( !$message ) {
314                         $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
315                         if( $revision ) {
316                                 $message = $revision->getText();
317                                 if ($this->mUseCache) {
318                                         $this->mCache[$title]=$message;
319                                         /* individual messages may be often
320                                            recached until proper purge code exists
321                                         */
322                                         $this->mMemc->set( $this->mMemcKey . ':' . $title, $message, 300 );
323                                 }
324                         }
325                 }
326
327                 return $message;
328         }
329
330         function transform( $message ) {
331                 if( !$this->mDisableTransform ) {
332                         if( strpos( $message, '{{' ) !== false ) {
333                                 $message = $this->mParser->transformMsg( $message, $this->mParserOptions );
334                         }
335                 }
336                 return $message;
337         }
338
339         function disable() { $this->mDisable = true; }
340         function enable() { $this->mDisable = false; }
341         function disableTransform() { $this->mDisableTransform = true; }
342         function enableTransform() { $this->mDisableTransform = false; }
343
344         /**
345          * Add a message to the cache
346          *
347          * @param mixed $key
348          * @param mixed $value
349          */
350         function addMessage( $key, $value ) {
351                 $this->mExtensionMessages[$key] = $value;
352         }
353
354         /**
355          * Add an associative array of message to the cache
356          *
357          * @param array $messages An associative array of key => values to be added
358          */
359         function addMessages( $messages ) {
360                 foreach ( $messages as $key => $value ) {
361                         $this->addMessage( $key, $value );
362                 }
363         }
364
365         /**
366          * Clear all stored messages. Mainly used after a mass rebuild.
367          */
368         function clear() {
369                 if( $this->mUseCache ) {
370                         $this->mMemc->delete( $this->mMemcKey );
371                 }
372         }
373 }
374 ?>