1<?php 2/** 3 * DokuWiki JavaScript creator 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); 10if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) 11if(!defined('NL')) define('NL',"\n"); 12if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here 13require_once(DOKU_INC.'inc/init.php'); 14require_once(DOKU_INC.'inc/pageutils.php'); 15require_once(DOKU_INC.'inc/httputils.php'); 16require_once(DOKU_INC.'inc/io.php'); 17require_once(DOKU_INC.'inc/JSON.php'); 18 19// Main (don't run when UNIT test) 20if(!defined('SIMPLE_TEST')){ 21 header('Content-Type: text/javascript; charset=utf-8'); 22 js_out(); 23} 24 25 26// ---------------------- functions ------------------------------ 27 28/** 29 * Output all needed JavaScript 30 * 31 * @author Andreas Gohr <andi@splitbrain.org> 32 */ 33function js_out(){ 34 global $conf; 35 global $lang; 36 $edit = (bool) $_REQUEST['edit']; // edit or preview mode? 37 $write = (bool) $_REQUEST['write']; // writable? 38 39 // The generated script depends on some dynamic options 40 $cache = getCacheName('scripts'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].$edit.'x'.$write,'.js'); 41 42 // Array of needed files 43 $files = array( 44 DOKU_INC.'lib/scripts/helpers.js', 45 DOKU_INC.'lib/scripts/events.js', 46 DOKU_INC.'lib/scripts/cookie.js', 47 DOKU_INC.'lib/scripts/script.js', 48 DOKU_INC.'lib/scripts/tw-sack.js', 49 DOKU_INC.'lib/scripts/ajax.js', 50 DOKU_INC.'lib/scripts/index.js', 51 ); 52 if($edit){ 53 if($write){ 54 $files[] = DOKU_INC.'lib/scripts/toolbar.js'; 55 $files[] = DOKU_INC.'lib/scripts/edit.js'; 56 } 57 $files[] = DOKU_INC.'lib/scripts/media.js'; 58 } 59 $files[] = DOKU_TPLINC.'script.js'; 60 61 // get possible plugin scripts 62 $plugins = js_pluginscripts(); 63 64 // check cache age & handle conditional request 65 header('Cache-Control: public, max-age=3600'); 66 header('Pragma: public'); 67 if(js_cacheok($cache,array_merge($files,$plugins))){ 68 http_conditionalRequest(filemtime($cache)); 69 if($conf['allowdebug']) header("X-CacheUsed: $cache"); 70 71 // finally send output 72 if ($conf['gzip_output'] && http_gzip_valid($cache)) { 73 header('Vary: Accept-Encoding'); 74 header('Content-Encoding: gzip'); 75 readfile($cache.".gz"); 76 } else { 77 if (!http_sendfile($cache)) readfile($cache); 78 } 79 80 return; 81 } else { 82 http_conditionalRequest(time()); 83 } 84 85 // start output buffering and build the script 86 ob_start(); 87 88 // add some global variables 89 print "var DOKU_BASE = '".DOKU_BASE."';"; 90 print "var DOKU_TPL = '".DOKU_TPL."';"; 91 92 //FIXME: move thes into LANG 93 print "var alertText = '".js_escape($lang['qb_alert'])."';"; 94 print "var notSavedYet = '".js_escape($lang['notsavedyet'])."';"; 95 print "var reallyDel = '".js_escape($lang['del_confirm'])."';"; 96 97 // load JS strings form plugins 98 $lang['js']['plugins'] = js_pluginstrings(); 99 100 // load JS specific translations 101 $json = new JSON(); 102 echo 'LANG = '.$json->encode($lang['js']).";\n"; 103 104 // load files 105 foreach($files as $file){ 106 echo "\n\n/* XXXXXXXXXX begin of $file XXXXXXXXXX */\n\n"; 107 js_load($file); 108 echo "\n\n/* XXXXXXXXXX end of $file XXXXXXXXXX */\n\n"; 109 } 110 111 // init stuff 112 js_runonstart("ajax_qsearch.init('qsearch__in','qsearch__out')"); 113 js_runonstart("addEvent(document,'click',closePopups)"); 114 js_runonstart('addTocToggle()'); 115 116 if($edit){ 117 // size controls 118 js_runonstart("initSizeCtl('size__ctl','wiki__text')"); 119 120 if($write){ 121 require_once(DOKU_INC.'inc/toolbar.php'); 122 toolbar_JSdefines('toolbar'); 123 js_runonstart("initToolbar('tool__bar','wiki__text',toolbar)"); 124 125 // add pageleave check 126 js_runonstart("initChangeCheck('".js_escape($lang['notsavedyet'])."')"); 127 128 // add lock timer 129 js_runonstart("locktimer.init(".($conf['locktime'] - 60).",'".js_escape($lang['willexpire'])."',".$conf['usedraft'].")"); 130 } 131 } 132 133 // load plugin scripts (suppress warnings for missing ones) 134 foreach($plugins as $plugin){ 135 if (@file_exists($plugin)) { 136 echo "\n\n/* XXXXXXXXXX begin of $plugin XXXXXXXXXX */\n\n"; 137 js_load($plugin); 138 echo "\n\n/* XXXXXXXXXX end of $plugin XXXXXXXXXX */\n\n"; 139 } 140 } 141 142 // load user script 143 @readfile(DOKU_CONF.'userscript.js'); 144 145 // add scroll event and tooltip rewriting 146 js_runonstart('scrollToMarker()'); 147 js_runonstart('focusMarker()'); 148 149 // end output buffering and get contents 150 $js = ob_get_contents(); 151 ob_end_clean(); 152 153 // compress whitespace and comments 154 if($conf['compress']){ 155 $js = js_compress($js); 156 } 157 158 $js .= "\n"; // https://bugzilla.mozilla.org/show_bug.cgi?id=316033 159 160 // save cache file 161 io_saveFile($cache,$js); 162 copy($cache,"compress.zlib://$cache.gz"); 163 164 // finally send output 165 if ($conf['gzip_output']) { 166 header('Vary: Accept-Encoding'); 167 header('Content-Encoding: gzip'); 168 print gzencode($js,9,FORCE_GZIP); 169 } else { 170 print $js; 171 } 172} 173 174/** 175 * Load the given file, handle include calls and print it 176 * 177 * @author Andreas Gohr <andi@splitbrain.org> 178 */ 179function js_load($file){ 180 if(!@file_exists($file)) return; 181 static $loaded = array(); 182 183 $data = io_readFile($file); 184 while(preg_match('#/\*\s*DOKUWIKI:include(_once)?\s+([\w\./]+)\s*\*/#',$data,$match)){ 185 $ifile = $match[2]; 186 187 // is it a include_once? 188 if($match[1]){ 189 $base = basename($ifile); 190 if($loaded[$base]) continue; 191 $loaded[$base] = true; 192 } 193 194 if($ifile{0} != '/') $ifile = dirname($file).'/'.$ifile; 195 196 if(@file_exists($ifile)){ 197 $idata = io_readFile($ifile); 198 }else{ 199 $idata = ''; 200 } 201 $data = str_replace($match[0],$idata,$data); 202 } 203 echo $data; 204} 205 206/** 207 * Checks if a JavaScript Cache file still is valid 208 * 209 * @author Andreas Gohr <andi@splitbrain.org> 210 */ 211function js_cacheok($cache,$files){ 212 if($_REQUEST['purge']) return false; //support purge request 213 214 $ctime = @filemtime($cache); 215 if(!$ctime) return false; //There is no cache 216 217 // some additional files to check 218 $files = array_merge($files, getConfigFiles('main')); 219 $files[] = DOKU_CONF.'userscript.js'; 220 $files[] = __FILE__; 221 222 // now walk the files 223 foreach($files as $file){ 224 if(@filemtime($file) > $ctime){ 225 return false; 226 } 227 } 228 return true; 229} 230 231/** 232 * Returns a list of possible Plugin Scripts (no existance check here) 233 * 234 * @author Andreas Gohr <andi@splitbrain.org> 235 */ 236function js_pluginscripts(){ 237 $list = array(); 238 $plugins = plugin_list(); 239 foreach ($plugins as $p){ 240 $list[] = DOKU_PLUGIN."$p/script.js"; 241 } 242 return $list; 243} 244 245/** 246 * Return an two-dimensional array with strings from the language file of each plugin. 247 * 248 * - $lang['js'] must be an array. 249 * - Nothing is returned for plugins without an entry for $lang['js'] 250 * 251 * @author Gabriel Birke <birke@d-scribe.de> 252 */ 253function js_pluginstrings() 254{ 255 global $conf; 256 $pluginstrings = array(); 257 $plugins = plugin_list(); 258 foreach ($plugins as $p){ 259 if (isset($lang)) unset($lang); 260 if (@file_exists(DOKU_PLUGIN."$p/lang/en/lang.php")) { 261 include DOKU_PLUGIN."$p/lang/en/lang.php"; 262 } 263 if (isset($conf['lang']) && $conf['lang']!='en' && @file_exists(DOKU_PLUGIN."$p/lang/".$conf['lang']."/lang.php")) { 264 include DOKU_PLUGIN."$p/lang/".$conf['lang']."/lang.php"; 265 } 266 if (isset($lang['js'])) { 267 $pluginstrings[$p] = $lang['js']; 268 } 269 } 270 return $pluginstrings; 271} 272 273/** 274 * Escapes a String to be embedded in a JavaScript call, keeps \n 275 * as newline 276 * 277 * @author Andreas Gohr <andi@splitbrain.org> 278 */ 279function js_escape($string){ 280 return str_replace('\\\\n','\\n',addslashes($string)); 281} 282 283/** 284 * Adds the given JavaScript code to the window.onload() event 285 * 286 * @author Andreas Gohr <andi@splitbrain.org> 287 */ 288function js_runonstart($func){ 289 echo "addInitEvent(function(){ $func; });".NL; 290} 291 292/** 293 * Strip comments and whitespaces from given JavaScript Code 294 * 295 * This is a port of Nick Galbreath's python tool jsstrip.py which is 296 * released under BSD license. See link for original code. 297 * 298 * @author Nick Galbreath <nickg@modp.com> 299 * @author Andreas Gohr <andi@splitbrain.org> 300 * @link http://code.google.com/p/jsstrip/ 301 */ 302function js_compress($s){ 303 $s = ltrim($s); // strip all initial whitespace 304 $s .= "\n"; 305 $i = 0; // char index for input string 306 $j = 0; // char forward index for input string 307 $line = 0; // line number of file (close to it anyways) 308 $slen = strlen($s); // size of input string 309 $lch = ''; // last char added 310 $result = ''; // we store the final result here 311 312 // items that don't need spaces next to them 313 $chars = "^&|!+\-*\/%=\?:;,{}()<>% \t\n\r'\"[]"; 314 315 while($i < $slen){ 316 // skip all "boring" characters. This is either 317 // reserved word (e.g. "for", "else", "if") or a 318 // variable/object/method (e.g. "foo.color") 319 while ($i < $slen && (strpos($chars,$s[$i]) === false) ){ 320 $result .= $s{$i}; 321 $i = $i + 1; 322 } 323 324 $ch = $s{$i}; 325 // multiline comments (keeping IE conditionals) 326 if($ch == '/' && $s{$i+1} == '*' && $s{$i+2} != '@'){ 327 $endC = strpos($s,'*/',$i+2); 328 if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR); 329 $i = $endC + 2; 330 continue; 331 } 332 333 // singleline 334 if($ch == '/' && $s{$i+1} == '/'){ 335 $endC = strpos($s,"\n",$i+2); 336 if($endC === false) trigger_error('Invalid comment', E_USER_ERROR); 337 $i = $endC; 338 continue; 339 } 340 341 // tricky. might be an RE 342 if($ch == '/'){ 343 // rewind, skip white space 344 $j = 1; 345 while($s{$i-$j} == ' '){ 346 $j = $j + 1; 347 } 348 if( ($s{$i-$j} == '=') || ($s{$i-$j} == '(') ){ 349 // yes, this is an re 350 // now move forward and find the end of it 351 $j = 1; 352 while($s{$i+$j} != '/'){ 353 while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '/')){ 354 $j = $j + 1; 355 } 356 if($s{$i+$j} == '\\') $j = $j + 2; 357 } 358 $result .= substr($s,$i,$j+1); 359 $i = $i + $j + 1; 360 continue; 361 } 362 } 363 364 // double quote strings 365 if($ch == '"'){ 366 $j = 1; 367 while( $s{$i+$j} != '"' && ($i+$j < $slen)){ 368 if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == '"' || $s{$i+$j+1} == '\\') ){ 369 $j += 2; 370 }else{ 371 $j += 1; 372 } 373 } 374 $result .= substr($s,$i,$j+1); 375 $i = $i + $j + 1; 376 continue; 377 } 378 379 // single quote strings 380 if($ch == "'"){ 381 $j = 1; 382 while( $s{$i+$j} != "'" && ($i+$j < $slen)){ 383 if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == "'" || $s{$i+$j+1} == '\\') ){ 384 $j += 2; 385 }else{ 386 $j += 1; 387 } 388 } 389 $result .= substr($s,$i,$j+1); 390 $i = $i + $j + 1; 391 continue; 392 } 393 394 // whitespaces 395 if( $ch == ' ' || $ch == "\r" || $ch == "\n" || $ch == "\t" ){ 396 // leading spaces 397 if($i+1 < $slen && (strpos($chars,$s[$i+1]) !== false)){ 398 $i = $i + 1; 399 continue; 400 } 401 // trailing spaces 402 // if this ch is space AND the last char processed 403 // is special, then skip the space 404 $lch = substr($result,-1); 405 if($lch && (strpos($chars,$lch) !== false)){ 406 $i = $i + 1; 407 continue; 408 } 409 // else after all of this convert the "whitespace" to 410 // a single space. It will get appended below 411 $ch = ' '; 412 } 413 414 // other chars 415 $result .= $ch; 416 $i = $i + 1; 417 } 418 419 return trim($result); 420} 421 422//Setup VIM: ex: et ts=4 enc=utf-8 : 423?> 424