Index: branches/resourceloader/phase3/maintenance/archives/patch-msg_resource.sql |
— | — | @@ -0,0 +1,22 @@ |
| 2 | +-- Table for storing JSON message blobs for the resource loader |
| 3 | +CREATE TABLE /*_*/msg_resource ( |
| 4 | + -- Resource name |
| 5 | + mr_resource varchar(255) NOT NULL, |
| 6 | + -- Language code |
| 7 | + mr_lang varbinary(32) NOT NULL, |
| 8 | + -- JSON blob. This is an incomplete JSON object, i.e. without the wrapping {} |
| 9 | + mr_blob mediumblob NOT NULL, |
| 10 | + -- Timestamp of last update |
| 11 | + mr_timestamp binary(14) NOT NULL |
| 12 | +) /*$wgDBTableOptions*/; |
| 13 | +CREATE UNIQUE INDEX /*i*/mr_resource_lang(mr_resource, mr_lang); |
| 14 | + |
| 15 | +-- Table for administering which message is contained in which resource |
| 16 | +CREATE TABLE /*_*/msg_resource_links ( |
| 17 | + mrl_resource varchar(255) NOT NULL, |
| 18 | + -- Language code |
| 19 | + mrl_lang varbinary(32) NOT NULL, |
| 20 | + -- Message key |
| 21 | + mrl_message varchar(255) NOT NULL |
| 22 | +) /*$wgDBTableOptions*/; |
| 23 | +CREATE UNIQUE INDEX /*i*/mrl_lang_message_resource(mrl_lang, mrl_message, mrl_resource); |
Index: branches/resourceloader/phase3/maintenance/updaters.inc |
— | — | @@ -176,6 +176,7 @@ |
177 | 177 | // 1.17 |
178 | 178 | array( 'add_table', 'iwlinks', 'patch-iwlinks.sql' ), |
179 | 179 | array( 'add_index', 'iwlinks', 'iwl_prefix_from_title', 'patch-rename-iwl_prefix.sql' ), |
| 180 | + array( 'add_table', 'msg_resource', 'patch-msg_resource.sql' ), |
180 | 181 | ), |
181 | 182 | |
182 | 183 | 'sqlite' => array( |
Index: branches/resourceloader/phase3/maintenance/tables.sql |
— | — | @@ -1369,4 +1369,27 @@ |
1370 | 1370 | ) /*$wgDBTableOptions*/; |
1371 | 1371 | CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); |
1372 | 1372 | |
| 1373 | +-- Table for storing JSON message blobs for the resource loader |
| 1374 | +CREATE TABLE /*_*/msg_resource ( |
| 1375 | + -- Resource name |
| 1376 | + mr_resource varchar(255) NOT NULL, |
| 1377 | + -- Language code |
| 1378 | + mr_lang varbinary(32) NOT NULL, |
| 1379 | + -- JSON blob. This is an incomplete JSON object, i.e. without the wrapping {} |
| 1380 | + mr_blob mediumblob NOT NULL, |
| 1381 | + -- Timestamp of last update |
| 1382 | + mr_timestamp binary(14) NOT NULL |
| 1383 | +) /*$wgDBTableOptions*/; |
| 1384 | +CREATE UNIQUE INDEX /*i*/mr_lang_resource(mr_lang, mr_resource); |
| 1385 | + |
| 1386 | +-- Table for administering which message is contained in which resource |
| 1387 | +CREATE TABLE /*_*/msg_resource_links ( |
| 1388 | + mrl_resource varchar(255) NOT NULL, |
| 1389 | + -- Language code |
| 1390 | + mrl_lang varbinary(32) NOT NULL, |
| 1391 | + -- Message key |
| 1392 | + mrl_message varchar(255) NOT NULL |
| 1393 | +) /*$wgDBTableOptions*/; |
| 1394 | +CREATE UNIQUE INDEX /*i*/mrl_lang_message_resource(mrl_lang, mrl_message, mrl_resource); |
| 1395 | + |
1373 | 1396 | -- vim: sw=2 sts=2 et |
Index: branches/resourceloader/phase3/includes/ResourceLoader.php |
— | — | @@ -50,7 +50,13 @@ |
51 | 51 | private $useCSSMin = true; |
52 | 52 | private $useCSSJanus = true; |
53 | 53 | |
| 54 | + private $lang; |
54 | 55 | |
| 56 | + public function __construct( $lang ) { |
| 57 | + $this->lang = $lang; |
| 58 | + } |
| 59 | + |
| 60 | + |
55 | 61 | /** |
56 | 62 | * Add a module to the output. This includes the module's |
57 | 63 | * JS itself, its style and its messages. |
— | — | @@ -95,9 +101,16 @@ |
96 | 102 | } |
97 | 103 | |
98 | 104 | private function getMessagesJS( $modules ) { |
99 | | - return "mw.addMessages( {\n" . |
100 | | - implode( ",\n", array_map( array( 'MessageBlobStore', 'get' ), $modules ) ) . |
101 | | - "\n} );"; |
| 105 | + $blobs = array(); |
| 106 | + $dbr = wfGetDB( DB_SLAVE ); |
| 107 | + $res = $dbr->select( 'msg_resource', 'msg_blob', |
| 108 | + array( 'msg_resource' => $modules, 'msg_lang' => $this->lang ), |
| 109 | + __METHOD__ |
| 110 | + ); |
| 111 | + foreach ( $res as $row ) { |
| 112 | + $blobs[] = $row->msg_blob; |
| 113 | + } |
| 114 | + return "mw.addMessages( {\n" . implode( ",\n", $blobs ) . "\n} );"; |
102 | 115 | } |
103 | 116 | |
104 | 117 | public function getOutput() { |
— | — | @@ -152,12 +165,63 @@ |
153 | 166 | |
154 | 167 | class MessageBlobStore { |
155 | 168 | /** |
156 | | - * Get the message blob for a module |
157 | | - * @param $module string Module name |
158 | | - * @return string An incomplete JSON object (i.e. without the {} ) with messages keys and their values. |
| 169 | + * Get the message blobs for a set of modules |
| 170 | + * @param $lang string Language code |
| 171 | + * @param $modules array Array of module names |
| 172 | + * @return array An array of incomplete JSON objects (i.e. without the {} ) with messages keys and their values. |
159 | 173 | */ |
160 | | - public static function get( $module ) { |
161 | | - // TODO: Implement |
162 | | - return ''; |
| 174 | + public static function get( $lang, $modules ) { |
| 175 | + // Try getting from the DB first |
| 176 | + $blobs = self::getFromDB( $lang, $modules ); |
| 177 | + |
| 178 | + // Generate blobs for any missing modules and store them in the DB |
| 179 | + $missing = array_diff( $modules, array_keys( $blobs ) ); |
| 180 | + foreach ( $missing as $module ) { |
| 181 | + $blob = self::generateMessageBlob( $lang, $module ); |
| 182 | + if ( $blob ) { |
| 183 | + self::set( $lang, $module, $blob ); |
| 184 | + $blobs[$module] = $blob; |
| 185 | + } |
| 186 | + } |
| 187 | + return implode( ",\n", $blobs ); |
163 | 188 | } |
| 189 | + |
| 190 | + public static function set( $lang, $module, $blob ) { |
| 191 | + $dbw = wfGetDb( DB_MASTER ); |
| 192 | + // TODO: Timestamp stuff to handle concurrency |
| 193 | + $dbw->replace( 'msg_resource', array( array( 'mr_lang', 'mr_resource' ) ), |
| 194 | + array( array( |
| 195 | + 'mr_lang' => $lang, |
| 196 | + 'mr_module' => $module, |
| 197 | + 'mr_blob' => $blob, |
| 198 | + 'mr_timestamp' => wfTimestampNow(), |
| 199 | + ) ) |
| 200 | + ); |
| 201 | + } |
| 202 | + |
| 203 | + private static function getFromDB( $lang, $modules ) { |
| 204 | + $retval = array(); |
| 205 | + $dbr = wfGetDB( DB_SLAVE ); |
| 206 | + $res = $dbr->select( 'msg_resource', array( 'mr_blob', 'mr_resource' ), |
| 207 | + array( 'mr_resource' => $modules, 'mr_lang' => $this->lang ), |
| 208 | + __METHOD__ |
| 209 | + ); |
| 210 | + foreach ( $res as $row ) { |
| 211 | + $retval[$row->mr_resource] = $row->mr_blob; |
| 212 | + } |
| 213 | + return $retval; |
| 214 | + } |
| 215 | + |
| 216 | + private static function generateMessageBlob( $lang, $module ) { |
| 217 | + if ( !isset ( ResourceLoader::$modules[$module]['messages'] ) ) { |
| 218 | + return false; |
| 219 | + } |
| 220 | + $messages = array(); |
| 221 | + foreach ( ResourceLoader::$modules[$module]['messages'] as $key ) { |
| 222 | + $encKey = Xml::escapeJsString( $key ); |
| 223 | + $encValue = Xml::escapeJsString( wfMsg( $key ) ); // TODO: Use something rawer than wfMsg()? |
| 224 | + $messages[] = "'$encKey': '$encValue'"; |
| 225 | + } |
| 226 | + return implode( ",", $messages ); |
| 227 | + } |
164 | 228 | } |
\ No newline at end of file |
Index: branches/resourceloader/phase3/load.php |
— | — | @@ -32,11 +32,10 @@ |
33 | 33 | // |
34 | 34 | // See RawPage.php for details; summary is that MSIE can override the |
35 | 35 | // Content-Type if it sees a recognized extension on the URL, such as |
36 | | -// might be appended via PATH_INFO after 'api.php'. |
| 36 | +// might be appended via PATH_INFO after 'load.php'. |
37 | 37 | // |
38 | | -// Some data formats can end up containing unfiltered user-provided data |
39 | | -// which will end up triggering HTML detection and execution, hence |
40 | | -// XSS injection and all that entails. |
| 38 | +// Some resources can contain HTML-like strings (e.g. in messages) |
| 39 | +// which will end up triggering HTML detection and execution. |
41 | 40 | // |
42 | 41 | if ( $wgRequest->isPathInfoBad() ) { |
43 | 42 | wfHttpError( 403, 'Forbidden', |
— | — | @@ -47,7 +46,7 @@ |
48 | 47 | // Was taken from api.php so I guess it's maybe OK but it doesn't look good. |
49 | 48 | } |
50 | 49 | |
51 | | -$loader = new ResourceLoader(); |
| 50 | +$loader = new ResourceLoader( $wgRequest->getVal( 'lang', 'en' ) ); |
52 | 51 | $moduleParam = $wgRequest->getVal( 'modules' ); |
53 | 52 | $modules = $moduleParam ? explode( '|', $moduleParam ) : array(); |
54 | 53 | foreach ( $modules as $module ) { |
— | — | @@ -55,6 +54,7 @@ |
56 | 55 | } |
57 | 56 | |
58 | 57 | // TODO: Cache-Control header |
| 58 | +header( 'Content-Type', 'text/javascript' ); |
59 | 59 | echo $loader->getOutput(); |
60 | 60 | |
61 | 61 | wfProfileOut( 'loader.php' ); |