[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Resource message blobs storage used by the resource loader. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @author Roan Kattouw 22 * @author Trevor Parscal 23 */ 24 25 /** 26 * This class provides access to the resource message blobs storage used by 27 * the ResourceLoader. 28 * 29 * A message blob is a JSON object containing the interface messages for a 30 * certain resource in a certain language. These message blobs are cached 31 * in the msg_resource table and automatically invalidated when one of their 32 * constituent messages or the resource itself is changed. 33 */ 34 class MessageBlobStore { 35 /** 36 * Get the singleton instance 37 * 38 * @since 1.24 39 * @return MessageBlobStore 40 */ 41 public static function getInstance() { 42 static $instance = null; 43 if ( $instance === null ) { 44 $instance = new self; 45 } 46 47 return $instance; 48 } 49 50 /** 51 * Get the message blobs for a set of modules 52 * 53 * @param ResourceLoader $resourceLoader 54 * @param array $modules Array of module objects keyed by module name 55 * @param string $lang Language code 56 * @return array An array mapping module names to message blobs 57 */ 58 public function get( ResourceLoader $resourceLoader, $modules, $lang ) { 59 wfProfileIn( __METHOD__ ); 60 if ( !count( $modules ) ) { 61 wfProfileOut( __METHOD__ ); 62 return array(); 63 } 64 // Try getting from the DB first 65 $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang ); 66 67 // Generate blobs for any missing modules and store them in the DB 68 $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) ); 69 foreach ( $missing as $name ) { 70 $blob = $this->insertMessageBlob( $name, $modules[$name], $lang ); 71 if ( $blob ) { 72 $blobs[$name] = $blob; 73 } 74 } 75 76 wfProfileOut( __METHOD__ ); 77 return $blobs; 78 } 79 80 /** 81 * Generate and insert a new message blob. If the blob was already 82 * present, it is not regenerated; instead, the preexisting blob 83 * is fetched and returned. 84 * 85 * @param string $name Module name 86 * @param ResourceLoaderModule $module 87 * @param string $lang Language code 88 * @return mixed Message blob or false if the module has no messages 89 */ 90 public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) { 91 $blob = $this->generateMessageBlob( $module, $lang ); 92 93 if ( !$blob ) { 94 return false; 95 } 96 97 try { 98 $dbw = wfGetDB( DB_MASTER ); 99 $success = $dbw->insert( 'msg_resource', array( 100 'mr_lang' => $lang, 101 'mr_resource' => $name, 102 'mr_blob' => $blob, 103 'mr_timestamp' => $dbw->timestamp() 104 ), 105 __METHOD__, 106 array( 'IGNORE' ) 107 ); 108 109 if ( $success ) { 110 if ( $dbw->affectedRows() == 0 ) { 111 // Blob was already present, fetch it 112 $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array( 113 'mr_resource' => $name, 114 'mr_lang' => $lang, 115 ), 116 __METHOD__ 117 ); 118 } else { 119 // Update msg_resource_links 120 $rows = array(); 121 122 foreach ( $module->getMessages() as $key ) { 123 $rows[] = array( 124 'mrl_resource' => $name, 125 'mrl_message' => $key 126 ); 127 } 128 $dbw->insert( 'msg_resource_links', $rows, 129 __METHOD__, array( 'IGNORE' ) 130 ); 131 } 132 } 133 } catch ( Exception $e ) { 134 wfDebug( __METHOD__ . " failed to update DB: $e\n" ); 135 } 136 return $blob; 137 } 138 139 /** 140 * Update the message blob for a given module in a given language 141 * 142 * @param string $name Module name 143 * @param ResourceLoaderModule $module 144 * @param string $lang Language code 145 * @return string Regenerated message blob, or null if there was no blob for 146 * the given module/language pair. 147 */ 148 public function updateModule( $name, ResourceLoaderModule $module, $lang ) { 149 $dbw = wfGetDB( DB_MASTER ); 150 $row = $dbw->selectRow( 'msg_resource', 'mr_blob', 151 array( 'mr_resource' => $name, 'mr_lang' => $lang ), 152 __METHOD__ 153 ); 154 if ( !$row ) { 155 return null; 156 } 157 158 // Save the old and new blobs for later 159 $oldBlob = $row->mr_blob; 160 $newBlob = $this->generateMessageBlob( $module, $lang ); 161 162 try { 163 $newRow = array( 164 'mr_resource' => $name, 165 'mr_lang' => $lang, 166 'mr_blob' => $newBlob, 167 'mr_timestamp' => $dbw->timestamp() 168 ); 169 170 $dbw->replace( 'msg_resource', 171 array( array( 'mr_resource', 'mr_lang' ) ), 172 $newRow, __METHOD__ 173 ); 174 175 // Figure out which messages were added and removed 176 $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) ); 177 $newMessages = array_keys( FormatJson::decode( $newBlob, true ) ); 178 $added = array_diff( $newMessages, $oldMessages ); 179 $removed = array_diff( $oldMessages, $newMessages ); 180 181 // Delete removed messages, insert added ones 182 if ( $removed ) { 183 $dbw->delete( 'msg_resource_links', array( 184 'mrl_resource' => $name, 185 'mrl_message' => $removed 186 ), __METHOD__ 187 ); 188 } 189 190 $newLinksRows = array(); 191 192 foreach ( $added as $message ) { 193 $newLinksRows[] = array( 194 'mrl_resource' => $name, 195 'mrl_message' => $message 196 ); 197 } 198 199 if ( $newLinksRows ) { 200 $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__, 201 array( 'IGNORE' ) // just in case 202 ); 203 } 204 } catch ( Exception $e ) { 205 wfDebug( __METHOD__ . " failed to update DB: $e\n" ); 206 } 207 return $newBlob; 208 } 209 210 /** 211 * Update a single message in all message blobs it occurs in. 212 * 213 * @param string $key Message key 214 */ 215 public function updateMessage( $key ) { 216 try { 217 $dbw = wfGetDB( DB_MASTER ); 218 219 // Keep running until the updates queue is empty. 220 // Due to update conflicts, the queue might not be emptied 221 // in one iteration. 222 $updates = null; 223 do { 224 $updates = $this->getUpdatesForMessage( $key, $updates ); 225 226 foreach ( $updates as $k => $update ) { 227 // Update the row on the condition that it 228 // didn't change since we fetched it by putting 229 // the timestamp in the WHERE clause. 230 $success = $dbw->update( 'msg_resource', 231 array( 232 'mr_blob' => $update['newBlob'], 233 'mr_timestamp' => $dbw->timestamp() ), 234 array( 235 'mr_resource' => $update['resource'], 236 'mr_lang' => $update['lang'], 237 'mr_timestamp' => $update['timestamp'] ), 238 __METHOD__ 239 ); 240 241 // Only requeue conflicted updates. 242 // If update() returned false, don't retry, for 243 // fear of getting into an infinite loop 244 if ( !( $success && $dbw->affectedRows() == 0 ) ) { 245 // Not conflicted 246 unset( $updates[$k] ); 247 } 248 } 249 } while ( count( $updates ) ); 250 251 // No need to update msg_resource_links because we didn't add 252 // or remove any messages, we just changed their contents. 253 } catch ( Exception $e ) { 254 wfDebug( __METHOD__ . " failed to update DB: $e\n" ); 255 } 256 } 257 258 public function clear() { 259 // TODO: Give this some more thought 260 try { 261 // Not using TRUNCATE, because that needs extra permissions, 262 // which maybe not granted to the database user. 263 $dbw = wfGetDB( DB_MASTER ); 264 $dbw->delete( 'msg_resource', '*', __METHOD__ ); 265 $dbw->delete( 'msg_resource_links', '*', __METHOD__ ); 266 } catch ( Exception $e ) { 267 wfDebug( __METHOD__ . " failed to update DB: $e\n" ); 268 } 269 } 270 271 /** 272 * Create an update queue for updateMessage() 273 * 274 * @param string $key Message key 275 * @param array $prevUpdates Updates queue to refresh or null to build a fresh update queue 276 * @return array Updates queue 277 */ 278 private function getUpdatesForMessage( $key, $prevUpdates = null ) { 279 $dbw = wfGetDB( DB_MASTER ); 280 281 if ( is_null( $prevUpdates ) ) { 282 // Fetch all blobs referencing $key 283 $res = $dbw->select( 284 array( 'msg_resource', 'msg_resource_links' ), 285 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ), 286 array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ), 287 __METHOD__ 288 ); 289 } else { 290 // Refetch the blobs referenced by $prevUpdates 291 292 // Reorganize the (resource, lang) pairs in the format 293 // expected by makeWhereFrom2d() 294 $twoD = array(); 295 296 foreach ( $prevUpdates as $update ) { 297 $twoD[$update['resource']][$update['lang']] = true; 298 } 299 300 $res = $dbw->select( 'msg_resource', 301 array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ), 302 $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ), 303 __METHOD__ 304 ); 305 } 306 307 // Build the new updates queue 308 $updates = array(); 309 310 foreach ( $res as $row ) { 311 $updates[] = array( 312 'resource' => $row->mr_resource, 313 'lang' => $row->mr_lang, 314 'timestamp' => $row->mr_timestamp, 315 'newBlob' => $this->reencodeBlob( $row->mr_blob, $key, $row->mr_lang ) 316 ); 317 } 318 319 return $updates; 320 } 321 322 /** 323 * Reencode a message blob with the updated value for a message 324 * 325 * @param string $blob Message blob (JSON object) 326 * @param string $key Message key 327 * @param string $lang Language code 328 * @return string Message blob with $key replaced with its new value 329 */ 330 private function reencodeBlob( $blob, $key, $lang ) { 331 $decoded = FormatJson::decode( $blob, true ); 332 $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain(); 333 334 return FormatJson::encode( (object)$decoded ); 335 } 336 337 /** 338 * Get the message blobs for a set of modules from the database. 339 * Modules whose blobs are not in the database are silently dropped. 340 * 341 * @param ResourceLoader $resourceLoader 342 * @param array $modules Array of module names 343 * @param string $lang Language code 344 * @throws MWException 345 * @return array Array mapping module names to blobs 346 */ 347 private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) { 348 $config = $resourceLoader->getConfig(); 349 $retval = array(); 350 $dbr = wfGetDB( DB_SLAVE ); 351 $res = $dbr->select( 'msg_resource', 352 array( 'mr_blob', 'mr_resource', 'mr_timestamp' ), 353 array( 'mr_resource' => $modules, 'mr_lang' => $lang ), 354 __METHOD__ 355 ); 356 357 foreach ( $res as $row ) { 358 $module = $resourceLoader->getModule( $row->mr_resource ); 359 if ( !$module ) { 360 // This shouldn't be possible 361 throw new MWException( __METHOD__ . ' passed an invalid module name' ); 362 } 363 364 // Update the module's blobs if the set of messages changed or if the blob is 365 // older than the CacheEpoch setting 366 $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) ); 367 $values = array_values( array_unique( $module->getMessages() ) ); 368 if ( $keys !== $values 369 || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' ) 370 ) { 371 $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang ); 372 } else { 373 $retval[$row->mr_resource] = $row->mr_blob; 374 } 375 } 376 377 return $retval; 378 } 379 380 /** 381 * Generate the message blob for a given module in a given language. 382 * 383 * @param ResourceLoaderModule $module 384 * @param string $lang Language code 385 * @return string JSON object 386 */ 387 private function generateMessageBlob( ResourceLoaderModule $module, $lang ) { 388 $messages = array(); 389 390 foreach ( $module->getMessages() as $key ) { 391 $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain(); 392 } 393 394 return FormatJson::encode( (object)$messages ); 395 } 396 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |