From cc3eb919690d463667a8310bb96352d4b9584454 Mon Sep 17 00:00:00 2001 From: Merci Jacob <68013195+jacob-js@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:18:18 +0200 Subject: [PATCH] Allow the exclusion of certain JS libraries from being bundled when integrating Cypht with third-party software (#1301) * Allow the exclusion of certain JS libraries from being bundled when integrating Cypht with third-party software * Fix unit tests and add the new variable to .env.example * Bundle js files contents instead of the script tags --- .env.example | 4 +- config/app.php | 2 + lib/framework.php | 1 + lib/js_libs.php | 32 +++++++++ lib/modules_exec.php | 3 +- modules/core/js_modules/[cash]/extend.js | 62 ++++++++++++++++ modules/core/output_modules.php | 14 ++-- modules/core/site.js | 70 ------------------- scripts/config_gen.php | 13 ++-- .../modules/core/output_modules_debug.php | 2 + 10 files changed, 120 insertions(+), 83 deletions(-) create mode 100644 lib/js_libs.php create mode 100644 modules/core/js_modules/[cash]/extend.js diff --git a/.env.example b/.env.example index 1b227412c2..297b515afe 100644 --- a/.env.example +++ b/.env.example @@ -208,4 +208,6 @@ FANCY_LOGIN=false #Windows CA certificates #Get ON https://curl.se/ca/cacert.pem #Depending on your PHP Directory, e.g. "c:\php\extras\ssl\cacert.pem" -WIN_CACERT_DIR= \ No newline at end of file +WIN_CACERT_DIR= + +JS_EXCLUDE_DEPS= diff --git a/config/app.php b/config/app.php index 1f0cf46162..063e7dd542 100644 --- a/config/app.php +++ b/config/app.php @@ -1317,4 +1317,6 @@ | Use this setting switch between the legacy login page and the fancy one */ 'fancy_login' => env('FANCY_LOGIN', false), + + 'js_exclude_deps' => env('JS_EXCLUDE_DEPS', ''), ]; diff --git a/lib/framework.php b/lib/framework.php index bf5fdecbb4..1faf4c0a8d 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -35,6 +35,7 @@ require APP_PATH.'lib/servers.php'; require APP_PATH.'lib/api.php'; require APP_PATH.'lib/webdav_formats.php'; +require APP_PATH.'lib/js_libs.php'; require_once APP_PATH.'modules/core/functions.php'; diff --git a/lib/js_libs.php b/lib/js_libs.php new file mode 100644 index 0000000000..eb0b0a476c --- /dev/null +++ b/lib/js_libs.php @@ -0,0 +1,32 @@ + 'vendor/twbs/bootstrap/dist/js/bootstrap.bundle.min.js', + 'cash' => 'third_party/cash.min.js', + 'resumable' => 'third_party/resumable.min.js', + 'ays-beforeunload-shim' => 'third_party/ays-beforeunload-shim.js', + 'jquery-are-you-sure' => 'third_party/jquery.are-you-sure.js', + 'sortable' => 'third_party/sortable.min.js' +]); + +function get_js_libs($exclude_deps = []) { + $js_lib = ''; + + foreach (JS_LIBS as $dep) { + if (!in_array($dep, $exclude_deps)) { + $js_lib .= ''; + } + } + return $js_lib; +} + +function get_js_libs_content($exclude_deps = []) { + $js_lib = ''; + + foreach (JS_LIBS as $dep) { + if (!in_array($dep, $exclude_deps)) { + $js_lib .= file_get_contents(APP_PATH.$dep); + } + } + return $js_lib; +} diff --git a/lib/modules_exec.php b/lib/modules_exec.php index 52080ce6b3..d14027451e 100644 --- a/lib/modules_exec.php +++ b/lib/modules_exec.php @@ -201,7 +201,8 @@ public function merge_response($request, $session, $page) { 'router_login_state' => $session->is_active(), 'router_url_path' => $request->path, 'router_module_list' => $this->site_config->get_modules(), - 'router_app_name' => $this->site_config->get('app_name', 'HM3') + 'router_app_name' => $this->site_config->get('app_name', 'HM3'), + 'router_js_exclude_deps' => $this->site_config->get('js_exclude_deps'), )); } } diff --git a/modules/core/js_modules/[cash]/extend.js b/modules/core/js_modules/[cash]/extend.js new file mode 100644 index 0000000000..a1b29a427b --- /dev/null +++ b/modules/core/js_modules/[cash]/extend.js @@ -0,0 +1,62 @@ +/* extend cash.js with some useful bits */ +$.inArray = function(item, list) { + for (var i in list) { + if (list[i] === item) { + return i; + } + } + return -1; +}; +$.isEmptyObject = function(obj) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + return false; + } + } + return true; +}; +$.fn.submit = function() { this[0].submit(); } +$.fn.focus = function() { this[0].focus(); }; +$.fn.serializeArray = function() { + var parts; + var res = []; + var args = this.serialize().split('&'); + for (var i in args) { + parts = args[i].split('='); + if (parts[0] && parts[1]) { + res.push({'name': parts[0], 'value': parts[1]}); + } + } + return res.map(function(x) {return {name: x.name, value: decodeURIComponent(x.value.replace(/\+/g, " "))}}); +}; +$.fn.sort = function(sort_function) { + var list = []; + for (var i=0, len=this.length; i < len; i++) { + list.push(this[i]); + } + return $(list.sort(sort_function)); +}; +$.fn.fadeOut = function(timeout = 600) { + return this.css("opacity", 0) + .css("transition", `opacity ${timeout}ms`) +}; +$.fn.fadeOutAndRemove = function(timeout = 600) { + this.fadeOut(timeout) + var tm = setTimeout(() => { + this.remove(); + clearTimeout(tm) + }, timeout); + return this; +}; + +$.fn.modal = function(action) { + const modalElement = this[0]; + if (modalElement) { + const modal = new bootstrap.Modal(modalElement); + if (action === 'show') { + modal.show(); + } else if (action === 'hide') { + modal.hide(); + } + } +}; \ No newline at end of file diff --git a/modules/core/output_modules.php b/modules/core/output_modules.php index 4020c82a4c..3719bf9f3c 100644 --- a/modules/core/output_modules.php +++ b/modules/core/output_modules.php @@ -541,12 +541,8 @@ class Hm_Output_page_js extends Hm_Output_Module { protected function output() { if (DEBUG_MODE) { $res = ''; - $js_lib = ''; - $js_lib .= ''; - $js_lib .= ''; - $js_lib .= ''; - $js_lib .= ''; - $js_lib .= ''; + $js_exclude_dependencies = explode(',', $this->get('router_js_exclude_deps', '')); + $js_lib = get_js_libs($js_exclude_dependencies); if ($this->get('encrypt_ajax_requests', '') || $this->get('encrypt_local_storage', '')) { $js_lib .= ''; } @@ -558,6 +554,12 @@ protected function output() { if (in_array($mod, $mods, true)) { $directoriesPattern = str_replace('/', DIRECTORY_SEPARATOR, "{*,*/*}"); foreach (glob($name.'js_modules' . DIRECTORY_SEPARATOR . $directoriesPattern . "*.js", GLOB_BRACE) as $js) { + if (preg_match('/\[(.+)\]/', $js, $matches)) { + $dep = $matches[1]; + if (in_array($dep, $js_exclude_dependencies)) { + continue; + } + } $res .= ''; } diff --git a/modules/core/site.js b/modules/core/site.js index e8c8cefd41..16f8dd9ab5 100644 --- a/modules/core/site.js +++ b/modules/core/site.js @@ -1,75 +1,5 @@ 'use strict'; -/** - * NOTE: Tiki-Cypht integration dynamically removes everything from the begining of this file - * up to swipe_event function definition as it uses jquery (over cash.js) and has bootstrap - * framework already included. If you add code here that you wish to be included in Tiki-Cypht - * integration, add it below swipe_event function definition. - */ - -/* extend cash.js with some useful bits */ -$.inArray = function(item, list) { - for (var i in list) { - if (list[i] === item) { - return i; - } - } - return -1; -}; -$.isEmptyObject = function(obj) { - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - return false; - } - } - return true; -}; -$.fn.submit = function() { this[0].submit(); } -$.fn.focus = function() { this[0].focus(); }; -$.fn.serializeArray = function() { - var parts; - var res = []; - var args = this.serialize().split('&'); - for (var i in args) { - parts = args[i].split('='); - if (parts[0] && parts[1]) { - res.push({'name': parts[0], 'value': parts[1]}); - } - } - return res.map(function(x) {return {name: x.name, value: decodeURIComponent(x.value.replace(/\+/g, " "))}}); -}; -$.fn.sort = function(sort_function) { - var list = []; - for (var i=0, len=this.length; i < len; i++) { - list.push(this[i]); - } - return $(list.sort(sort_function)); -}; -$.fn.fadeOut = function(timeout = 600) { - return this.css("opacity", 0) - .css("transition", `opacity ${timeout}ms`) -}; -$.fn.fadeOutAndRemove = function(timeout = 600) { - this.fadeOut(timeout) - var tm = setTimeout(() => { - this.remove(); - clearTimeout(tm) - }, timeout); - return this; -}; - -$.fn.modal = function(action) { - const modalElement = this[0]; - if (modalElement) { - const modal = new bootstrap.Modal(modalElement); - if (action === 'show') { - modal.show(); - } else if (action === 'hide') { - modal.hide(); - } - } -}; - /* swipe event handler */ var swipe_event = function(el, callback, direction) { var start_x, start_y, dist_x, dist_y, threshold = 150, restraint = 100, diff --git a/scripts/config_gen.php b/scripts/config_gen.php index b4c98770cc..13b07f2797 100644 --- a/scripts/config_gen.php +++ b/scripts/config_gen.php @@ -176,6 +176,7 @@ function get_module_assignments($settings) { $js = ''; $css = ''; $assets = array(); + $js_exclude_dependencies = explode(',', ($settings['js_exclude_deps'] ?? '')); $filters = array('allowed_output' => array(), 'allowed_get' => array(), 'allowed_cookie' => array(), 'allowed_post' => array(), 'allowed_server' => array(), 'allowed_pages' => array()); @@ -190,6 +191,12 @@ function get_module_assignments($settings) { } $directoriesPattern = str_replace('/', DIRECTORY_SEPARATOR, "{*,*/*}"); foreach (glob('modules' . DIRECTORY_SEPARATOR . $mod . DIRECTORY_SEPARATOR . 'js_modules' . DIRECTORY_SEPARATOR . $directoriesPattern . '*.js', GLOB_BRACE) as $js_module) { + if (preg_match('/\[(.+)\]/', $js_module, $matches)) { + $dep = $matches[1]; + if (in_array($dep, $js_exclude_dependencies)) { + continue; + } + } $js .= file_get_contents($js_module); } if (is_readable(sprintf("modules/%s/site.js", $mod))) { @@ -255,7 +262,7 @@ function combine_includes($js, $js_compress, $css, $css_compress, $settings) { } if ($js) { $mods = get_modules($settings); - $js_lib = file_get_contents(VENDOR_PATH . "twbs/bootstrap/dist/js/bootstrap.bundle.min.js"); + $js_lib = get_js_libs_content(explode(',', $settings['js_exclude_deps'])); $js_lib .= file_get_contents("third_party/cash.min.js"); if (in_array('desktop_notifications', $mods, true)) { $js_lib .= file_get_contents("third_party/push.min.js"); @@ -266,10 +273,6 @@ function combine_includes($js, $js_compress, $css, $css_compress, $settings) { $settings['encrypt_local_storage'])) { $js_lib .= file_get_contents("third_party/forge.min.js"); } - $js_lib .= file_get_contents("third_party/resumable.min.js"); - $js_lib .= file_get_contents("third_party/ays-beforeunload-shim.js"); - $js_lib .= file_get_contents("third_party/jquery.are-you-sure.js"); - $js_lib .= file_get_contents("third_party/sortable.min.js"); file_put_contents('tmp.js', $js); $js_out = $js_lib.compress($js, $js_compress, 'tmp.js'); $js_hash = build_integrity_hash($js_out); diff --git a/tests/phpunit/modules/core/output_modules_debug.php b/tests/phpunit/modules/core/output_modules_debug.php index c956bdcf21..3b2384dbea 100644 --- a/tests/phpunit/modules/core/output_modules_debug.php +++ b/tests/phpunit/modules/core/output_modules_debug.php @@ -65,6 +65,8 @@ public function test_page_js_debug($given_router_module_list) { $this->assertEquals(array($expected_output), $res->output_response); } + // TODO: Add a test case excluding some js dependencies + static function router_module_list_provider() { return [ 'one module' => [['core']],