From e0791e0c90408997a6cc67ecfbab31495be016ce Mon Sep 17 00:00:00 2001 From: gtbu <manbu@mail.de> Date: Thu, 10 Apr 2025 19:56:26 +0200 Subject: [PATCH] update combine Better path-resolution --- favicon.ico | Bin 17542 -> 15406 bytes include/tool/Output/Combine.php | 20 +- include/tool/Output/CombineCss.php | 442 +++++++++++------- themes/Bootstrap5.3_mult/1-white/style.scss | 2 - themes/Bootstrap5.3_mult/2-white/style.scss | 1 - themes/Bootstrap5.3_mult/3-blue/style.scss | 2 - themes/Bootstrap5.3_mult/4-green/style.scss | 2 - .../_common/common_style.scss | 7 - .../_common/compensate_fixed_navbar.scss | 4 +- themes/Bootstrap5.3_mult/template.php | 9 +- 10 files changed, 303 insertions(+), 186 deletions(-) diff --git a/favicon.ico b/favicon.ico index e66ba939c8fbc79262c9aaaa0ed0d51e7b24b5d8..c9f102244eed332e2616cfb2b8446860dcfc859a 100644 GIT binary patch literal 15406 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 LqOV$D#x3w4<7&J2 literal 17542 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 bNGa#1T|A4&xpe|(*%1TR!#b?XHf;M}yh72o diff --git a/include/tool/Output/Combine.php b/include/tool/Output/Combine.php index 1d9aa6a..0b1d097 100644 --- a/include/tool/Output/Combine.php +++ b/include/tool/Output/Combine.php @@ -669,7 +669,7 @@ class Combine{ /** * Combine CSS files */ - public function CombineCSS($full_paths){ + public function CombineCSSold($full_paths){ $imports = ''; $combined_content = ''; @@ -699,6 +699,24 @@ class Combine{ } + public function CombineCSS($full_paths){ // Renamed for clarity, use your actual function name + + $all_prepended_imports = ''; + $combined_content = ''; + // $new_imported = []; // Caching logic needs rethinking based on new class + + foreach($full_paths as $file => $full_path){ + $temp = new \gp\tool\Output\CombineCss($file); + $combined_content .= "\n/* " . htmlspecialchars($file) . " */\n"; + $combined_content .= $temp->final_content; // final_content instead of content + $all_prepended_imports .= $temp->prepended_imports; // prepended_imports instead of imports + } + + $combined_content = $all_prepended_imports . $combined_content; + + return $combined_content; + } + /** * Combine JS files * diff --git a/include/tool/Output/CombineCss.php b/include/tool/Output/CombineCss.php index 2215ba6..8782753 100644 --- a/include/tool/Output/CombineCss.php +++ b/include/tool/Output/CombineCss.php @@ -1,209 +1,325 @@ <?php - namespace gp\tool\Output; defined('is_running') or die('Not an entry point...'); -/** - * Get the contents of $file and fix paths: - * - url(..) - * - @import - * - @import url(..) - */ -class CombineCSS{ +class CombineCSS { - public $content; - public $file; - public $full_path; - public $imported = array(); - public $imports = ''; + // Public properties to access results + public $combined_content_raw = ''; // The combined, path-fixed CSS before minification + public $final_content = ''; // The final output (minified if requested/possible) + public $prepended_imports = ''; // @import rules that were kept (remote, media queries) + public $processed_files = []; // Files processed in this instance to prevent loops - public function __construct($file){ - global $dataDir; + // Internal properties + private $entry_file_path; // Normalized path relative to $dataDir for the initial file + private $minify_output; // Flag whether to minify the final output + private $dataDir_norm; // Normalized $dataDir path - includeFile('thirdparty/cssmin_v.1.0.php'); + /** + * Constructor + * + * @param string $file Path to the entry CSS file, relative to $dataDir + * @param bool $minify Whether to minify the final output + */ + public function __construct($file, $minify = true) { + global $dataDir; + $this->minify_output = $minify; - $this->file = $file; - $this->full_path = $dataDir.$file; + // --- Path Normalization --- + // Ensure $dataDir ends with a slash and uses forward slashes + $this->dataDir_norm = rtrim(str_replace('\\', '/', $dataDir), '/') . '/'; + // Ensure $file starts without a slash and uses forward slashes + $this->entry_file_path = ltrim(str_replace('\\', '/', $file), '/'); + $full_path = $this->dataDir_norm . $this->entry_file_path; + $real_full_path = realpath($full_path); - $this->content = file_get_contents($this->full_path); - $this->content = \cssmin::minify($this->content); + if (!$real_full_path || !file_exists($real_full_path)) { + trigger_error('CombineCSS: Entry file not found: ' . htmlspecialchars($full_path), E_USER_WARNING); + $this->final_content = '/* CombineCSS Error: Entry file not found: ' . htmlspecialchars($file) . ' */'; + return; + } - $this->CSS_Import(); - $this->CSS_FixUrls(); - } + // --- Load Minifier if needed --- + if ($this->minify_output) { + // Consider adding a check if the class/function exists after include + if (!class_exists('\cssmin')) { + includeFile('thirdparty/cssmin_v.1.0.php'); + if (!class_exists('\cssmin')) { + trigger_error('CombineCSS: cssmin class not found after including thirdparty/cssmin_v.1.0.php. Minification disabled.', E_USER_WARNING); + $this->minify_output = false; // Disable minification if class isn't there + } + } + } + // --- Start Processing --- + $this->processed_files = []; // Reset for this instance + $this->prepended_imports = ''; + $this->combined_content_raw = $this->processFile($real_full_path); - /** - * Include the css from @imported css - * - * Will include the css from these - * @import "../styles.css"; - * @import url("../styles.css"); - * @import styles.css; - * - * - * Will preserve the @import rule for these - * @import "styles.css" screen,tv; - * @import url('http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/smoothness/jquery-ui.css.css'); - * - */ - public function CSS_Import($offset=0){ - global $dataDir; + // Prepend any imports that were kept + $this->combined_content_raw = $this->prepended_imports . $this->combined_content_raw; - $pos = strpos($this->content,'@import ',$offset); - if( !is_numeric($pos) ){ - return; - } - $replace_start = $pos; - $pos += 8; + // --- Final Minification --- + if ($this->minify_output) { + try { + // Ensure cssmin class exists before calling + if (class_exists('\cssmin')) { + $this->final_content = \cssmin::minify($this->combined_content_raw); + } else { + // Should have been caught earlier, but as a fallback + $this->final_content = $this->combined_content_raw; + } + } catch (\Exception $e) { + trigger_error('CombineCSS: Minification failed for ' . htmlspecialchars($file) . ': ' . $e->getMessage(), E_USER_WARNING); + $this->final_content = $this->combined_content_raw; // Fallback to raw content on error + } + } else { + $this->final_content = $this->combined_content_raw; + } + } - $replace_end = strpos($this->content,';',$pos); - if( !is_numeric($replace_end) ){ - return; - } + /** + * Process a single CSS file: read, handle imports, fix URLs. + * Returns the processed (but not minified) CSS content. + * + * @param string $real_path Absolute filesystem path to the CSS file. + * @return string Processed CSS content. + */ + private function processFile($real_path) { + // --- Prevent Infinite Loops --- + if (isset($this->processed_files[$real_path])) { + trigger_error('CombineCSS: Recursive import detected and skipped for: ' . htmlspecialchars($real_path), E_USER_WARNING); + return '/* CombineCSS Error: Recursive import skipped: ' . htmlspecialchars(basename($real_path)) . ' */'; + } + $this->processed_files[$real_path] = true; - $import_orig = substr($this->content,$pos,$replace_end-$pos); - $import_orig = trim($import_orig); - $replace_len = $replace_end-$replace_start+1; + // --- Read File --- + $content = @file_get_contents($real_path); // Use @ to suppress warning, check result + if ($content === false) { + trigger_error('CombineCSS: Could not read file: ' . htmlspecialchars($real_path), E_USER_WARNING); + unset($this->processed_files[$real_path]); // Allow reprocessing if attempted again? Maybe not. + return '/* CombineCSS Error: Could not read file: ' . htmlspecialchars(basename($real_path)) . ' */'; + } - //get url(..) - $media = ''; - if( substr($import_orig,0,4) == 'url(' ){ - $end_url_pos = strpos($import_orig,')'); - $import = substr($import_orig,4, $end_url_pos-4); - $import = trim($import); - $import = trim($import,'"\''); - $media = substr($import_orig,$end_url_pos+1); - }elseif( $import_orig[0] == '"' || $import_orig[0] == "'" ){ - $end_url_pos = strpos($import_orig,$import_orig[0],1); - $import = substr($import_orig,1, $end_url_pos-1); - $import = trim($import); - $media = substr($import_orig,$end_url_pos+1); - } + $source_dir = dirname($real_path); // Directory of the current file + $output_buffer = ''; + $offset = 0; + // --- Process @import Rules --- + // Regex to find @import rules, handling url() and quoted strings + $import_pattern = '/@import\s+(?:url\(\s*([\'"]?)(.*?)\1\s*\)|([\'"])(.*?)\3)\s*([^;]*);/i'; - // keep @import when the file is on a remote server? - if( strpos($import,'//') !== false ){ - $this->imports .= substr($this->content, $replace_start, $replace_len ); - $this->content = substr_replace( $this->content, '', $replace_start, $replace_len); - $this->CSS_Import($offset); - return; - } + while (preg_match($import_pattern, $content, $matches, PREG_OFFSET_CAPTURE, $offset)) { + $match_info = $matches[0]; // [full_match_string, offset] + $full_match = $match_info[0]; + $match_start = $match_info[1]; + // Append the content *before* this @import rule, fixing its URLs + $content_before = substr($content, $offset, $match_start - $offset); + $output_buffer .= $this->fixUrlsInBlock($content_before, $source_dir); - //if a media type is set, keep the @import - $media = trim($media); - if( !empty($media) ){ - $import = \gp\tool::GetDir(dirname($this->file).'/'.$import); - $import = $this->ReduceUrl($import); - $this->imports .= '@import url("'.$import.'") '.$media.';'; - $this->content = substr_replace( $this->content, '', $replace_start, $replace_len); - $this->CSS_Import($offset); - return; - } + // Extract import details + // Group 2 is URL from url('...'), Group 4 is URL from "..." + $import_url = trim(!empty($matches[2][0]) ? $matches[2][0] : $matches[4][0]); + $media_query = trim($matches[5][0]); + $is_remote = preg_match('#^(https?:|//)#i', $import_url); + $has_media = !empty($media_query); - //include the css - $full_path = false; - if( $import[0] != '/' ){ - $import = dirname($this->file).'/'.$import; - $import = $this->ReduceUrl($import); - } - $full_path = $dataDir.$import; + if ($is_remote || $has_media) { + // --- Keep Import Rule --- + $resolved_import_url = $import_url; + if (!$is_remote && !$this->isAbsoluteUrl($import_url)) { + // Resolve local path relative to *this* file's directory + $resolved_import_url = $this->resolvePath($import_url, $source_dir); + } + // Add to the list of imports to prepend later + $this->prepended_imports .= '@import url("' . $resolved_import_url . '")' . ($has_media ? ' ' . $media_query : '') . ";\n"; - if( file_exists($full_path) ){ + } else { + // --- Inline Import Rule --- + if ($this->isAbsoluteUrl($import_url)) { + // Handle root-relative imports (e.g., /css/other.css) + $import_full_path = $this->dataDir_norm . ltrim($import_url, '/'); + } else { + // Handle relative imports (e.g., ../common.css) + $import_full_path = $source_dir . '/' . $import_url; + } - $temp = new \gp\tool\Output\CombineCss($import); - $this->content = substr_replace($this->content,$temp->content,$replace_start,$replace_end-$replace_start+1); - $this->imported[] = $full_path; - $this->imported = array_merge($this->imported,$temp->imported); - $this->imports .= $temp->imports; + // Canonicalize and get real path + $import_real_path = realpath($this->normalizePath($import_full_path)); - $this->CSS_Import($offset); - return; - } + if ($import_real_path && file_exists($import_real_path)) { + // Recursively process the imported file + $imported_content = $this->processFile($import_real_path); + $output_buffer .= $imported_content; // Append processed content + } else { + trigger_error('CombineCSS: Imported file not found or path error: ' . htmlspecialchars($import_full_path) . ' (referenced in ' . htmlspecialchars($real_path) . ')', E_USER_WARNING); + $output_buffer .= '/* CombineCSS Error: Import not found: ' . htmlspecialchars($import_url) . ' */'; + } + } - $this->CSS_Import($pos); - } + // Move offset past the processed @import rule + $offset = $match_start + strlen($full_match); + } // End while loop for @import - public function CSS_FixUrls($offset=0){ - $pos = strpos($this->content,'url(',$offset); - if( !is_numeric($pos) ){ - return; - } - $pos += 4; + // --- Process Remaining Content --- + // Append the rest of the file content (after the last @import), fixing its URLs + $content_after = substr($content, $offset); + $output_buffer .= $this->fixUrlsInBlock($content_after, $source_dir); - $pos2 = strpos($this->content,')',$pos); - if( !is_numeric($pos2) ){ - return; - } - $url = substr($this->content,$pos,$pos2-$pos); + // Clean up recursion tracking for this file (allows it to be imported again via a different path if needed, though generally discouraged) + // unset($this->processed_files[$real_path]); // Optional: Decide if a file should *never* be processed twice per instance - $this->CSS_FixUrl($url,$pos,$pos2); + return $output_buffer; + } - return $this->CSS_FixUrls($pos2); - } + /** + * Finds all url() references in a block of CSS and fixes relative paths. + * + * @param string $content_block CSS content segment. + * @param string $source_dir Absolute directory path of the file this content came from. + * @return string CSS content segment with URLs fixed. + */ + private function fixUrlsInBlock($content_block, $source_dir) { + if (empty($content_block) || strpos($content_block, 'url(') === false) { + return $content_block; + } - public function CSS_FixUrl($url,$pos,$pos2){ - global $dataDir; + // Regex to find url(...) patterns, handling optional quotes + $url_pattern = '/url\(\s*([\'"]?)(.*?)\1\s*\)/i'; - $url = trim($url); - $url = trim($url,'"\''); + return preg_replace_callback( + $url_pattern, + function ($matches) use ($source_dir) { + $original_match = $matches[0]; // The full url(...) match + $url = trim($matches[2]); // The actual URL inside - if( empty($url) ){ - return; - } + // Don't modify absolute URLs, data URIs, or empty URLs + if (empty($url) || $this->isAbsoluteUrl($url) || strncasecmp($url, 'data:', 5) === 0) { + return $original_match; // Return the original match unchanged + } - //relative url - if( $url[0] == '/' ){ - return; - }elseif( strpos($url,'://') > 0 ){ - return; - }elseif( preg_match('/^data:/i', $url) ){ - return; - } + // Resolve the relative path + $resolved_url = $this->resolvePath($url, $source_dir); + // Return the corrected url() string, always quoted for safety + return 'url("' . $resolved_url . '")'; + }, + $content_block + ); + } - //use a relative path so sub.domain.com and domain.com/sub both work - $replacement = \gp\tool::GetDir(dirname($this->file).'/'.$url); - $replacement = $this->ReduceUrl($replacement); + /** + * Resolves a relative URL from a CSS file to be relative to the web root ($dataDir). + * + * @param string $relative_url The relative URL found in the CSS. + * @param string $source_dir The absolute directory path where the CSS file resides. + * @return string The resolved URL, typically starting with '/' relative to the web root. + */ + private function resolvePath($relative_url, $source_dir) { + // Separate query string and fragment + $query_fragment = ''; + $path_only = $relative_url; + if (($pos = strpos($relative_url, '?')) !== false || ($pos = strpos($relative_url, '#')) !== false) { + $query_fragment = substr($relative_url, $pos); + $path_only = substr($relative_url, 0, $pos); + } + $combined_path = $source_dir . '/' . $path_only; + $reduced_path = $this->reducePath($this->normalizePath($combined_path)); - $replacement = '"'.$replacement.'"'; - $this->content = substr_replace($this->content,$replacement,$pos,$pos2-$pos); - } + error_log("--- resolvePath Check ---"); + error_log("Relative URL: " . $relative_url); + error_log("Source Dir: " . $source_dir); + error_log("Reduced FS Path: " . $reduced_path); + error_log("DataDir Norm: " . $this->dataDir_norm); - /** - * Canonicalize a path by resolving references to '/./', '/../' - * Does not remove leading "../" - * @param string path or url - * @return string Canonicalized path - * - */ - public function ReduceUrl($url){ + $starts_with_check = (stripos($reduced_path, $this->dataDir_norm) === 0); + error_log("Does Reduced start w/ DataDir (Case-Insensitive)? " . ($starts_with_check ? 'YES' : 'NO')); - $temp = explode('/',$url); - $result = array(); - foreach($temp as $i => $path){ - if( $path == '.' ){ - continue; - } - if( $path == '..' ){ - for($j=$i-1;$j>0;$j--){ - if( isset($result[$j]) ){ - unset($result[$j]); - continue 2; - } - } - } - $result[$i] = $path; - } - - return implode('/',$result); - } + if ($starts_with_check) { + // Get path part after dataDir + $path_relative_to_datadir = substr($reduced_path, strlen($this->dataDir_norm)); + // --- Use Typesetter's Base URL --- + // Assumes \gp\tool::GetUrl('') returns http://localhost/T53test5g5/ + // Or use \gp\tool::GetDir('') if that returns /T53test5g5/ + $base_url = \gp\tool::GetDir(''); // Get base path like /T53test5g5/ + $final_web_path = rtrim($base_url, '/') . '/' . ltrim($path_relative_to_datadir, '/'); + // --- End Modification --- + error_log("Resolved Web Path (Using Base Path): " . $final_web_path . $query_fragment); + return $final_web_path . $query_fragment; + } else { + // ... (existing trigger_error code) ... + $fallback_path = $path_only; + error_log("Resolved Fallback Path (Outside dataDir): " . $fallback_path . $query_fragment); + return $fallback_path . $query_fragment; + } } + + /** + * Normalizes a path: forward slashes, no duplicate slashes. + * + * @param string $path + * @return string + */ + private function normalizePath($path) { + return preg_replace('#/+#', '/', str_replace('\\', '/', $path)); + } + + /** + * Canonicalizes a path by resolving '/./' and '/../'. + * + * @param string $path Normalized path. + * @return string Canonicalized path. + */ + private function reducePath($path) { + $parts = explode('/', $path); + $result = []; + $is_absolute = str_starts_with($path, '/'); + + foreach ($parts as $part) { + if ($part === '.' || $part === '') { + continue; + } + if ($part === '..') { + // Only pop if result is not empty and the last element is not '..' + // This prevents going above the root in relative paths like ../../file + if (!empty($result) && end($result) !== '..') { + array_pop($result); + } elseif (!$is_absolute) { + // Keep '..' if path is relative and we are at the start or after other '..' + $result[] = '..'; + } + // If absolute, popping '..' at the root does nothing + } else { + $result[] = $part; + } + } + + // Handle the case of an absolute path resolving to root ('/') + $final_path = implode('/', $result); + if ($is_absolute) { + return '/' . $final_path; + } else { + // Handle empty result for relative path (e.g. "dir/..") -> "." + return ($final_path === '') ? '.' : $final_path; + } + } + + /** + * Checks if a URL is absolute (protocol-relative, http, https, data, or root-relative). + * + * @param string $url + * @return bool + */ + private function isAbsoluteUrl($url) { + // Scheme relative (//), http/https, data URI, or root relative (/) + return preg_match('#^(\/\/|https?:|data:|/)#i', $url); + } +} \ No newline at end of file diff --git a/themes/Bootstrap5.3_mult/1-white/style.scss b/themes/Bootstrap5.3_mult/1-white/style.scss index aa7ac4b..a6e801c 100644 --- a/themes/Bootstrap5.3_mult/1-white/style.scss +++ b/themes/Bootstrap5.3_mult/1-white/style.scss @@ -2,8 +2,6 @@ @import 'include/thirdparty/Bootstrap5.3/scss/_bootstrap.scss'; -/*! @import '_variables.scss'; */ - $text-shadow: 0 1px 0 rgba(0, 0, 0, .05) !default; $navbar-height: ($nav-link-height + $navbar-padding-y * 2); diff --git a/themes/Bootstrap5.3_mult/2-white/style.scss b/themes/Bootstrap5.3_mult/2-white/style.scss index f37912c..d1cb10b 100644 --- a/themes/Bootstrap5.3_mult/2-white/style.scss +++ b/themes/Bootstrap5.3_mult/2-white/style.scss @@ -1,7 +1,6 @@ @import 'include/thirdparty/Bootstrap5.3/scss/_bootstrap.scss'; -/*! @import '_variables.scss'; */ $text-shadow: 0 1px 0 rgba(0, 0, 0, .05) !default; diff --git a/themes/Bootstrap5.3_mult/3-blue/style.scss b/themes/Bootstrap5.3_mult/3-blue/style.scss index bb2339e..2877687 100644 --- a/themes/Bootstrap5.3_mult/3-blue/style.scss +++ b/themes/Bootstrap5.3_mult/3-blue/style.scss @@ -1,8 +1,6 @@ @import 'include/thirdparty/Bootstrap5.3/scss/_bootstrap.scss'; -/*! @import '_variables.scss'; */ - $text-shadow: 0 1px 0 rgba(0, 0, 0, .05) !default; $navbar-height: ($nav-link-height + $navbar-padding-y * 2); diff --git a/themes/Bootstrap5.3_mult/4-green/style.scss b/themes/Bootstrap5.3_mult/4-green/style.scss index b1a58d3..0692c88 100644 --- a/themes/Bootstrap5.3_mult/4-green/style.scss +++ b/themes/Bootstrap5.3_mult/4-green/style.scss @@ -1,7 +1,5 @@ @import 'include/thirdparty/Bootstrap5.3/scss/_bootstrap.scss'; -/*! @import '_variables.scss'; */ - $text-shadow: 0 1px 0 rgba(0, 0, 0, .05) !default; $navbar-height: ($nav-link-height + $navbar-padding-y * 2); diff --git a/themes/Bootstrap5.3_mult/_common/common_style.scss b/themes/Bootstrap5.3_mult/_common/common_style.scss index d6872d0..820b052 100644 --- a/themes/Bootstrap5.3_mult/_common/common_style.scss +++ b/themes/Bootstrap5.3_mult/_common/common_style.scss @@ -1,10 +1,3 @@ -// Theme Bootswatch 4 Scss -// Common styles used in all layouts -// -// Instead of Autoprefixer we use vendor-prefix mixins, -// see /include/thirdparty/Bootstrap4/scss/bootstrap/vp-mixins -// -// Migrate to Bootstrap 4: check out https://getbootstrap.com/docs/4.4/migration/ // Imports diff --git a/themes/Bootstrap5.3_mult/_common/compensate_fixed_navbar.scss b/themes/Bootstrap5.3_mult/_common/compensate_fixed_navbar.scss index 492685c..b535fc6 100644 --- a/themes/Bootstrap5.3_mult/_common/compensate_fixed_navbar.scss +++ b/themes/Bootstrap5.3_mult/_common/compensate_fixed_navbar.scss @@ -1,6 +1,4 @@ -// Theme Bootswatch 4 Scss -// Compensations for fixed navbar -// only required when the main navbar has the css class 'fixed-top' + // padding-top for the body element body { diff --git a/themes/Bootstrap5.3_mult/template.php b/themes/Bootstrap5.3_mult/template.php index 60573c6..0e21ec9 100644 --- a/themes/Bootstrap5.3_mult/template.php +++ b/themes/Bootstrap5.3_mult/template.php @@ -1,10 +1,9 @@ <?php -/** - * Theme Bootswatch 5 Scss 5.2.3 - * Typesetter CMS theme template - * based on https://bootswatch.com - * + + /** + * Typesetter CMS theme template */ + global $page, $config; $path = $page->theme_dir . '/drop_down_menu.php'; //include_once($path);