MediaWiki  REL1_22
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 
00403     public function getHashMtime( ResourceLoaderContext $context ) {
00404         $hash = $this->getModifiedHash( $context );
00405         if ( !is_string( $hash ) ) {
00406             return 0;
00407         }
00408 
00409         $cache = wfGetCache( CACHE_ANYTHING );
00410         $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() );
00411 
00412         $data = $cache->get( $key );
00413         if ( is_array( $data ) && $data['hash'] === $hash ) {
00414             // Hash is still the same, re-use the timestamp of when we first saw this hash.
00415             return $data['timestamp'];
00416         }
00417 
00418         $timestamp = wfTimestamp();
00419         $cache->set( $key, array(
00420             'hash' => $hash,
00421             'timestamp' => $timestamp,
00422         ) );
00423 
00424         return $timestamp;
00425     }
00426 
00434     public function getModifiedHash( ResourceLoaderContext $context ) {
00435         return null;
00436     }
00437 
00447     public function isKnownEmpty( ResourceLoaderContext $context ) {
00448         return false;
00449     }
00450 
00452     private static $jsParser;
00453     private static $parseCacheVersion = 1;
00454 
00463     protected function validateScriptFile( $fileName, $contents ) {
00464         global $wgResourceLoaderValidateJS;
00465         if ( $wgResourceLoaderValidateJS ) {
00466             // Try for cache hit
00467             // Use CACHE_ANYTHING since filtering is very slow compared to DB queries
00468             $key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) );
00469             $cache = wfGetCache( CACHE_ANYTHING );
00470             $cacheEntry = $cache->get( $key );
00471             if ( is_string( $cacheEntry ) ) {
00472                 return $cacheEntry;
00473             }
00474 
00475             $parser = self::javaScriptParser();
00476             try {
00477                 $parser->parse( $contents, $fileName, 1 );
00478                 $result = $contents;
00479             } catch ( Exception $e ) {
00480                 // We'll save this to cache to avoid having to validate broken JS over and over...
00481                 $err = $e->getMessage();
00482                 $result = "throw new Error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");";
00483             }
00484 
00485             $cache->set( $key, $result );
00486             return $result;
00487         } else {
00488             return $contents;
00489         }
00490     }
00491 
00495     protected static function javaScriptParser() {
00496         if ( !self::$jsParser ) {
00497             self::$jsParser = new JSParser();
00498         }
00499         return self::$jsParser;
00500     }
00501 
00508     protected static function safeFilemtime( $filename ) {
00509         if ( file_exists( $filename ) ) {
00510             return filemtime( $filename );
00511         } else {
00512             // We only ever map this function on an array if we're gonna call max() after,
00513             // so return our standard minimum timestamps here. This is 1, not 0, because
00514             // wfTimestamp(0) == NOW
00515             return 1;
00516         }
00517     }
00518 }