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']],