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