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