First Commit
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
<?php
|
||||
/**
|
||||
* Assets Class
|
||||
*
|
||||
* Handles bundling of CSS and JavaScript files into single files
|
||||
* for improved performance of static HTML pages.
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_To_HTML_Assets {
|
||||
|
||||
/**
|
||||
* Single instance
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
// Nothing to initialize
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle CSS and JS assets in the HTML
|
||||
*
|
||||
* @param string $html The HTML content
|
||||
* @param int $post_id The post ID
|
||||
* @param string $page_url The original page URL
|
||||
* @return string Modified HTML with bundled assets
|
||||
*/
|
||||
public function bundle_assets($html, $post_id, $page_url) {
|
||||
$settings = WP_To_HTML::get_settings();
|
||||
|
||||
// Bundle CSS if enabled
|
||||
if (!empty($settings['bundle_css'])) {
|
||||
$html = $this->bundle_css($html, $post_id, $page_url);
|
||||
}
|
||||
|
||||
// Bundle JS if enabled
|
||||
if (!empty($settings['bundle_js'])) {
|
||||
$html = $this->bundle_js($html, $post_id, $page_url);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle all CSS files into a single file
|
||||
*
|
||||
* @param string $html The HTML content
|
||||
* @param int $post_id The post ID
|
||||
* @param string $page_url The original page URL
|
||||
* @return string Modified HTML with bundled CSS
|
||||
*/
|
||||
private function bundle_css($html, $post_id, $page_url) {
|
||||
// Extract all stylesheet links
|
||||
$stylesheets = $this->extract_stylesheets($html);
|
||||
|
||||
if (empty($stylesheets)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$bundled_css = '';
|
||||
$base_url = $this->get_base_url($page_url);
|
||||
|
||||
// Download and combine all CSS files
|
||||
foreach ($stylesheets as $stylesheet) {
|
||||
$url = $stylesheet['href'];
|
||||
$absolute_url = $this->make_absolute_url($url, $base_url);
|
||||
|
||||
$css_content = $this->download_asset($absolute_url);
|
||||
|
||||
if ($css_content !== false) {
|
||||
// Rewrite URLs in CSS to be absolute
|
||||
$css_content = $this->rewrite_css_urls($css_content, $absolute_url);
|
||||
|
||||
// Add source comment and content
|
||||
$bundled_css .= "\n/* Source: {$absolute_url} */\n";
|
||||
$bundled_css .= $css_content . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($bundled_css)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Generate a hash for the bundle filename
|
||||
$bundle_hash = md5($bundled_css);
|
||||
$bundle_filename = "bundle-{$post_id}-{$bundle_hash}.css";
|
||||
|
||||
// Save the bundled CSS file
|
||||
$assets_dir = WP_TO_HTML_CACHE_DIR . 'assets/';
|
||||
if (!file_exists($assets_dir)) {
|
||||
wp_mkdir_p($assets_dir);
|
||||
}
|
||||
|
||||
$bundle_path = $assets_dir . $bundle_filename;
|
||||
file_put_contents($bundle_path, $bundled_css);
|
||||
|
||||
// Get the URL for the bundled file
|
||||
$bundle_url = WP_TO_HTML_CACHE_URL . 'assets/' . $bundle_filename;
|
||||
|
||||
// Remove all original stylesheet links
|
||||
foreach ($stylesheets as $stylesheet) {
|
||||
$html = str_replace($stylesheet['full_tag'], '', $html);
|
||||
}
|
||||
|
||||
// Add the bundled CSS link before </head>
|
||||
$bundled_link = '<link rel="stylesheet" href="' . esc_url($bundle_url) . '" type="text/css" media="all" />';
|
||||
$html = str_replace('</head>', $bundled_link . "\n</head>", $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle all JS files into a single file
|
||||
*
|
||||
* @param string $html The HTML content
|
||||
* @param int $post_id The post ID
|
||||
* @param string $page_url The original page URL
|
||||
* @return string Modified HTML with bundled JS
|
||||
*/
|
||||
private function bundle_js($html, $post_id, $page_url) {
|
||||
// Extract all script tags with src attributes
|
||||
$scripts = $this->extract_scripts($html);
|
||||
|
||||
if (empty($scripts)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$bundled_js = '';
|
||||
$base_url = $this->get_base_url($page_url);
|
||||
$has_defer = false;
|
||||
$has_async = false;
|
||||
|
||||
// Download and combine all JS files
|
||||
foreach ($scripts as $script) {
|
||||
$url = $script['src'];
|
||||
$absolute_url = $this->make_absolute_url($url, $base_url);
|
||||
|
||||
$js_content = $this->download_asset($absolute_url);
|
||||
|
||||
if ($js_content !== false) {
|
||||
// Add source comment and content
|
||||
$bundled_js .= "\n/* Source: {$absolute_url} */\n";
|
||||
$bundled_js .= $js_content . ";\n";
|
||||
}
|
||||
|
||||
// Track defer/async attributes
|
||||
if (!empty($script['defer'])) {
|
||||
$has_defer = true;
|
||||
}
|
||||
if (!empty($script['async'])) {
|
||||
$has_async = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($bundled_js)) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Generate a hash for the bundle filename
|
||||
$bundle_hash = md5($bundled_js);
|
||||
$bundle_filename = "bundle-{$post_id}-{$bundle_hash}.js";
|
||||
|
||||
// Save the bundled JS file
|
||||
$assets_dir = WP_TO_HTML_CACHE_DIR . 'assets/';
|
||||
if (!file_exists($assets_dir)) {
|
||||
wp_mkdir_p($assets_dir);
|
||||
}
|
||||
|
||||
$bundle_path = $assets_dir . $bundle_filename;
|
||||
file_put_contents($bundle_path, $bundled_js);
|
||||
|
||||
// Get the URL for the bundled file
|
||||
$bundle_url = WP_TO_HTML_CACHE_URL . 'assets/' . $bundle_filename;
|
||||
|
||||
// Remove all original script tags
|
||||
foreach ($scripts as $script) {
|
||||
$html = str_replace($script['full_tag'], '', $html);
|
||||
}
|
||||
|
||||
// Build the bundled script tag with appropriate attributes
|
||||
$attributes = '';
|
||||
if ($has_defer) {
|
||||
$attributes .= ' defer';
|
||||
}
|
||||
if ($has_async) {
|
||||
$attributes .= ' async';
|
||||
}
|
||||
|
||||
// Add the bundled JS script before </body>
|
||||
$bundled_script = '<script src="' . esc_url($bundle_url) . '"' . $attributes . '></script>';
|
||||
$html = str_replace('</body>', $bundled_script . "\n</body>", $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all stylesheet links from HTML
|
||||
*
|
||||
* @param string $html The HTML content
|
||||
* @return array Array of stylesheet data
|
||||
*/
|
||||
private function extract_stylesheets($html) {
|
||||
$stylesheets = array();
|
||||
|
||||
// Match link tags with rel="stylesheet"
|
||||
$pattern = '/<link[^>]*rel=["\']stylesheet["\'][^>]*>/i';
|
||||
preg_match_all($pattern, $html, $matches);
|
||||
|
||||
// Also match link tags where rel comes after href
|
||||
$pattern2 = '/<link[^>]*href=["\'][^"\']+["\'][^>]*rel=["\']stylesheet["\'][^>]*>/i';
|
||||
preg_match_all($pattern2, $html, $matches2);
|
||||
|
||||
$all_matches = array_unique(array_merge($matches[0], $matches2[0]));
|
||||
|
||||
foreach ($all_matches as $tag) {
|
||||
// Extract href
|
||||
if (preg_match('/href=["\']([^"\']+)["\']/i', $tag, $href_match)) {
|
||||
$href = $href_match[1];
|
||||
|
||||
// Skip data URIs and inline styles
|
||||
if (strpos($href, 'data:') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stylesheets[] = array(
|
||||
'full_tag' => $tag,
|
||||
'href' => $href,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $stylesheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all script tags with src from HTML
|
||||
*
|
||||
* @param string $html The HTML content
|
||||
* @return array Array of script data
|
||||
*/
|
||||
private function extract_scripts($html) {
|
||||
$scripts = array();
|
||||
|
||||
// Match script tags with src attribute
|
||||
$pattern = '/<script[^>]*src=["\']([^"\']+)["\'][^>]*><\/script>/i';
|
||||
preg_match_all($pattern, $html, $matches, PREG_SET_ORDER);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$tag = $match[0];
|
||||
$src = $match[1];
|
||||
|
||||
// Skip data URIs
|
||||
if (strpos($src, 'data:') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for defer/async attributes
|
||||
$defer = (stripos($tag, 'defer') !== false);
|
||||
$async = (stripos($tag, 'async') !== false);
|
||||
|
||||
$scripts[] = array(
|
||||
'full_tag' => $tag,
|
||||
'src' => $src,
|
||||
'defer' => $defer,
|
||||
'async' => $async,
|
||||
);
|
||||
}
|
||||
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an asset from a URL
|
||||
*
|
||||
* @param string $url The asset URL
|
||||
* @return string|false The asset content or false on failure
|
||||
*/
|
||||
private function download_asset($url) {
|
||||
$response = wp_remote_get($url, array(
|
||||
'timeout' => 15,
|
||||
'sslverify' => false,
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code($response);
|
||||
if ($status_code !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return wp_remote_retrieve_body($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite relative URLs in CSS to be absolute
|
||||
*
|
||||
* @param string $css The CSS content
|
||||
* @param string $css_url The original CSS file URL
|
||||
* @return string CSS with absolute URLs
|
||||
*/
|
||||
private function rewrite_css_urls($css, $css_url) {
|
||||
$base_url = dirname($css_url) . '/';
|
||||
|
||||
// Match url() patterns
|
||||
$pattern = '/url\s*\(\s*["\']?(?!data:)(?!https?:\/\/)(?!\/\/)([^"\'\)]+)["\']?\s*\)/i';
|
||||
|
||||
$css = preg_replace_callback($pattern, function($matches) use ($base_url) {
|
||||
$relative_url = $matches[1];
|
||||
|
||||
// Handle ../ paths
|
||||
if (strpos($relative_url, '../') === 0) {
|
||||
$absolute_url = $this->resolve_relative_path($base_url, $relative_url);
|
||||
} elseif (strpos($relative_url, './') === 0) {
|
||||
$absolute_url = $base_url . substr($relative_url, 2);
|
||||
} else {
|
||||
$absolute_url = $base_url . $relative_url;
|
||||
}
|
||||
|
||||
return 'url("' . $absolute_url . '")';
|
||||
}, $css);
|
||||
|
||||
// Handle @import statements
|
||||
$import_pattern = '/@import\s+["\'](?!data:)(?!https?:\/\/)(?!\/\/)([^"\']+)["\']/i';
|
||||
|
||||
$css = preg_replace_callback($import_pattern, function($matches) use ($base_url) {
|
||||
$relative_url = $matches[1];
|
||||
$absolute_url = $this->resolve_relative_path($base_url, $relative_url);
|
||||
return '@import "' . $absolute_url . '"';
|
||||
}, $css);
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a relative path against a base URL
|
||||
*
|
||||
* @param string $base_url The base URL
|
||||
* @param string $relative_path The relative path
|
||||
* @return string The absolute URL
|
||||
*/
|
||||
private function resolve_relative_path($base_url, $relative_path) {
|
||||
// Parse the base URL
|
||||
$parsed = parse_url($base_url);
|
||||
$scheme = isset($parsed['scheme']) ? $parsed['scheme'] . '://' : 'https://';
|
||||
$host = isset($parsed['host']) ? $parsed['host'] : '';
|
||||
$path = isset($parsed['path']) ? $parsed['path'] : '/';
|
||||
|
||||
// Remove filename from path if exists
|
||||
if (substr($path, -1) !== '/') {
|
||||
$path = dirname($path) . '/';
|
||||
}
|
||||
|
||||
// Process the relative path
|
||||
while (strpos($relative_path, '../') === 0) {
|
||||
$relative_path = substr($relative_path, 3);
|
||||
$path = dirname(rtrim($path, '/')) . '/';
|
||||
}
|
||||
|
||||
// Remove leading ./
|
||||
if (strpos($relative_path, './') === 0) {
|
||||
$relative_path = substr($relative_path, 2);
|
||||
}
|
||||
|
||||
return $scheme . $host . $path . $relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a URL absolute
|
||||
*
|
||||
* @param string $url The URL to make absolute
|
||||
* @param string $base_url The base URL
|
||||
* @return string The absolute URL
|
||||
*/
|
||||
private function make_absolute_url($url, $base_url) {
|
||||
// Already absolute
|
||||
if (preg_match('/^https?:\/\//i', $url)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Protocol-relative URL
|
||||
if (strpos($url, '//') === 0) {
|
||||
$parsed_base = parse_url($base_url);
|
||||
$scheme = isset($parsed_base['scheme']) ? $parsed_base['scheme'] : 'https';
|
||||
return $scheme . ':' . $url;
|
||||
}
|
||||
|
||||
// Absolute path (starts with /)
|
||||
if (strpos($url, '/') === 0) {
|
||||
$parsed_base = parse_url($base_url);
|
||||
$scheme = isset($parsed_base['scheme']) ? $parsed_base['scheme'] : 'https';
|
||||
$host = isset($parsed_base['host']) ? $parsed_base['host'] : '';
|
||||
return $scheme . '://' . $host . $url;
|
||||
}
|
||||
|
||||
// Relative path
|
||||
return $this->resolve_relative_path($base_url, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL from a page URL
|
||||
*
|
||||
* @param string $url The page URL
|
||||
* @return string The base URL (without path)
|
||||
*/
|
||||
private function get_base_url($url) {
|
||||
$parsed = parse_url($url);
|
||||
$scheme = isset($parsed['scheme']) ? $parsed['scheme'] : 'https';
|
||||
$host = isset($parsed['host']) ? $parsed['host'] : '';
|
||||
$path = isset($parsed['path']) ? $parsed['path'] : '/';
|
||||
|
||||
return $scheme . '://' . $host . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear bundled assets from cache
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clear_bundled_assets() {
|
||||
$assets_dir = WP_TO_HTML_CACHE_DIR . 'assets/';
|
||||
|
||||
if (!file_exists($assets_dir)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$files = glob($assets_dir . 'bundle-*');
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete bundled assets for a specific post
|
||||
*
|
||||
* @param int $post_id The post ID
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_post_assets($post_id) {
|
||||
$assets_dir = WP_TO_HTML_CACHE_DIR . 'assets/';
|
||||
|
||||
if (!file_exists($assets_dir)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$files = glob($assets_dir . "bundle-{$post_id}-*");
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user