From 2f2c120ec0a6271fa729d863ef3b70ac438ebf5b Mon Sep 17 00:00:00 2001 From: gtbu Date: Thu, 10 Apr 2025 13:18:27 +0200 Subject: [PATCH] improved cssmin and combine --- include/thirdparty/cssmin_v.1.0.1.php | 46 +++ include/thirdparty/cssmin_v.1.0.php | 124 ++++-- include/tool/Output/CombineCss-old.php | 209 ++++++++++ include/tool/Output/CombineCss.php | 393 ++++++++++--------- themes/Bootswatch5.2_Scss/drop_down_menu.php | 23 +- 5 files changed, 577 insertions(+), 218 deletions(-) create mode 100644 include/thirdparty/cssmin_v.1.0.1.php create mode 100644 include/tool/Output/CombineCss-old.php diff --git a/include/thirdparty/cssmin_v.1.0.1.php b/include/thirdparty/cssmin_v.1.0.1.php new file mode 100644 index 0000000..e7481bf --- /dev/null +++ b/include/thirdparty/cssmin_v.1.0.1.php @@ -0,0 +1,46 @@ + + * include("cssmin.php"); + * file_put_contents("path/to/target.css", cssmin::minify(file_get_contents("path/to/source.css"))); + * + * -- + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * -- + * + * @package cssmin + * @author Joe Scylla + * @copyright 2008 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.0 (2008-01-31) + */ +class cssmin + { + /** + * Minifies stylesheet definitions + * + * @param string $v Stylesheet definitions as string + * @return string Minified stylesheet definitions + */ + static function minify($v) + { + $v = trim($v); + $v = str_replace("\r\n", "\n", $v); + $search = array("/\/\*[\d\D]*?\*\/|\t+/", "/\s+/", "/\}\s+/"); + $replace = array(null, " ", "}\n"); + $v = preg_replace($search, $replace, $v); + $search = array("/\\;\s/", "/\s*\{\\s*/", "/\\:\s+\\#/", "/,\s+/i", "/\\:\s+\\\'/i", "/\\:\s+([0-9A-Z\-]+)/i"); + $replace = array(";", "{", ":#", ",", ":\'", ":$1"); + $v = preg_replace($search, $replace, $v); + $v = str_replace("\n", "" , $v); + return $v; + } + } diff --git a/include/thirdparty/cssmin_v.1.0.php b/include/thirdparty/cssmin_v.1.0.php index e7481bf..1686679 100644 --- a/include/thirdparty/cssmin_v.1.0.php +++ b/include/thirdparty/cssmin_v.1.0.php @@ -1,11 +1,14 @@ * include("cssmin.php"); - * file_put_contents("path/to/target.css", cssmin::minify(file_get_contents("path/to/source.css"))); + * $minifiedCss = cssmin::minify(file_get_contents("path/to/source.css")); + * file_put_contents("path/to/target.css", $minifiedCss); * * -- * @@ -16,31 +19,98 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * -- * - * @package cssmin - * @author Joe Scylla - * @copyright 2008 Joe Scylla - * @license http://opensource.org/licenses/mit-license.php MIT License - * @version 1.0 (2008-01-31) + * @package cssmin + * @author Joe Scylla + * @copyright 2008 Joe Scylla (Modernized 2023) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.0.2 + * modified 2025 by github.com/gtbu */ class cssmin - { - /** - * Minifies stylesheet definitions - * - * @param string $v Stylesheet definitions as string - * @return string Minified stylesheet definitions - */ - static function minify($v) - { - $v = trim($v); - $v = str_replace("\r\n", "\n", $v); - $search = array("/\/\*[\d\D]*?\*\/|\t+/", "/\s+/", "/\}\s+/"); - $replace = array(null, " ", "}\n"); - $v = preg_replace($search, $replace, $v); - $search = array("/\\;\s/", "/\s*\{\\s*/", "/\\:\s+\\#/", "/,\s+/i", "/\\:\s+\\\'/i", "/\\:\s+([0-9A-Z\-]+)/i"); - $replace = array(";", "{", ":#", ",", ":\'", ":$1"); - $v = preg_replace($search, $replace, $v); - $v = str_replace("\n", "" , $v); - return $v; - } - } +{ + /** + * Minifies CSS definitions. + * + * @param mixed $css CSS content as a string. Accepts mixed for basic type check. + * @return string Minified CSS definitions, or an empty string if input is invalid or empty. + */ + public static function minify($css) + { + // Basic input validation: Ensure it's a string. + if (!is_string($css)) { + // error_log('cssmin::minify() expected a string, got ' . gettype($css)); // Optional logging + return ''; // Return empty string for invalid input + } + + // 1. Initial cleanup: Remove leading/trailing whitespace and normalize line endings. + $css = trim($css); + if ($css === '') { + return ''; // Return early if string is empty after trimming + } + $css = str_replace("\r\n", "\n", $css); // Normalize line endings to LF + + // 2. First round of regex replacements: + // - Remove comments (/* ... */) + // - Remove tabs + // - Collapse multiple whitespace chars into a single space + // - Remove whitespace after '}' but add a newline (for structure before next step) + $search = array( + "/\/\*[\s\S]*?\*\//", // Remove /* ... */ comments. [\s\S] matches any char incl. newline. *? is non-greedy. + "/\t+/", // Remove tabs. + "/\s+/", // Collapse whitespace (includes space, tab, newline) into a single space. + "/\}\s+/" // Remove whitespace following a '}' and add a newline. + ); + $replace = array( + "", // Remove comments. + "", // Remove tabs. + " ", // Collapse whitespace to a single space. + "}\n" // Add newline after closing brace. + ); + $css = preg_replace($search, $replace, $css); + + // Check if preg_replace failed (returned null) + if ($css === null) { + // error_log('cssmin::minify() preg_replace step 1 failed'); // Optional logging + return ''; // Return empty on regex error + } + + + // 3. Second round of regex replacements: + // - Remove whitespace around critical CSS characters: ;, {, :, #, ,, ' + // - Remove whitespace between ':' and simple values (keywords, numbers). + $search = array( + "/;\s+/", // Remove whitespace after semicolons (e.g., "; " => ";"). + "/\s*\{\s*/", // Remove whitespace around opening braces (e.g., " { " => "{"). + "/:\s+#/", // Remove whitespace after colon before # (e.g., ": #" => ":#"). + "/,\s+/", // Remove whitespace after commas (e.g., ", " => ","). + "/:\s+'/", // Remove whitespace after colon before single quotes (e.g., ": '" => ":'"). + "/:\s+\"/", // Remove whitespace after colon before double quotes (e.g., ': "' => ':"'). (Added for consistency) + "/:\s+([a-zA-Z0-9\-]+)/i" // Remove whitespace after colon before common values (keywords, numbers, units like 'px'). Case-insensitive. + // (e.g., "color: red" => "color:red", "margin: 10px" => "margin:10px"). Uses backreference $1. + ); + $replace = array( + ";", // ; + "{", // { + ":#", // :# + ",", // , + ":'", // :' + ":\"", // :" (Added) + ":$1" // :value (using backreference) + ); + $css = preg_replace($search, $replace, $css); + + // Check if preg_replace failed (returned null) + if ($css === null) { + // error_log('cssmin::minify() preg_replace step 2 failed'); // Optional logging + return ''; // Return empty on regex error + } + + // 4. Final step: Remove all remaining newline characters. + // This contradicts step 2 adding newlines after '}', but matches the original code's behavior + // resulting in a single-line output. + $css = str_replace("\n", "", $css); + + // Final trim just in case (though unlikely needed after previous steps) + return trim($css); + } +} \ No newline at end of file diff --git a/include/tool/Output/CombineCss-old.php b/include/tool/Output/CombineCss-old.php new file mode 100644 index 0000000..2215ba6 --- /dev/null +++ b/include/tool/Output/CombineCss-old.php @@ -0,0 +1,209 @@ +file = $file; + $this->full_path = $dataDir.$file; + + + $this->content = file_get_contents($this->full_path); + $this->content = \cssmin::minify($this->content); + + $this->CSS_Import(); + $this->CSS_FixUrls(); + } + + + /** + * 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; + + $pos = strpos($this->content,'@import ',$offset); + if( !is_numeric($pos) ){ + return; + } + $replace_start = $pos; + $pos += 8; + + $replace_end = strpos($this->content,';',$pos); + if( !is_numeric($replace_end) ){ + return; + } + + $import_orig = substr($this->content,$pos,$replace_end-$pos); + $import_orig = trim($import_orig); + $replace_len = $replace_end-$replace_start+1; + + //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); + } + + + // 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; + } + + + //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; + } + + + //include the css + $full_path = false; + if( $import[0] != '/' ){ + $import = dirname($this->file).'/'.$import; + $import = $this->ReduceUrl($import); + } + $full_path = $dataDir.$import; + + if( file_exists($full_path) ){ + + $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; + + $this->CSS_Import($offset); + return; + } + + $this->CSS_Import($pos); + } + + + public function CSS_FixUrls($offset=0){ + $pos = strpos($this->content,'url(',$offset); + if( !is_numeric($pos) ){ + return; + } + $pos += 4; + + $pos2 = strpos($this->content,')',$pos); + if( !is_numeric($pos2) ){ + return; + } + $url = substr($this->content,$pos,$pos2-$pos); + + $this->CSS_FixUrl($url,$pos,$pos2); + + return $this->CSS_FixUrls($pos2); + } + + public function CSS_FixUrl($url,$pos,$pos2){ + global $dataDir; + + $url = trim($url); + $url = trim($url,'"\''); + + if( empty($url) ){ + return; + } + + //relative url + if( $url[0] == '/' ){ + return; + }elseif( strpos($url,'://') > 0 ){ + return; + }elseif( preg_match('/^data:/i', $url) ){ + return; + } + + + //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); + + + $replacement = '"'.$replacement.'"'; + $this->content = substr_replace($this->content,$replacement,$pos,$pos2-$pos); + } + + /** + * Canonicalize a path by resolving references to '/./', '/../' + * Does not remove leading "../" + * @param string path or url + * @return string Canonicalized path + * + */ + public function ReduceUrl($url){ + + $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); + } + + +} diff --git a/include/tool/Output/CombineCss.php b/include/tool/Output/CombineCss.php index 2215ba6..7f9e6fc 100644 --- a/include/tool/Output/CombineCss.php +++ b/include/tool/Output/CombineCss.php @@ -4,206 +4,225 @@ namespace gp\tool\Output; defined('is_running') or die('Not an entry point...'); +includeFile('thirdparty/cssmin_v.1.0.php'); + /** - * Get the contents of $file and fix paths: - * - url(..) - * - @import - * - @import url(..) + * Combines CSS files, handling @import rules and fixing relative url() paths. */ -class CombineCSS{ +class CombineCSS { - public $content; - public $file; - public $full_path; - public $imported = array(); - public $imports = ''; + private string $base_dir; + private string $entry_file_rel; // Relative path from base_dir + private array $processed_files = []; // Prevent infinite loops + private string $final_css = ''; + private string $preserved_imports = ''; // For external or media-specific imports - public function __construct($file){ - global $dataDir; + /** + * Constructor + * + * @param string $entry_file Relative path to the main CSS file from $dataDir. + */ + public function __construct(string $entry_file) { + global $dataDir; // Assuming $dataDir ends *without* a slash - includeFile('thirdparty/cssmin_v.1.0.php'); + if (!class_exists('\cssmin')) { + throw new \Exception('cssmin class not found. Ensure thirdparty/cssmin_v.1.0.php is included correctly.'); + } + if (empty($dataDir) || !is_dir($dataDir)) { + throw new \Exception('$dataDir is not a valid directory.'); + } - $this->file = $file; - $this->full_path = $dataDir.$file; + $this->base_dir = rtrim($dataDir, '/'); + $this->entry_file_rel = ltrim($entry_file, '/'); + + $full_entry_path = $this->base_dir . '/' . $this->entry_file_rel; + + if (!file_exists($full_entry_path) || !is_readable($full_entry_path)) { + trigger_error('CombineCSS: Entry file not found or not readable: ' . $full_entry_path, E_USER_WARNING); + $this->final_css = '/* CombineCSS Error: Entry file not found: ' . htmlspecialchars($entry_file) . ' */'; + return; + } + + $this->processFile($this->entry_file_rel); + + // Minify *after* all processing + $this->final_css = \cssmin::minify($this->preserved_imports . $this->final_css); + } + + /** + * Public getter for the combined and minified CSS content. + * + * @return string + */ + public function getContent(): string { + return $this->final_css; + } + + /** + * Recursively processes a CSS file, handling imports and fixing URLs. + * @param string $file_rel Relative path of the CSS file from base_dir. + * @return string The processed CSS content of this file and its imports. + */ + private function processFile(string $file_rel): string { + $full_path = $this->base_dir . '/' . $file_rel; + $real_path = realpath($full_path); // Resolve symlinks, etc. for accurate tracking + + // Prevent infinite loops for circular imports + if (!$real_path || isset($this->processed_files[$real_path])) { + trigger_error('CombineCSS: Skipped duplicate or invalid import: ' . $file_rel, E_USER_NOTICE); + return '/* CombineCSS Warning: Skipped duplicate import of ' . htmlspecialchars($file_rel) . ' */' . "\n"; + } + + if (!is_readable($full_path)) { + trigger_error('CombineCSS: Could not read file: ' . $full_path, E_USER_WARNING); + return '/* CombineCSS Error: Could not read ' . htmlspecialchars($file_rel) . ' */' . "\n"; + } + + $this->processed_files[$real_path] = true; // Mark as processed + + $content = file_get_contents($full_path); + if ($content === false) { + trigger_error('CombineCSS: Failed to get contents of: ' . $full_path, E_USER_WARNING); + return '/* CombineCSS Error: Failed read for ' . htmlspecialchars($file_rel) . ' */' . "\n"; + } + + // 1. Process @import rules first + $content = $this->processImports($content, $file_rel); + + // 2. Fix relative url() paths + $content = $this->processUrls($content, $file_rel); + + // Remove this file from processed list *after* processing its children + // Allows the same file to be imported via different paths if needed, though generally avoided + // unset($this->processed_files[$real_path]); + + return $content; + } + + /** + * Finds and processes @import rules within CSS content. + * @return string CSS content with local imports replaced. + */ + private function processImports(string $content, string $file_rel): string { + + $regex = '/@import\s+(?:url\(\s*(?:(["\']?)([^)"\'\s]+)\1?)\s*\)|(["\'])([^"\']+)\3)\s*([^;]*)?;/i'; + + return preg_replace_callback($regex, function ($matches) use ($file_rel) { + $import_statement = $matches[0]; + $url = !empty($matches[2]) ? trim($matches[2]) : trim($matches[4]); + $media_query = isset($matches[5]) ? trim($matches[5]) : ''; + + // --- Conditions to PRESERVE @import --- + // 1. External URL + if (str_contains($url, '//') || str_starts_with($url, 'http:') || str_starts_with($url, 'https:')) { + // Add unique preserved imports + if (strpos($this->preserved_imports, $import_statement) === false) { + $this->preserved_imports .= $import_statement . "\n"; + } + return ''; // Remove from current content + } + + // 2. Media Query is present + if (!empty($media_query)) { + // Resolve the path relative to the *current* file for the preserved import + $import_file_rel = $this->resolvePath(dirname($file_rel), $url); + $preserved_import_rule = '@import url("' . htmlspecialchars($import_file_rel) . '") ' . $media_query . ';'; + if (strpos($this->preserved_imports, $preserved_import_rule) === false) { + $this->preserved_imports .= $preserved_import_rule . "\n"; + } + return ''; // Remove from current content + } + + // --- Condition to INLINE @import --- + // Local import without media query + $import_file_rel = $this->resolvePath(dirname($file_rel), $url); + $import_full_path = $this->base_dir . '/' . $import_file_rel; + + if (!file_exists($import_full_path)) { + trigger_error('CombineCSS: Imported file not found: ' . $import_full_path . ' (referenced in ' . $file_rel . ')', E_USER_WARNING); + return '/* CombineCSS Error: Import not found: ' . htmlspecialchars($url) . ' */'; + } + + // Recursively process the imported file + return $this->processFile($import_file_rel); + + }, $content); + } - $this->content = file_get_contents($this->full_path); - $this->content = \cssmin::minify($this->content); + /** + * Finds and fixes relative url() paths within CSS content. + * @return string CSS content with url() paths fixed. + */ + private function processUrls(string $content, string $file_rel): string { + + $regex = '/url\(\s*(["\']?)(?!(?:["\']?(?:(?:[a-z]+:)?\/\/|\/|data:|#)))([^)"\']+)\1\s*\)/i'; - $this->CSS_Import(); - $this->CSS_FixUrls(); - } + return preg_replace_callback($regex, function ($matches) use ($file_rel) { + $original_url = trim($matches[2]); + $quote = $matches[1]; // Preserve original quote style + + // Resolve the path relative to the *current* file's directory + $absolute_path = $this->resolvePath(dirname($file_rel), $original_url); + + // Return the corrected url() statement with an absolute path from base_dir + + return 'url(' . $quote . '/' . ltrim($absolute_path,'/') . $quote . ')'; + + }, $content); + } - /** - * 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; + /** + * Resolves a relative path or URL against a base directory path. + * Handles "../", "./", and ensures the path is relative to the base_dir. + * @return string The canonicalized path relative to $this->base_dir. + */ + private function resolvePath(string $base_path, string $relative_path): string { + // Normalize base path (remove trailing '.', ensure it's a dir) + if ($base_path === '.') { + $base_path = ''; + } else { + $base_path = rtrim($base_path, '/'); + } - $pos = strpos($this->content,'@import ',$offset); - if( !is_numeric($pos) ){ - return; - } - $replace_start = $pos; - $pos += 8; + // If relative path is already absolute (shouldn't happen with URL regex but check anyway) + if (str_starts_with($relative_path, '/')) { + return ltrim($relative_path, '/'); + } - $replace_end = strpos($this->content,';',$pos); - if( !is_numeric($replace_end) ){ - return; - } + $full_path = $base_path ? $base_path . '/' . $relative_path : $relative_path; - $import_orig = substr($this->content,$pos,$replace_end-$pos); - $import_orig = trim($import_orig); - $replace_len = $replace_end-$replace_start+1; + // Canonicalize the path (resolve ../ and ./) - Stack-based approach + $parts = explode('/', $full_path); + $absolutes = []; + foreach ($parts as $part) { + if ('.' == $part || '' == $part) { + continue; + } + if ('..' == $part) { + array_pop($absolutes); + } else { + $absolutes[] = $part; + } + } + return implode('/', $absolutes); + } - //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); - } + /** + * Public access to the list of processed files (absolute paths). + * Useful for debugging or cache invalidation. + * @return array + */ + public function getProcessedFiles(): array { + return array_keys($this->processed_files); + } - - // 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; - } - - - //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; - } - - - //include the css - $full_path = false; - if( $import[0] != '/' ){ - $import = dirname($this->file).'/'.$import; - $import = $this->ReduceUrl($import); - } - $full_path = $dataDir.$import; - - if( file_exists($full_path) ){ - - $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; - - $this->CSS_Import($offset); - return; - } - - $this->CSS_Import($pos); - } - - - public function CSS_FixUrls($offset=0){ - $pos = strpos($this->content,'url(',$offset); - if( !is_numeric($pos) ){ - return; - } - $pos += 4; - - $pos2 = strpos($this->content,')',$pos); - if( !is_numeric($pos2) ){ - return; - } - $url = substr($this->content,$pos,$pos2-$pos); - - $this->CSS_FixUrl($url,$pos,$pos2); - - return $this->CSS_FixUrls($pos2); - } - - public function CSS_FixUrl($url,$pos,$pos2){ - global $dataDir; - - $url = trim($url); - $url = trim($url,'"\''); - - if( empty($url) ){ - return; - } - - //relative url - if( $url[0] == '/' ){ - return; - }elseif( strpos($url,'://') > 0 ){ - return; - }elseif( preg_match('/^data:/i', $url) ){ - return; - } - - - //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); - - - $replacement = '"'.$replacement.'"'; - $this->content = substr_replace($this->content,$replacement,$pos,$pos2-$pos); - } - - /** - * Canonicalize a path by resolving references to '/./', '/../' - * Does not remove leading "../" - * @param string path or url - * @return string Canonicalized path - * - */ - public function ReduceUrl($url){ - - $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); - } - - -} + /** + * Public access to the preserved @import rules. + * @return string + */ + public function getPreservedImports(): string { + return $this->preserved_imports; + } +} \ No newline at end of file diff --git a/themes/Bootswatch5.2_Scss/drop_down_menu.php b/themes/Bootswatch5.2_Scss/drop_down_menu.php index 7d111b4..9bb655c 100644 --- a/themes/Bootswatch5.2_Scss/drop_down_menu.php +++ b/themes/Bootswatch5.2_Scss/drop_down_menu.php @@ -5,11 +5,25 @@ $GP_MENU_ELEMENTS = 'Bootstrap5_menu'; /** * Generates menu link elements compatible with Bootstrap 5.2 navbars/dropdowns. + * + * @param string $node The HTML node type being processed (expects 'a'). + * @param array $attributes Associative array of link attributes: + * 'href_text' => The URL (value for href attribute). + * 'attr' => String containing existing HTML attributes (e.g., 'id="my-link"'). + * 'label' => The visible text label of the link. + * 'title' => The title attribute text (tooltip). + * 'class' => An array of classes assigned by the menu system (used to check for 'dropdown-toggle'). + * @param int $level The depth level of the menu item (0 for top level). + * @param mixed $menu_id The ID of the menu being processed. + * @param int $item_position The position of the item within its level. + * + * @return string|null The generated HTML for the tag, or null if $node is not 'a'. */ function Bootstrap5_menu($node, $attributes, $level, $menu_id, $item_position){ GLOBAL $GP_MENU_LINKS; if( $node == 'a' ){ + // --- Add Bootstrap specific classes --- $strpos_class = strpos($attributes['attr'], 'class="'); $add_class = ( $level > 0 ) ? "dropdown-item" : "nav-link"; @@ -17,9 +31,9 @@ function Bootstrap5_menu($node, $attributes, $level, $menu_id, $item_position){ $attributes['attr'] .= ' class="' . $add_class . '"'; $strpos_class = strpos($attributes['attr'], 'class="'); } else { - $attributes['attr'] = substr($attributes['attr'], 0, $strpos_class + 7) - . $add_class . ' ' - . substr($attributes['attr'], $strpos_class + 7); + $attributes['attr'] = substr($attributes['attr'], 0, $strpos_class + 7) // Part before classes + . $add_class . ' ' // The new class + space + . substr($attributes['attr'], $strpos_class + 7); // The rest of the original classes } // Ensure 'title' is included if it might be used in $GP_MENU_LINKS @@ -34,7 +48,8 @@ function Bootstrap5_menu($node, $attributes, $level, $menu_id, $item_position){ $format = '{$label}'; } } - + // --- End Determine the link format --- + return str_replace( $search, $attributes, $format ); } return null;