From f11f311660d1c60991f5d6b06a862a595d101758 Mon Sep 17 00:00:00 2001 From: sharklatan Date: Sat, 30 May 2026 18:57:17 -0600 Subject: [PATCH 1/4] Implement URL entity normalization in DOM Add normalization for encoded URL characters in document. --- pluginable.php | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pluginable.php b/pluginable.php index ff6d92d..050c7e1 100644 --- a/pluginable.php +++ b/pluginable.php @@ -164,6 +164,11 @@ public function append() { // Run all pages actions after specifics for xpath $xpath = $this->do_action( 'hcpp_all_xpath', $xpath ); $dom = $xpath->document; + + // Some plugins or PHP HTML5 encoders can convert URL characters like + // '=' and '.' to named entities (e.g. =, .). Normalize + // URL-like attributes before final render so panel links remain valid. + $this->normalize_url_entities_in_document( $dom ); $html = $dom->saveHTML(); // Run the path specific actions for html @@ -176,6 +181,53 @@ public function append() { echo $html; } + /** + * Normalize encoded URL characters in common link attributes. + * + * This prevents broken query strings like: + * /login/?loginas=api&token=... + * while keeping DOM serialization responsible for final escaping. + * + * @param DOMDocument $dom The DOM document to normalize. + */ + public function normalize_url_entities_in_document( $dom ) { + if ( !( $dom instanceof DOMDocument ) ) { + return; + } + + $entity_map = [ + '=' => '=', + '.' => '.', + '=' => '=', + '=' => '=', + '.' => '.', + '.' => '.', + ]; + + $xpath = new DOMXPath( $dom ); + $nodes = $xpath->query( '//*[@href or @src or @action]' ); + if ( $nodes === false ) { + return; + } + + foreach ( $nodes as $node ) { + foreach ( ['href', 'src', 'action'] as $attr ) { + if ( !$node->hasAttribute( $attr ) ) { + continue; + } + + $value = $node->getAttribute( $attr ); + $normalized = strtr( $value, $entity_map ); + $normalized = html_entity_decode( $normalized, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); + $normalized = strtr( $normalized, $entity_map ); + + if ( $normalized !== $value ) { + $node->setAttribute( $attr, $normalized ); + } + } + } + } + /** * Copy a folder recursively, quickly, and retain/restore executable permissions. */ From 08761b1d1024f29a3301d834b92ce619718e71f0 Mon Sep 17 00:00:00 2001 From: sharklatan Date: Sat, 30 May 2026 20:36:54 -0600 Subject: [PATCH 2/4] Refactor normalization function for HTML entities Updated the normalization function to handle HTML entities and adjusted related comments. --- pluginable.php | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/pluginable.php b/pluginable.php index 050c7e1..fd7d2a2 100644 --- a/pluginable.php +++ b/pluginable.php @@ -165,10 +165,11 @@ public function append() { $xpath = $this->do_action( 'hcpp_all_xpath', $xpath ); $dom = $xpath->document; - // Some plugins or PHP HTML5 encoders can convert URL characters like - // '=' and '.' to named entities (e.g. =, .). Normalize - // URL-like attributes before final render so panel links remain valid. - $this->normalize_url_entities_in_document( $dom ); + // Some plugins or PHP HTML5 encoders can convert visible text and + // URL characters to named entities (e.g. _, @, + // =, .). Normalize them before final render so the + // panel UI remains readable and links remain valid. + $this->normalize_html_entities_in_document( $dom ); $html = $dom->saveHTML(); // Run the path specific actions for html @@ -190,22 +191,51 @@ public function append() { * * @param DOMDocument $dom The DOM document to normalize. */ - public function normalize_url_entities_in_document( $dom ) { + public function normalize_html_entities_in_document( $dom ) { if ( !( $dom instanceof DOMDocument ) ) { return; } $entity_map = [ + '_' => '_', + '@' => '@', '=' => '=', '.' => '.', + // Also handle common double-escaped forms (&...) + '_' => '_', + '@' => '@', + '=' => '=', + '.' => '.', + // Numeric entities possibly double-escaped + '_' => '_', + '@' => '@', + '=' => '=', + '.' => '.', + '_' => '_', + '@' => '@', '=' => '=', '=' => '=', + '_' => '_', + '@' => '@', '.' => '.', '.' => '.', ]; $xpath = new DOMXPath( $dom ); - $nodes = $xpath->query( '//*[@href or @src or @action]' ); + $textNodes = $xpath->query( '//text()[not(parent::script or parent::style)]' ); + if ( $textNodes !== false ) { + foreach ( $textNodes as $textNode ) { + $value = $textNode->nodeValue; + $normalized = strtr( $value, $entity_map ); + $normalized = html_entity_decode( $normalized, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); + $normalized = strtr( $normalized, $entity_map ); + if ( $normalized !== $value ) { + $textNode->nodeValue = $normalized; + } + } + } + + $nodes = $xpath->query( '//*[@href or @src or @action or @title or @alt or @value or @placeholder]' ); if ( $nodes === false ) { return; } @@ -628,6 +658,14 @@ public function install() { 'auto_prepend_file = /etc/hestiacp/hooks/pluginable.php' ); + // Patch the internal Hestia PHP-FPM pool so the panel actually loads + // the pluginable prepend/append hooks after Hestia updates. + $this->patch_file( + '/usr/local/hestia/php/etc/php-fpm.conf', + "php_admin_value[session.save_path] = /usr/local/hestia/data/sessions\n", + "php_admin_value[session.save_path] = /usr/local/hestia/data/sessions\nphp_admin_value[auto_prepend_file] = /etc/hestiacp/hooks/pluginable.php\nphp_admin_value[auto_append_file] = /etc/hestiacp/hooks/pluginable.php" + ); + // Patch Hestia templates php-fpm templates ..templates/web/php-fpm/*.tpl $folderPath = "/usr/local/hestia/data/templates/web/php-fpm"; $files = glob( "$folderPath/*.tpl" ); @@ -850,6 +888,7 @@ public function remove() { shell_exec( 'rm -f /etc/hestiacp/local.conf' ); shell_exec( 'rm -f /usr/local/hestia/bin/v-invoke-plugin' ); $this->restore_backup( '/usr/local/hestia/php/lib/php.ini' ); + $this->restore_backup( '/usr/local/hestia/php/etc/php-fpm.conf' ); // Remove jQuery 3.7.1 shell_exec( 'rm -f /usr/local/hestia/web/js/dist/jquery-3.7.1.min.js' ); From e01c3bbdcff9866fc7f88752910e4a1738382d25 Mon Sep 17 00:00:00 2001 From: sharklatan Date: Sat, 30 May 2026 20:55:50 -0600 Subject: [PATCH 3/4] Change git clone command for hooks folder Updated the clone command to use the latest repository URL. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63f63d6..9ce2c6c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ First, back up your system! This install process will patch (__Read__: ___Perman Clone the latest release version (i.e. replace **v2.0.4** below with the latest release version) to the hooks folder: ``` -sudo git clone --branch v2.0.4 https://github.com/virtuosoft-dev/hestiacp-pluginable /etc/hestiacp/hooks +sudo git clone https://github.com/sharklatan/hestiacp-pluginable /etc/hestiacp/hooks ``` Run the post_install.sh script: From ab2a7f4218057f984e052a868b9443809b7f5332 Mon Sep 17 00:00:00 2001 From: sharklatan Date: Sat, 30 May 2026 21:18:39 -0600 Subject: [PATCH 4/4] Remove HTML entity normalization from pluginable.php Removed normalization of HTML entities in the document and related function to simplify the rendering process. --- pluginable.php | 93 ++------------------------------------------------ 1 file changed, 2 insertions(+), 91 deletions(-) diff --git a/pluginable.php b/pluginable.php index fd7d2a2..d06d062 100644 --- a/pluginable.php +++ b/pluginable.php @@ -164,13 +164,9 @@ public function append() { // Run all pages actions after specifics for xpath $xpath = $this->do_action( 'hcpp_all_xpath', $xpath ); $dom = $xpath->document; - - // Some plugins or PHP HTML5 encoders can convert visible text and - // URL characters to named entities (e.g. _, @, - // =, .). Normalize them before final render so the - // panel UI remains readable and links remain valid. - $this->normalize_html_entities_in_document( $dom ); $html = $dom->saveHTML(); + $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, "UTF-8"); + $html = str_replace(["=","."], ["=","."], $html); // Run the path specific actions for html if ( $path != 'index.php' ) { @@ -182,82 +178,6 @@ public function append() { echo $html; } - /** - * Normalize encoded URL characters in common link attributes. - * - * This prevents broken query strings like: - * /login/?loginas=api&token=... - * while keeping DOM serialization responsible for final escaping. - * - * @param DOMDocument $dom The DOM document to normalize. - */ - public function normalize_html_entities_in_document( $dom ) { - if ( !( $dom instanceof DOMDocument ) ) { - return; - } - - $entity_map = [ - '_' => '_', - '@' => '@', - '=' => '=', - '.' => '.', - // Also handle common double-escaped forms (&...) - '_' => '_', - '@' => '@', - '=' => '=', - '.' => '.', - // Numeric entities possibly double-escaped - '_' => '_', - '@' => '@', - '=' => '=', - '.' => '.', - '_' => '_', - '@' => '@', - '=' => '=', - '=' => '=', - '_' => '_', - '@' => '@', - '.' => '.', - '.' => '.', - ]; - - $xpath = new DOMXPath( $dom ); - $textNodes = $xpath->query( '//text()[not(parent::script or parent::style)]' ); - if ( $textNodes !== false ) { - foreach ( $textNodes as $textNode ) { - $value = $textNode->nodeValue; - $normalized = strtr( $value, $entity_map ); - $normalized = html_entity_decode( $normalized, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); - $normalized = strtr( $normalized, $entity_map ); - if ( $normalized !== $value ) { - $textNode->nodeValue = $normalized; - } - } - } - - $nodes = $xpath->query( '//*[@href or @src or @action or @title or @alt or @value or @placeholder]' ); - if ( $nodes === false ) { - return; - } - - foreach ( $nodes as $node ) { - foreach ( ['href', 'src', 'action'] as $attr ) { - if ( !$node->hasAttribute( $attr ) ) { - continue; - } - - $value = $node->getAttribute( $attr ); - $normalized = strtr( $value, $entity_map ); - $normalized = html_entity_decode( $normalized, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); - $normalized = strtr( $normalized, $entity_map ); - - if ( $normalized !== $value ) { - $node->setAttribute( $attr, $normalized ); - } - } - } - } - /** * Copy a folder recursively, quickly, and retain/restore executable permissions. */ @@ -658,14 +578,6 @@ public function install() { 'auto_prepend_file = /etc/hestiacp/hooks/pluginable.php' ); - // Patch the internal Hestia PHP-FPM pool so the panel actually loads - // the pluginable prepend/append hooks after Hestia updates. - $this->patch_file( - '/usr/local/hestia/php/etc/php-fpm.conf', - "php_admin_value[session.save_path] = /usr/local/hestia/data/sessions\n", - "php_admin_value[session.save_path] = /usr/local/hestia/data/sessions\nphp_admin_value[auto_prepend_file] = /etc/hestiacp/hooks/pluginable.php\nphp_admin_value[auto_append_file] = /etc/hestiacp/hooks/pluginable.php" - ); - // Patch Hestia templates php-fpm templates ..templates/web/php-fpm/*.tpl $folderPath = "/usr/local/hestia/data/templates/web/php-fpm"; $files = glob( "$folderPath/*.tpl" ); @@ -888,7 +800,6 @@ public function remove() { shell_exec( 'rm -f /etc/hestiacp/local.conf' ); shell_exec( 'rm -f /usr/local/hestia/bin/v-invoke-plugin' ); $this->restore_backup( '/usr/local/hestia/php/lib/php.ini' ); - $this->restore_backup( '/usr/local/hestia/php/etc/php-fpm.conf' ); // Remove jQuery 3.7.1 shell_exec( 'rm -f /usr/local/hestia/web/js/dist/jquery-3.7.1.min.js' );