MediaWiki
REL1_23
|
00001 <?php 00028 abstract class ResourceLoaderModule { 00029 00030 # Type of resource 00031 const TYPE_SCRIPTS = 'scripts'; 00032 const TYPE_STYLES = 'styles'; 00033 const TYPE_MESSAGES = 'messages'; 00034 const TYPE_COMBINED = 'combined'; 00035 00036 # sitewide core module like a skin file or jQuery component 00037 const ORIGIN_CORE_SITEWIDE = 1; 00038 00039 # per-user module generated by the software 00040 const ORIGIN_CORE_INDIVIDUAL = 2; 00041 00042 # sitewide module generated from user-editable files, like MediaWiki:Common.js, or 00043 # modules accessible to multiple users, such as those generated by the Gadgets extension. 00044 const ORIGIN_USER_SITEWIDE = 3; 00045 00046 # per-user module generated from user-editable files, like User:Me/vector.js 00047 const ORIGIN_USER_INDIVIDUAL = 4; 00048 00049 # an access constant; make sure this is kept as the largest number in this group 00050 const ORIGIN_ALL = 10; 00051 00052 # script and style modules form a hierarchy of trustworthiness, with core modules like 00053 # skins and jQuery as most trustworthy, and user scripts as least trustworthy. We can 00054 # limit the types of scripts and styles we allow to load on, say, sensitive special 00055 # pages like Special:UserLogin and Special:Preferences 00056 protected $origin = self::ORIGIN_CORE_SITEWIDE; 00057 00058 /* Protected Members */ 00059 00060 protected $name = null; 00061 protected $targets = array( 'desktop' ); 00062 00063 // In-object cache for file dependencies 00064 protected $fileDeps = array(); 00065 // In-object cache for message blob mtime 00066 protected $msgBlobMtime = array(); 00067 00068 /* Methods */ 00069 00076 public function getName() { 00077 return $this->name; 00078 } 00079 00086 public function setName( $name ) { 00087 $this->name = $name; 00088 } 00089 00097 public function getOrigin() { 00098 return $this->origin; 00099 } 00100 00107 public function setOrigin( $origin ) { 00108 $this->origin = $origin; 00109 } 00110 00115 public function getFlip( $context ) { 00116 global $wgContLang; 00117 00118 return $wgContLang->getDir() !== $context->getDirection(); 00119 } 00120 00128 public function getScript( ResourceLoaderContext $context ) { 00129 // Stub, override expected 00130 return ''; 00131 } 00132 00147 public function getScriptURLsForDebug( ResourceLoaderContext $context ) { 00148 $url = ResourceLoader::makeLoaderURL( 00149 array( $this->getName() ), 00150 $context->getLanguage(), 00151 $context->getSkin(), 00152 $context->getUser(), 00153 $context->getVersion(), 00154 true, // debug 00155 'scripts', // only 00156 $context->getRequest()->getBool( 'printable' ), 00157 $context->getRequest()->getBool( 'handheld' ) 00158 ); 00159 return array( $url ); 00160 } 00161 00168 public function supportsURLLoading() { 00169 return true; 00170 } 00171 00180 public function getStyles( ResourceLoaderContext $context ) { 00181 // Stub, override expected 00182 return array(); 00183 } 00184 00194 public function getStyleURLsForDebug( ResourceLoaderContext $context ) { 00195 $url = ResourceLoader::makeLoaderURL( 00196 array( $this->getName() ), 00197 $context->getLanguage(), 00198 $context->getSkin(), 00199 $context->getUser(), 00200 $context->getVersion(), 00201 true, // debug 00202 'styles', // only 00203 $context->getRequest()->getBool( 'printable' ), 00204 $context->getRequest()->getBool( 'handheld' ) 00205 ); 00206 return array( 'all' => array( $url ) ); 00207 } 00208 00216 public function getMessages() { 00217 // Stub, override expected 00218 return array(); 00219 } 00220 00226 public function getGroup() { 00227 // Stub, override expected 00228 return null; 00229 } 00230 00236 public function getSource() { 00237 // Stub, override expected 00238 return 'local'; 00239 } 00240 00248 public function getPosition() { 00249 return 'bottom'; 00250 } 00251 00259 public function isRaw() { 00260 return false; 00261 } 00262 00268 public function getLoaderScript() { 00269 // Stub, override expected 00270 return false; 00271 } 00272 00283 public function getDependencies() { 00284 // Stub, override expected 00285 return array(); 00286 } 00287 00293 public function getTargets() { 00294 return $this->targets; 00295 } 00296 00304 public function getFileDependencies( $skin ) { 00305 // Try in-object cache first 00306 if ( isset( $this->fileDeps[$skin] ) ) { 00307 return $this->fileDeps[$skin]; 00308 } 00309 00310 $dbr = wfGetDB( DB_SLAVE ); 00311 $deps = $dbr->selectField( 'module_deps', 'md_deps', array( 00312 'md_module' => $this->getName(), 00313 'md_skin' => $skin, 00314 ), __METHOD__ 00315 ); 00316 if ( !is_null( $deps ) ) { 00317 $this->fileDeps[$skin] = (array)FormatJson::decode( $deps, true ); 00318 } else { 00319 $this->fileDeps[$skin] = array(); 00320 } 00321 return $this->fileDeps[$skin]; 00322 } 00323 00330 public function setFileDependencies( $skin, $deps ) { 00331 $this->fileDeps[$skin] = $deps; 00332 } 00333 00340 public function getMsgBlobMtime( $lang ) { 00341 if ( !isset( $this->msgBlobMtime[$lang] ) ) { 00342 if ( !count( $this->getMessages() ) ) { 00343 return 0; 00344 } 00345 00346 $dbr = wfGetDB( DB_SLAVE ); 00347 $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array( 00348 'mr_resource' => $this->getName(), 00349 'mr_lang' => $lang 00350 ), __METHOD__ 00351 ); 00352 // If no blob was found, but the module does have messages, that means we need 00353 // to regenerate it. Return NOW 00354 if ( $msgBlobMtime === false ) { 00355 $msgBlobMtime = wfTimestampNow(); 00356 } 00357 $this->msgBlobMtime[$lang] = wfTimestamp( TS_UNIX, $msgBlobMtime ); 00358 } 00359 return $this->msgBlobMtime[$lang]; 00360 } 00361 00368 public function setMsgBlobMtime( $lang, $mtime ) { 00369 $this->msgBlobMtime[$lang] = $mtime; 00370 } 00371 00372 /* Abstract Methods */ 00373 00392 public function getModifiedTime( ResourceLoaderContext $context ) { 00393 // 0 would mean now 00394 return 1; 00395 } 00396 00404 public function getHashMtime( ResourceLoaderContext $context ) { 00405 $hash = $this->getModifiedHash( $context ); 00406 if ( !is_string( $hash ) ) { 00407 return 0; 00408 } 00409 00410 $cache = wfGetCache( CACHE_ANYTHING ); 00411 $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash ); 00412 00413 $data = $cache->get( $key ); 00414 if ( is_array( $data ) && $data['hash'] === $hash ) { 00415 // Hash is still the same, re-use the timestamp of when we first saw this hash. 00416 return $data['timestamp']; 00417 } 00418 00419 $timestamp = wfTimestamp(); 00420 $cache->set( $key, array( 00421 'hash' => $hash, 00422 'timestamp' => $timestamp, 00423 ) ); 00424 00425 return $timestamp; 00426 } 00427 00437 public function getModifiedHash( ResourceLoaderContext $context ) { 00438 return null; 00439 } 00440 00447 public function getDefinitionMtime( ResourceLoaderContext $context ) { 00448 wfProfileIn( __METHOD__ ); 00449 $summary = $this->getDefinitionSummary( $context ); 00450 if ( $summary === null ) { 00451 wfProfileOut( __METHOD__ ); 00452 return 0; 00453 } 00454 00455 $hash = md5( json_encode( $summary ) ); 00456 00457 $cache = wfGetCache( CACHE_ANYTHING ); 00458 00459 // Embed the hash itself in the cache key. This allows for a few nifty things: 00460 // - During deployment, servers with old and new versions of the code communicating 00461 // with the same memcached will not override the same key repeatedly increasing 00462 // the timestamp. 00463 // - In case of the definition changing and then changing back in a short period of time 00464 // (e.g. in case of a revert or a corrupt server) the old timestamp and client-side cache 00465 // url will be re-used. 00466 // - If different context-combinations (e.g. same skin, same language or some combination 00467 // thereof) result in the same definition, they will use the same hash and timestamp. 00468 $key = wfMemcKey( 'resourceloader', 'moduledefinition', $this->getName(), $hash ); 00469 00470 $data = $cache->get( $key ); 00471 if ( is_int( $data ) && $data > 0 ) { 00472 // We've seen this hash before, re-use the timestamp of when we first saw it. 00473 wfProfileOut( __METHOD__ ); 00474 return $data; 00475 } 00476 00477 wfDebugLog( 'resourceloader', __METHOD__ . ": New definition hash for module {$this->getName()} in context {$context->getHash()}: $hash." ); 00478 00479 $timestamp = time(); 00480 $cache->set( $key, $timestamp ); 00481 00482 wfProfileOut( __METHOD__ ); 00483 return $timestamp; 00484 } 00485 00510 public function getDefinitionSummary( ResourceLoaderContext $context ) { 00511 return array( 00512 'class' => get_class( $this ), 00513 ); 00514 } 00515 00525 public function isKnownEmpty( ResourceLoaderContext $context ) { 00526 return false; 00527 } 00528 00530 private static $jsParser; 00531 private static $parseCacheVersion = 1; 00532 00541 protected function validateScriptFile( $fileName, $contents ) { 00542 global $wgResourceLoaderValidateJS; 00543 if ( $wgResourceLoaderValidateJS ) { 00544 // Try for cache hit 00545 // Use CACHE_ANYTHING since filtering is very slow compared to DB queries 00546 $key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) ); 00547 $cache = wfGetCache( CACHE_ANYTHING ); 00548 $cacheEntry = $cache->get( $key ); 00549 if ( is_string( $cacheEntry ) ) { 00550 return $cacheEntry; 00551 } 00552 00553 $parser = self::javaScriptParser(); 00554 try { 00555 $parser->parse( $contents, $fileName, 1 ); 00556 $result = $contents; 00557 } catch ( Exception $e ) { 00558 // We'll save this to cache to avoid having to validate broken JS over and over... 00559 $err = $e->getMessage(); 00560 $result = "throw new Error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");"; 00561 } 00562 00563 $cache->set( $key, $result ); 00564 return $result; 00565 } else { 00566 return $contents; 00567 } 00568 } 00569 00573 protected static function javaScriptParser() { 00574 if ( !self::$jsParser ) { 00575 self::$jsParser = new JSParser(); 00576 } 00577 return self::$jsParser; 00578 } 00579 00586 protected static function safeFilemtime( $filename ) { 00587 if ( file_exists( $filename ) ) { 00588 return filemtime( $filename ); 00589 } else { 00590 // We only ever map this function on an array if we're gonna call max() after, 00591 // so return our standard minimum timestamps here. This is 1, not 0, because 00592 // wfTimestamp(0) == NOW 00593 return 1; 00594 } 00595 } 00596 }