3 * This core jsScriptLoader class provides the script loader functionality
6 // check if we are being invoked in MediaWiki context or stand alone usage:
7 if ( !defined( 'MEDIAWIKI' ) ){
8 // load noMediaWiki helper:
9 require_once( realpath( dirname( __FILE__
) ) . '/php/noMediaWikiConfig.php' );
11 // run the main action:
12 $myScriptLoader = new jsScriptLoader();
13 // preset request values via normal $_GET operation:
14 $myScriptLoader->doScriptLoader();
16 $wgExtensionMessagesFiles['mwEmbed'] = realpath( dirname( __FILE__
) ) . '/php/mwEmbed.i18n.php';
19 // setup page output hook
20 class jsScriptLoader
{
21 var $jsFileList = array();
23 var $rKey = ''; // the request key
26 var $jsvarurl = false; // if we should include generated js (special class '-')
27 var $doProcReqFlag = true;
29 function doScriptLoader(){
30 global $wgJSAutoloadClasses, $wgJSAutoloadLocalClasses, $wgEnableScriptLoaderJsFile, $IP,
31 $wgEnableScriptMinify, $wgUseFileCache;
33 // process the request
34 $this->procRequestVars();
36 // if cache is on and file is present grab it from there:
37 if( $wgUseFileCache && !$this->debug
) {
38 // setup file cache obj:
39 $this->sFileCache
= new simpleFileCache( $this->rKey
);
40 if( $this->sFileCache
->isFileCached() ){
41 // just output headers so we can use php "efficient" readfile
42 $this->outputJsHeaders();
43 $this->sFileCache
->outputFromFileCache();
48 // setup script loader header info
49 $this->jsout
.= 'var mwSlScript = "' . $_SERVER['SCRIPT_NAME'] . '";' . "\n";
50 $this->jsout
.= 'var mwSlGenISODate = "' . date( 'c' ) . '";' ."\n";
51 $this->jsout
.= 'var mwSlURID = "' . $this->urid
. '";' ."\n";
53 // swap in the appropriate language per js_file
54 foreach( $this->jsFileList
as $classKey => $file_name ){
55 // special case: - title classes:
56 if( substr( $classKey, 0, 3 ) == 'WT:' ){
58 // get just the tile part:
59 $title_block = substr( $classKey, 3 );
60 if( $title_block[0] == '-' && strpos( $title_block, '|' ) !== false ){
61 // special case of "-" title with skin
62 $parts = explode( '|', $title_block );
63 $title = array_shift( $parts );
64 foreach( $parts as $tparam ){
65 list( $key, $val ) = explode( '=', $tparam );
66 if( $key == 'useskin' ){
70 $sk = $wgUser->getSkin();
71 // make sure the skin name is valid
72 $skinNames = Skin
::getSkinNames();
73 // get the lower case skin name (array keys)
74 $skinNames = array_keys( $skinNames );
75 if( in_array( strtolower( $skin ), $skinNames ) ){
76 $this->jsout
.= $sk->generateUserJs( $skin ) . "\n";
81 // it's a wikiTitle append the output of the wikitext:
82 $t = Title
::newFromText( $title_block );
83 $a = new Article( $t );
84 // only get content if the page is not empty:
85 if( $a->getID() !== 0 ){
86 $this->jsout
.= $a->getContent() . "\n";
92 if( trim( $file_name ) != '' ){
93 // if in debug add a comment with the file name:
95 $this->jsout
.= "\n/**
98 $this->jsout
.= ( $this->doProccessJsFile( $file_name ) ) . "\n";
101 // check if we should minify :
102 if( $wgEnableScriptMinify && !$this->debug
){
103 // do the minification and output
104 $this->jsout
= JSMin
::minify( $this->jsout
);
106 // save to the file cache:
107 if( $wgUseFileCache && !$this->debug
) {
108 $status = $this->sFileCache
->saveToFileCache( $this->jsout
);
109 if( $status !== true )
110 $this->error_msg
.= $status;
112 // check for error msg:
113 if( $this->error_msg
!= ''){
114 echo 'alert(\'Error With ScriptLoader.php ::' . str_replace( "\n", '\'+"\n"+'."\n'", $this->error_msg
) . '\');';
115 echo trim( $this->jsout
);
117 // all good lets output cache forever headers:
118 $this->outputJsWithHeaders();
122 function outputJsHeaders(){
123 global $wgJsMimeType;
124 // output js mime type:
125 header( 'Content-type: ' . $wgJsMimeType );
126 header( 'Pragma: public' );
128 // (the point is we never have to revalidate since we should always change the request url based on the svn or article version)
129 $one_year = 60*60*24*365;
130 header( "Expires: " . gmdate( "D, d M Y H:i:s", time() +
$one_year ) . " GM" );
133 function outputJsWithHeaders(){
135 $this->outputJsHeaders();
137 if( wfClientAcceptsGzip() ) {
138 header( 'Content-Encoding: gzip' );
139 echo gzencode( $this->jsout
);
149 * updates the proc Request
151 function procRequestVars(){
152 global $wgContLanguageCode, $wgEnableScriptMinify, $wgJSAutoloadClasses,
153 $wgJSAutoloadLocalClasses, $wgStyleVersion, $wgEnableScriptLoaderJsFile;
156 if( ( isset( $_GET['debug'] ) && $_GET['debug'] == 'true' ) ||
( isset( $wgEnableScriptDebug ) && $wgEnableScriptDebug == true ) ){
160 // set the urid: (be sure to escape it as it goes into our js output)
161 if( isset( $_GET['urid'] ) && $_GET['urid'] !=''){
162 $this->urid
= htmlspecialchars( $_GET['urid'] );
164 // just give it the current style sheet id:
165 // @@todo read the svn version number
166 $this->urid
= $wgStyleVersion;
169 $reqClassList = false;
170 if( isset( $_GET['class'] ) && $_GET['class'] != '' ){
171 $reqClassList = explode( ',', $_GET['class'] );
174 // check for the requested classes
176 // clean the class list and populate jsFileList
177 foreach( $reqClassList as $reqClass ){
178 if( trim( $reqClass ) != '' ){
179 // check for special case '-' class for user generated js
180 if( substr( $reqClass, 0, 3 ) == 'WT:' ){
181 $this->jsFileList
[$reqClass] = true;
182 $this->rKey
.= $reqClass;
183 $this->jsvarurl
= true;
187 $reqClass = preg_replace("/[^A-Za-z0-9_\-\.]/", '', $reqClass );
189 if( isset( $wgJSAutoloadLocalClasses[$reqClass] ) ){
190 $this->jsFileList
[$reqClass] = $wgJSAutoloadLocalClasses[$reqClass];
191 $this->rKey
.= $reqClass;
192 } else if( isset( $wgJSAutoloadClasses[$reqClass] ) ) {
193 $this->jsFileList
[$reqClass] = $wgJSAutoloadClasses[$reqClass];
194 $this->rKey
.= $reqClass;
196 $this->error_msg
.= 'Requested class: ' . $reqClass . ' not found' . "\n";
202 // check for requested files if enabled:
203 if( $wgEnableScriptLoaderJsFile ){
204 if( isset( $_GET['files'] ) ){
205 $reqFileList = explode( ',', isset( $_GET['files'] ) );
206 // clean the file list and populate jsFileList
207 foreach( $reqFileList as $reqFile ){
209 $reqFile = str_replace( '../', '', $reqFile );
210 // only allow alphanumeric underscores periods and ending with .js
211 $reqFile = ereg_replace( "[^A-Za-z0-9_\-\/\.]", '', $reqFile );
212 if( substr( $reqFile, -3 ) == '.js' ){
213 // don't add it twice:
214 if( !in_array( $reqFile, $jsFileList ) ) {
215 $this->jsFileList
[] = $IP . $reqFile;
216 $this->rKey
.= $reqFile;
219 $this->error_msg
.= 'Not valid requsted JavaScript file' . "\n";
225 // add the language code to the rKey:
226 $this->rKey
.= '_' . $wgContLanguageCode;
228 // add the unique rid to the rKey
229 $this->rKey
.= $this->urid
;
232 if( $wgEnableScriptMinify ){
233 $this->rKey
.= '_min';
237 function doProccessJsFile( $file_name ){
238 global $IP, $wgEnableScriptLocalization, $IP;
241 $str = @file_get_contents
( "{$IP}/{$file_name}" );
243 if( $str === false ){
244 // @@todo check php error level (don't want to expose paths if errors are hidden)
245 $this->error_msg
.= 'Requested File: ' . htmlspecialchars( $file_name ) . ' could not be read' . "\n";
248 $this->cur_file
= $file_name;
250 // strip out js_log debug lines not much luck with this regExp yet:
251 //if( !$this->debug )
252 // $str = preg_replace('/\n\s*js_log\s*\([^\)]([^;]|\n])*;/', "\n", $str);
255 if( $wgEnableScriptLocalization )
256 $str = preg_replace_callback(
257 '/loadGM\s*\(\s*{(.*)}\s*\)\s*/siU', // @@todo fix: will break down if someone does }) in their msg text
258 array( $this, 'languageMsgReplace' ),
265 function languageMsgReplace( $jvar ){
266 if( !isset( $jvar[1] ) )
269 $jmsg = json_decode( '{' . $jvar[1] . '}', true );
270 // do the language lookup:
272 foreach( $jmsg as $msgKey => $default_en_value ){
273 $jmsg[$msgKey] = wfMsgNoTrans( $msgKey );
275 //return the updated loadGM json with fixed new lines:
276 return 'loadGM( ' . json_encode( $jmsg ) . ')';
278 $this->error_msg
.= "Could not parse JSON language msg in File:\n" .
279 $this->cur_file
. "\n";
281 // could not parse json (throw error?)
286 //a simple version of HTMLFileCache (@@todo abstract shared pieces)
287 class simpleFileCache
{
289 var $filename = null;
292 public function __construct( &$rKey ) {
294 $this->filename
= $this->fileCacheName(); // init name
297 public function fileCacheName() {
299 if( !$this->mFileCache
) {
300 global $wgFileCacheDirectory;
302 $hash = md5( $this->rKey
);
303 # Avoid extension confusion
304 $key = str_replace( '.', '%2E', urlencode( $this->rKey
) );
306 $hash1 = substr( $hash, 0, 1 );
307 $hash2 = substr( $hash, 0, 2 );
308 $this->mFileCache
= "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$this->rKey}.js";
311 $this->mFileCache
.= '.gz';
313 wfDebug( " fileCacheName() - {$this->mFileCache}\n" );
315 return $this->mFileCache
;
318 public function isFileCached() {
319 return file_exists( $this->filename
);
322 public function outputFromFileCache(){
325 if( wfClientAcceptsGzip() ) {
326 header( 'Content-Encoding: gzip' );
327 readfile( $this->filename
);
329 /* Send uncompressed (check if fileCache is in compressed state (ends with .gz)
330 * (unlikely to execute this since $wgUseGzip would have created a new file above.. but just in case:
332 if( substr( $this->filename
, -3 ) == '.gz' ){
333 readgzfile( $this->filename
);
335 readfile( $this->filename
);
339 // just output the file
340 readfile( $this->filename
);
346 public function saveToFileCache( &$text ) {
347 global $wgUseFileCache, $wgUseGzip;
348 if( !$wgUseFileCache ) {
349 return 'Error: Called saveToFileCache with $wgUseFileCache off';
351 if( strcmp( $text, '' ) == 0 ) return 'saveToFileCache: empty output file';
353 // check the directories if we could not create them error out:
354 $status = $this->checkCacheDirs();
357 $outputText = gzencode( trim( $text ) );
359 $outputText = trim( $text );
362 if( $status !== true )
364 $f = fopen( $this->filename
, 'w' );
366 fwrite( $f, $outputText );
369 return 'Could not open file for writing. Check your cache directory permissions?';
374 protected function checkCacheDirs() {
375 $mydir2 = substr( $this->filename
, 0, strrpos( $this->filename
, '/' ) ); # subdirectory level 2
376 $mydir1 = substr( $mydir2, 0, strrpos( $mydir2, '/' ) ); # subdirectory level 1
378 if( wfMkdirParents( $mydir1 ) === false ||
wfMkdirParents( $mydir2 ) === false ){
379 return 'Could not create cache directory. Check your cache directory permissions?';