3a33a3d946a976bc781b22e60dbcb065a0eda895
[lhc/web/wiklou.git] / js2 / mwEmbed / jsScriptLoader.php
1 <?php
2 /**
3 * This core jsScriptLoader class provides the script loader functionality
4 * @file
5 */
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' );
10
11 // run the main action:
12 $myScriptLoader = new jsScriptLoader();
13 // preset request values via normal $_GET operation:
14 $myScriptLoader->doScriptLoader();
15 } else {
16 $wgExtensionMessagesFiles['mwEmbed'] = realpath( dirname( __FILE__ ) ) . '/php/mwEmbed.i18n.php';
17 }
18
19 // setup page output hook
20 class jsScriptLoader {
21 var $jsFileList = array();
22 var $jsout = '';
23 var $rKey = ''; // the request key
24 var $error_msg = '';
25 var $debug = false;
26 var $jsvarurl = false; // if we should include generated js (special class '-')
27 var $doProcReqFlag = true;
28
29 function doScriptLoader(){
30 global $wgJSAutoloadClasses, $wgJSAutoloadLocalClasses, $wgEnableScriptLoaderJsFile, $IP,
31 $wgEnableScriptMinify, $wgUseFileCache;
32
33 // process the request
34 $this->procRequestVars();
35
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();
44 die();
45 }
46 }
47
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";
52 // Build the output:
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:' ){
57 // get just the tile part:
58 $title_block = substr( $classKey, 3 );
59 if( $title_block[0] == '-' && strpos( $title_block, '|' ) !== false ){
60 // special case of "-" title with skin
61 $parts = explode( '|', $title_block );
62 $title = array_shift( $parts );
63 foreach( $parts as $tparam ){
64 list( $key, $val ) = explode( '=', $tparam );
65 if( $key == 'useskin' ){
66 $skin = $val;
67 }
68 }
69 // make sure the skin name is valid
70 $skinNames = Skin::getSkinNames();
71 // get the lower case skin name (array keys)
72 $skinNames = array_keys( $skinNames );
73 if( in_array( strtolower( $skin ), $skinNames ) ){
74 $this->jsout .= Skin::generateUserJs( $skin ) . "\n";
75 // success continue:
76 continue;
77 }
78 } else {
79 // it's a wikiTitle append the output of the wikitext:
80 $t = Title::newFromText( $title_block );
81 $a = new Article( $t );
82 // only get content if the page is not empty:
83 if( $a->getID() !== 0 ){
84 $this->jsout .= $a->getContent() . "\n";
85 }
86 continue;
87 }
88 }
89
90 if( trim( $file_name ) != '' ){
91 // if in debug add a comment with the file name:
92 if( $this->debug )
93 $this->jsout .= "\n/**
94 * File: $file_name
95 */\n";
96 $this->jsout .= ( $this->doProccessJsFile( $file_name ) ) . "\n";
97 }
98 }
99 // check if we should minify :
100 if( $wgEnableScriptMinify && !$this->debug ){
101 // do the minification and output
102 $this->jsout = JSMin::minify( $this->jsout);
103 }
104 // save to the file cache:
105 if( $wgUseFileCache && !$this->debug ) {
106 $status = $this->sFileCache->saveToFileCache( $this->jsout );
107 if( $status !== true )
108 $this->error_msg.= $status;
109 }
110 // check for error msg:
111 if( $this->error_msg != ''){
112 echo 'alert(\'Error With ScriptLoader.php ::' . str_replace( "\n", '\'+"\n"+'."\n'", $this->error_msg ) . '\');';
113 echo trim( $this->jsout );
114 } else {
115 // all good lets output cache forever headers:
116 $this->outputJsWithHeaders();
117 }
118 }
119
120 function outputJsHeaders(){
121 global $wgJsMimeType;
122 // output js mime type:
123 header( 'Content-type: ' . $wgJsMimeType );
124 header( 'Pragma: public' );
125 // cache forever:
126 // (the point is we never have to revalidate since we should always change the request url based on the svn or article version)
127 $one_year = 60*60*24*365;
128 header( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $one_year ) . " GM" );
129 }
130
131 function outputJsWithHeaders(){
132 global $wgUseGzip;
133 $this->outputJsHeaders();
134 if( $wgUseGzip ) {
135 if( wfClientAcceptsGzip() ) {
136 header( 'Content-Encoding: gzip' );
137 echo gzencode( $this->jsout );
138 } else {
139 echo $this->jsout;
140 }
141 } else {
142 echo $this->jsout;
143 }
144 }
145
146 /**
147 * updates the proc Request
148 */
149 function procRequestVars(){
150 global $wgContLanguageCode, $wgEnableScriptMinify, $wgJSAutoloadClasses, $wgJSAutoloadLocalClasses, $wgStyleVersion;
151
152 // set debug flag:
153 if( ( isset( $_GET['debug'] ) && $_GET['debug'] == 'true' ) || ( isset( $wgEnableScriptDebug ) && $wgEnableScriptDebug == true ) ){
154 $this->debug = true;
155 }
156
157 // set the urid: (be sure to escape it as it goes into our js output)
158 if( isset( $_GET['urid'] ) && $_GET['urid'] !=''){
159 $this->urid = htmlspecialchars( $_GET['urid'] );
160 } else {
161 // just give it the current style sheet id:
162 // @@todo read the svn version number
163 $this->urid = $wgStyleVersion;
164 }
165
166 $reqClassList = false;
167 if( isset( $_GET['class'] ) && $_GET['class'] != '' ){
168 $reqClassList = explode( ',', $_GET['class'] );
169 }
170
171 // check for the requested classes
172 if( $reqClassList ){
173 // clean the class list and populate jsFileList
174 foreach( $reqClassList as $reqClass ){
175 if( trim( $reqClass ) != '' ){
176 // check for special case '-' class for user generated js
177 if( substr( $reqClass, 0, 3 ) == 'WT:' ){
178 $this->jsFileList[$reqClass] = true;
179 $this->rKey .= $reqClass;
180 $this->jsvarurl = true;
181 continue;
182 }
183
184 $reqClass = ereg_replace("[^A-Za-z0-9_\-\.]", '', $reqClass );
185
186 if( isset( $wgJSAutoloadLocalClasses[$reqClass] ) ){
187 $this->jsFileList[$reqClass] = $wgJSAutoloadLocalClasses[$reqClass];
188 $this->rKey.= $reqClass;
189 } else if( isset( $wgJSAutoloadClasses[$reqClass] ) ) {
190 $this->jsFileList[$reqClass] = $wgJSAutoloadClasses[$reqClass];
191 $this->rKey.= $reqClass;
192 } else {
193 $this->error_msg.= 'Requested class: ' . $reqClass . ' not found' . "\n";
194 }
195 }
196 }
197 }
198
199 // check for requested files if enabled:
200 if( $wgEnableScriptLoaderJsFile ){
201 if( isset( $_GET['files'] ) ){
202 $reqFileList = explode( ',', isset( $_GET['files'] ) );
203 // clean the file list and populate jsFileList
204 foreach( $reqFileList as $reqFile ){
205 // no jumping dirs:
206 $reqFile = str_replace( '../', '', $reqFile );
207 // only allow alphanumeric underscores periods and ending with .js
208 $reqFile = ereg_replace( "[^A-Za-z0-9_\-\/\.]", '', $reqFile );
209 if( substr( $reqFile, -3 ) == '.js' ){
210 // don't add it twice:
211 if( !in_array( $reqFile, $jsFileList ) ) {
212 $this->jsFileList[] = $IP . $reqFile;
213 $this->rKey.= $reqFile;
214 }
215 } else {
216 $this->error_msg.= 'Not valid requsted JavaScript file' . "\n";
217 }
218 }
219 }
220 }
221
222 // add the language code to the rKey:
223 $this->rKey .= '_' . $wgContLanguageCode;
224
225 // add the unique rid to the rKey
226 $this->rKey .= $this->urid;
227
228 // add a min flag:
229 if( $wgEnableScriptMinify ){
230 $this->rKey.= '_min';
231 }
232 }
233
234 function doProccessJsFile( $file_name ){
235 global $IP, $wgEnableScriptLocalization, $IP;
236
237 // load the file:
238 $str = @file_get_contents( "{$IP}/{$file_name}" );
239
240 if( $str === false ){
241 // @@todo check php error level (don't want to expose paths if errors are hidden)
242 $this->error_msg.= 'Requested File: ' . htmlspecialchars( $file_name ) . ' could not be read' . "\n";
243 return '';
244 }
245 $this->cur_file = $file_name;
246
247 // strip out js_log debug lines not much luck with this regExp yet:
248 //if( !$this->debug )
249 // $str = preg_replace('/\n\s*js_log\s*\([^\)]([^;]|\n])*;/', "\n", $str);
250
251 // do language swap
252 if( $wgEnableScriptLocalization )
253 $str = preg_replace_callback(
254 '/loadGM\s*\(\s*{(.*)}\s*\)\s*/siU', // @@todo fix: will break down if someone does }) in their msg text
255 array( $this, 'languageMsgReplace' ),
256 $str
257 );
258
259 return $str;
260 }
261
262 function languageMsgReplace( $jvar ){
263 if( !isset( $jvar[1] ) )
264 return;
265
266 $jmsg = json_decode( '{' . $jvar[1] . '}', true );
267 // do the language lookup:
268 if( $jmsg ){
269 foreach( $jmsg as $msgKey => $default_en_value ){
270 $jmsg[$msgKey] = wfMsgNoTrans( $msgKey );
271 }
272 //return the updated loadGM json with fixed new lines:
273 return 'loadGM( ' . json_encode( $jmsg ) . ')';
274 } else {
275 $this->error_msg.= "Could not parse JSON language msg in File:\n" .
276 $this->cur_file . "\n";
277 }
278 // could not parse json (throw error?)
279 return $jvar[0];
280 }
281 }
282
283 //a simple version of HTMLFileCache (@@todo abstract shared pieces)
284 class simpleFileCache {
285 var $mFileCache;
286 var $filename = null;
287 var $rKey = null;
288
289 public function __construct( &$rKey ) {
290 $this->rKey = $rKey;
291 $this->filename = $this->fileCacheName(); // init name
292 }
293
294 public function fileCacheName() {
295 global $wgUseGzip;
296 if( !$this->mFileCache ) {
297 global $wgFileCacheDirectory;
298
299 $hash = md5( $this->rKey );
300 # Avoid extension confusion
301 $key = str_replace( '.', '%2E', urlencode( $this->rKey ) );
302
303 $hash1 = substr( $hash, 0, 1 );
304 $hash2 = substr( $hash, 0, 2 );
305 $this->mFileCache = "{$wgFileCacheDirectory}/{$subdir}{$hash1}/{$hash2}/{$this->rKey}.js";
306
307 if( $wgUseGzip )
308 $this->mFileCache .= '.gz';
309
310 wfDebug( " fileCacheName() - {$this->mFileCache}\n" );
311 }
312 return $this->mFileCache;
313 }
314
315 public function isFileCached() {
316 return file_exists( $this->filename );
317 }
318
319 public function outputFromFileCache(){
320 global $wgUseGzip;
321 if( $wgUseGzip ) {
322 if( wfClientAcceptsGzip() ) {
323 header( 'Content-Encoding: gzip' );
324 readfile( $this->filename );
325 } else {
326 /* Send uncompressed (check if fileCache is in compressed state (ends with .gz)
327 * (unlikely to execute this since $wgUseGzip would have created a new file above.. but just in case:
328 */
329 if( substr( $this->filename, -3 ) == '.gz' ){
330 readgzfile( $this->filename );
331 } else {
332 readfile( $this->filename );
333 }
334 }
335 } else {
336 // just output the file
337 readfile( $this->filename );
338 }
339 //return true
340 return true;
341 }
342
343 public function saveToFileCache( &$text ) {
344 global $wgUseFileCache, $wgUseGzip;
345 if( !$wgUseFileCache ) {
346 return 'Error: Called saveToFileCache with $wgUseFileCache off';
347 }
348 if( strcmp( $text, '' ) == 0 ) return 'saveToFileCache: empty output file';
349
350 // check the directories if we could not create them error out:
351 $status = $this->checkCacheDirs();
352
353 if( $wgUseGzip ){
354 $outputText = gzencode( trim( $text ) );
355 } else {
356 $outputText = trim( $text );
357 }
358
359 if( $status !== true )
360 return $status;
361 $f = fopen( $this->filename, 'w' );
362 if( $f ) {
363 fwrite( $f, $outputText );
364 fclose( $f );
365 } else {
366 return 'Could not open file for writing. Check your cache directory permissions?';
367 }
368 return true;
369 }
370
371 protected function checkCacheDirs() {
372 $mydir2 = substr( $this->filename, 0, strrpos( $this->filename, '/' ) ); # subdirectory level 2
373 $mydir1 = substr( $mydir2, 0, strrpos( $mydir2, '/' ) ); # subdirectory level 1
374
375 if( wfMkdirParents( $mydir1 ) === false || wfMkdirParents( $mydir2 ) === false ){
376 return 'Could not create cache directory. Check your cache directory permissions?';
377 } else {
378 return true;
379 }
380 }
381 }