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