You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

341 lines
11 KiB

  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. use dokuwiki\Utf8\PhpString;
  9. use dokuwiki\Cache\Cache;
  10. use dokuwiki\Extension\Event;
  11. use splitbrain\JSStrip\Exception as JSStripException;
  12. use splitbrain\JSStrip\JSStrip;
  13. if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../../');
  14. if (!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching)
  15. if (!defined('NL')) define('NL', "\n");
  16. if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1); // we gzip ourself here
  17. require_once(DOKU_INC . 'inc/init.php');
  18. // Main (don't run when UNIT test)
  19. if (!defined('SIMPLE_TEST')) {
  20. header('Content-Type: application/javascript; charset=utf-8');
  21. js_out();
  22. }
  23. // ---------------------- functions ------------------------------
  24. /**
  25. * Output all needed JavaScript
  26. *
  27. * @author Andreas Gohr <andi@splitbrain.org>
  28. */
  29. function js_out()
  30. {
  31. global $conf;
  32. global $lang;
  33. global $config_cascade;
  34. global $INPUT;
  35. // decide from where to get the template
  36. $tpl = trim(preg_replace('/[^\w-]+/', '', $INPUT->str('t')));
  37. if (!$tpl) $tpl = $conf['template'];
  38. // array of core files
  39. $files = [
  40. DOKU_INC . 'lib/scripts/jquery/jquery.cookie.js',
  41. DOKU_INC . 'inc/lang/' . $conf['lang'] . '/jquery.ui.datepicker.js',
  42. DOKU_INC . "lib/scripts/fileuploader.js",
  43. DOKU_INC . "lib/scripts/fileuploaderextended.js",
  44. DOKU_INC . 'lib/scripts/helpers.js',
  45. DOKU_INC . 'lib/scripts/delay.js',
  46. DOKU_INC . 'lib/scripts/cookie.js',
  47. DOKU_INC . 'lib/scripts/script.js',
  48. DOKU_INC . 'lib/scripts/qsearch.js',
  49. DOKU_INC . 'lib/scripts/search.js',
  50. DOKU_INC . 'lib/scripts/tree.js',
  51. DOKU_INC . 'lib/scripts/index.js',
  52. DOKU_INC . 'lib/scripts/textselection.js',
  53. DOKU_INC . 'lib/scripts/toolbar.js',
  54. DOKU_INC . 'lib/scripts/edit.js',
  55. DOKU_INC . 'lib/scripts/editor.js',
  56. DOKU_INC . 'lib/scripts/locktimer.js',
  57. DOKU_INC . 'lib/scripts/linkwiz.js',
  58. DOKU_INC . 'lib/scripts/media.js',
  59. DOKU_INC . 'lib/scripts/compatibility.js',
  60. # disabled for FS#1958 DOKU_INC.'lib/scripts/hotkeys.js',
  61. DOKU_INC . 'lib/scripts/behaviour.js',
  62. DOKU_INC . 'lib/scripts/page.js',
  63. tpl_incdir($tpl) . 'script.js',
  64. ];
  65. // add possible plugin scripts and userscript
  66. $files = array_merge($files, js_pluginscripts());
  67. if (is_array($config_cascade['userscript']['default'])) {
  68. foreach ($config_cascade['userscript']['default'] as $userscript) {
  69. $files[] = $userscript;
  70. }
  71. }
  72. // Let plugins decide to either put more scripts here or to remove some
  73. Event::createAndTrigger('JS_SCRIPT_LIST', $files);
  74. // The generated script depends on some dynamic options
  75. $cache = new Cache('scripts' . $_SERVER['HTTP_HOST'] . $_SERVER['SERVER_PORT'] . md5(serialize($files)), '.js');
  76. $cache->setEvent('JS_CACHE_USE');
  77. $cache_files = array_merge($files, getConfigFiles('main'));
  78. $cache_files[] = __FILE__;
  79. // check cache age & handle conditional request
  80. // This may exit if a cache can be used
  81. $cache_ok = $cache->useCache(['files' => $cache_files]);
  82. http_cached($cache->cache, $cache_ok);
  83. // start output buffering and build the script
  84. ob_start();
  85. // add some global variables
  86. echo "var DOKU_BASE = '" . DOKU_BASE . "';";
  87. echo "var DOKU_TPL = '" . tpl_basedir($tpl) . "';";
  88. echo "var DOKU_COOKIE_PARAM = " . json_encode([
  89. 'path' => empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'],
  90. 'secure' => $conf['securecookie'] && is_ssl()
  91. ], JSON_THROW_ON_ERROR) . ";";
  92. // FIXME: Move those to JSINFO
  93. echo "Object.defineProperty(window, 'DOKU_UHN', { get: function() {" .
  94. "console.warn('Using DOKU_UHN is deprecated. Please use JSINFO.useHeadingNavigation instead');" .
  95. "return JSINFO.useHeadingNavigation; } });";
  96. echo "Object.defineProperty(window, 'DOKU_UHC', { get: function() {" .
  97. "console.warn('Using DOKU_UHC is deprecated. Please use JSINFO.useHeadingContent instead');" .
  98. "return JSINFO.useHeadingContent; } });";
  99. // load JS specific translations
  100. $lang['js']['plugins'] = js_pluginstrings();
  101. $templatestrings = js_templatestrings($tpl);
  102. if (!empty($templatestrings)) {
  103. $lang['js']['template'] = $templatestrings;
  104. }
  105. echo 'LANG = ' . json_encode($lang['js'], JSON_THROW_ON_ERROR) . ";\n";
  106. // load toolbar
  107. toolbar_JSdefines('toolbar');
  108. // load files
  109. foreach ($files as $file) {
  110. if (!file_exists($file)) continue;
  111. $ismin = str_ends_with($file, '.min.js');
  112. $debugjs = ($conf['allowdebug'] && strpos($file, DOKU_INC . 'lib/scripts/') !== 0);
  113. echo "\n\n/* XXXXXXXXXX begin of " . str_replace(DOKU_INC, '', $file) . " XXXXXXXXXX */\n\n";
  114. if ($ismin) echo "\n/* BEGIN NOCOMPRESS */\n";
  115. if ($debugjs) echo "\ntry {\n";
  116. js_load($file);
  117. if ($debugjs) echo "\n} catch (e) {\n logError(e, '" . str_replace(DOKU_INC, '', $file) . "');\n}\n";
  118. if ($ismin) echo "\n/* END NOCOMPRESS */\n";
  119. echo "\n\n/* XXXXXXXXXX end of " . str_replace(DOKU_INC, '', $file) . " XXXXXXXXXX */\n\n";
  120. }
  121. // init stuff
  122. if ($conf['locktime'] != 0) {
  123. js_runonstart("dw_locktimer.init(" . ($conf['locktime'] - 60) . "," . $conf['usedraft'] . ")");
  124. }
  125. // init hotkeys - must have been done after init of toolbar
  126. # disabled for FS#1958 js_runonstart('initializeHotkeys()');
  127. // end output buffering and get contents
  128. $js = ob_get_contents();
  129. ob_end_clean();
  130. // strip any source maps
  131. stripsourcemaps($js);
  132. // compress whitespace and comments
  133. if ($conf['compress']) {
  134. try {
  135. $js = (new JSStrip())->compress($js);
  136. } catch (JSStripException $e) {
  137. $js .= "\nconsole.error(" . json_encode($e->getMessage(), JSON_THROW_ON_ERROR) . ");\n";
  138. }
  139. }
  140. $js .= "\n"; // https://bugzilla.mozilla.org/show_bug.cgi?id=316033
  141. http_cached_finish($cache->cache, $js);
  142. }
  143. /**
  144. * Load the given file, handle include calls and print it
  145. *
  146. * @param string $file filename path to file
  147. *
  148. * @author Andreas Gohr <andi@splitbrain.org>
  149. */
  150. function js_load($file)
  151. {
  152. if (!file_exists($file)) return;
  153. static $loaded = [];
  154. $data = io_readFile($file);
  155. while (preg_match('#/\*\s*DOKUWIKI:include(_once)?\s+([\w\.\-_/]+)\s*\*/#', $data, $match)) {
  156. $ifile = $match[2];
  157. // is it a include_once?
  158. if ($match[1]) {
  159. $base = PhpString::basename($ifile);
  160. if (array_key_exists($base, $loaded) && $loaded[$base] === true) {
  161. $data = str_replace($match[0], '', $data);
  162. continue;
  163. }
  164. $loaded[$base] = true;
  165. }
  166. if ($ifile[0] != '/') $ifile = dirname($file) . '/' . $ifile;
  167. $idata = '';
  168. if (file_exists($ifile)) {
  169. $ismin = str_ends_with($ifile, '.min.js');
  170. if ($ismin) $idata .= "\n/* BEGIN NOCOMPRESS */\n";
  171. $idata .= io_readFile($ifile);
  172. if ($ismin) $idata .= "\n/* END NOCOMPRESS */\n";
  173. }
  174. $data = str_replace($match[0], $idata, $data);
  175. }
  176. echo "$data\n";
  177. }
  178. /**
  179. * Returns a list of possible Plugin Scripts (no existance check here)
  180. *
  181. * @return array
  182. *
  183. * @author Andreas Gohr <andi@splitbrain.org>
  184. */
  185. function js_pluginscripts()
  186. {
  187. $list = [];
  188. $plugins = plugin_list();
  189. foreach ($plugins as $p) {
  190. $list[] = DOKU_PLUGIN . "$p/script.js";
  191. }
  192. return $list;
  193. }
  194. /**
  195. * Return an two-dimensional array with strings from the language file of each plugin.
  196. *
  197. * - $lang['js'] must be an array.
  198. * - Nothing is returned for plugins without an entry for $lang['js']
  199. *
  200. * @return array
  201. * @author Gabriel Birke <birke@d-scribe.de>
  202. *
  203. */
  204. function js_pluginstrings()
  205. {
  206. global $conf, $config_cascade;
  207. $pluginstrings = [];
  208. $plugins = plugin_list();
  209. foreach ($plugins as $p) {
  210. $path = DOKU_PLUGIN . $p . '/lang/';
  211. if (isset($lang)) unset($lang);
  212. if (file_exists($path . "en/lang.php")) {
  213. include $path . "en/lang.php";
  214. }
  215. foreach ($config_cascade['lang']['plugin'] as $config_file) {
  216. if (file_exists($config_file . $p . '/en/lang.php')) {
  217. include($config_file . $p . '/en/lang.php');
  218. }
  219. }
  220. if (isset($conf['lang']) && $conf['lang'] != 'en') {
  221. if (file_exists($path . $conf['lang'] . "/lang.php")) {
  222. include($path . $conf['lang'] . '/lang.php');
  223. }
  224. foreach ($config_cascade['lang']['plugin'] as $config_file) {
  225. if (file_exists($config_file . $p . '/' . $conf['lang'] . '/lang.php')) {
  226. include($config_file . $p . '/' . $conf['lang'] . '/lang.php');
  227. }
  228. }
  229. }
  230. if (isset($lang['js'])) {
  231. $pluginstrings[$p] = $lang['js'];
  232. }
  233. }
  234. return $pluginstrings;
  235. }
  236. /**
  237. * Return an two-dimensional array with strings from the language file of current active template.
  238. *
  239. * - $lang['js'] must be an array.
  240. * - Nothing is returned for template without an entry for $lang['js']
  241. *
  242. * @param string $tpl
  243. * @return array
  244. */
  245. function js_templatestrings($tpl)
  246. {
  247. global $conf, $config_cascade;
  248. $path = tpl_incdir() . 'lang/';
  249. $templatestrings = [];
  250. if (file_exists($path . "en/lang.php")) {
  251. include $path . "en/lang.php";
  252. }
  253. foreach ($config_cascade['lang']['template'] as $config_file) {
  254. if (file_exists($config_file . $conf['template'] . '/en/lang.php')) {
  255. include($config_file . $conf['template'] . '/en/lang.php');
  256. }
  257. }
  258. if (isset($conf['lang']) && $conf['lang'] != 'en' && file_exists($path . $conf['lang'] . "/lang.php")) {
  259. include $path . $conf['lang'] . "/lang.php";
  260. }
  261. if (isset($conf['lang']) && $conf['lang'] != 'en') {
  262. if (file_exists($path . $conf['lang'] . "/lang.php")) {
  263. include $path . $conf['lang'] . "/lang.php";
  264. }
  265. foreach ($config_cascade['lang']['template'] as $config_file) {
  266. if (file_exists($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php')) {
  267. include($config_file . $conf['template'] . '/' . $conf['lang'] . '/lang.php');
  268. }
  269. }
  270. }
  271. if (isset($lang['js'])) {
  272. $templatestrings[$tpl] = $lang['js'];
  273. }
  274. return $templatestrings;
  275. }
  276. /**
  277. * Escapes a String to be embedded in a JavaScript call, keeps \n
  278. * as newline
  279. *
  280. * @param string $string
  281. * @return string
  282. *
  283. * @author Andreas Gohr <andi@splitbrain.org>
  284. */
  285. function js_escape($string)
  286. {
  287. return str_replace('\\\\n', '\\n', addslashes($string));
  288. }
  289. /**
  290. * Adds the given JavaScript code to the window.onload() event
  291. *
  292. * @param string $func
  293. *
  294. * @author Andreas Gohr <andi@splitbrain.org>
  295. */
  296. function js_runonstart($func)
  297. {
  298. echo "jQuery(function(){ $func; });" . NL;
  299. }