Created
April 16, 2025 07:42
-
-
Save isuke01/d3fa4516ec69713af3e1ea1aa7859bbb to your computer and use it in GitHub Desktop.
WordPress Asset Version Manager A class to manage asset versioning in WordPress by replacing version strings with customizable alternatives like file modification times.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* WordPress Asset Version Manager | |
* | |
* A class to manage asset versioning in WordPress by replacing version strings | |
* with customizable alternatives like file modification times. | |
*/ | |
class WP_Asset_Version_Manager { | |
// Default settings. | |
private $settings = [ | |
'handle_core' => true, | |
'core_strategy' => 'file_time', | |
'plugin_strategy' => 'file_time', | |
'theme_strategy' => 'keep_original', | |
'target_plugins' => [], | |
'target_themes' => [], | |
'fallback_version' => 'ft', | |
]; | |
private $content_dir; | |
private $content_url; | |
private $content_path; | |
/** | |
* Constructor. | |
* | |
* @param array $options Configuration options. | |
*/ | |
public function __construct($options = []) { | |
// Determine content directory. | |
$this->content_dir = \defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR : ABSPATH . 'wp-content'; | |
$this->content_url = \defined( 'WP_CONTENT_URL' ) ? WP_CONTENT_URL : site_url('wp-content'); | |
// Get relative content path for URL matching. | |
$this->content_path = \str_replace( \site_url(), '', $this->content_url ); | |
// Merge user options with defaults. | |
$this->settings = \array_merge( $this->settings, $options ); | |
// Hook our method into WordPress. | |
\add_filter( 'script_loader_src', [$this, 'process_asset_url'], 50, 1 ); | |
\add_filter( 'style_loader_src', [$this, 'process_asset_url'], 50, 1 ); | |
} | |
/** | |
* Process asset URL and modify version parameter if needed. | |
* | |
* @param string $src The asset URL. | |
* @return string Modified URL. | |
*/ | |
public function process_asset_url( $src ) { | |
// Parse the URL to get components. | |
$url_parts = \parse_url( $src ); | |
if ( ! isset($url_parts['query'] ) ) { | |
return $src; // No query parameters to modify. | |
} | |
\parse_str( $url_parts['query'], $query_params ); | |
// If no version parameter, nothing to modify. | |
if ( ! isset( $query_params['ver'] ) ) { | |
return $src; | |
} | |
// Determine asset type | |
$asset_type = $this->get_asset_type( $src ); | |
// If asset type is unknown, return original URL. | |
if ( $asset_type === 'unknown' ) { | |
return $src; | |
} | |
// Skip core assets if not enabled | |
if ( $asset_type === 'core' && ! $this->settings['handle_core'] ) { | |
return $src; | |
} | |
// For plugins, check if we should process this plugin | |
if ( $asset_type === 'plugin' ) { | |
$plugin_slug = $this->get_plugin_slug( $src ); | |
if ( empty( $this->settings['target_plugins'] ) || ! in_array( $plugin_slug, $this->settings['target_plugins'] ) ) { | |
return $src; | |
} | |
} | |
// For themes, check if we should process this theme | |
else if ( $asset_type === 'theme' ) { | |
$theme_slug = $this->get_theme_slug( $src ); | |
if ( empty( $this->settings['target_themes'] ) || ! in_array( $theme_slug, $this->settings['target_themes'] ) ) { | |
return $src; | |
} | |
} | |
// Get the appropriate versioning strategy. | |
$strategy = $this->settings[$asset_type . '_strategy']; | |
// Apply the strategy. | |
switch ( $strategy ) { | |
case 'file_time': | |
$version = $this->get_file_time_version( $src ); | |
break; | |
case 'remove': | |
unset( $query_params['ver'] ); | |
$version = null; | |
break; | |
case 'fixed': | |
$version = $this->settings['fallback_version']; | |
break; | |
case 'keep_original': | |
default: | |
return $src; // No change | |
} | |
// Set the new version if applicable. | |
if ( $version !== null ) { | |
$query_params['ver'] = $version; | |
} | |
// Rebuild the query string. | |
$new_query = \http_build_query( $query_params ); | |
// Rebuild the URL. | |
$new_src = $url_parts['scheme'] . '://' . $url_parts['host']; | |
if ( isset($url_parts['port'] ) ) { | |
$new_src .= ':' . $url_parts['port']; | |
} | |
$new_src .= $url_parts['path']; | |
if ( ! empty( $new_query ) ) { | |
$new_src .= '?' . $new_query; | |
} | |
if ( isset( $url_parts['fragment'] ) ) { | |
$new_src .= '#' . $url_parts['fragment']; | |
} | |
return $new_src; | |
} | |
/** | |
* Determine the type of asset from the URL. | |
* | |
* @param string $src The asset URL. | |
* @return string 'core', 'plugin', 'theme', or 'unknown'. | |
*/ | |
private function get_asset_type( $src ) { | |
if ( \strpos( $src, '/wp-includes/' ) !== false || \strpos( $src, '/wp-admin/' ) !== false ) { | |
return 'core'; | |
} | |
if ( \strpos( $src, $this->content_path . '/plugins/' ) !== false ) { | |
return 'plugin'; | |
} | |
if ( \strpos( $src, $this->content_path . '/themes/' ) !== false ) { | |
return 'theme'; | |
} | |
return 'unknown'; | |
} | |
/** | |
* Extract plugin slug from URL. | |
* | |
* @param string $src The asset URL. | |
* @return string|null Plugin slug or null if not found. | |
*/ | |
private function get_plugin_slug( $src ) { | |
preg_match( '|' . preg_quote($this->content_path ) . '/plugins/([^/]+)|', $src, $matches ); | |
return isset( $matches[1] ) ? $matches[1] : null; | |
} | |
/** | |
* Extract theme slug from URL. | |
* | |
* @param string $src The asset URL. | |
* @return string|null Theme slug or null if not found. | |
*/ | |
private function get_theme_slug( $src ) { | |
\preg_match( '|' . \preg_quote( $this->content_path ) . '/themes/([^/]+)|', $src, $matches ); | |
return isset( $matches[1] ) ? $matches[1] : null; | |
} | |
/** | |
* Get file modification time to use as version. | |
* | |
* @param string $src The asset URL. | |
* @return string|int File modification time or fallback. | |
*/ | |
private function get_file_time_version( $src ) { | |
// First try to use full URL to path conversion. | |
$site_url = \site_url(); | |
$file_path = ABSPATH . \str_replace( $site_url, '', \preg_replace('/\?.*/', '', $src ) ); | |
// If file doesn't exist, try alternate path resolution methods. | |
if ( ! \file_exists( $file_path ) ) { | |
// Try with content directory mapping. | |
if ( \strpos( $src, $this->content_url ) !== false ) { | |
$file_path = \str_replace( $this->content_url, $this->content_dir, \preg_replace( '/\?.*/', '', $src ) ); | |
} | |
} | |
// Make sure the file exists and is readable. | |
if ( \file_exists( $file_path ) && \is_readable( $file_path ) ) { | |
return \filemtime( $file_path ); | |
} | |
// Fallback. | |
return $this->settings['fallback_version'] . \time(); | |
} | |
/** | |
* Static convenience method to quickly setup core file versioning. | |
* | |
* @param string $strategy Versioning strategy. | |
* @return WP_Asset_Version_Manager Instance of the manager. | |
*/ | |
public static function setup_core_versioning( $strategy = 'file_time' ) { | |
return new self([ | |
'handle_core' => true, | |
'core_strategy' => $strategy | |
]); | |
} | |
/** | |
* Static convenience method to setup plugin versioning. | |
* | |
* @param array|string $plugin_slugs Plugin slug(s) to target. | |
* @param string $strategy Versioning strategy. | |
* @return WP_Asset_Version_Manager Instance of the manager. | |
*/ | |
public static function setup_plugin_versioning( $plugin_slugs, $strategy = 'plugin_time' ) { | |
return new self([ | |
'handle_core' => false, | |
'plugin_strategy' => $strategy, | |
'target_plugins' => (array) $plugin_slugs | |
]); | |
} | |
/** | |
* Static convenience method to setup theme versioning. | |
* | |
* @param array|string $theme_slugs Theme slug(s) to target. | |
* @param string $strategy Versioning strategy. | |
* @return WP_Asset_Version_Manager Instance of the manager. | |
*/ | |
public static function setup_theme_versioning( $theme_slugs, $strategy = 'file_time' ) { | |
return new self([ | |
'handle_core' => false, | |
'theme_strategy' => $strategy, | |
'target_themes' => (array) $theme_slugs | |
]); | |
} | |
} | |
// Example usage: | |
// 1. Basic setup to version core files with file modification time | |
// $version_manager = WP_Asset_Version_Manager::setup_core_versioning(); | |
// 2. Setup to version a specific plugin's assets based on its main file time | |
// $version_manager = WP_Asset_Version_Manager::setup_plugin_versioning('woocommerce'); | |
// 3. Custom configuration | |
// $version_manager = new WP_Asset_Version_Manager([ | |
// 'handle_core' => true, | |
// 'target_plugins' => ['woocommerce', 'elementor'] | |
// ]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment