178a6aeb1SAndreas Gohr<?php 278a6aeb1SAndreas Gohr/** 378a6aeb1SAndreas Gohr * DokuWiki JavaScript creator 478a6aeb1SAndreas Gohr * 578a6aeb1SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 678a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 778a6aeb1SAndreas Gohr */ 878a6aeb1SAndreas Gohr 978a6aeb1SAndreas Gohrif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 101c2d1019SAndreas Gohrif(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) 11b1a4fe22SAndreas Gohrif(!defined('NL')) define('NL',"\n"); 1278a6aeb1SAndreas Gohrrequire_once(DOKU_INC.'inc/init.php'); 1378a6aeb1SAndreas Gohrrequire_once(DOKU_INC.'inc/pageutils.php'); 1478a6aeb1SAndreas Gohrrequire_once(DOKU_INC.'inc/io.php'); 153df72098SAndreas Gohrrequire_once(DOKU_INC.'inc/JSON.php'); 1678a6aeb1SAndreas Gohr 1778a6aeb1SAndreas Gohr// Main (don't run when UNIT test) 1878a6aeb1SAndreas Gohrif(!defined('SIMPLE_TEST')){ 1978a6aeb1SAndreas Gohr header('Content-Type: text/javascript; charset=utf-8'); 2078a6aeb1SAndreas Gohr js_out(); 2178a6aeb1SAndreas Gohr} 2278a6aeb1SAndreas Gohr 2378a6aeb1SAndreas Gohr 2478a6aeb1SAndreas Gohr// ---------------------- functions ------------------------------ 2578a6aeb1SAndreas Gohr 2678a6aeb1SAndreas Gohr/** 2778a6aeb1SAndreas Gohr * Output all needed JavaScript 2878a6aeb1SAndreas Gohr * 2978a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 3078a6aeb1SAndreas Gohr */ 3178a6aeb1SAndreas Gohrfunction js_out(){ 3278a6aeb1SAndreas Gohr global $conf; 3378a6aeb1SAndreas Gohr global $lang; 3478a6aeb1SAndreas Gohr $edit = (bool) $_REQUEST['edit']; // edit or preview mode? 3578a6aeb1SAndreas Gohr $write = (bool) $_REQUEST['write']; // writable? 3678a6aeb1SAndreas Gohr 3778a6aeb1SAndreas Gohr // The generated script depends on some dynamic options 38cf6894dfSAndreas Gohr $cache = getCacheName('scripts'.$edit.'x'.$write,'.js'); 3978a6aeb1SAndreas Gohr 4078a6aeb1SAndreas Gohr // Array of needed files 4178a6aeb1SAndreas Gohr $files = array( 4286045fe9Swingedfox DOKU_INC.'lib/scripts/helpers.js', 4378a6aeb1SAndreas Gohr DOKU_INC.'lib/scripts/events.js', 443df72098SAndreas Gohr DOKU_INC.'lib/scripts/cookie.js', 4578a6aeb1SAndreas Gohr DOKU_INC.'lib/scripts/script.js', 4678a6aeb1SAndreas Gohr DOKU_INC.'lib/scripts/tw-sack.js', 4778a6aeb1SAndreas Gohr DOKU_INC.'lib/scripts/ajax.js', 4878a6aeb1SAndreas Gohr ); 493df72098SAndreas Gohr if($edit){ 503df72098SAndreas Gohr if($write){ 5178a6aeb1SAndreas Gohr $files[] = DOKU_INC.'lib/scripts/edit.js'; 5278a6aeb1SAndreas Gohr if($conf['spellchecker']){ 5378a6aeb1SAndreas Gohr $files[] = DOKU_INC.'lib/scripts/spellcheck.js'; 5478a6aeb1SAndreas Gohr } 5578a6aeb1SAndreas Gohr } 563df72098SAndreas Gohr $files[] = DOKU_INC.'lib/scripts/media.js'; 573df72098SAndreas Gohr } 58d4df8f5cSAndreas Gohr $files[] = DOKU_TPLINC.'script.js'; 5978a6aeb1SAndreas Gohr 6078a6aeb1SAndreas Gohr // get possible plugin scripts 6178a6aeb1SAndreas Gohr $plugins = js_pluginscripts(); 6278a6aeb1SAndreas Gohr 6338f56bffSBen Coburn // check cache age & handle conditional request 6407525e80SBen Coburn header('Cache-Control: public, max-age=3600'); 6538f56bffSBen Coburn header('Pragma: public'); 6678a6aeb1SAndreas Gohr if(js_cacheok($cache,array_merge($files,$plugins))){ 6738f56bffSBen Coburn http_conditionalRequest(filemtime($cache)); 68cf6894dfSAndreas Gohr if($conf['allowdebug']) header("X-CacheUsed: $cache"); 6978a6aeb1SAndreas Gohr readfile($cache); 7078a6aeb1SAndreas Gohr return; 7138f56bffSBen Coburn } else { 7238f56bffSBen Coburn http_conditionalRequest(time()); 7378a6aeb1SAndreas Gohr } 7478a6aeb1SAndreas Gohr 7578a6aeb1SAndreas Gohr // start output buffering and build the script 7678a6aeb1SAndreas Gohr ob_start(); 7778a6aeb1SAndreas Gohr 783df72098SAndreas Gohr // add some global variables 793df72098SAndreas Gohr print "var DOKU_BASE = '".DOKU_BASE."';"; 803df72098SAndreas Gohr 813df72098SAndreas Gohr //FIXME: move thes into LANG 822411dd85SAndreas Gohr print "var alertText = '".js_escape($lang['qb_alert'])."';"; 832411dd85SAndreas Gohr print "var notSavedYet = '".js_escape($lang['notsavedyet'])."';"; 842411dd85SAndreas Gohr print "var reallyDel = '".js_escape($lang['del_confirm'])."';"; 853df72098SAndreas Gohr 863df72098SAndreas Gohr // load JS specific translations 873df72098SAndreas Gohr $json = new JSON(); 883df72098SAndreas Gohr echo 'LANG = '.$json->encode($lang['js']).";\n"; 8978a6aeb1SAndreas Gohr 9078a6aeb1SAndreas Gohr // load files 9178a6aeb1SAndreas Gohr foreach($files as $file){ 92a531dd93SAndreas Gohr echo "\n\n/* XXXXXXXXXX begin of $file XXXXXXXXXX */\n\n"; 9392715ebaSAndreas Gohr @readfile($file); 94a531dd93SAndreas Gohr echo "\n\n/* XXXXXXXXXX end of $file XXXXXXXXXX */\n\n"; 9578a6aeb1SAndreas Gohr } 9678a6aeb1SAndreas Gohr 9778a6aeb1SAndreas Gohr // init stuff 9824a33b42SAndreas Gohr js_runonstart("ajax_qsearch.init('qsearch__in','qsearch__out')"); 9978a6aeb1SAndreas Gohr js_runonstart("addEvent(document,'click',closePopups)"); 10000540a38SAndreas Gohr js_runonstart('addTocToggle()'); 10178a6aeb1SAndreas Gohr 10278a6aeb1SAndreas Gohr if($edit){ 10378a6aeb1SAndreas Gohr // size controls 10424a33b42SAndreas Gohr js_runonstart("initSizeCtl('size__ctl','wiki__text')"); 10578a6aeb1SAndreas Gohr 10678a6aeb1SAndreas Gohr if($write){ 10778a6aeb1SAndreas Gohr require_once(DOKU_INC.'inc/toolbar.php'); 10878a6aeb1SAndreas Gohr toolbar_JSdefines('toolbar'); 10924a33b42SAndreas Gohr js_runonstart("initToolbar('tool__bar','wiki__text',toolbar)"); 11078a6aeb1SAndreas Gohr 11178a6aeb1SAndreas Gohr // add pageleave check 11278a6aeb1SAndreas Gohr js_runonstart("initChangeCheck('".js_escape($lang['notsavedyet'])."')"); 11378a6aeb1SAndreas Gohr 11478a6aeb1SAndreas Gohr // add lock timer 115ee4c4a1bSAndreas Gohr js_runonstart("locktimer.init(".($conf['locktime'] - 60).",'".js_escape($lang['willexpire'])."',".$conf['usedraft'].")"); 11678a6aeb1SAndreas Gohr 11778a6aeb1SAndreas Gohr // load spell checker 11878a6aeb1SAndreas Gohr if($conf['spellchecker']){ 11978a6aeb1SAndreas Gohr js_runonstart("ajax_spell.init('". 12078a6aeb1SAndreas Gohr js_escape($lang['spell_start'])."','". 12178a6aeb1SAndreas Gohr js_escape($lang['spell_stop'])."','". 12278a6aeb1SAndreas Gohr js_escape($lang['spell_wait'])."','". 12378a6aeb1SAndreas Gohr js_escape($lang['spell_noerr'])."','". 12478a6aeb1SAndreas Gohr js_escape($lang['spell_nosug'])."','". 12578a6aeb1SAndreas Gohr js_escape($lang['spell_change'])."')"); 12678a6aeb1SAndreas Gohr } 12778a6aeb1SAndreas Gohr } 12878a6aeb1SAndreas Gohr } 12978a6aeb1SAndreas Gohr 13078a6aeb1SAndreas Gohr // load plugin scripts (suppress warnings for missing ones) 13178a6aeb1SAndreas Gohr foreach($plugins as $plugin){ 1323f4c2ce3Schris if (@file_exists($plugin)) { 1333f4c2ce3Schris echo "\n\n/* XXXXXXXXXX begin of $plugin XXXXXXXXXX */\n\n"; 13478a6aeb1SAndreas Gohr @readfile($plugin); 1353f4c2ce3Schris echo "\n\n/* XXXXXXXXXX end of $plugin XXXXXXXXXX */\n\n"; 1363f4c2ce3Schris } 13778a6aeb1SAndreas Gohr } 13878a6aeb1SAndreas Gohr 13978a6aeb1SAndreas Gohr // load user script 14078a6aeb1SAndreas Gohr @readfile(DOKU_CONF.'userscript.js'); 14178a6aeb1SAndreas Gohr 142b1112787SBen Coburn // add scroll event and tooltip rewriting 143b1112787SBen Coburn js_runonstart('updateAccessKeyTooltip()'); 14450835be7SAndreas Gohr js_runonstart('scrollToMarker()'); 1458d975344SAndreas Gohr js_runonstart('focusMarker()'); 14600540a38SAndreas Gohr 14778a6aeb1SAndreas Gohr // end output buffering and get contents 14878a6aeb1SAndreas Gohr $js = ob_get_contents(); 14978a6aeb1SAndreas Gohr ob_end_clean(); 15078a6aeb1SAndreas Gohr 15178a6aeb1SAndreas Gohr // compress whitespace and comments 15278a6aeb1SAndreas Gohr if($conf['compress']){ 15378a6aeb1SAndreas Gohr $js = js_compress($js); 15478a6aeb1SAndreas Gohr } 15578a6aeb1SAndreas Gohr 156*4d2d451eSAndreas Gohr $js .= "\n"; // https://bugzilla.mozilla.org/show_bug.cgi?id=316033 157*4d2d451eSAndreas Gohr 15878a6aeb1SAndreas Gohr // save cache file 15978a6aeb1SAndreas Gohr io_saveFile($cache,$js); 16078a6aeb1SAndreas Gohr 16178a6aeb1SAndreas Gohr // finally send output 16278a6aeb1SAndreas Gohr print $js; 16378a6aeb1SAndreas Gohr} 16478a6aeb1SAndreas Gohr 16578a6aeb1SAndreas Gohr/** 16678a6aeb1SAndreas Gohr * Checks if a JavaScript Cache file still is valid 16778a6aeb1SAndreas Gohr * 16878a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 16978a6aeb1SAndreas Gohr */ 17078a6aeb1SAndreas Gohrfunction js_cacheok($cache,$files){ 1710df6f150SAndreas Gohr if($_REQUEST['purge']) return false; //support purge request 1720df6f150SAndreas Gohr 17378a6aeb1SAndreas Gohr $ctime = @filemtime($cache); 17478a6aeb1SAndreas Gohr if(!$ctime) return false; //There is no cache 17578a6aeb1SAndreas Gohr 17678a6aeb1SAndreas Gohr // some additional files to check 177c591aabeSAndreas Gohr $files[] = DOKU_CONF.'dokuwiki.php'; 178c591aabeSAndreas Gohr $files[] = DOKU_CONF.'local.php'; 17978a6aeb1SAndreas Gohr $files[] = DOKU_CONF.'userscript.js'; 18078a6aeb1SAndreas Gohr $files[] = __FILE__; 18178a6aeb1SAndreas Gohr 18278a6aeb1SAndreas Gohr // now walk the files 18378a6aeb1SAndreas Gohr foreach($files as $file){ 18478a6aeb1SAndreas Gohr if(@filemtime($file) > $ctime){ 18578a6aeb1SAndreas Gohr return false; 18678a6aeb1SAndreas Gohr } 18778a6aeb1SAndreas Gohr } 18878a6aeb1SAndreas Gohr return true; 18978a6aeb1SAndreas Gohr} 19078a6aeb1SAndreas Gohr 19178a6aeb1SAndreas Gohr/** 19278a6aeb1SAndreas Gohr * Returns a list of possible Plugin Scripts (no existance check here) 19378a6aeb1SAndreas Gohr * 19478a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 19578a6aeb1SAndreas Gohr */ 19678a6aeb1SAndreas Gohrfunction js_pluginscripts(){ 19778a6aeb1SAndreas Gohr $list = array(); 19878a6aeb1SAndreas Gohr $plugins = plugin_list(); 19978a6aeb1SAndreas Gohr foreach ($plugins as $p){ 20078a6aeb1SAndreas Gohr $list[] = DOKU_PLUGIN."$p/script.js"; 20178a6aeb1SAndreas Gohr } 20278a6aeb1SAndreas Gohr return $list; 20378a6aeb1SAndreas Gohr} 20478a6aeb1SAndreas Gohr 20578a6aeb1SAndreas Gohr/** 20678a6aeb1SAndreas Gohr * Escapes a String to be embedded in a JavaScript call, keeps \n 20778a6aeb1SAndreas Gohr * as newline 20878a6aeb1SAndreas Gohr * 20978a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 21078a6aeb1SAndreas Gohr */ 21178a6aeb1SAndreas Gohrfunction js_escape($string){ 21278a6aeb1SAndreas Gohr return str_replace('\\\\n','\\n',addslashes($string)); 21378a6aeb1SAndreas Gohr} 21478a6aeb1SAndreas Gohr 21578a6aeb1SAndreas Gohr/** 21678a6aeb1SAndreas Gohr * Adds the given JavaScript code to the window.onload() event 21778a6aeb1SAndreas Gohr * 21878a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 21978a6aeb1SAndreas Gohr */ 22078a6aeb1SAndreas Gohrfunction js_runonstart($func){ 221b1a4fe22SAndreas Gohr echo "addInitEvent(function(){ $func; });".NL; 22278a6aeb1SAndreas Gohr} 22378a6aeb1SAndreas Gohr 22478a6aeb1SAndreas Gohr/** 22578a6aeb1SAndreas Gohr * Strip comments and whitespaces from given JavaScript Code 22678a6aeb1SAndreas Gohr * 22773bea65dSAndreas Gohr * This is a port of Nick Galbreath's python tool jsstrip.py which is 22878a6aeb1SAndreas Gohr * released under BSD license. See link for original code. 22978a6aeb1SAndreas Gohr * 23078a6aeb1SAndreas Gohr * @author Nick Galbreath <nickg@modp.com> 23178a6aeb1SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 23273bea65dSAndreas Gohr * @link http://code.google.com/p/jsstrip/ 23378a6aeb1SAndreas Gohr */ 23478a6aeb1SAndreas Gohrfunction js_compress($s){ 23573bea65dSAndreas Gohr $s = ltrim($s); // strip all initial whitespace 23678a6aeb1SAndreas Gohr $s .= "\n"; 23773bea65dSAndreas Gohr $i = 0; // char index for input string 23873bea65dSAndreas Gohr $j = 0; // char forward index for input string 23973bea65dSAndreas Gohr $line = 0; // line number of file (close to it anyways) 24073bea65dSAndreas Gohr $slen = strlen($s); // size of input string 24173bea65dSAndreas Gohr $lch = ''; // last char added 24273bea65dSAndreas Gohr $result = ''; // we store the final result here 24378a6aeb1SAndreas Gohr 24478a6aeb1SAndreas Gohr // items that don't need spaces next to them 24573bea65dSAndreas Gohr $chars = "^&|!+\-*\/%=\?:;,{}()<>% \t\n\r'\"[]"; 24678a6aeb1SAndreas Gohr 24773bea65dSAndreas Gohr while($i < $slen){ 24873bea65dSAndreas Gohr // skip all "boring" characters. This is either 24973bea65dSAndreas Gohr // reserved word (e.g. "for", "else", "if") or a 25073bea65dSAndreas Gohr // variable/object/method (e.g. "foo.color") 25173bea65dSAndreas Gohr while ($i < $slen && (strpos($chars,$s[$i]) === false) ){ 25273bea65dSAndreas Gohr $result .= $s{$i}; 25373bea65dSAndreas Gohr $i = $i + 1; 25473bea65dSAndreas Gohr } 25573bea65dSAndreas Gohr 25678a6aeb1SAndreas Gohr $ch = $s{$i}; 2575cafff96SAndreas Gohr // multiline comments (keeping IE conditionals) 2585cafff96SAndreas Gohr if($ch == '/' && $s{$i+1} == '*' && $s{$i+2} != '@'){ 25978a6aeb1SAndreas Gohr $endC = strpos($s,'*/',$i+2); 26078a6aeb1SAndreas Gohr if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR); 26178a6aeb1SAndreas Gohr $i = $endC + 2; 26278a6aeb1SAndreas Gohr continue; 26378a6aeb1SAndreas Gohr } 26478a6aeb1SAndreas Gohr 26578a6aeb1SAndreas Gohr // singleline 26678a6aeb1SAndreas Gohr if($ch == '/' && $s{$i+1} == '/'){ 26778a6aeb1SAndreas Gohr $endC = strpos($s,"\n",$i+2); 26878a6aeb1SAndreas Gohr if($endC === false) trigger_error('Invalid comment', E_USER_ERROR); 26978a6aeb1SAndreas Gohr $i = $endC; 27078a6aeb1SAndreas Gohr continue; 27178a6aeb1SAndreas Gohr } 27278a6aeb1SAndreas Gohr 27378a6aeb1SAndreas Gohr // tricky. might be an RE 27478a6aeb1SAndreas Gohr if($ch == '/'){ 27578a6aeb1SAndreas Gohr // rewind, skip white space 27678a6aeb1SAndreas Gohr $j = 1; 27778a6aeb1SAndreas Gohr while($s{$i-$j} == ' '){ 27878a6aeb1SAndreas Gohr $j = $j + 1; 27978a6aeb1SAndreas Gohr } 28078a6aeb1SAndreas Gohr if( ($s{$i-$j} == '=') || ($s{$i-$j} == '(') ){ 28178a6aeb1SAndreas Gohr // yes, this is an re 28278a6aeb1SAndreas Gohr // now move forward and find the end of it 28378a6aeb1SAndreas Gohr $j = 1; 28478a6aeb1SAndreas Gohr while($s{$i+$j} != '/'){ 28578a6aeb1SAndreas Gohr while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '/')){ 28678a6aeb1SAndreas Gohr $j = $j + 1; 28778a6aeb1SAndreas Gohr } 28878a6aeb1SAndreas Gohr if($s{$i+$j} == '\\') $j = $j + 2; 28978a6aeb1SAndreas Gohr } 29073bea65dSAndreas Gohr $result .= substr($s,$i,$j+1); 29178a6aeb1SAndreas Gohr $i = $i + $j + 1; 29278a6aeb1SAndreas Gohr continue; 29378a6aeb1SAndreas Gohr } 29478a6aeb1SAndreas Gohr } 29578a6aeb1SAndreas Gohr 29678a6aeb1SAndreas Gohr // double quote strings 29778a6aeb1SAndreas Gohr if($ch == '"'){ 29878a6aeb1SAndreas Gohr $j = 1; 29973bea65dSAndreas Gohr while( $s{$i+$j} != '"' && ($i+$j < $slen)){ 300e5cafda0SAndreas Gohr if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == '"' || $s{$i+$j+1} == '\\') ){ 30145be45c5SAndreas Gohr $j += 2; 30245be45c5SAndreas Gohr }else{ 30345be45c5SAndreas Gohr $j += 1; 30478a6aeb1SAndreas Gohr } 30578a6aeb1SAndreas Gohr } 30673bea65dSAndreas Gohr $result .= substr($s,$i,$j+1); 30778a6aeb1SAndreas Gohr $i = $i + $j + 1; 30878a6aeb1SAndreas Gohr continue; 30978a6aeb1SAndreas Gohr } 31078a6aeb1SAndreas Gohr 31178a6aeb1SAndreas Gohr // single quote strings 31278a6aeb1SAndreas Gohr if($ch == "'"){ 31378a6aeb1SAndreas Gohr $j = 1; 31473bea65dSAndreas Gohr while( $s{$i+$j} != "'" && ($i+$j < $slen)){ 315e5cafda0SAndreas Gohr if( $s{$i+$j} == '\\' && ($s{$i+$j+1} == "'" || $s{$i+$j+1} == '\\') ){ 31645be45c5SAndreas Gohr $j += 2; 31745be45c5SAndreas Gohr }else{ 31845be45c5SAndreas Gohr $j += 1; 31978a6aeb1SAndreas Gohr } 32078a6aeb1SAndreas Gohr } 32173bea65dSAndreas Gohr $result .= substr($s,$i,$j+1); 32278a6aeb1SAndreas Gohr $i = $i + $j + 1; 32378a6aeb1SAndreas Gohr continue; 32478a6aeb1SAndreas Gohr } 32578a6aeb1SAndreas Gohr 32673bea65dSAndreas Gohr // whitespaces 32773bea65dSAndreas Gohr if( $ch == ' ' || $ch == "\r" || $ch == "\n" || $ch == "\t" ){ 32878a6aeb1SAndreas Gohr // leading spaces 32973bea65dSAndreas Gohr if($i+1 < $slen && (strpos($chars,$s[$i+1]) !== false)){ 33078a6aeb1SAndreas Gohr $i = $i + 1; 33178a6aeb1SAndreas Gohr continue; 33278a6aeb1SAndreas Gohr } 33378a6aeb1SAndreas Gohr // trailing spaces 33473bea65dSAndreas Gohr // if this ch is space AND the last char processed 33573bea65dSAndreas Gohr // is special, then skip the space 33673bea65dSAndreas Gohr $lch = substr($result,-1); 33773bea65dSAndreas Gohr if($lch && (strpos($chars,$lch) !== false)){ 33878a6aeb1SAndreas Gohr $i = $i + 1; 33978a6aeb1SAndreas Gohr continue; 34078a6aeb1SAndreas Gohr } 34173bea65dSAndreas Gohr // else after all of this convert the "whitespace" to 34273bea65dSAndreas Gohr // a single space. It will get appended below 34373bea65dSAndreas Gohr $ch = ' '; 34473bea65dSAndreas Gohr } 34578a6aeb1SAndreas Gohr 34678a6aeb1SAndreas Gohr // other chars 34773bea65dSAndreas Gohr $result .= $ch; 34878a6aeb1SAndreas Gohr $i = $i + 1; 34978a6aeb1SAndreas Gohr } 35078a6aeb1SAndreas Gohr 35173bea65dSAndreas Gohr return trim($result); 35278a6aeb1SAndreas Gohr} 35378a6aeb1SAndreas Gohr 35478a6aeb1SAndreas Gohr//Setup VIM: ex: et ts=4 enc=utf-8 : 35578a6aeb1SAndreas Gohr?> 356