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