some firefogg fixes for target form selection
[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 global $wgUser;
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' ){
67 $skin = $val;
68 }
69 }
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";
77 // success continue:
78 continue;
79 }
80 } else {
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";
87 }
88 continue;
89 }
90 }
91
92 if( trim( $file_name ) != '' ){
93 // if in debug add a comment with the file name:
94 if( $this->debug )
95 $this->jsout .= "\n/**
96 * File: $file_name
97 */\n";
98 $this->jsout .= ( $this->doProccessJsFile( $file_name ) ) . "\n";
99 }
100 }
101 // check if we should minify :
102 if( $wgEnableScriptMinify && !$this->debug ){
103 // do the minification and output
104 $this->jsout = JSMin::minify( $this->jsout);
105 }
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;
111 }
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 );
116 } else {
117 // all good lets output cache forever headers:
118 $this->outputJsWithHeaders();
119 }
120 }
121
122 function outputJsHeaders(){
123 global $wgJsMimeType;
124 // output js mime type:
125 header( 'Content-type: ' . $wgJsMimeType );
126 header( 'Pragma: public' );
127 // cache forever:
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" );
131 }
132
133 function outputJsWithHeaders(){
134 global $wgUseGzip;
135 $this->outputJsHeaders();
136 if( $wgUseGzip ) {
137 if( wfClientAcceptsGzip() ) {
138 header( 'Content-Encoding: gzip' );
139 echo gzencode( $this->jsout );
140 } else {
141 echo $this->jsout;
142 }
143 } else {
144 echo $this->jsout;
145 }
146 }
147
148 /**
149 * updates the proc Request
150 */
151 function procRequestVars(){
152 global $wgContLanguageCode, $wgEnableScriptMinify, $wgJSAutoloadClasses,
153 $wgJSAutoloadLocalClasses, $wgStyleVersion, $wgEnableScriptLoaderJsFile;
154
155 // set debug flag:
156 if( ( isset( $_GET['debug'] ) && $_GET['debug'] == 'true' ) || ( isset( $wgEnableScriptDebug ) && $wgEnableScriptDebug == true ) ){
157 $this->debug = true;
158 }
159
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'] );
163 } else {
164 // just give it the current style sheet id:
165 // @@todo read the svn version number
166 $this->urid = $wgStyleVersion;
167 }
168
169 $reqClassList = false;
170 if( isset( $_GET['class'] ) && $_GET['class'] != '' ){
171 $reqClassList = explode( ',', $_GET['class'] );
172 }
173
174 // check for the requested classes
175 if( $reqClassList ){
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;
184 continue;
185 }
186
187 $reqClass = ereg_replace("[^A-Za-z0-9_\-\.]", '', $reqClass );
188
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;
195 } else {
196 $this->error_msg.= 'Requested class: ' . $reqClass . ' not found' . "\n";
197 }
198 }
199 }
200 }
201
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 ){
208 // no jumping dirs:
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;
217 }
218 } else {
219 $this->error_msg.= 'Not valid requsted JavaScript file' . "\n";
220 }
221 }
222 }
223 }
224
225 // add the language code to the rKey:
226 $this->rKey .= '_' . $wgContLanguageCode;
227
228 // add the unique rid to the rKey
229 $this->rKey .= $this->urid;
230
231 // add a min flag:
232 if( $wgEnableScriptMinify ){
233 $this->rKey.= '_min';
234 }
235 }
236
237 function doProccessJsFile( $file_name ){
238 global $IP, $wgEnableScriptLocalization, $IP;
239
240 // load the file:
241 $str = @file_get_contents( "{$IP}/{$file_name}" );
242
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";
246 return '';
247 }
248 $this->cur_file = $file_name;
249
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);
253
254 // do language swap
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' ),
259 $str
260 );
261
262 return $str;
263 }
264
265 function languageMsgReplace( $jvar ){
266 if( !isset( $jvar[1] ) )
267 return;
268
269 $jmsg = json_decode( '{' . $jvar[1] . '}', true );
270 // do the language lookup:
271 if( $jmsg ){
272 foreach( $jmsg as $msgKey => $default_en_value ){
273 $jmsg[$msgKey] = wfMsgNoTrans( $msgKey );
274 }
275 //return the updated loadGM json with fixed new lines:
276 return 'loadGM( ' . json_encode( $jmsg ) . ')';
277 } else {
278 $this->error_msg.= "Could not parse JSON language msg in File:\n" .
279 $this->cur_file . "\n";
280 }
281 // could not parse json (throw error?)
282 return $jvar[0];
283 }
284 }
285
286 //a simple version of HTMLFileCache (@@todo abstract shared pieces)
287 class simpleFileCache {
288 var $mFileCache;
289 var $filename = null;
290 var $rKey = null;
291
292 public function __construct( &$rKey ) {
293 $this->rKey = $rKey;
294 $this->filename = $this->fileCacheName(); // init name
295 }
296
297 public function fileCacheName() {
298 global $wgUseGzip;
299 if( !$this->mFileCache ) {
300 global $wgFileCacheDirectory;
301
302 $hash = md5( $this->rKey );
303 # Avoid extension confusion
304 $key = str_replace( '.', '%2E', urlencode( $this->rKey ) );
305
306 $hash1 = substr( $hash, 0, 1 );
307 $hash2 = substr( $hash, 0, 2 );
308 $this->mFileCache = "{$wgFileCacheDirectory}/{$subdir}{$hash1}/{$hash2}/{$this->rKey}.js";
309
310 if( $wgUseGzip )
311 $this->mFileCache .= '.gz';
312
313 wfDebug( " fileCacheName() - {$this->mFileCache}\n" );
314 }
315 return $this->mFileCache;
316 }
317
318 public function isFileCached() {
319 return file_exists( $this->filename );
320 }
321
322 public function outputFromFileCache(){
323 global $wgUseGzip;
324 if( $wgUseGzip ) {
325 if( wfClientAcceptsGzip() ) {
326 header( 'Content-Encoding: gzip' );
327 readfile( $this->filename );
328 } else {
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:
331 */
332 if( substr( $this->filename, -3 ) == '.gz' ){
333 readgzfile( $this->filename );
334 } else {
335 readfile( $this->filename );
336 }
337 }
338 } else {
339 // just output the file
340 readfile( $this->filename );
341 }
342 //return true
343 return true;
344 }
345
346 public function saveToFileCache( &$text ) {
347 global $wgUseFileCache, $wgUseGzip;
348 if( !$wgUseFileCache ) {
349 return 'Error: Called saveToFileCache with $wgUseFileCache off';
350 }
351 if( strcmp( $text, '' ) == 0 ) return 'saveToFileCache: empty output file';
352
353 // check the directories if we could not create them error out:
354 $status = $this->checkCacheDirs();
355
356 if( $wgUseGzip ){
357 $outputText = gzencode( trim( $text ) );
358 } else {
359 $outputText = trim( $text );
360 }
361
362 if( $status !== true )
363 return $status;
364 $f = fopen( $this->filename, 'w' );
365 if( $f ) {
366 fwrite( $f, $outputText );
367 fclose( $f );
368 } else {
369 return 'Could not open file for writing. Check your cache directory permissions?';
370 }
371 return true;
372 }
373
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
377
378 if( wfMkdirParents( $mydir1 ) === false || wfMkdirParents( $mydir2 ) === false ){
379 return 'Could not create cache directory. Check your cache directory permissions?';
380 } else {
381 return true;
382 }
383 }
384 }