MediaWiki  REL1_24
ResourceLoaderModule.php
Go to the documentation of this file.
00001 <?php
00028 abstract class ResourceLoaderModule {
00029     # Type of resource
00030     const TYPE_SCRIPTS = 'scripts';
00031     const TYPE_STYLES = 'styles';
00032     const TYPE_MESSAGES = 'messages';
00033     const TYPE_COMBINED = 'combined';
00034 
00035     # sitewide core module like a skin file or jQuery component
00036     const ORIGIN_CORE_SITEWIDE = 1;
00037 
00038     # per-user module generated by the software
00039     const ORIGIN_CORE_INDIVIDUAL = 2;
00040 
00041     # sitewide module generated from user-editable files, like MediaWiki:Common.js, or
00042     # modules accessible to multiple users, such as those generated by the Gadgets extension.
00043     const ORIGIN_USER_SITEWIDE = 3;
00044 
00045     # per-user module generated from user-editable files, like User:Me/vector.js
00046     const ORIGIN_USER_INDIVIDUAL = 4;
00047 
00048     # an access constant; make sure this is kept as the largest number in this group
00049     const ORIGIN_ALL = 10;
00050 
00051     # script and style modules form a hierarchy of trustworthiness, with core modules like
00052     # skins and jQuery as most trustworthy, and user scripts as least trustworthy.  We can
00053     # limit the types of scripts and styles we allow to load on, say, sensitive special
00054     # pages like Special:UserLogin and Special:Preferences
00055     protected $origin = self::ORIGIN_CORE_SITEWIDE;
00056 
00057     /* Protected Members */
00058 
00059     protected $name = null;
00060     protected $targets = array( 'desktop' );
00061 
00062     // In-object cache for file dependencies
00063     protected $fileDeps = array();
00064     // In-object cache for message blob mtime
00065     protected $msgBlobMtime = array();
00066 
00070     protected $config;
00071 
00072     /* Methods */
00073 
00080     public function getName() {
00081         return $this->name;
00082     }
00083 
00090     public function setName( $name ) {
00091         $this->name = $name;
00092     }
00093 
00101     public function getOrigin() {
00102         return $this->origin;
00103     }
00104 
00111     public function setOrigin( $origin ) {
00112         $this->origin = $origin;
00113     }
00114 
00119     public function getFlip( $context ) {
00120         global $wgContLang;
00121 
00122         return $wgContLang->getDir() !== $context->getDirection();
00123     }
00124 
00132     public function getScript( ResourceLoaderContext $context ) {
00133         // Stub, override expected
00134         return '';
00135     }
00136 
00141     public function getConfig() {
00142         if ( $this->config === null ) {
00143             // Ugh, fall back to default
00144             $this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
00145         }
00146 
00147         return $this->config;
00148     }
00149 
00154     public function setConfig( Config $config ) {
00155         $this->config = $config;
00156     }
00157 
00172     public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
00173         $resourceLoader = $context->getResourceLoader();
00174         $derivative = new DerivativeResourceLoaderContext( $context );
00175         $derivative->setModules( array( $this->getName() ) );
00176         $derivative->setOnly( 'scripts' );
00177         $derivative->setDebug( true );
00178 
00179         $url = $resourceLoader->createLoaderURL(
00180             $this->getSource(),
00181             $derivative
00182         );
00183 
00184         return array( $url );
00185     }
00186 
00193     public function supportsURLLoading() {
00194         return true;
00195     }
00196 
00205     public function getStyles( ResourceLoaderContext $context ) {
00206         // Stub, override expected
00207         return array();
00208     }
00209 
00219     public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
00220         $resourceLoader = $context->getResourceLoader();
00221         $derivative = new DerivativeResourceLoaderContext( $context );
00222         $derivative->setModules( array( $this->getName() ) );
00223         $derivative->setOnly( 'styles' );
00224         $derivative->setDebug( true );
00225 
00226         $url = $resourceLoader->createLoaderURL(
00227             $this->getSource(),
00228             $derivative
00229         );
00230 
00231         return array( 'all' => array( $url ) );
00232     }
00233 
00241     public function getMessages() {
00242         // Stub, override expected
00243         return array();
00244     }
00245 
00251     public function getGroup() {
00252         // Stub, override expected
00253         return null;
00254     }
00255 
00261     public function getSource() {
00262         // Stub, override expected
00263         return 'local';
00264     }
00265 
00273     public function getPosition() {
00274         return 'bottom';
00275     }
00276 
00284     public function isRaw() {
00285         return false;
00286     }
00287 
00293     public function getLoaderScript() {
00294         // Stub, override expected
00295         return false;
00296     }
00297 
00308     public function getDependencies() {
00309         // Stub, override expected
00310         return array();
00311     }
00312 
00318     public function getTargets() {
00319         return $this->targets;
00320     }
00321 
00336     public function getSkipFunction() {
00337         return null;
00338     }
00339 
00347     public function getFileDependencies( $skin ) {
00348         // Try in-object cache first
00349         if ( isset( $this->fileDeps[$skin] ) ) {
00350             return $this->fileDeps[$skin];
00351         }
00352 
00353         $dbr = wfGetDB( DB_SLAVE );
00354         $deps = $dbr->selectField( 'module_deps', 'md_deps', array(
00355                 'md_module' => $this->getName(),
00356                 'md_skin' => $skin,
00357             ), __METHOD__
00358         );
00359         if ( !is_null( $deps ) ) {
00360             $this->fileDeps[$skin] = (array)FormatJson::decode( $deps, true );
00361         } else {
00362             $this->fileDeps[$skin] = array();
00363         }
00364         return $this->fileDeps[$skin];
00365     }
00366 
00373     public function setFileDependencies( $skin, $deps ) {
00374         $this->fileDeps[$skin] = $deps;
00375     }
00376 
00383     public function getMsgBlobMtime( $lang ) {
00384         if ( !isset( $this->msgBlobMtime[$lang] ) ) {
00385             if ( !count( $this->getMessages() ) ) {
00386                 return 0;
00387             }
00388 
00389             $dbr = wfGetDB( DB_SLAVE );
00390             $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
00391                     'mr_resource' => $this->getName(),
00392                     'mr_lang' => $lang
00393                 ), __METHOD__
00394             );
00395             // If no blob was found, but the module does have messages, that means we need
00396             // to regenerate it. Return NOW
00397             if ( $msgBlobMtime === false ) {
00398                 $msgBlobMtime = wfTimestampNow();
00399             }
00400             $this->msgBlobMtime[$lang] = wfTimestamp( TS_UNIX, $msgBlobMtime );
00401         }
00402         return $this->msgBlobMtime[$lang];
00403     }
00404 
00411     public function setMsgBlobMtime( $lang, $mtime ) {
00412         $this->msgBlobMtime[$lang] = $mtime;
00413     }
00414 
00415     /* Abstract Methods */
00416 
00435     public function getModifiedTime( ResourceLoaderContext $context ) {
00436         // 0 would mean now
00437         return 1;
00438     }
00439 
00447     public function getHashMtime( ResourceLoaderContext $context ) {
00448         $hash = $this->getModifiedHash( $context );
00449         if ( !is_string( $hash ) ) {
00450             return 0;
00451         }
00452 
00453         $cache = wfGetCache( CACHE_ANYTHING );
00454         $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash );
00455 
00456         $data = $cache->get( $key );
00457         if ( is_array( $data ) && $data['hash'] === $hash ) {
00458             // Hash is still the same, re-use the timestamp of when we first saw this hash.
00459             return $data['timestamp'];
00460         }
00461 
00462         $timestamp = wfTimestamp();
00463         $cache->set( $key, array(
00464             'hash' => $hash,
00465             'timestamp' => $timestamp,
00466         ) );
00467 
00468         return $timestamp;
00469     }
00470 
00480     public function getModifiedHash( ResourceLoaderContext $context ) {
00481         return null;
00482     }
00483 
00493     public function getDefinitionMtime( ResourceLoaderContext $context ) {
00494         wfProfileIn( __METHOD__ );
00495         $summary = $this->getDefinitionSummary( $context );
00496         if ( $summary === null ) {
00497             wfProfileOut( __METHOD__ );
00498             return 0;
00499         }
00500 
00501         $hash = md5( json_encode( $summary ) );
00502 
00503         $cache = wfGetCache( CACHE_ANYTHING );
00504 
00505         // Embed the hash itself in the cache key. This allows for a few nifty things:
00506         // - During deployment, servers with old and new versions of the code communicating
00507         //   with the same memcached will not override the same key repeatedly increasing
00508         //   the timestamp.
00509         // - In case of the definition changing and then changing back in a short period of time
00510         //   (e.g. in case of a revert or a corrupt server) the old timestamp and client-side cache
00511         //   url will be re-used.
00512         // - If different context-combinations (e.g. same skin, same language or some combination
00513         //   thereof) result in the same definition, they will use the same hash and timestamp.
00514         $key = wfMemcKey( 'resourceloader', 'moduledefinition', $this->getName(), $hash );
00515 
00516         $data = $cache->get( $key );
00517         if ( is_int( $data ) && $data > 0 ) {
00518             // We've seen this hash before, re-use the timestamp of when we first saw it.
00519             wfProfileOut( __METHOD__ );
00520             return $data;
00521         }
00522 
00523         wfDebugLog( 'resourceloader', __METHOD__ . ": New definition hash for module "
00524             . "{$this->getName()} in context {$context->getHash()}: $hash." );
00525 
00526         $timestamp = time();
00527         $cache->set( $key, $timestamp );
00528 
00529         wfProfileOut( __METHOD__ );
00530         return $timestamp;
00531     }
00532 
00560     public function getDefinitionSummary( ResourceLoaderContext $context ) {
00561         return array(
00562             'class' => get_class( $this ),
00563         );
00564     }
00565 
00575     public function isKnownEmpty( ResourceLoaderContext $context ) {
00576         return false;
00577     }
00578 
00580     private static $jsParser;
00581     private static $parseCacheVersion = 1;
00582 
00591     protected function validateScriptFile( $fileName, $contents ) {
00592         if ( $this->getConfig()->get( 'ResourceLoaderValidateJS' ) ) {
00593             // Try for cache hit
00594             // Use CACHE_ANYTHING since filtering is very slow compared to DB queries
00595             $key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) );
00596             $cache = wfGetCache( CACHE_ANYTHING );
00597             $cacheEntry = $cache->get( $key );
00598             if ( is_string( $cacheEntry ) ) {
00599                 return $cacheEntry;
00600             }
00601 
00602             $parser = self::javaScriptParser();
00603             try {
00604                 $parser->parse( $contents, $fileName, 1 );
00605                 $result = $contents;
00606             } catch ( Exception $e ) {
00607                 // We'll save this to cache to avoid having to validate broken JS over and over...
00608                 $err = $e->getMessage();
00609                 $result = "throw new Error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");";
00610             }
00611 
00612             $cache->set( $key, $result );
00613             return $result;
00614         } else {
00615             return $contents;
00616         }
00617     }
00618 
00622     protected static function javaScriptParser() {
00623         if ( !self::$jsParser ) {
00624             self::$jsParser = new JSParser();
00625         }
00626         return self::$jsParser;
00627     }
00628 
00635     protected static function safeFilemtime( $filename ) {
00636         if ( file_exists( $filename ) ) {
00637             return filemtime( $filename );
00638         } else {
00639             // We only ever map this function on an array if we're gonna call max() after,
00640             // so return our standard minimum timestamps here. This is 1, not 0, because
00641             // wfTimestamp(0) == NOW
00642             return 1;
00643         }
00644     }
00645 }