MediaWiki  REL1_23
ResourceLoaderModule.php
Go to the documentation of this file.
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 }