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 = "' . htmlspecialchars( $_SERVER['SCRIPT_NAME'] ) . '";' . "\n";
50 $this->jsout
.= 'var mwSlGenISODate = "' . date( 'c' ) . '";' ."\n";
51 $this->jsout
.= 'var mwSlURID = "' . htmlspecialchars( $this->urid
) . '";' ."\n";
53 // swap in the appropriate language per js_file
54 foreach( $this->jsFileList
as $classKey => $file_name ){
56 // special case: - title classes:
57 if( substr( $classKey, 0, 3 ) == 'WT:' ){
59 // get just the tile part:
60 $title_block = substr( $classKey, 3 );
61 if( $title_block[0] == '-' && strpos( $title_block, '|' ) !== false ){
62 // special case of "-" title with skin
63 $parts = explode( '|', $title_block );
64 $title = array_shift( $parts );
65 foreach( $parts as $tparam ){
66 list( $key, $val ) = explode( '=', $tparam );
67 if( $key == 'useskin' ){
71 $sk = $wgUser->getSkin();
72 // make sure the skin name is valid
73 $skinNames = Skin
::getSkinNames();
74 // get the lower case skin name (array keys)
75 $skinNames = array_keys( $skinNames );
76 if( in_array( strtolower( $skin ), $skinNames ) ){
77 $this->jsout
.= $sk->generateUserJs( $skin ) . "\n";
82 //make sure the wiki title ends with .js
83 if( substr( $title_block, -3 ) != '.js'){
84 $this->error_msg
.= 'WikiTitle includes should end with .js';
87 // it's a wikiTitle append the output of the wikitext:
88 $t = Title
::newFromText( $title_block );
89 $a = new Article( $t );
90 // only get content if the page is not empty:
91 if( $a->getID() !== 0 ){
92 $this->jsout
.= $a->getContent() . "\n";
97 //dealing with files::
98 //check that the filename ends with .js and does not include ../ traversing
99 if( substr( $file_name, -3 ) != '.js'){
100 $this->error_msg
.= "\nError file name must end with .js: ". htmlspecialchars( $file_name ) . " \n ";
103 if( strpos($file_name, '../') !== false ){
104 $this->error_msg
.= "\nError file name must not traverse paths: ". htmlspecialchars( $file_name ) . " \n ";
108 if( trim( $file_name ) != '' ){
109 // if in debug add a comment with the file name:
111 $this->jsout
.= "\n/**
112 * File: ". htmlspecialchars( $file_name ) ."
114 $this->jsout
.= ( $this->doProccessJsFile( $file_name ) ) . "\n";
118 // check if we should minify :
119 if( $wgEnableScriptMinify && !$this->debug
){
120 // do the minification and output
121 $this->jsout
= JSMin
::minify( $this->jsout
);
123 // save to the file cache:
124 if( $wgUseFileCache && !$this->debug
) {
125 $status = $this->sFileCache
->saveToFileCache( $this->jsout
);
126 if( $status !== true )
127 $this->error_msg
.= $status;
129 // check for error msg:
130 if( $this->error_msg
!= ''){
131 echo 'alert(\'Error With ScriptLoader.php ::' . str_replace( "\n", '\'+"\n"+'."\n'", $this->error_msg
) . '\');';
132 echo trim( $this->jsout
);
134 // all good lets output cache forever headers:
135 $this->outputJsWithHeaders();
139 function outputJsHeaders(){
140 global $wgJsMimeType;
141 // output js mime type:
142 header( 'Content-type: ' . $wgJsMimeType );
143 header( 'Pragma: public' );
145 // (the point is we never have to revalidate since we should always change the request url based on the svn or article version)
146 $one_year = 60*60*24*365;
147 header( "Expires: " . gmdate( "D, d M Y H:i:s", time() +
$one_year ) . " GM" );
150 function outputJsWithHeaders(){
152 $this->outputJsHeaders();
154 if( wfClientAcceptsGzip() ) {
155 header( 'Content-Encoding: gzip' );
156 echo gzencode( $this->jsout
);
166 * updates the proc Request
168 function procRequestVars(){
169 global $wgContLanguageCode, $wgEnableScriptMinify, $wgJSAutoloadClasses,
170 $wgJSAutoloadLocalClasses, $wgStyleVersion, $wgEnableScriptLoaderJsFile;
173 if( ( isset( $_GET['debug'] ) && $_GET['debug'] == 'true' ) ||
( isset( $wgEnableScriptDebug ) && $wgEnableScriptDebug == true ) ){
177 // set the urid: (be sure to escape it as it goes into our js output)
178 if( isset( $_GET['urid'] ) && $_GET['urid'] !=''){
179 $this->urid
= htmlspecialchars( $_GET['urid'] );
181 // just give it the current style sheet id:
182 // @@todo read the svn version number
183 $this->urid
= $wgStyleVersion;
186 $reqClassList = false;
187 if( isset( $_GET['class'] ) && $_GET['class'] != '' ){
188 $reqClassList = explode( ',', $_GET['class'] );
191 // check for the requested classes
193 // clean the class list and populate jsFileList
194 foreach( $reqClassList as $reqClass ){
195 if( trim( $reqClass ) != '' ){
196 // check for special case '-' class for user generated js
197 if( substr( $reqClass, 0, 3 ) == 'WT:' ){
198 $this->jsFileList
[$reqClass] = true;
199 $this->rKey
.= $reqClass;
200 $this->jsvarurl
= true;
204 $reqClass = preg_replace("/[^A-Za-z0-9_\-\.]/", '', $reqClass );
206 if( isset( $wgJSAutoloadLocalClasses[$reqClass] ) ){
207 $this->jsFileList
[$reqClass] = $wgJSAutoloadLocalClasses[$reqClass];
208 $this->rKey
.= $reqClass;
209 } else if( isset( $wgJSAutoloadClasses[$reqClass] ) ) {
210 $this->jsFileList
[$reqClass] = $wgJSAutoloadClasses[$reqClass];
211 $this->rKey
.= $reqClass;
213 $this->error_msg
.= 'Requested class: ' . htmlspecialchars( $reqClass ) . ' not found' . "\n";
219 // check for requested files if enabled:
220 if( $wgEnableScriptLoaderJsFile ){
221 if( isset( $_GET['files'] ) ){
222 $reqFileList = explode( ',', isset( $_GET['files'] ) );
223 // clean the file list and populate jsFileList
224 foreach( $reqFileList as $reqFile ){
226 $reqFile = str_replace( '../', '', $reqFile );
227 // only allow alphanumeric underscores periods and ending with .js
228 $reqFile = ereg_replace( "[^A-Za-z0-9_\-\/\.]", '', $reqFile );
229 if( substr( $reqFile, -3 ) == '.js' ){
230 // don't add it twice:
231 if( !in_array( $reqFile, $jsFileList ) ) {
232 $this->jsFileList
[] = $IP . $reqFile;
233 $this->rKey
.= $reqFile;
236 $this->error_msg
.= 'Not valid requsted JavaScript file' . "\n";
242 // add the language code to the rKey:
243 $this->rKey
.= '_' . $wgContLanguageCode;
245 // add the unique rid to the rKey
246 $this->rKey
.= $this->urid
;
249 if( $wgEnableScriptMinify ){
250 $this->rKey
.= '_min';
254 function doProccessJsFile( $file_name ){
255 global $IP, $wgEnableScriptLocalization, $IP;
258 $str = @file_get_contents
( "{$IP}/{$file_name}" );
260 if( $str === false ){
261 // @@todo check php error level (don't want to expose paths if errors are hidden)
262 $this->error_msg
.= 'Requested File: ' . htmlspecialchars( $file_name ) . ' could not be read' . "\n";
265 $this->cur_file
= $file_name;
267 // strip out js_log debug lines not much luck with this regExp yet:
268 //if( !$this->debug )
269 // $str = preg_replace('/\n\s*js_log\s*\([^\)]([^;]|\n])*;/', "\n", $str);
272 if( $wgEnableScriptLocalization )
273 $str = preg_replace_callback(
274 '/loadGM\s*\(\s*{(.*)}\s*\)\s*/siU', // @@todo fix: will break down if someone does }) in their msg text
275 array( $this, 'languageMsgReplace' ),
282 function languageMsgReplace( $jvar ){
283 if( !isset( $jvar[1] ) )
286 $jmsg = json_decode( '{' . $jvar[1] . '}', true );
287 // do the language lookup:
289 foreach( $jmsg as $msgKey => $default_en_value ){
290 $jmsg[$msgKey] = wfMsgNoTrans( $msgKey );
292 //return the updated loadGM json with fixed new lines:
293 return 'loadGM( ' . json_encode( $jmsg ) . ')';
295 $this->error_msg
.= "Could not parse JSON language msg in File:\n" .
296 htmlspecialchars ( $this->cur_file
) . "\n";
298 // could not parse json (throw error?)
303 //a simple version of HTMLFileCache (@@todo abstract shared pieces)
304 class simpleFileCache
{
306 var $filename = null;
309 public function __construct( &$rKey ) {
311 $this->filename
= $this->fileCacheName(); // init name
314 public function fileCacheName() {
316 if( !$this->mFileCache
) {
317 global $wgFileCacheDirectory;
319 $hash = md5( $this->rKey
);
320 # Avoid extension confusion
321 $key = str_replace( '.', '%2E', urlencode( $this->rKey
) );
323 $hash1 = substr( $hash, 0, 1 );
324 $hash2 = substr( $hash, 0, 2 );
325 $this->mFileCache
= "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$this->rKey}.js";
328 $this->mFileCache
.= '.gz';
330 wfDebug( " fileCacheName() - {$this->mFileCache}\n" );
332 return $this->mFileCache
;
335 public function isFileCached() {
336 return file_exists( $this->filename
);
339 public function outputFromFileCache(){
342 if( wfClientAcceptsGzip() ) {
343 header( 'Content-Encoding: gzip' );
344 readfile( $this->filename
);
346 /* Send uncompressed (check if fileCache is in compressed state (ends with .gz)
347 * (unlikely to execute this since $wgUseGzip would have created a new file above.. but just in case:
349 if( substr( $this->filename
, -3 ) == '.gz' ){
350 readgzfile( $this->filename
);
352 readfile( $this->filename
);
356 // just output the file
357 readfile( $this->filename
);
363 public function saveToFileCache( &$text ) {
364 global $wgUseFileCache, $wgUseGzip;
365 if( !$wgUseFileCache ) {
366 return 'Error: Called saveToFileCache with $wgUseFileCache off';
368 if( strcmp( $text, '' ) == 0 ) return 'saveToFileCache: empty output file';
370 // check the directories if we could not create them error out:
371 $status = $this->checkCacheDirs();
374 $outputText = gzencode( trim( $text ) );
376 $outputText = trim( $text );
379 if( $status !== true )
381 $f = fopen( $this->filename
, 'w' );
383 fwrite( $f, $outputText );
386 return 'Could not open file for writing. Check your cache directory permissions?';
391 protected function checkCacheDirs() {
392 $mydir2 = substr( $this->filename
, 0, strrpos( $this->filename
, '/' ) ); # subdirectory level 2
393 $mydir1 = substr( $mydir2, 0, strrpos( $mydir2, '/' ) ); # subdirectory level 1
395 if( wfMkdirParents( $mydir1 ) === false ||
wfMkdirParents( $mydir2 ) === false ){
396 return 'Could not create cache directory. Check your cache directory permissions?';