Index: trunk/phase3/maintenance/tests/ResourceLoaderTest.php |
— | — | @@ -1,16 +1,15 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | class ResourceLoaderTest extends PHPUnit_Framework_TestCase { |
5 | | - |
6 | 5 | /* Provider Methods */ |
7 | | - |
| 6 | + |
8 | 7 | public function provide() { |
9 | | - |
| 8 | + |
10 | 9 | } |
11 | | - |
| 10 | + |
12 | 11 | /* Test Methods */ |
13 | | - |
| 12 | + |
14 | 13 | public function test() { |
15 | | - |
| 14 | + |
16 | 15 | } |
17 | | -} |
\ No newline at end of file |
| 16 | +} |
Index: trunk/phase3/maintenance/tests/ResourceLoaderFileModuleTest.php |
— | — | @@ -1,16 +1,15 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | class ResourceLoaderFileModuleTest extends PHPUnit_Framework_TestCase { |
5 | | - |
6 | 5 | /* Provider Methods */ |
7 | | - |
| 6 | + |
8 | 7 | public function provide() { |
9 | | - |
| 8 | + |
10 | 9 | } |
11 | | - |
| 10 | + |
12 | 11 | /* Test Methods */ |
13 | | - |
| 12 | + |
14 | 13 | public function test() { |
15 | | - |
| 14 | + |
16 | 15 | } |
17 | | -} |
\ No newline at end of file |
| 16 | +} |
Index: trunk/phase3/includes/CSSMin.php |
— | — | @@ -1,22 +1,21 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | class CSSMin { |
5 | | - |
6 | 5 | /* Constants */ |
7 | | - |
| 6 | + |
8 | 7 | /** |
9 | 8 | * Maximum file size to still qualify for in-line embedding as a data-URI |
10 | | - * |
| 9 | + * |
11 | 10 | * 24,576 is used because Internet Explorer has a 32,768 byte limit for data URIs, which when base64 encoded will |
12 | 11 | * result in a 1/3 increase in size. |
13 | 12 | */ |
14 | 13 | const EMBED_SIZE_LIMIT = 24576; |
15 | | - |
| 14 | + |
16 | 15 | /* Static Methods */ |
17 | | - |
| 16 | + |
18 | 17 | /** |
19 | 18 | * Gets a list of local file paths which are referenced in a CSS style sheet |
20 | | - * |
| 19 | + * |
21 | 20 | * @param $source string CSS data to remap |
22 | 21 | * @param $path string File path where the source was read from (optional) |
23 | 22 | * @return array List of local file references |
— | — | @@ -24,39 +23,46 @@ |
25 | 24 | public static function getLocalFileReferences( $source, $path = null ) { |
26 | 25 | $pattern = '/url\([\'"]?(?<file>[^\?\)\:]*)\??[^\)]*[\'"]?\)/'; |
27 | 26 | $files = array(); |
| 27 | + |
28 | 28 | if ( preg_match_all( $pattern, $source, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER ) ) { |
29 | 29 | foreach ( $matches as $match ) { |
30 | 30 | $file = ( isset( $path ) ? rtrim( $path, '/' ) . '/' : '' ) . "{$match['file'][0]}"; |
| 31 | + |
31 | 32 | // Only proceed if we can access the file |
32 | 33 | if ( file_exists( $file ) ) { |
33 | 34 | $files[] = $file; |
34 | 35 | } |
35 | 36 | } |
36 | 37 | } |
| 38 | + |
37 | 39 | return $files; |
38 | 40 | } |
39 | | - |
| 41 | + |
40 | 42 | /** |
41 | 43 | * Remaps CSS URL paths and automatically embeds data URIs for URL rules preceded by an /* @embed * / comment |
42 | | - * |
| 44 | + * |
43 | 45 | * @param $source string CSS data to remap |
44 | 46 | * @param $path string File path where the source was read from |
45 | 47 | * @return string Remapped CSS data |
46 | 48 | */ |
47 | 49 | public static function remap( $source, $path ) { |
48 | 50 | global $wgUseDataURLs; |
| 51 | + |
49 | 52 | $pattern = '/((?<embed>\s*\/\*\s*\@embed\s*\*\/)(?<rule>[^\;\}]*))?url\([\'"]?(?<file>[^\?\)\:]*)\??[^\)]*[\'"]?\)(?<extra>[^;]*)[\;]?/'; |
50 | 53 | $offset = 0; |
| 54 | + |
51 | 55 | while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) { |
52 | 56 | // Shortcuts |
53 | 57 | $embed = $match['embed'][0]; |
54 | 58 | $rule = $match['rule'][0]; |
55 | 59 | $extra = $match['extra'][0]; |
56 | 60 | $file = "{$path}/{$match['file'][0]}"; |
| 61 | + |
57 | 62 | // Only proceed if we can access the file |
58 | 63 | if ( file_exists( $file ) ) { |
59 | 64 | // Add version parameter as a time-stamp in ISO 8601 format, using Z for the timezone, meaning GMT |
60 | 65 | $url = "{$file}?" . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) ); |
| 66 | + |
61 | 67 | // Detect when URLs were preceeded with embed tags, and also verify file size is below the limit |
62 | 68 | if ( $wgUseDataURLs && $match['embed'][1] > 0 && filesize( $file ) < self::EMBED_SIZE_LIMIT ) { |
63 | 69 | // If we ever get to PHP 5.3, we should use the Fileinfo extension instead of mime_content_type |
— | — | @@ -71,6 +77,7 @@ |
72 | 78 | // Build a CSS property with a remapped and versioned URL |
73 | 79 | $replacement = "{$embed}{$rule}url({$url}){$extra};"; |
74 | 80 | } |
| 81 | + |
75 | 82 | // Perform replacement on the source |
76 | 83 | $source = substr_replace( $source, $replacement, $match[0][1], strlen( $match[0][0] ) ); |
77 | 84 | // Move the offset to the end of the replacement in the source |
— | — | @@ -80,14 +87,15 @@ |
81 | 88 | // Move the offset to the end of the match, leaving it alone |
82 | 89 | $offset = $match[0][1] + strlen( $match[0][0] ); |
83 | 90 | } |
| 91 | + |
84 | 92 | return $source; |
85 | 93 | } |
86 | | - |
| 94 | + |
87 | 95 | /** |
88 | 96 | * Removes whitespace from CSS data |
89 | | - * |
| 97 | + * |
90 | 98 | * @param $source string CSS data to minify |
91 | | - * @return string Minified CSS data |
| 99 | + * @return string Minified CSS data |
92 | 100 | */ |
93 | 101 | public static function minify( $css ) { |
94 | 102 | return trim( |
— | — | @@ -98,4 +106,4 @@ |
99 | 107 | ) |
100 | 108 | ); |
101 | 109 | } |
102 | | -} |
\ No newline at end of file |
| 110 | +} |
Index: trunk/phase3/includes/ResourceLoaderContext.php |
— | — | @@ -23,9 +23,8 @@ |
24 | 24 | * Object passed around to modules which contains information about the state of a specific loader request |
25 | 25 | */ |
26 | 26 | class ResourceLoaderContext { |
27 | | - |
28 | 27 | /* Protected Members */ |
29 | | - |
| 28 | + |
30 | 29 | protected $request; |
31 | 30 | protected $server; |
32 | 31 | protected $modules; |
— | — | @@ -35,12 +34,12 @@ |
36 | 35 | protected $debug; |
37 | 36 | protected $only; |
38 | 37 | protected $hash; |
39 | | - |
| 38 | + |
40 | 39 | /* Methods */ |
41 | | - |
| 40 | + |
42 | 41 | public function __construct( WebRequest $request, $server ) { |
43 | 42 | global $wgUser, $wgLang, $wgDefaultSkin; |
44 | | - |
| 43 | + |
45 | 44 | $this->request = $request; |
46 | 45 | $this->server = $server; |
47 | 46 | // Interperet request |
— | — | @@ -50,30 +49,33 @@ |
51 | 50 | $this->skin = $request->getVal( 'skin' ); |
52 | 51 | $this->debug = $request->getVal( 'debug' ) === 'true' || $request->getBool( 'debug' ); |
53 | 52 | $this->only = $request->getVal( 'only' ); |
| 53 | + |
54 | 54 | // Fallback on system defaults |
55 | 55 | if ( !$this->language ) { |
56 | 56 | $this->language = $wgLang->getCode(); |
57 | 57 | } |
| 58 | + |
58 | 59 | if ( !$this->direction ) { |
59 | 60 | $this->direction = Language::factory( $this->language )->getDir(); |
60 | 61 | } |
| 62 | + |
61 | 63 | if ( !$this->skin ) { |
62 | 64 | $this->skin = $wgDefaultSkin; |
63 | 65 | } |
64 | 66 | } |
65 | | - |
| 67 | + |
66 | 68 | public function getRequest() { |
67 | 69 | return $this->request; |
68 | 70 | } |
69 | | - |
| 71 | + |
70 | 72 | public function getServer() { |
71 | 73 | return $this->server; |
72 | 74 | } |
73 | | - |
| 75 | + |
74 | 76 | public function getModules() { |
75 | 77 | return $this->modules; |
76 | 78 | } |
77 | | - |
| 79 | + |
78 | 80 | public function getLanguage() { |
79 | 81 | return $this->language; |
80 | 82 | } |
— | — | @@ -81,33 +83,33 @@ |
82 | 84 | public function getDirection() { |
83 | 85 | return $this->direction; |
84 | 86 | } |
85 | | - |
| 87 | + |
86 | 88 | public function getSkin() { |
87 | 89 | return $this->skin; |
88 | 90 | } |
89 | | - |
| 91 | + |
90 | 92 | public function getDebug() { |
91 | 93 | return $this->debug; |
92 | 94 | } |
93 | | - |
| 95 | + |
94 | 96 | public function getOnly() { |
95 | 97 | return $this->only; |
96 | 98 | } |
97 | | - |
| 99 | + |
98 | 100 | public function shouldIncludeScripts() { |
99 | 101 | return is_null( $this->only ) || $this->only === 'scripts'; |
100 | 102 | } |
101 | | - |
| 103 | + |
102 | 104 | public function shouldIncludeStyles() { |
103 | 105 | return is_null( $this->only ) || $this->only === 'styles'; |
104 | 106 | } |
105 | | - |
| 107 | + |
106 | 108 | public function shouldIncludeMessages() { |
107 | 109 | return is_null( $this->only ) || $this->only === 'messages'; |
108 | 110 | } |
109 | | - |
| 111 | + |
110 | 112 | public function getHash() { |
111 | 113 | return isset( $this->hash ) ? |
112 | 114 | $this->hash : $this->hash = implode( '|', array( $this->language, $this->skin, $this->debug ) ); |
113 | 115 | } |
114 | | -} |
\ No newline at end of file |
| 116 | +} |
Index: trunk/phase3/includes/ResourceLoader.php |
— | — | @@ -61,14 +61,13 @@ |
62 | 62 | * ResourceLoader::respond( $wgRequest, $wgServer . $wgScriptPath . '/load.php' ); |
63 | 63 | */ |
64 | 64 | class ResourceLoader { |
65 | | - |
66 | 65 | /* Protected Static Members */ |
67 | | - |
| 66 | + |
68 | 67 | // @var array list of module name/ResourceLoaderModule object pairs |
69 | 68 | protected static $modules = array(); |
70 | | - |
| 69 | + |
71 | 70 | /* Protected Static Methods */ |
72 | | - |
| 71 | + |
73 | 72 | /** |
74 | 73 | * Runs text through a filter, caching the filtered result for future calls |
75 | 74 | * |
— | — | @@ -79,18 +78,20 @@ |
80 | 79 | */ |
81 | 80 | protected static function filter( $filter, $data ) { |
82 | 81 | global $wgMemc; |
| 82 | + |
83 | 83 | // For empty or whitespace-only things, don't do any processing |
84 | 84 | if ( trim( $data ) === '' ) { |
85 | 85 | return $data; |
86 | 86 | } |
87 | | - |
| 87 | + |
88 | 88 | // Try memcached |
89 | 89 | $key = wfMemcKey( 'resourceloader', 'filter', $filter, md5( $data ) ); |
90 | 90 | $cached = $wgMemc->get( $key ); |
| 91 | + |
91 | 92 | if ( $cached !== false && $cached !== null ) { |
92 | 93 | return $cached; |
93 | 94 | } |
94 | | - |
| 95 | + |
95 | 96 | // Run the filter |
96 | 97 | try { |
97 | 98 | switch ( $filter ) { |
— | — | @@ -110,24 +111,25 @@ |
111 | 112 | } catch ( Exception $exception ) { |
112 | 113 | throw new MWException( 'Filter threw an exception: ' . $exception->getMessage() ); |
113 | 114 | } |
114 | | - |
| 115 | + |
115 | 116 | // Save to memcached |
116 | 117 | $wgMemc->set( $key, $result ); |
| 118 | + |
117 | 119 | return $result; |
118 | 120 | } |
119 | | - |
| 121 | + |
120 | 122 | /* Static Methods */ |
121 | | - |
| 123 | + |
122 | 124 | /** |
123 | 125 | * Registers a module with the ResourceLoader system. |
124 | 126 | * |
125 | 127 | * Note that registering the same object under multiple names is not supported and may silently fail in all |
126 | 128 | * kinds of interesting ways. |
127 | | - * |
| 129 | + * |
128 | 130 | * @param {mixed} $name string of name of module or array of name/object pairs |
129 | 131 | * @param {ResourceLoaderModule} $object module object (optional when using multiple-registration calling style) |
130 | 132 | * @return {boolean} false if there were any errors, in which case one or more modules were not registered |
131 | | - * |
| 133 | + * |
132 | 134 | * @todo We need much more clever error reporting, not just in detailing what happened, but in bringing errors to |
133 | 135 | * the client in a way that they can easily see them if they want to, such as by using FireBug |
134 | 136 | */ |
— | — | @@ -137,8 +139,10 @@ |
138 | 140 | foreach ( $name as $key => $value ) { |
139 | 141 | self::register( $key, $value ); |
140 | 142 | } |
| 143 | + |
141 | 144 | return; |
142 | 145 | } |
| 146 | + |
143 | 147 | // Disallow duplicate registrations |
144 | 148 | if ( isset( self::$modules[$name] ) ) { |
145 | 149 | // A module has already been registered by this name |
— | — | @@ -148,7 +152,7 @@ |
149 | 153 | self::$modules[$name] = $object; |
150 | 154 | $object->setName( $name ); |
151 | 155 | } |
152 | | - |
| 156 | + |
153 | 157 | /** |
154 | 158 | * Gets a map of all modules and their options |
155 | 159 | * |
— | — | @@ -157,7 +161,7 @@ |
158 | 162 | public static function getModules() { |
159 | 163 | return self::$modules; |
160 | 164 | } |
161 | | - |
| 165 | + |
162 | 166 | /** |
163 | 167 | * Get the ResourceLoaderModule object for a given module name |
164 | 168 | * @param $name string Module name |
— | — | @@ -166,7 +170,7 @@ |
167 | 171 | public static function getModule( $name ) { |
168 | 172 | return isset( self::$modules[$name] ) ? self::$modules[$name] : null; |
169 | 173 | } |
170 | | - |
| 174 | + |
171 | 175 | /** |
172 | 176 | * Gets registration code for all modules, except pre-registered ones listed in self::$preRegisteredModules |
173 | 177 | * |
— | — | @@ -180,6 +184,7 @@ |
181 | 185 | public static function getModuleRegistrations( ResourceLoaderContext $context ) { |
182 | 186 | $scripts = ''; |
183 | 187 | $registrations = array(); |
| 188 | + |
184 | 189 | foreach ( self::$modules as $name => $module ) { |
185 | 190 | // Support module loader scripts |
186 | 191 | if ( ( $loader = $module->getLoaderScript() ) !== false ) { |
— | — | @@ -199,7 +204,7 @@ |
200 | 205 | } |
201 | 206 | return $scripts . "mediaWiki.loader.register( " . FormatJson::encode( $registrations ) . " );"; |
202 | 207 | } |
203 | | - |
| 208 | + |
204 | 209 | /** |
205 | 210 | * Get the highest modification time of all modules, based on a given combination of language code, |
206 | 211 | * skin name and debug mode flag. |
— | — | @@ -210,14 +215,16 @@ |
211 | 216 | */ |
212 | 217 | public static function getHighestModifiedTime( ResourceLoaderContext $context ) { |
213 | 218 | $time = 1; // wfTimestamp() treats 0 as 'now', so that's not a suitable choice |
| 219 | + |
214 | 220 | foreach ( self::$modules as $module ) { |
215 | 221 | $time = max( $time, $module->getModifiedTime( $context ) ); |
216 | 222 | } |
| 223 | + |
217 | 224 | return $time; |
218 | 225 | } |
219 | | - |
| 226 | + |
220 | 227 | /* Methods */ |
221 | | - |
| 228 | + |
222 | 229 | /* |
223 | 230 | * Outputs a response to a resource load-request, including a content-type header |
224 | 231 | * |
— | — | @@ -237,6 +244,7 @@ |
238 | 245 | // Split requested modules into two groups, modules and missing |
239 | 246 | $modules = array(); |
240 | 247 | $missing = array(); |
| 248 | + |
241 | 249 | foreach ( $context->getModules() as $name ) { |
242 | 250 | if ( isset( self::$modules[$name] ) ) { |
243 | 251 | $modules[] = $name; |
— | — | @@ -244,52 +252,58 @@ |
245 | 253 | $missing[] = $name; |
246 | 254 | } |
247 | 255 | } |
248 | | - |
| 256 | + |
249 | 257 | // Calculate the mtime and caching maxages for this request. We need this, 304 or no 304 |
250 | 258 | $mtime = 1; |
251 | 259 | $maxage = PHP_INT_MAX; |
252 | 260 | $smaxage = PHP_INT_MAX; |
| 261 | + |
253 | 262 | foreach ( $modules as $name ) { |
254 | 263 | $mtime = max( $mtime, self::$modules[$name]->getModifiedTime( $context ) ); |
255 | 264 | $maxage = min( $maxage, self::$modules[$name]->getClientMaxage() ); |
256 | 265 | $smaxage = min( $smaxage, self::$modules[$name]->getServerMaxage() ); |
257 | 266 | } |
258 | | - |
| 267 | + |
259 | 268 | // Output headers |
260 | 269 | if ( $context->getOnly() === 'styles' ) { |
261 | 270 | header( 'Content-Type: text/css' ); |
262 | 271 | } else { |
263 | 272 | header( 'Content-Type: text/javascript' ); |
264 | 273 | } |
| 274 | + |
265 | 275 | header( 'Last-Modified: ' . wfTimestamp( TS_RFC2822, $mtime ) ); |
266 | 276 | $expires = wfTimestamp( TS_RFC2822, min( $maxage, $smaxage ) + time() ); |
267 | 277 | header( "Cache-Control: public, maxage=$maxage, s-maxage=$smaxage" ); |
268 | 278 | header( "Expires: $expires" ); |
269 | | - |
| 279 | + |
270 | 280 | // Check if there's an If-Modified-Since header and respond with a 304 Not Modified if possible |
271 | 281 | $ims = $context->getRequest()->getHeader( 'If-Modified-Since' ); |
| 282 | + |
272 | 283 | if ( $ims !== false && wfTimestamp( TS_UNIX, $ims ) == $mtime ) { |
273 | 284 | header( 'HTTP/1.0 304 Not Modified' ); |
274 | 285 | header( 'Status: 304 Not Modified' ); |
275 | 286 | return; |
276 | 287 | } |
277 | | - |
| 288 | + |
278 | 289 | // Use output buffering |
279 | 290 | ob_start(); |
280 | | - |
| 291 | + |
281 | 292 | // Pre-fetch blobs |
282 | 293 | $blobs = $context->shouldIncludeMessages() ? |
283 | 294 | MessageBlobStore::get( $modules, $context->getLanguage() ) : array(); |
284 | | - |
| 295 | + |
285 | 296 | // Generate output |
286 | 297 | foreach ( $modules as $name ) { |
287 | 298 | // Scripts |
288 | 299 | $scripts = ''; |
| 300 | + |
289 | 301 | if ( $context->shouldIncludeScripts() ) { |
290 | 302 | $scripts .= self::$modules[$name]->getScript( $context ); |
291 | 303 | } |
| 304 | + |
292 | 305 | // Styles |
293 | 306 | $styles = ''; |
| 307 | + |
294 | 308 | if ( |
295 | 309 | $context->shouldIncludeStyles() && |
296 | 310 | ( $styles .= self::$modules[$name]->getStyle( $context ) ) !== '' |
— | — | @@ -299,8 +313,10 @@ |
300 | 314 | } |
301 | 315 | $styles = $context->getDebug() ? $styles : self::filter( 'minify-css', $styles ); |
302 | 316 | } |
| 317 | + |
303 | 318 | // Messages |
304 | 319 | $messages = isset( $blobs[$name] ) ? $blobs[$name] : '{}'; |
| 320 | + |
305 | 321 | // Output |
306 | 322 | if ( $context->getOnly() === 'styles' ) { |
307 | 323 | echo $styles; |
— | — | @@ -313,21 +329,26 @@ |
314 | 330 | echo "mediaWiki.loader.implement( '$name', function() {{$scripts}},\n'$styles',\n$messages );\n"; |
315 | 331 | } |
316 | 332 | } |
| 333 | + |
317 | 334 | // Update the status of script-only modules |
318 | 335 | if ( $context->getOnly() === 'scripts' && !in_array( 'startup', $modules ) ) { |
319 | 336 | $statuses = array(); |
| 337 | + |
320 | 338 | foreach ( $modules as $name ) { |
321 | 339 | $statuses[$name] = 'ready'; |
322 | 340 | } |
| 341 | + |
323 | 342 | $statuses = FormatJson::encode( $statuses ); |
324 | 343 | echo "mediaWiki.loader.state( $statuses );"; |
325 | 344 | } |
| 345 | + |
326 | 346 | // Register missing modules |
327 | 347 | if ( $context->shouldIncludeScripts() ) { |
328 | 348 | foreach ( $missing as $name ) { |
329 | 349 | echo "mediaWiki.loader.register( '$name', null, 'missing' );\n"; |
330 | 350 | } |
331 | 351 | } |
| 352 | + |
332 | 353 | // Output the appropriate header |
333 | 354 | if ( $context->getOnly() !== 'styles' ) { |
334 | 355 | if ( $context->getDebug() ) { |
— | — | @@ -340,4 +361,4 @@ |
341 | 362 | } |
342 | 363 | |
343 | 364 | // FIXME: Temp hack |
344 | | -require_once "$IP/resources/Resources.php"; |
\ No newline at end of file |
| 365 | +require_once "$IP/resources/Resources.php"; |
Index: trunk/phase3/includes/MessageBlobStore.php |
— | — | @@ -42,7 +42,7 @@ |
43 | 43 | } |
44 | 44 | // Try getting from the DB first |
45 | 45 | $blobs = self::getFromDB( $modules, $lang ); |
46 | | - |
| 46 | + |
47 | 47 | // Generate blobs for any missing modules and store them in the DB |
48 | 48 | $missing = array_diff( $modules, array_keys( $blobs ) ); |
49 | 49 | foreach ( $missing as $module ) { |
— | — | @@ -51,9 +51,10 @@ |
52 | 52 | $blobs[$module] = $blob; |
53 | 53 | } |
54 | 54 | } |
| 55 | + |
55 | 56 | return $blobs; |
56 | 57 | } |
57 | | - |
| 58 | + |
58 | 59 | /** |
59 | 60 | * Generate and insert a new message blob. If the blob was already |
60 | 61 | * present, it is not regenerated; instead, the preexisting blob |
— | — | @@ -64,10 +65,11 @@ |
65 | 66 | */ |
66 | 67 | public static function insertMessageBlob( $module, $lang ) { |
67 | 68 | $blob = self::generateMessageBlob( $module, $lang ); |
| 69 | + |
68 | 70 | if ( !$blob ) { |
69 | 71 | return false; |
70 | 72 | } |
71 | | - |
| 73 | + |
72 | 74 | $dbw = wfGetDb( DB_MASTER ); |
73 | 75 | $success = $dbw->insert( 'msg_resource', array( |
74 | 76 | 'mr_lang' => $lang, |
— | — | @@ -78,6 +80,7 @@ |
79 | 81 | __METHOD__, |
80 | 82 | array( 'IGNORE' ) |
81 | 83 | ); |
| 84 | + |
82 | 85 | if ( $success ) { |
83 | 86 | if ( $dbw->affectedRows() == 0 ) { |
84 | 87 | // Blob was already present, fetch it |
— | — | @@ -91,6 +94,7 @@ |
92 | 95 | // Update msg_resource_links |
93 | 96 | $rows = array(); |
94 | 97 | $mod = ResourceLoader::getModule( $module ); |
| 98 | + |
95 | 99 | foreach ( $mod->getMessages() as $key ) { |
96 | 100 | $rows[] = array( |
97 | 101 | 'mrl_resource' => $module, |
— | — | @@ -102,9 +106,10 @@ |
103 | 107 | ); |
104 | 108 | } |
105 | 109 | } |
| 110 | + |
106 | 111 | return $blob; |
107 | 112 | } |
108 | | - |
| 113 | + |
109 | 114 | /** |
110 | 115 | * Update all message blobs for a given module. |
111 | 116 | * @param $module string Module name |
— | — | @@ -113,22 +118,24 @@ |
114 | 119 | */ |
115 | 120 | public static function updateModule( $module, $lang = null ) { |
116 | 121 | $retval = null; |
117 | | - |
| 122 | + |
118 | 123 | // Find all existing blobs for this module |
119 | 124 | $dbw = wfGetDb( DB_MASTER ); |
120 | 125 | $res = $dbw->select( 'msg_resource', array( 'mr_lang', 'mr_blob' ), |
121 | 126 | array( 'mr_resource' => $module ), |
122 | 127 | __METHOD__ |
123 | 128 | ); |
124 | | - |
| 129 | + |
125 | 130 | // Build the new msg_resource rows |
126 | 131 | $newRows = array(); |
127 | 132 | $now = $dbw->timestamp(); |
128 | 133 | // Save the last-processed old and new blobs for later |
129 | 134 | $oldBlob = $newBlob = null; |
| 135 | + |
130 | 136 | foreach ( $res as $row ) { |
131 | 137 | $oldBlob = $row->mr_blob; |
132 | 138 | $newBlob = self::generateMessageBlob( $module, $row->mr_lang ); |
| 139 | + |
133 | 140 | if ( $row->mr_lang === $lang ) { |
134 | 141 | $retval = $newBlob; |
135 | 142 | } |
— | — | @@ -144,13 +151,13 @@ |
145 | 152 | array( array( 'mr_resource', 'mr_lang' ) ), |
146 | 153 | $newRows, __METHOD__ |
147 | 154 | ); |
148 | | - |
| 155 | + |
149 | 156 | // Figure out which messages were added and removed |
150 | 157 | $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) ); |
151 | 158 | $newMessages = array_keys( FormatJson::decode( $newBlob, true ) ); |
152 | 159 | $added = array_diff( $newMessages, $oldMessages ); |
153 | 160 | $removed = array_diff( $oldMessages, $newMessages ); |
154 | | - |
| 161 | + |
155 | 162 | // Delete removed messages, insert added ones |
156 | 163 | if ( $removed ) { |
157 | 164 | $dbw->delete( 'msg_resource_links', array( |
— | — | @@ -159,35 +166,39 @@ |
160 | 167 | ), __METHOD__ |
161 | 168 | ); |
162 | 169 | } |
| 170 | + |
163 | 171 | $newLinksRows = array(); |
| 172 | + |
164 | 173 | foreach ( $added as $message ) { |
165 | 174 | $newLinksRows[] = array( |
166 | 175 | 'mrl_resource' => $module, |
167 | 176 | 'mrl_message' => $message |
168 | 177 | ); |
169 | 178 | } |
| 179 | + |
170 | 180 | if ( $newLinksRows ) { |
171 | 181 | $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__, |
172 | 182 | array( 'IGNORE' ) // just in case |
173 | 183 | ); |
174 | 184 | } |
175 | | - |
| 185 | + |
176 | 186 | return $retval; |
177 | 187 | } |
178 | | - |
| 188 | + |
179 | 189 | /** |
180 | 190 | * Update a single message in all message blobs it occurs in. |
181 | 191 | * @param $key string Message key |
182 | 192 | */ |
183 | 193 | public static function updateMessage( $key ) { |
184 | 194 | $dbw = wfGetDb( DB_MASTER ); |
185 | | - |
| 195 | + |
186 | 196 | // Keep running until the updates queue is empty. |
187 | 197 | // Due to update conflicts, the queue might not be emptied |
188 | 198 | // in one iteration. |
189 | 199 | $updates = null; |
190 | 200 | do { |
191 | 201 | $updates = self::getUpdatesForMessage( $key, $updates ); |
| 202 | + |
192 | 203 | foreach ( $updates as $key => $update ) { |
193 | 204 | // Update the row on the condition that it |
194 | 205 | // didn't change since we fetched it by putting |
— | — | @@ -200,7 +211,7 @@ |
201 | 212 | 'mr_timestamp' => $update['timestamp'] ), |
202 | 213 | __METHOD__ |
203 | 214 | ); |
204 | | - |
| 215 | + |
205 | 216 | // Only requeue conflicted updates. |
206 | 217 | // If update() returned false, don't retry, for |
207 | 218 | // fear of getting into an infinite loop |
— | — | @@ -210,11 +221,11 @@ |
211 | 222 | } |
212 | 223 | } |
213 | 224 | } while ( count( $updates ) ); |
214 | | - |
| 225 | + |
215 | 226 | // No need to update msg_resource_links because we didn't add |
216 | 227 | // or remove any messages, we just changed their contents. |
217 | 228 | } |
218 | | - |
| 229 | + |
219 | 230 | public static function clear() { |
220 | 231 | // TODO: Give this some more thought |
221 | 232 | // TODO: Is TRUNCATE better? |
— | — | @@ -222,7 +233,7 @@ |
223 | 234 | $dbw->delete( 'msg_resource', '*', __METHOD__ ); |
224 | 235 | $dbw->delete( 'msg_resource_links', '*', __METHOD__ ); |
225 | 236 | } |
226 | | - |
| 237 | + |
227 | 238 | /** |
228 | 239 | * Create an update queue for updateMessage() |
229 | 240 | * @param $key string Message key |
— | — | @@ -231,6 +242,7 @@ |
232 | 243 | */ |
233 | 244 | private static function getUpdatesForMessage( $key, $prevUpdates = null ) { |
234 | 245 | $dbw = wfGetDb( DB_MASTER ); |
| 246 | + |
235 | 247 | if ( is_null( $prevUpdates ) ) { |
236 | 248 | // Fetch all blobs referencing $key |
237 | 249 | $res = $dbw->select( |
— | — | @@ -241,23 +253,25 @@ |
242 | 254 | ); |
243 | 255 | } else { |
244 | 256 | // Refetch the blobs referenced by $prevUpdates |
245 | | - |
| 257 | + |
246 | 258 | // Reorganize the (resource, lang) pairs in the format |
247 | 259 | // expected by makeWhereFrom2d() |
248 | 260 | $twoD = array(); |
| 261 | + |
249 | 262 | foreach ( $prevUpdates as $update ) { |
250 | 263 | $twoD[$update['resource']][$update['lang']] = true; |
251 | 264 | } |
252 | | - |
| 265 | + |
253 | 266 | $res = $dbw->select( 'msg_resource', |
254 | 267 | array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ), |
255 | 268 | $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ), |
256 | 269 | __METHOD__ |
257 | 270 | ); |
258 | 271 | } |
259 | | - |
| 272 | + |
260 | 273 | // Build the new updates queue |
261 | 274 | $updates = array(); |
| 275 | + |
262 | 276 | foreach ( $res as $row ) { |
263 | 277 | $updates[] = array( |
264 | 278 | 'resource' => $row->mr_resource, |
— | — | @@ -267,9 +281,10 @@ |
268 | 282 | $key, $row->mr_lang ) |
269 | 283 | ); |
270 | 284 | } |
| 285 | + |
271 | 286 | return $updates; |
272 | 287 | } |
273 | | - |
| 288 | + |
274 | 289 | /** |
275 | 290 | * Reencode a message blob with the updated value for a message |
276 | 291 | * @param $blob string Message blob (JSON object) |
— | — | @@ -280,9 +295,10 @@ |
281 | 296 | private static function reencodeBlob( $blob, $key, $lang ) { |
282 | 297 | $decoded = FormatJson::decode( $blob, true ); |
283 | 298 | $decoded[$key] = wfMsgExt( $key, array( 'language' => $lang ) ); |
| 299 | + |
284 | 300 | return FormatJson::encode( $decoded ); |
285 | 301 | } |
286 | | - |
| 302 | + |
287 | 303 | /** |
288 | 304 | * Get the message blobs for a set of modules from the database. |
289 | 305 | * Modules whose blobs are not in the database are silently dropped. |
— | — | @@ -297,6 +313,7 @@ |
298 | 314 | array( 'mr_resource' => $modules, 'mr_lang' => $lang ), |
299 | 315 | __METHOD__ |
300 | 316 | ); |
| 317 | + |
301 | 318 | foreach ( $res as $row ) { |
302 | 319 | $module = ResourceLoader::getModule( $row->mr_resource ); |
303 | 320 | if ( !$module ) { |
— | — | @@ -309,9 +326,10 @@ |
310 | 327 | $retval[$row->mr_resource] = $row->mr_blob; |
311 | 328 | } |
312 | 329 | } |
| 330 | + |
313 | 331 | return $retval; |
314 | 332 | } |
315 | | - |
| 333 | + |
316 | 334 | /** |
317 | 335 | * Generate the message blob for a given module in a given language. |
318 | 336 | * @param $module string Module name |
— | — | @@ -321,9 +339,11 @@ |
322 | 340 | private static function generateMessageBlob( $module, $lang ) { |
323 | 341 | $mod = ResourceLoader::getModule( $module ); |
324 | 342 | $messages = array(); |
| 343 | + |
325 | 344 | foreach ( $mod->getMessages() as $key ) { |
326 | 345 | $messages[$key] = wfMsgExt( $key, array( 'language' => $lang ) ); |
327 | 346 | } |
| 347 | + |
328 | 348 | return FormatJson::encode( $messages ); |
329 | 349 | } |
330 | 350 | } |
Index: trunk/phase3/includes/CSSJanus.php |
— | — | @@ -20,10 +20,10 @@ |
21 | 21 | /** |
22 | 22 | * This is a PHP port of CSSJanus, a utility that transforms CSS style sheets |
23 | 23 | * written for LTR to RTL. |
24 | | - * |
| 24 | + * |
25 | 25 | * The original Python version of CSSJanus is Copyright 2008 by Google Inc. and |
26 | | - * is distributed under the Apache license. |
27 | | - * |
| 26 | + * is distributed under the Apache license. |
| 27 | + * |
28 | 28 | * Original code: http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus.py |
29 | 29 | * License of original code: http://code.google.com/p/cssjanus/source/browse/trunk/LICENSE |
30 | 30 | * @author Roan Kattouw |
— | — | @@ -55,7 +55,6 @@ |
56 | 56 | 'lookbehind_not_letter' => '(?<![a-zA-Z])', |
57 | 57 | 'chars_within_selector' => '[^\}]*?', |
58 | 58 | 'noflip_annotation' => '\/\*\s*@noflip\s*\*\/', |
59 | | - |
60 | 59 | 'noflip_single' => null, |
61 | 60 | 'noflip_class' => null, |
62 | 61 | 'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//', |
— | — | @@ -74,7 +73,7 @@ |
75 | 74 | 'bg_horizontal_percentage' => null, |
76 | 75 | 'bg_horizontal_percentage_x' => null, |
77 | 76 | ); |
78 | | - |
| 77 | + |
79 | 78 | /** |
80 | 79 | * Build patterns we can't define above because they depend on other patterns. |
81 | 80 | */ |
— | — | @@ -83,6 +82,7 @@ |
84 | 83 | // Patterns have already been built |
85 | 84 | return; |
86 | 85 | } |
| 86 | + |
87 | 87 | $patterns =& self::$patterns; |
88 | 88 | $patterns['escape'] = "(?:{$patterns['unicode']}|\\[^\r\n\f0-9a-f])"; |
89 | 89 | $patterns['nmstart'] = "(?:[_a-z]|{$patterns['nonAscii']}|{$patterns['escape']})"; |
— | — | @@ -95,7 +95,6 @@ |
96 | 96 | $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>)*?{)"; |
97 | 97 | $patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))"; |
98 | 98 | $patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))"; |
99 | | - |
100 | 99 | $patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i"; |
101 | 100 | $patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i"; |
102 | 101 | $patterns['body_direction_ltr'] = "/({$patterns['body_selector']}{$patterns['chars_within_selector']}{$patterns['direction']})ltr/i"; |
— | — | @@ -115,7 +114,7 @@ |
116 | 115 | $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)({$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/"; |
117 | 116 | $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)({$patterns['num']})(%)/"; |
118 | 117 | } |
119 | | - |
| 118 | + |
120 | 119 | /** |
121 | 120 | * Transform an LTR stylesheet to RTL |
122 | 121 | * @param string $css Stylesheet to transform |
— | — | @@ -126,28 +125,29 @@ |
127 | 126 | public static function transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false ) { |
128 | 127 | // We wrap tokens in ` , not ~ like the original implementation does. |
129 | 128 | // This was done because ` is not a legal character in CSS and can only |
130 | | - // occur in URLs, where we escape it to %60 before inserting our tokens. |
| 129 | + // occur in URLs, where we escape it to %60 before inserting our tokens. |
131 | 130 | $css = str_replace( '`', '%60', $css ); |
132 | | - |
| 131 | + |
133 | 132 | self::buildPatterns(); |
134 | | - |
| 133 | + |
135 | 134 | // Tokenize single line rules with /* @noflip */ |
136 | 135 | $noFlipSingle = new CSSJanus_Tokenizer( self::$patterns['noflip_single'], '`NOFLIP_SINGLE`' ); |
137 | 136 | $css = $noFlipSingle->tokenize( $css ); |
138 | | - |
| 137 | + |
139 | 138 | // Tokenize class rules with /* @noflip */ |
140 | 139 | $noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class'], '`NOFLIP_CLASS`' ); |
141 | 140 | $css = $noFlipClass->tokenize( $css ); |
142 | | - |
| 141 | + |
143 | 142 | // Tokenize comments |
144 | 143 | $comments = new CSSJanus_Tokenizer( self::$patterns['comment'], '`C`' ); |
145 | 144 | $css = $comments->tokenize( $css ); |
146 | | - |
| 145 | + |
147 | 146 | // LTR->RTL fixes start here |
148 | 147 | $css = self::fixBodyDirection( $css ); |
149 | 148 | if ( $swapLtrRtlInURL ) { |
150 | 149 | $css = self::fixLtrRtlInURL( $css ); |
151 | 150 | } |
| 151 | + |
152 | 152 | if ( $swapLeftRightInURL ) { |
153 | 153 | $css = self::fixLeftRightInURL( $css ); |
154 | 154 | } |
— | — | @@ -155,18 +155,19 @@ |
156 | 156 | $css = self::fixCursorProperties( $css ); |
157 | 157 | $css = self::fixFourPartNotation( $css ); |
158 | 158 | $css = self::fixBackgroundPosition( $css ); |
159 | | - |
| 159 | + |
160 | 160 | // Detokenize stuff we tokenized before |
161 | 161 | $css = $comments->detokenize( $css ); |
162 | 162 | $css = $noFlipClass->detokenize( $css ); |
163 | 163 | $css = $noFlipSingle->detokenize( $css ); |
| 164 | + |
164 | 165 | return $css; |
165 | 166 | } |
166 | | - |
| 167 | + |
167 | 168 | /** |
168 | 169 | * Replace direction: ltr; with direction: rtl; and vice versa, but *only* |
169 | 170 | * those inside a body { .. } selector. |
170 | | - * |
| 171 | + * |
171 | 172 | * Unlike the original implementation, this function doesn't suffer from |
172 | 173 | * the bug causing "body\n{\ndirection: ltr;\n}" to be missed. |
173 | 174 | * See http://code.google.com/p/cssjanus/issues/detail?id=15 |
— | — | @@ -176,9 +177,10 @@ |
177 | 178 | '$1' . self::$patterns['tmpToken'], $css ); |
178 | 179 | $css = preg_replace( self::$patterns['body_direction_rtl'], '$1ltr', $css ); |
179 | 180 | $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css ); |
| 181 | + |
180 | 182 | return $css; |
181 | 183 | } |
182 | | - |
| 184 | + |
183 | 185 | /** |
184 | 186 | * Replace 'ltr' with 'rtl' and vice versa in background URLs |
185 | 187 | */ |
— | — | @@ -186,9 +188,10 @@ |
187 | 189 | $css = preg_replace( self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css ); |
188 | 190 | $css = preg_replace( self::$patterns['rtl_in_url'], 'ltr', $css ); |
189 | 191 | $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css ); |
| 192 | + |
190 | 193 | return $css; |
191 | 194 | } |
192 | | - |
| 195 | + |
193 | 196 | /** |
194 | 197 | * Replace 'left' with 'right' and vice versa in background URLs |
195 | 198 | */ |
— | — | @@ -196,9 +199,10 @@ |
197 | 200 | $css = preg_replace( self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css ); |
198 | 201 | $css = preg_replace( self::$patterns['right_in_url'], 'left', $css ); |
199 | 202 | $css = str_replace( self::$patterns['tmpToken'], 'right', $css ); |
| 203 | + |
200 | 204 | return $css; |
201 | 205 | } |
202 | | - |
| 206 | + |
203 | 207 | /** |
204 | 208 | * Flip rules like left: , padding-right: , etc. |
205 | 209 | */ |
— | — | @@ -206,9 +210,10 @@ |
207 | 211 | $css = preg_replace( self::$patterns['left'], self::$patterns['tmpToken'], $css ); |
208 | 212 | $css = preg_replace( self::$patterns['right'], 'left', $css ); |
209 | 213 | $css = str_replace( self::$patterns['tmpToken'], 'right', $css ); |
| 214 | + |
210 | 215 | return $css; |
211 | 216 | } |
212 | | - |
| 217 | + |
213 | 218 | /** |
214 | 219 | * Flip East and West in rules like cursor: nw-resize; |
215 | 220 | */ |
— | — | @@ -217,13 +222,14 @@ |
218 | 223 | '$1' . self::$patterns['tmpToken'], $css ); |
219 | 224 | $css = preg_replace( self::$patterns['cursor_west'], '$1e-resize', $css ); |
220 | 225 | $css = str_replace( self::$patterns['tmpToken'], 'w-resize', $css ); |
| 226 | + |
221 | 227 | return $css; |
222 | 228 | } |
223 | | - |
| 229 | + |
224 | 230 | /** |
225 | 231 | * Swap the second and fourth parts in four-part notation rules like |
226 | 232 | * padding: 1px 2px 3px 4px; |
227 | | - * |
| 233 | + * |
228 | 234 | * Unlike the original implementation, this function doesn't suffer from |
229 | 235 | * the bug where whitespace is not preserved when flipping four-part rules |
230 | 236 | * and four-part color rules with multiple whitespace characters between |
— | — | @@ -233,27 +239,29 @@ |
234 | 240 | private static function fixFourPartNotation( $css ) { |
235 | 241 | $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$7$4$5$6$3', $css ); |
236 | 242 | $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4', $css ); |
| 243 | + |
237 | 244 | return $css; |
238 | 245 | } |
239 | | - |
| 246 | + |
240 | 247 | /** |
241 | | - * Flip horizontal background percentages. |
| 248 | + * Flip horizontal background percentages. |
242 | 249 | */ |
243 | 250 | private static function fixBackgroundPosition( $css ) { |
244 | 251 | $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage'], |
245 | 252 | array( 'self', 'calculateNewBackgroundPosition' ), $css ); |
246 | 253 | $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x'], |
247 | 254 | array( 'self', 'calculateNewBackgroundPosition' ), $css ); |
| 255 | + |
248 | 256 | return $css; |
249 | 257 | } |
250 | | - |
| 258 | + |
251 | 259 | /** |
252 | | - * Callback for calculateNewBackgroundPosition() |
| 260 | + * Callback for calculateNewBackgroundPosition() |
253 | 261 | */ |
254 | 262 | private static function calculateNewBackgroundPosition( $matches ) { |
255 | 263 | return $matches[1] . ( 100 - $matches[2] ) . $matches[3]; |
256 | 264 | } |
257 | | -} |
| 265 | +} |
258 | 266 | |
259 | 267 | /** |
260 | 268 | * Utility class used by CSSJanus that tokenizes and untokenizes things we want |
— | — | @@ -263,7 +271,7 @@ |
264 | 272 | class CSSJanus_Tokenizer { |
265 | 273 | private $regex, $token; |
266 | 274 | private $originals; |
267 | | - |
| 275 | + |
268 | 276 | /** |
269 | 277 | * Constructor |
270 | 278 | * @param $regex string Regular expression whose matches to replace by a token. |
— | — | @@ -274,22 +282,22 @@ |
275 | 283 | $this->token = $token; |
276 | 284 | $this->originals = array(); |
277 | 285 | } |
278 | | - |
| 286 | + |
279 | 287 | /** |
280 | 288 | * Replace all occurrences of $regex in $str with a token and remember |
281 | | - * the original strings. |
| 289 | + * the original strings. |
282 | 290 | * @param $str string String to tokenize |
283 | 291 | * @return string Tokenized string |
284 | 292 | */ |
285 | 293 | public function tokenize( $str ) { |
286 | 294 | return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str ); |
287 | 295 | } |
288 | | - |
| 296 | + |
289 | 297 | private function tokenizeCallback( $matches ) { |
290 | 298 | $this->originals[] = $matches[0]; |
291 | 299 | return $this->token; |
292 | 300 | } |
293 | | - |
| 301 | + |
294 | 302 | /** |
295 | 303 | * Replace tokens with their originals. If multiple strings were tokenized, it's important they be |
296 | 304 | * detokenized in exactly the SAME ORDER. |
— | — | @@ -303,11 +311,11 @@ |
304 | 312 | return preg_replace_callback( '/' . preg_quote( $this->token, '/' ) . '/', |
305 | 313 | array( $this, 'detokenizeCallback' ), $str ); |
306 | 314 | } |
307 | | - |
| 315 | + |
308 | 316 | private function detokenizeCallback( $matches ) { |
309 | 317 | $retval = current( $this->originals ); |
310 | 318 | next( $this->originals ); |
| 319 | + |
311 | 320 | return $retval; |
312 | 321 | } |
313 | | - |
314 | | -} |
\ No newline at end of file |
| 322 | +} |
Index: trunk/phase3/includes/ResourceLoaderModule.php |
— | — | @@ -14,7 +14,7 @@ |
15 | 15 | * with this program; if not, write to the Free Software Foundation, Inc., |
16 | 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | * http://www.gnu.org/copyleft/gpl.html |
18 | | - * |
| 18 | + * |
19 | 19 | * @author Trevor Parscal |
20 | 20 | * @author Roan Kattouw |
21 | 21 | */ |
— | — | @@ -23,13 +23,12 @@ |
24 | 24 | * Interface for resource loader modules, with name registration and maxage functionality. |
25 | 25 | */ |
26 | 26 | abstract class ResourceLoaderModule { |
27 | | - |
28 | 27 | /* Protected Members */ |
29 | | - |
| 28 | + |
30 | 29 | protected $name = null; |
31 | | - |
| 30 | + |
32 | 31 | /* Methods */ |
33 | | - |
| 32 | + |
34 | 33 | /** |
35 | 34 | * Get this module's name. This is set when the module is registered |
36 | 35 | * with ResourceLoader::register() |
— | — | @@ -38,7 +37,7 @@ |
39 | 38 | public function getName() { |
40 | 39 | return $this->name; |
41 | 40 | } |
42 | | - |
| 41 | + |
43 | 42 | /** |
44 | 43 | * Set this module's name. This is called by ResourceLodaer::register() |
45 | 44 | * when registering the module. Other code should not call this. |
— | — | @@ -47,7 +46,7 @@ |
48 | 47 | public function setName( $name ) { |
49 | 48 | $this->name = $name; |
50 | 49 | } |
51 | | - |
| 50 | + |
52 | 51 | /** |
53 | 52 | * The maximum number of seconds to cache this module for in the |
54 | 53 | * client-side (browser) cache. Override this only if you have a good |
— | — | @@ -58,7 +57,7 @@ |
59 | 58 | global $wgResourceLoaderClientMaxage; |
60 | 59 | return $wgResourceLoaderClientMaxage; |
61 | 60 | } |
62 | | - |
| 61 | + |
63 | 62 | /** |
64 | 63 | * The maximum number of seconds to cache this module for in the |
65 | 64 | * server-side (Squid / proxy) cache. Override this only if you have a |
— | — | @@ -76,9 +75,9 @@ |
77 | 76 | public function getFlip( $context ) { |
78 | 77 | return $context->getDirection() === 'rtl'; |
79 | 78 | } |
80 | | - |
| 79 | + |
81 | 80 | /* Abstract Methods */ |
82 | | - |
| 81 | + |
83 | 82 | /** |
84 | 83 | * Get all JS for this module for a given language and skin. |
85 | 84 | * Includes all relevant JS except loader scripts. |
— | — | @@ -88,14 +87,14 @@ |
89 | 88 | * @return string JS |
90 | 89 | */ |
91 | 90 | public abstract function getScript( ResourceLoaderContext $context ); |
92 | | - |
| 91 | + |
93 | 92 | /** |
94 | 93 | * Get all CSS for this module for a given skin. |
95 | 94 | * @param $skin string Skin name |
96 | 95 | * @return string CSS |
97 | 96 | */ |
98 | 97 | public abstract function getStyle( ResourceLoaderContext $context ); |
99 | | - |
| 98 | + |
100 | 99 | /** |
101 | 100 | * Get the messages needed for this module. |
102 | 101 | * |
— | — | @@ -103,13 +102,13 @@ |
104 | 103 | * @return array of message keys. Keys may occur more than once |
105 | 104 | */ |
106 | 105 | public abstract function getMessages(); |
107 | | - |
| 106 | + |
108 | 107 | /** |
109 | 108 | * Get the loader JS for this module, if set. |
110 | 109 | * @return mixed Loader JS (string) or false if no custom loader set |
111 | 110 | */ |
112 | 111 | public abstract function getLoaderScript(); |
113 | | - |
| 112 | + |
114 | 113 | /** |
115 | 114 | * Get a list of modules this module depends on. |
116 | 115 | * |
— | — | @@ -126,7 +125,7 @@ |
127 | 126 | * @return array Array of module names (strings) |
128 | 127 | */ |
129 | 128 | public abstract function getDependencies(); |
130 | | - |
| 129 | + |
131 | 130 | /** |
132 | 131 | * Get this module's last modification timestamp for a given |
133 | 132 | * combination of language, skin and debug mode flag. This is typically |
— | — | @@ -145,9 +144,8 @@ |
146 | 145 | * Module based on local JS/CSS files. This is the most common type of module. |
147 | 146 | */ |
148 | 147 | class ResourceLoaderFileModule extends ResourceLoaderModule { |
149 | | - |
150 | 148 | /* Protected Members */ |
151 | | - |
| 149 | + |
152 | 150 | protected $scripts = array(); |
153 | 151 | protected $styles = array(); |
154 | 152 | protected $messages = array(); |
— | — | @@ -158,24 +156,24 @@ |
159 | 157 | protected $skinStyles = array(); |
160 | 158 | protected $loaders = array(); |
161 | 159 | protected $parameters = array(); |
162 | | - |
| 160 | + |
163 | 161 | // In-object cache for file dependencies |
164 | 162 | protected $fileDeps = array(); |
165 | 163 | // In-object cache for mtime |
166 | 164 | protected $modifiedTime = array(); |
167 | | - |
| 165 | + |
168 | 166 | /* Methods */ |
169 | | - |
| 167 | + |
170 | 168 | /** |
171 | 169 | * Construct a new module from an options array. |
172 | 170 | * |
173 | 171 | * @param $options array Options array. If empty, an empty module will be constructed |
174 | | - * |
| 172 | + * |
175 | 173 | * $options format: |
176 | 174 | * array( |
177 | 175 | * // Required module options (mutually exclusive) |
178 | 176 | * 'scripts' => 'dir/script.js' | array( 'dir/script1.js', 'dir/script2.js' ... ), |
179 | | - * |
| 177 | + * |
180 | 178 | * // Optional module options |
181 | 179 | * 'languageScripts' => array( |
182 | 180 | * '[lang name]' => 'dir/lang.js' | '[lang name]' => array( 'dir/lang1.js', 'dir/lang2.js' ... ) |
— | — | @@ -183,7 +181,7 @@ |
184 | 182 | * ), |
185 | 183 | * 'skinScripts' => 'dir/skin.js' | array( 'dir/skin1.js', 'dir/skin2.js' ... ), |
186 | 184 | * 'debugScripts' => 'dir/debug.js' | array( 'dir/debug1.js', 'dir/debug2.js' ... ), |
187 | | - * |
| 185 | + * |
188 | 186 | * // Non-raw module options |
189 | 187 | * 'dependencies' => 'module' | array( 'module1', 'module2' ... ) |
190 | 188 | * 'loaderScripts' => 'dir/loader.js' | array( 'dir/loader1.js', 'dir/loader2.js' ... ), |
— | — | @@ -228,7 +226,7 @@ |
229 | 227 | } |
230 | 228 | } |
231 | 229 | } |
232 | | - |
| 230 | + |
233 | 231 | /** |
234 | 232 | * Add script files to this module. In order to be valid, a module |
235 | 233 | * must contain at least one script file. |
— | — | @@ -237,7 +235,7 @@ |
238 | 236 | public function addScripts( $scripts ) { |
239 | 237 | $this->scripts = array_merge( $this->scripts, (array)$scripts ); |
240 | 238 | } |
241 | | - |
| 239 | + |
242 | 240 | /** |
243 | 241 | * Add style (CSS) files to this module. |
244 | 242 | * @param $styles mixed Path to CSS file (string) or array of paths |
— | — | @@ -245,7 +243,7 @@ |
246 | 244 | public function addStyles( $styles ) { |
247 | 245 | $this->styles = array_merge( $this->styles, (array)$styles ); |
248 | 246 | } |
249 | | - |
| 247 | + |
250 | 248 | /** |
251 | 249 | * Add messages to this module. |
252 | 250 | * @param $messages mixed Message key (string) or array of message keys |
— | — | @@ -253,7 +251,7 @@ |
254 | 252 | public function addMessages( $messages ) { |
255 | 253 | $this->messages = array_merge( $this->messages, (array)$messages ); |
256 | 254 | } |
257 | | - |
| 255 | + |
258 | 256 | /** |
259 | 257 | * Add dependencies. Dependency information is taken into account when |
260 | 258 | * loading a module on the client side. When adding a module on the |
— | — | @@ -271,7 +269,7 @@ |
272 | 270 | public function addDependencies( $dependencies ) { |
273 | 271 | $this->dependencies = array_merge( $this->dependencies, (array)$dependencies ); |
274 | 272 | } |
275 | | - |
| 273 | + |
276 | 274 | /** |
277 | 275 | * Add debug scripts to the module. These scripts are only included |
278 | 276 | * in debug mode. |
— | — | @@ -280,7 +278,7 @@ |
281 | 279 | public function addDebugScripts( $scripts ) { |
282 | 280 | $this->debugScripts = array_merge( $this->debugScripts, (array)$scripts ); |
283 | 281 | } |
284 | | - |
| 282 | + |
285 | 283 | /** |
286 | 284 | * Add language-specific scripts. These scripts are only included for |
287 | 285 | * a given language. |
— | — | @@ -306,7 +304,7 @@ |
307 | 305 | array( $skin => $scripts ) |
308 | 306 | ); |
309 | 307 | } |
310 | | - |
| 308 | + |
311 | 309 | /** |
312 | 310 | * Add skin-specific CSS. These CSS files are only included for a |
313 | 311 | * given skin. If there are no skin-specific CSS files for a skin, |
— | — | @@ -320,7 +318,7 @@ |
321 | 319 | array( $skin => $scripts ) |
322 | 320 | ); |
323 | 321 | } |
324 | | - |
| 322 | + |
325 | 323 | /** |
326 | 324 | * Add loader scripts. These scripts are loaded on every page and are |
327 | 325 | * responsible for registering this module using |
— | — | @@ -337,23 +335,25 @@ |
338 | 336 | public function addLoaders( $scripts ) { |
339 | 337 | $this->loaders = array_merge( $this->loaders, (array)$scripts ); |
340 | 338 | } |
341 | | - |
| 339 | + |
342 | 340 | public function getScript( ResourceLoaderContext $context ) { |
343 | 341 | $retval = $this->getPrimaryScript() . "\n" . |
344 | 342 | $this->getLanguageScript( $context->getLanguage() ) . "\n" . |
345 | 343 | $this->getSkinScript( $context->getSkin() ); |
| 344 | + |
346 | 345 | if ( $context->getDebug() ) { |
347 | 346 | $retval .= $this->getDebugScript(); |
348 | 347 | } |
| 348 | + |
349 | 349 | return $retval; |
350 | 350 | } |
351 | | - |
| 351 | + |
352 | 352 | public function getStyle( ResourceLoaderContext $context ) { |
353 | 353 | $style = $this->getPrimaryStyle() . "\n" . $this->getSkinStyle( $context->getSkin() ); |
354 | | - |
| 354 | + |
355 | 355 | // Extract and store the list of referenced files |
356 | 356 | $files = CSSMin::getLocalFileReferences( $style ); |
357 | | - |
| 357 | + |
358 | 358 | // Only store if modified |
359 | 359 | if ( $files !== $this->getFileDependencies( $context->getSkin() ) ) { |
360 | 360 | $encFiles = FormatJson::encode( $files ); |
— | — | @@ -365,30 +365,33 @@ |
366 | 366 | 'md_deps' => $encFiles, |
367 | 367 | ) |
368 | 368 | ); |
369 | | - |
| 369 | + |
370 | 370 | // Save into memcached |
371 | 371 | global $wgMemc; |
| 372 | + |
372 | 373 | $key = wfMemcKey( 'resourceloader', 'module_deps', $this->getName(), $context->getSkin() ); |
373 | 374 | $wgMemc->set( $key, $encFiles ); |
374 | 375 | } |
| 376 | + |
375 | 377 | return $style; |
376 | 378 | } |
377 | | - |
| 379 | + |
378 | 380 | public function getMessages() { |
379 | 381 | return $this->messages; |
380 | 382 | } |
381 | | - |
| 383 | + |
382 | 384 | public function getDependencies() { |
383 | 385 | return $this->dependencies; |
384 | 386 | } |
385 | | - |
| 387 | + |
386 | 388 | public function getLoaderScript() { |
387 | 389 | if ( count( $this->loaders ) == 0 ) { |
388 | 390 | return false; |
389 | 391 | } |
| 392 | + |
390 | 393 | return self::concatScripts( $this->loaders ); |
391 | 394 | } |
392 | | - |
| 395 | + |
393 | 396 | /** |
394 | 397 | * Get the last modified timestamp of this module, which is calculated |
395 | 398 | * as the highest last modified timestamp of its constituent files and |
— | — | @@ -405,7 +408,7 @@ |
406 | 409 | if ( isset( $this->modifiedTime[$context->getHash()] ) ) { |
407 | 410 | return $this->modifiedTime[$context->getHash()]; |
408 | 411 | } |
409 | | - |
| 412 | + |
410 | 413 | $files = array_merge( |
411 | 414 | $this->scripts, |
412 | 415 | $this->styles, |
— | — | @@ -417,8 +420,9 @@ |
418 | 421 | $this->loaders, |
419 | 422 | $this->getFileDependencies( $context->getSkin() ) |
420 | 423 | ); |
| 424 | + |
421 | 425 | $filesMtime = max( array_map( 'filemtime', array_map( array( __CLASS__, 'remapFilename' ), $files ) ) ); |
422 | | - |
| 426 | + |
423 | 427 | // Get the mtime of the message blob |
424 | 428 | // TODO: This timestamp is queried a lot and queried separately for each module. Maybe it should be put in memcached? |
425 | 429 | $dbr = wfGetDb( DB_SLAVE ); |
— | — | @@ -428,13 +432,13 @@ |
429 | 433 | ), __METHOD__ |
430 | 434 | ); |
431 | 435 | $msgBlobMtime = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0; |
432 | | - |
| 436 | + |
433 | 437 | $this->modifiedTime[$context->getHash()] = max( $filesMtime, $msgBlobMtime ); |
434 | 438 | return $this->modifiedTime[$context->getHash()]; |
435 | 439 | } |
436 | | - |
| 440 | + |
437 | 441 | /* Protected Members */ |
438 | | - |
| 442 | + |
439 | 443 | /** |
440 | 444 | * Get the primary JS for this module. This is pulled from the |
441 | 445 | * script files added through addScripts() |
— | — | @@ -443,7 +447,7 @@ |
444 | 448 | protected function getPrimaryScript() { |
445 | 449 | return self::concatScripts( $this->scripts ); |
446 | 450 | } |
447 | | - |
| 451 | + |
448 | 452 | /** |
449 | 453 | * Get the primary CSS for this module. This is pulled from the CSS |
450 | 454 | * files added through addStyles() |
— | — | @@ -452,7 +456,7 @@ |
453 | 457 | protected function getPrimaryStyle() { |
454 | 458 | return self::concatStyles( $this->styles ); |
455 | 459 | } |
456 | | - |
| 460 | + |
457 | 461 | /** |
458 | 462 | * Get the debug JS for this module. This is pulled from the script |
459 | 463 | * files added through addDebugScripts() |
— | — | @@ -461,7 +465,7 @@ |
462 | 466 | protected function getDebugScript() { |
463 | 467 | return self::concatScripts( $this->debugScripts ); |
464 | 468 | } |
465 | | - |
| 469 | + |
466 | 470 | /** |
467 | 471 | * Get the language-specific JS for a given language. This is pulled |
468 | 472 | * from the language-specific script files added through addLanguageScripts() |
— | — | @@ -473,7 +477,7 @@ |
474 | 478 | } |
475 | 479 | return self::concatScripts( $this->languageScripts[$lang] ); |
476 | 480 | } |
477 | | - |
| 481 | + |
478 | 482 | /** |
479 | 483 | * Get the skin-specific JS for a given skin. This is pulled from the |
480 | 484 | * skin-specific JS files added through addSkinScripts() |
— | — | @@ -482,7 +486,7 @@ |
483 | 487 | protected function getSkinScript( $skin ) { |
484 | 488 | return self::concatScripts( self::getSkinFiles( $skin, $this->skinScripts ) ); |
485 | 489 | } |
486 | | - |
| 490 | + |
487 | 491 | /** |
488 | 492 | * Get the skin-specific CSS for a given skin. This is pulled from the |
489 | 493 | * skin-specific CSS files added through addSkinStyles() |
— | — | @@ -491,7 +495,7 @@ |
492 | 496 | protected function getSkinStyle( $skin ) { |
493 | 497 | return self::concatStyles( self::getSkinFiles( $skin, $this->skinStyles ) ); |
494 | 498 | } |
495 | | - |
| 499 | + |
496 | 500 | /** |
497 | 501 | * Helper function to get skin-specific data from an array. |
498 | 502 | * @param $skin string Skin name |
— | — | @@ -500,14 +504,16 @@ |
501 | 505 | */ |
502 | 506 | protected static function getSkinFiles( $skin, $map ) { |
503 | 507 | $retval = array(); |
| 508 | + |
504 | 509 | if ( isset( $map[$skin] ) && $map[$skin] ) { |
505 | 510 | $retval = $map[$skin]; |
506 | 511 | } else if ( isset( $map['default'] ) ) { |
507 | 512 | $retval = $map['default']; |
508 | 513 | } |
| 514 | + |
509 | 515 | return $retval; |
510 | 516 | } |
511 | | - |
| 517 | + |
512 | 518 | /** |
513 | 519 | * Get the files this module depends on indirectly for a given skin. |
514 | 520 | * Currently these are only image files referenced by the module's CSS. |
— | — | @@ -519,12 +525,13 @@ |
520 | 526 | if ( isset( $this->fileDeps[$skin] ) ) { |
521 | 527 | return $this->fileDeps[$skin]; |
522 | 528 | } |
523 | | - |
| 529 | + |
524 | 530 | // Now try memcached |
525 | 531 | global $wgMemc; |
| 532 | + |
526 | 533 | $key = wfMemcKey( 'resourceloader', 'module_deps', $this->getName(), $skin ); |
527 | 534 | $deps = $wgMemc->get( $key ); |
528 | | - |
| 535 | + |
529 | 536 | if ( !$deps ) { |
530 | 537 | $dbr = wfGetDb( DB_SLAVE ); |
531 | 538 | $deps = $dbr->selectField( 'module_deps', 'md_deps', array( |
— | — | @@ -537,10 +544,12 @@ |
538 | 545 | } |
539 | 546 | $wgMemc->set( $key, $deps ); |
540 | 547 | } |
| 548 | + |
541 | 549 | $this->fileDeps = FormatJson::decode( $deps, true ); |
| 550 | + |
542 | 551 | return $this->fileDeps; |
543 | 552 | } |
544 | | - |
| 553 | + |
545 | 554 | /** |
546 | 555 | * Get the contents of a set of files and concatenate them, with |
547 | 556 | * newlines in between. Each file is used only once. |
— | — | @@ -550,7 +559,7 @@ |
551 | 560 | protected static function concatScripts( $files ) { |
552 | 561 | return implode( "\n", array_map( 'file_get_contents', array_map( array( __CLASS__, 'remapFilename' ), array_unique( (array) $files ) ) ) ); |
553 | 562 | } |
554 | | - |
| 563 | + |
555 | 564 | /** |
556 | 565 | * Get the contents of a set of CSS files, remap then and concatenate |
557 | 566 | * them, with newlines in between. Each file is used only once. |
— | — | @@ -560,7 +569,7 @@ |
561 | 570 | protected static function concatStyles( $files ) { |
562 | 571 | return implode( "\n", array_map( array( __CLASS__, 'remapStyle' ), array_unique( (array) $files ) ) ); |
563 | 572 | } |
564 | | - |
| 573 | + |
565 | 574 | /** |
566 | 575 | * Remap a relative to $IP. Used as a callback for array_map() |
567 | 576 | * @param $file string File name |
— | — | @@ -568,9 +577,10 @@ |
569 | 578 | */ |
570 | 579 | protected static function remapFilename( $file ) { |
571 | 580 | global $IP; |
| 581 | + |
572 | 582 | return "$IP/$file"; |
573 | 583 | } |
574 | | - |
| 584 | + |
575 | 585 | /** |
576 | 586 | * Get the contents of a CSS file and run it through CSSMin::remap(). |
577 | 587 | * This wrapper is needed so we can use array_map() in concatStyles() |
— | — | @@ -587,44 +597,45 @@ |
588 | 598 | * TODO: Add Site CSS functionality too |
589 | 599 | */ |
590 | 600 | class ResourceLoaderSiteModule extends ResourceLoaderModule { |
591 | | - |
592 | 601 | /* Protected Members */ |
593 | | - |
| 602 | + |
594 | 603 | // In-object cache for modified time |
595 | 604 | protected $modifiedTime = null; |
596 | | - |
| 605 | + |
597 | 606 | /* Methods */ |
598 | | - |
| 607 | + |
599 | 608 | public function getScript( ResourceLoaderContext $context ) { |
600 | 609 | return Skin::newFromKey( $context->getSkin() )->generateUserJs(); |
601 | 610 | } |
602 | | - |
| 611 | + |
603 | 612 | public function getModifiedTime( ResourceLoaderContext $context ) { |
604 | 613 | if ( isset( $this->modifiedTime[$context->getHash()] ) ) { |
605 | 614 | return $this->modifiedTime[$context->getHash()]; |
606 | 615 | } |
607 | | - |
| 616 | + |
608 | 617 | // HACK: We duplicate the message names from generateUserJs() |
609 | 618 | // here and weird things (i.e. mtime moving backwards) can happen |
610 | 619 | // when a MediaWiki:Something.js page is deleted |
611 | 620 | $jsPages = array( Title::makeTitle( NS_MEDIAWIKI, 'Common.js' ), |
612 | 621 | Title::makeTitle( NS_MEDIAWIKI, ucfirst( $context->getSkin() ) . '.js' ) |
613 | 622 | ); |
614 | | - |
| 623 | + |
615 | 624 | // Do batch existence check |
616 | 625 | // TODO: This would work better if page_touched were loaded by this as well |
617 | 626 | $lb = new LinkBatch( $jsPages ); |
618 | 627 | $lb->execute(); |
619 | | - |
| 628 | + |
620 | 629 | $this->modifiedTime = 1; // wfTimestamp() interprets 0 as "now" |
| 630 | + |
621 | 631 | foreach ( $jsPages as $jsPage ) { |
622 | 632 | if ( $jsPage->exists() ) { |
623 | 633 | $this->modifiedTime = max( $this->modifiedTime, wfTimestamp( TS_UNIX, $jsPage->getTouched() ) ); |
624 | 634 | } |
625 | 635 | } |
| 636 | + |
626 | 637 | return $this->modifiedTime; |
627 | 638 | } |
628 | | - |
| 639 | + |
629 | 640 | public function getStyle( ResourceLoaderContext $context ) { return ''; } |
630 | 641 | public function getMessages() { return array(); } |
631 | 642 | public function getLoaderScript() { return ''; } |
— | — | @@ -633,17 +644,17 @@ |
634 | 645 | |
635 | 646 | |
636 | 647 | class ResourceLoaderStartUpModule extends ResourceLoaderModule { |
637 | | - |
638 | 648 | /* Protected Members */ |
639 | | - |
| 649 | + |
640 | 650 | protected $modifiedTime = null; |
641 | | - |
| 651 | + |
642 | 652 | /* Methods */ |
643 | | - |
| 653 | + |
644 | 654 | public function getScript( ResourceLoaderContext $context ) { |
645 | 655 | global $IP; |
646 | | - |
| 656 | + |
647 | 657 | $scripts = file_get_contents( "$IP/resources/startup.js" ); |
| 658 | + |
648 | 659 | if ( $context->getOnly() === 'scripts' ) { |
649 | 660 | // Get all module registrations |
650 | 661 | $registration = ResourceLoader::getModuleRegistrations( $context ); |
— | — | @@ -668,6 +679,7 @@ |
669 | 680 | ), -2 ) ) |
670 | 681 | ) |
671 | 682 | ); |
| 683 | + |
672 | 684 | // Build HTML code for loading jquery and mediawiki modules |
673 | 685 | $loadScript = Html::linkedScript( $context->getServer() . "?$query" ); |
674 | 686 | // Add code to add jquery and mediawiki loading code; only if the current client is compatible |
— | — | @@ -675,34 +687,39 @@ |
676 | 688 | // Delete the compatible function - it's not needed anymore |
677 | 689 | $scripts .= "delete window['isCompatible'];"; |
678 | 690 | } |
| 691 | + |
679 | 692 | return $scripts; |
680 | 693 | } |
681 | | - |
| 694 | + |
682 | 695 | public function getModifiedTime( ResourceLoaderContext $context ) { |
683 | 696 | global $IP; |
| 697 | + |
684 | 698 | if ( !is_null( $this->modifiedTime ) ) { |
685 | 699 | return $this->modifiedTime; |
686 | 700 | } |
| 701 | + |
687 | 702 | // HACK getHighestModifiedTime() calls this function, so protect against infinite recursion |
688 | 703 | $this->modifiedTime = filemtime( "$IP/resources/startup.js" ); |
689 | 704 | $this->modifiedTime = ResourceLoader::getHighestModifiedTime( $context ); |
690 | 705 | return $this->modifiedTime; |
691 | 706 | } |
692 | | - |
| 707 | + |
693 | 708 | public function getClientMaxage() { |
694 | 709 | return 300; // 5 minutes |
695 | 710 | } |
696 | | - |
| 711 | + |
697 | 712 | public function getServerMaxage() { |
698 | 713 | return 300; // 5 minutes |
699 | 714 | } |
700 | | - |
| 715 | + |
701 | 716 | public function getStyle( ResourceLoaderContext $context ) { return ''; } |
| 717 | + |
702 | 718 | public function getFlip( $context ) { |
703 | 719 | global $wgContLang; |
| 720 | + |
704 | 721 | return $wgContLang->getDir() !== $context->getDirection(); |
705 | 722 | } |
706 | 723 | public function getMessages() { return array(); } |
707 | 724 | public function getLoaderScript() { return ''; } |
708 | 725 | public function getDependencies() { return array(); } |
709 | | -} |
\ No newline at end of file |
| 726 | +} |
Index: trunk/phase3/load.php |
— | — | @@ -22,7 +22,7 @@ |
23 | 23 | * @author Trevor Parscal |
24 | 24 | * |
25 | 25 | */ |
26 | | - |
| 26 | + |
27 | 27 | require ( dirname( __FILE__ ) . '/includes/WebStart.php' ); |
28 | 28 | wfProfileIn( 'load.php' ); |
29 | 29 | |
— | — | @@ -51,4 +51,4 @@ |
52 | 52 | wfLogProfilingData(); |
53 | 53 | |
54 | 54 | // Shut down the database |
55 | | -wfGetLBFactory()->shutdown(); |
\ No newline at end of file |
| 55 | +wfGetLBFactory()->shutdown(); |