mirror of
https://github.com/gtbu/Typesetter-5.3-p8.git
synced 2025-04-18 20:23:14 +02:00
improved cssmin and combine
This commit is contained in:
parent
93955abbd7
commit
2f2c120ec0
5 changed files with 577 additions and 218 deletions
46
include/thirdparty/cssmin_v.1.0.1.php
vendored
Normal file
46
include/thirdparty/cssmin_v.1.0.1.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/**
|
||||
* cssmin.php - A simple CSS minifier.
|
||||
* --
|
||||
*
|
||||
* <code>
|
||||
* include("cssmin.php");
|
||||
* file_put_contents("path/to/target.css", cssmin::minify(file_get_contents("path/to/source.css")));
|
||||
* </code>
|
||||
* --
|
||||
*
|
||||
* 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 <joe.scylla@gmail.com>
|
||||
* @copyright 2008 Joe Scylla <joe.scylla@gmail.com>
|
||||
* @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;
|
||||
}
|
||||
}
|
124
include/thirdparty/cssmin_v.1.0.php
vendored
124
include/thirdparty/cssmin_v.1.0.php
vendored
|
@ -1,11 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* cssmin.php - A simple CSS minifier.
|
||||
* --
|
||||
* Provides basic CSS minification by removing comments and unnecessary whitespace.
|
||||
*
|
||||
* <code>
|
||||
* 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);
|
||||
* </code>
|
||||
* --
|
||||
*
|
||||
|
@ -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 <joe.scylla@gmail.com>
|
||||
* @copyright 2008 Joe Scylla <joe.scylla@gmail.com>
|
||||
* @license http://opensource.org/licenses/mit-license.php MIT License
|
||||
* @version 1.0 (2008-01-31)
|
||||
* @package cssmin
|
||||
* @author Joe Scylla <joe.scylla@gmail.com>
|
||||
* @copyright 2008 Joe Scylla <joe.scylla@gmail.com> (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);
|
||||
}
|
||||
}
|
209
include/tool/Output/CombineCss-old.php
Normal file
209
include/tool/Output/CombineCss-old.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?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{
|
||||
|
||||
public $content;
|
||||
public $file;
|
||||
public $full_path;
|
||||
public $imported = array();
|
||||
public $imports = '';
|
||||
|
||||
public function __construct($file){
|
||||
global $dataDir;
|
||||
|
||||
includeFile('thirdparty/cssmin_v.1.0.php');
|
||||
|
||||
$this->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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 <a> 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 = '<a {$attr} href="{$href_text}">{$label}</a>';
|
||||
}
|
||||
}
|
||||
|
||||
// --- End Determine the link format ---
|
||||
|
||||
return str_replace( $search, $attributes, $format );
|
||||
}
|
||||
return null;
|
||||
|
|
Loading…
Reference in a new issue