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 9use dokuwiki\Cache\Cache; 10 11if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); 12if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) 13if(!defined('NL')) define('NL',"\n"); 14if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here 15require_once(DOKU_INC.'inc/init.php'); 16 17// Main (don't run when UNIT test) 18if(!defined('SIMPLE_TEST')){ 19 header('Content-Type: application/javascript; charset=utf-8'); 20 js_out(); 21} 22 23 24// ---------------------- functions ------------------------------ 25 26/** 27 * Output all needed JavaScript 28 * 29 * @author Andreas Gohr <andi@splitbrain.org> 30 */ 31function js_out(){ 32 global $conf; 33 global $lang; 34 global $config_cascade; 35 global $INPUT; 36 37 // decide from where to get the template 38 $tpl = trim(preg_replace('/[^\w-]+/','',$INPUT->str('t'))); 39 if(!$tpl) $tpl = $conf['template']; 40 41 // array of core files 42 $files = array( 43 DOKU_INC.'lib/scripts/jquery/jquery.cookie.js', 44 DOKU_INC.'inc/lang/'.$conf['lang'].'/jquery.ui.datepicker.js', 45 DOKU_INC."lib/scripts/fileuploader.js", 46 DOKU_INC."lib/scripts/fileuploaderextended.js", 47 DOKU_INC.'lib/scripts/helpers.js', 48 DOKU_INC.'lib/scripts/delay.js', 49 DOKU_INC.'lib/scripts/cookie.js', 50 DOKU_INC.'lib/scripts/script.js', 51 DOKU_INC.'lib/scripts/qsearch.js', 52 DOKU_INC.'lib/scripts/search.js', 53 DOKU_INC.'lib/scripts/tree.js', 54 DOKU_INC.'lib/scripts/index.js', 55 DOKU_INC.'lib/scripts/textselection.js', 56 DOKU_INC.'lib/scripts/toolbar.js', 57 DOKU_INC.'lib/scripts/edit.js', 58 DOKU_INC.'lib/scripts/editor.js', 59 DOKU_INC.'lib/scripts/locktimer.js', 60 DOKU_INC.'lib/scripts/linkwiz.js', 61 DOKU_INC.'lib/scripts/media.js', 62 DOKU_INC.'lib/scripts/compatibility.js', 63# disabled for FS#1958 DOKU_INC.'lib/scripts/hotkeys.js', 64 DOKU_INC.'lib/scripts/behaviour.js', 65 DOKU_INC.'lib/scripts/page.js', 66 tpl_incdir($tpl).'script.js', 67 ); 68 69 // add possible plugin scripts and userscript 70 $files = array_merge($files,js_pluginscripts()); 71 if(!empty($config_cascade['userscript']['default'])) { 72 foreach($config_cascade['userscript']['default'] as $userscript) { 73 $files[] = $userscript; 74 } 75 } 76 77 // Let plugins decide to either put more scripts here or to remove some 78 trigger_event('JS_SCRIPT_LIST', $files); 79 80 // The generated script depends on some dynamic options 81 $cache = new Cache('scripts'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].md5(serialize($files)),'.js'); 82 $cache->_event = 'JS_CACHE_USE'; 83 84 $cache_files = array_merge($files, getConfigFiles('main')); 85 $cache_files[] = __FILE__; 86 87 // check cache age & handle conditional request 88 // This may exit if a cache can be used 89 $cache_ok = $cache->useCache(array('files' => $cache_files)); 90 http_cached($cache->cache, $cache_ok); 91 92 // start output buffering and build the script 93 ob_start(); 94 95 // add some global variables 96 print "var DOKU_BASE = '".DOKU_BASE."';"; 97 print "var DOKU_TPL = '".tpl_basedir($tpl)."';"; 98 print "var DOKU_COOKIE_PARAM = " . json_encode( 99 array( 100 'path' => empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'], 101 'secure' => $conf['securecookie'] && is_ssl() 102 )).";"; 103 // FIXME: Move those to JSINFO 104 print "Object.defineProperty(window, 'DOKU_UHN', { get: function() {". 105 "console.warn('Using DOKU_UHN is deprecated. Please use JSINFO.useHeadingNavigation instead');". 106 "return JSINFO.useHeadingNavigation; } });"; 107 print "Object.defineProperty(window, 'DOKU_UHC', { get: function() {". 108 "console.warn('Using DOKU_UHC is deprecated. Please use JSINFO.useHeadingContent instead');". 109 "return JSINFO.useHeadingContent; } });"; 110 111 // load JS specific translations 112 $lang['js']['plugins'] = js_pluginstrings(); 113 $templatestrings = js_templatestrings($tpl); 114 if(!empty($templatestrings)) { 115 $lang['js']['template'] = $templatestrings; 116 } 117 echo 'LANG = '.json_encode($lang['js']).";\n"; 118 119 // load toolbar 120 toolbar_JSdefines('toolbar'); 121 122 // load files 123 foreach($files as $file){ 124 if(!file_exists($file)) continue; 125 $ismin = (substr($file,-7) == '.min.js'); 126 $debugjs = ($conf['allowdebug'] && strpos($file, DOKU_INC.'lib/scripts/') !== 0); 127 128 echo "\n\n/* XXXXXXXXXX begin of ".str_replace(DOKU_INC, '', $file) ." XXXXXXXXXX */\n\n"; 129 if($ismin) echo "\n/* BEGIN NOCOMPRESS */\n"; 130 if ($debugjs) echo "\ntry {\n"; 131 js_load($file); 132 if ($debugjs) echo "\n} catch (e) {\n logError(e, '".str_replace(DOKU_INC, '', $file)."');\n}\n"; 133 if($ismin) echo "\n/* END NOCOMPRESS */\n"; 134 echo "\n\n/* XXXXXXXXXX end of " . str_replace(DOKU_INC, '', $file) . " XXXXXXXXXX */\n\n"; 135 } 136 137 // init stuff 138 if($conf['locktime'] != 0){ 139 js_runonstart("dw_locktimer.init(".($conf['locktime'] - 60).",".$conf['usedraft'].")"); 140 } 141 // init hotkeys - must have been done after init of toolbar 142# disabled for FS#1958 js_runonstart('initializeHotkeys()'); 143 144 // end output buffering and get contents 145 $js = ob_get_contents(); 146 ob_end_clean(); 147 148 // strip any source maps 149 stripsourcemaps($js); 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 http_cached_finish($cache->cache, $js); 159} 160 161/** 162 * Load the given file, handle include calls and print it 163 * 164 * @author Andreas Gohr <andi@splitbrain.org> 165 * 166 * @param string $file filename path to file 167 */ 168function js_load($file){ 169 if(!file_exists($file)) return; 170 static $loaded = array(); 171 172 $data = io_readFile($file); 173 while(preg_match('#/\*\s*DOKUWIKI:include(_once)?\s+([\w\.\-_/]+)\s*\*/#',$data,$match)){ 174 $ifile = $match[2]; 175 176 // is it a include_once? 177 if($match[1]){ 178 $base = utf8_basename($ifile); 179 if(array_key_exists($base, $loaded) && $loaded[$base] === true){ 180 $data = str_replace($match[0], '' ,$data); 181 continue; 182 } 183 $loaded[$base] = true; 184 } 185 186 if($ifile{0} != '/') $ifile = dirname($file).'/'.$ifile; 187 188 if(file_exists($ifile)){ 189 $idata = io_readFile($ifile); 190 }else{ 191 $idata = ''; 192 } 193 $data = str_replace($match[0],$idata,$data); 194 } 195 echo "$data\n"; 196} 197 198/** 199 * Returns a list of possible Plugin Scripts (no existance check here) 200 * 201 * @author Andreas Gohr <andi@splitbrain.org> 202 * 203 * @return array 204 */ 205function js_pluginscripts(){ 206 $list = array(); 207 $plugins = plugin_list(); 208 foreach ($plugins as $p){ 209 $list[] = DOKU_PLUGIN."$p/script.js"; 210 } 211 return $list; 212} 213 214/** 215 * Return an two-dimensional array with strings from the language file of each plugin. 216 * 217 * - $lang['js'] must be an array. 218 * - Nothing is returned for plugins without an entry for $lang['js'] 219 * 220 * @author Gabriel Birke <birke@d-scribe.de> 221 * 222 * @return array 223 */ 224function js_pluginstrings() { 225 global $conf, $config_cascade; 226 $pluginstrings = array(); 227 $plugins = plugin_list(); 228 foreach($plugins as $p) { 229 $path = DOKU_PLUGIN . $p . '/lang/'; 230 231 if(isset($lang)) unset($lang); 232 if(file_exists($path . "en/lang.php")) { 233 include $path . "en/lang.php"; 234 } 235 foreach($config_cascade['lang']['plugin'] as $config_file) { 236 if(file_exists($config_file . $p . '/en/lang.php')) { 237 include($config_file . $p . '/en/lang.php'); 238 } 239 } 240 if(isset($conf['lang']) && $conf['lang'] != 'en') { 241 if(file_exists($path . $conf['lang'] . "/lang.php")) { 242 include($path . $conf['lang'] . '/lang.php'); 243 } 244 foreach($config_cascade['lang']['plugin'] as $config_file) { 245 if(file_exists($config_file . $p . '/' . $conf['lang'] . '/lang.php')) { 246 include($config_file . $p . '/' . $conf['lang'] . '/lang.php'); 247 } 248 } 249 } 250 251 if(isset($lang['js'])) { 252 $pluginstrings[$p] = $lang['js']; 253 } 254 } 255 return $pluginstrings; 256} 257 258/** 259 * Return an two-dimensional array with strings from the language file of current active template. 260 * 261 * - $lang['js'] must be an array. 262 * - Nothing is returned for template without an entry for $lang['js'] 263 * 264 * @param string $tpl 265 * @return array 266 */ 267function js_templatestrings($tpl) { 268 global $conf, $config_cascade; 269 270 $path = tpl_incdir() . 'lang/'; 271 272 $templatestrings = array(); 273 if(file_exists($path . "en/lang.php")) { 274 include $path . "en/lang.php"; 275 } 276 foreach($config_cascade['lang']['template'] as $config_file) { 277 if(file_exists($config_file . $conf['template'] . '/en/lang.php')) { 278 include($config_file . $conf['template'] . '/en/lang.php'); 279 } 280 } 281 if(isset($conf['lang']) && $conf['lang'] != 'en' && file_exists($path . $conf['lang'] . "/lang.php")) { 282 include $path . $conf['lang'] . "/lang.php"; 283 } 284 if(isset($conf['lang']) && $conf['lang'] != 'en') { 285 if(file_exists($path . $conf['lang'] . "/lang.php")) { 286 include $path . $conf['lang'] . "/lang.php"; 287 } 288 foreach($config_cascade['lang']['template'] as $config_file) { 289 if(file_exists($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php')) { 290 include($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php'); 291 } 292 } 293 } 294 295 if(isset($lang['js'])) { 296 $templatestrings[$tpl] = $lang['js']; 297 } 298 return $templatestrings; 299} 300 301/** 302 * Escapes a String to be embedded in a JavaScript call, keeps \n 303 * as newline 304 * 305 * @author Andreas Gohr <andi@splitbrain.org> 306 * 307 * @param string $string 308 * @return string 309 */ 310function js_escape($string){ 311 return str_replace('\\\\n','\\n',addslashes($string)); 312} 313 314/** 315 * Adds the given JavaScript code to the window.onload() event 316 * 317 * @author Andreas Gohr <andi@splitbrain.org> 318 * 319 * @param string $func 320 */ 321function js_runonstart($func){ 322 echo "jQuery(function(){ $func; });".NL; 323} 324 325/** 326 * Strip comments and whitespaces from given JavaScript Code 327 * 328 * This is a port of Nick Galbreath's python tool jsstrip.py which is 329 * released under BSD license. See link for original code. 330 * 331 * @author Nick Galbreath <nickg@modp.com> 332 * @author Andreas Gohr <andi@splitbrain.org> 333 * @link http://code.google.com/p/jsstrip/ 334 * 335 * @param string $s 336 * @return string 337 */ 338function js_compress($s){ 339 $s = ltrim($s); // strip all initial whitespace 340 $s .= "\n"; 341 $i = 0; // char index for input string 342 $j = 0; // char forward index for input string 343 $line = 0; // line number of file (close to it anyways) 344 $slen = strlen($s); // size of input string 345 $lch = ''; // last char added 346 $result = ''; // we store the final result here 347 348 // items that don't need spaces next to them 349 $chars = "^&|!+\-*\/%=\?:;,{}()<>% \t\n\r'\"[]"; 350 351 // items which need a space if the sign before and after whitespace is equal. 352 // E.g. '+ ++' may not be compressed to '+++' --> syntax error. 353 $ops = "+-"; 354 355 $regex_starters = array("(", "=", "[", "," , ":", "!", "&", "|"); 356 357 $whitespaces_chars = array(" ", "\t", "\n", "\r", "\0", "\x0B"); 358 359 while($i < $slen){ 360 // skip all "boring" characters. This is either 361 // reserved word (e.g. "for", "else", "if") or a 362 // variable/object/method (e.g. "foo.color") 363 while ($i < $slen && (strpos($chars,$s[$i]) === false) ){ 364 $result .= $s{$i}; 365 $i = $i + 1; 366 } 367 368 $ch = $s{$i}; 369 // multiline comments (keeping IE conditionals) 370 if($ch == '/' && $s{$i+1} == '*' && $s{$i+2} != '@'){ 371 $endC = strpos($s,'*/',$i+2); 372 if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR); 373 374 // check if this is a NOCOMPRESS comment 375 if(substr($s, $i, $endC+2-$i) == '/* BEGIN NOCOMPRESS */'){ 376 $endNC = strpos($s, '/* END NOCOMPRESS */', $endC+2); 377 if($endNC === false) trigger_error('Found invalid NOCOMPRESS comment', E_USER_ERROR); 378 379 // verbatim copy contents, trimming but putting it on its own line 380 $result .= "\n".trim(substr($s, $i + 22, $endNC - ($i + 22)))."\n"; // BEGIN comment = 22 chars 381 $i = $endNC + 20; // END comment = 20 chars 382 }else{ 383 $i = $endC + 2; 384 } 385 continue; 386 } 387 388 // singleline 389 if($ch == '/' && $s{$i+1} == '/'){ 390 $endC = strpos($s,"\n",$i+2); 391 if($endC === false) trigger_error('Invalid comment', E_USER_ERROR); 392 $i = $endC; 393 continue; 394 } 395 396 // tricky. might be an RE 397 if($ch == '/'){ 398 // rewind, skip white space 399 $j = 1; 400 while(in_array($s{$i-$j}, $whitespaces_chars)){ 401 $j = $j + 1; 402 } 403 if( in_array($s{$i-$j}, $regex_starters) ){ 404 // yes, this is an re 405 // now move forward and find the end of it 406 $j = 1; 407 while($s{$i+$j} != '/'){ 408 if($s{$i+$j} == '\\') $j = $j + 2; 409 else $j++; 410 } 411 $result .= substr($s,$i,$j+1); 412 $i = $i + $j + 1; 413 continue; 414 } 415 } 416 417 // double quote strings 418 if($ch == '"'){ 419 $j = 1; 420 while( $s{$i+$j} != '"' && ($i+$j < $slen)){ 421 if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == '"' || $s{$i+$j+1} == '\\') ){ 422 $j += 2; 423 }else{ 424 $j += 1; 425 } 426 } 427 $string = substr($s,$i,$j+1); 428 // remove multiline markers: 429 $string = str_replace("\\\n",'',$string); 430 $result .= $string; 431 $i = $i + $j + 1; 432 continue; 433 } 434 435 // single quote strings 436 if($ch == "'"){ 437 $j = 1; 438 while( $s{$i+$j} != "'" && ($i+$j < $slen)){ 439 if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == "'" || $s{$i+$j+1} == '\\') ){ 440 $j += 2; 441 }else{ 442 $j += 1; 443 } 444 } 445 $string = substr($s,$i,$j+1); 446 // remove multiline markers: 447 $string = str_replace("\\\n",'',$string); 448 $result .= $string; 449 $i = $i + $j + 1; 450 continue; 451 } 452 453 // whitespaces 454 if( $ch == ' ' || $ch == "\r" || $ch == "\n" || $ch == "\t" ){ 455 $lch = substr($result,-1); 456 457 // Only consider deleting whitespace if the signs before and after 458 // are not equal and are not an operator which may not follow itself. 459 if ($i+1 < $slen && ((!$lch || $s[$i+1] == ' ') 460 || $lch != $s[$i+1] 461 || strpos($ops,$s[$i+1]) === false)) { 462 // leading spaces 463 if($i+1 < $slen && (strpos($chars,$s[$i+1]) !== false)){ 464 $i = $i + 1; 465 continue; 466 } 467 // trailing spaces 468 // if this ch is space AND the last char processed 469 // is special, then skip the space 470 if($lch && (strpos($chars,$lch) !== false)){ 471 $i = $i + 1; 472 continue; 473 } 474 } 475 476 // else after all of this convert the "whitespace" to 477 // a single space. It will get appended below 478 $ch = ' '; 479 } 480 481 // other chars 482 $result .= $ch; 483 $i = $i + 1; 484 } 485 486 return trim($result); 487} 488 489//Setup VIM: ex: et ts=4 : 490