2

I was able to add the file last modified time as version to css and js files. As you can see I have to repeatly adding filemtime(get_theme_file_path('...')) every time when a new link is added.

function _enqueue_scripts() {
    wp_enqueue_style('_base', get_theme_file_uri('/assets/css/base.css'), array(), filemtime(get_theme_file_path('/assets/css/base.css')));
    wp_enqueue_script('_base', get_theme_file_uri('/assets/js/base.js'), array(), filemtime(get_theme_file_path('/assets/js/base.js')));
}
add_action('wp_enqueue_scripts', '_enqueue_scripts');

Is there a way to use a custom filter or so for it rather than manually add that line every time?

Similar to the below function (for removing version numbers), but I'd like to add version numbers.

function _remove_script_version($src) {
    return $src ? esc_url(remove_query_arg('ver', $src)) : false;
}
add_filter('style_loader_src', '_remove_script_version', 15, 1);
add_filter('script_loader_src', '_remove_script_version', 15, 1);
0
5

You could use add_query_arg() but then you'd have to parse the uri everytime, I'd rather create a wrapper function for wp_enqueue_script/style:

function my_enqueuer($my_handle, $relpath, $type='script', $my_deps=array()) {
    $uri = get_theme_file_uri($relpath);
    $vsn = filemtime(get_theme_file_path($relpath));
    if($type == 'script') wp_enqueue_script($my_handle, $uri, $my_deps, $vsn);
    else if($type == 'style') wp_enqueue_style($my_handle, $uri, $my_deps, $vsn);      
}

Add this in your functions file and then in place of e.g.

wp_enqueue_script('_base', get_theme_file_uri('/assets/js/base.js'), array(), filemtime(get_theme_file_path('/assets/js/base.js')));

call:

my_enqueuer('_base', '/assets/js/base.js');

and in place of e.g.

wp_enqueue_style('_base', get_theme_file_uri('/assets/css/base.css'), array(), filemtime(get_theme_file_path('/assets/css/base.css')));

call:

my_enqueuer('_base', '/assets/css/base.css', 'style');

You can pass the dependency array as the last argument when needed. For scripts, if you pass the dependency array, you will have to pass the third parameter 'script' as well, even though I've set it as the default value.

0
4

I used to use YYYYMMDD as my version number for enqueued files, which was reasonably good, but caused issues when it changed more than once in a day and still meant having to remember to update the version number when changes were made to the file. An example of an enqueue might have looked like this:

<?php wp_enqueue_style( 'child-theme', get_stylesheet_directory_uri() . '/css/styles.css', array(), '20150731' ); ?>

My revised approach starts by creating a variable for the path of the CSS/JS file and then using filemtime in the version number instead of YYYYMMDD:

<?php
$themecsspath = get_stylesheet_directory() . '/css/styles.css';
wp_enqueue_style(
    'child-theme',
    get_stylesheet_directory_uri() . '/css/styles.css',
    array(),
    filemtime( $themecsspath )
);
?>

Now, instead of my enqueued files containing the WordPress version number, like this:

<link rel='stylesheet' id='child-theme-css'  href='http://example.com/wp-content/themes/child/css/styles.css?ver=4.3.1' type='text/css' media='all' />

They look like this:

<link rel='stylesheet' id='child-theme-css'  href='http://example.com/wp-content/themes/child/css/styles.css?ver=1447781986' type='text/css' media='all' />
4

This answer was adapted from @CGodo's solution.

I was getting Fatal Errors with the original code. I've tested this code and it does what I believe the original answer was intended to do.

/**
 * Replaces query version in registered scripts or styles with file modified time
 *
 * @param $src
 *
 * @return string
 */
function add_modified_time( $src ) {

    $clean_src = remove_query_arg( 'ver', $src );
    $path      = wp_parse_url( $src, PHP_URL_PATH );

    if ( $modified_time = @filemtime( untrailingslashit( ABSPATH ) . $path ) ) {
        $src = add_query_arg( 'ver', $modified_time, $clean_src );
    } else {
        $src = add_query_arg( 'ver', time(), $clean_src );
    }

    return $src;

}

add_filter( 'style_loader_src', 'add_modified_time', 99999999, 1 );
add_filter( 'script_loader_src', 'add_modified_time', 99999999, 1 );
2

If for some reason somebody needs it, these hooks will put modified time versions on all Wordpress scripts and styles, except for those loaded from PHP.

The reason it uses WP_Scripts and WP_Styles singletons is because those instances already have calculated the base_url.

/**
 * Replaces query version in registered scripts or styles with file modified time
 * @param string $src Source url
 * @param string $baseUrl Site base url
 * @return string
 */
function put_modified_time_version($src, $baseUrl)
{
    // Only work with objects from baseUrl
    if ($src && strpos($src, $baseUrl) === 0) {
        // Remove any version
        $newSrc = remove_query_arg('ver', $src);
        // Get path after base_url
        $path = substr($newSrc, strlen($baseUrl));
        $path = wp_parse_url($path, PHP_URL_PATH);
        // Apply modified time version if exists
        if ($mtime = @filemtime(untrailingslashit(ABSPATH) . $path)) {
            $src = add_query_arg('ver', $mtime, $newSrc);
        }
    }
    return $src;
}

/**
 * Filters style sources to put file modified time as query string
 * @param $src
 * @return string
 */
function modified_time_version_style($src) {
    // base_url from WP_Versions is already in memory
    return ($src) ? put_modified_time_version($src, wp_styles()->base_url) : $src;
}

/**
 * Filters script sources to put file modified time as query string
 * @param $src
 * @return string
 */
function modified_time_version_script($src) {
    // base_url from WP_Styles is already in memory
    return ($src) ? put_modified_time_version($src, wp_scripts()->base_url) : $src;
}

add_filter('style_loader_src', 'modified_time_version_style', 15, 1);
add_filter('script_loader_src', 'modified_time_version_script', 15, 1);
1
  • I searched for this all day for a way to version my stylesheets. I ran into an issue using this for all scripts but just commented out the filter for scripts as I didn't need for that use. Note, I did have to change priority of 15 to 9999 to get it to work for me. I'm sure the plugins I was targeting pushed priority higher than they should have. Dec 13 '20 at 22:57
0

I just wrote two methods for my plugin class, that makes it super easy to register/enqueue scripts and styles. And of course, as desired, the last modification time is always added as version parameter. Maybe this will help someone 😏

How to use them:

/**
 * For these examples we assume that your plugin assets are located in
 * wp-content/plugins/my-plugin/path/to/assets/
 */

// add your style
$this->registerAsset('path/to/assets/your-style.css');

// add your script works exactly the same
$this->registerAsset('path/to/assets/your-script.js');

// add script with dependency
$this->registerAsset('path/to/assets/script-with-dependencies.js', [
    'jquery'
]);

// add script with internal dependency
$this->registerAsset('path/to/assets/script-with-dependencies.js', [
    'jquery',
    $this->getAssetHandle('path/to/assets/your-script.js')
]);

// for internal dependencies you can also pass the path of the dependency directly
$this->registerAsset('path/to/assets/script-with-dependencies.js', [
    'jquery',
    'path/to/assets/your-script.js'
]);

There are a few more options, but just check out the documentation about the methods in the source code you need:

class myPlugin
{
    public function __construct()
    {
    }
    
    /**
     * Registers and enqueues a script or style
     * 
     * @param   STRING        $path          The path of the file you want to register or enqueue (relative to your plugin folder).
     * @param   ARRAY         $dependencies  The dependencies as you know from wp_enqueue_script, but allows also paths of other assets that were registered with this method.
     * @param   BOOLEAN       $enqueue       If FALSE it will only be registered, otherwise it will also be enqueued.
     * @param   STRING|NULL   $type          Type of the asset. Allowed types are 'script' and 'style'. If it is NULL, the type is automatically detected from the file extension.
     * @param   STRING        $media         To define 'media' when adding CSS (Ignored for JS assets - JS assets always get TRUE for the in_footer parameter as this is usually the recommended way)
     * 
     * @return  BOOLEAN|NULL  When $enqueue is TRUE, the return value is always NULL. Otherwise TRUE on success, FALSE on failure.
     */
    public function registerAsset($path, $dependencies = [], $enqueue = true, $type = null, $media = 'all')
    {
        $path = '/' . ltrim($path, '/');
        $pluginDirName = explode(DIRECTORY_SEPARATOR, ltrim(str_replace(realpath(WP_PLUGIN_DIR), '', realpath(__FILE__)), DIRECTORY_SEPARATOR), 2)[0];
        $pluginDir = realpath(WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $pluginDirName);

        if ($type === null) {
            $extensions = ['js' => 'script', 'css' => 'style'];
            $extension = pathinfo($path, PATHINFO_EXTENSION);
            $type = isset($extensions[$extension]) ? $extensions[$extension] : null;
        }
        if (!in_array($type, ['script', 'style']) || !file_exists($pluginDir . $path)) {
            return;
        }

        foreach ($dependencies as $index => $dependency) {
            if (preg_match('/\.(js|css)$/', $dependency) && file_exists($pluginDir . DIRECTORY_SEPARATOR . ltrim($dependency, '\\/'))) {
                $dependencies[$index] = $this->getAssetHandle($dependency);
            }
        }

        $func = 'wp_' . ($enqueue ? 'enqueue' : 'register') . '_' . $type;
        return $func($this->getAssetHandle($path), plugins_url($pluginDirName . $path), $dependencies, filemtime($pluginDir . $path), $type === 'script' ? true : $media);
    }

    /**
     * Gets the handle of an asset that registerAsset() uses automatically
     * 
     * @param   STRING        $path          The path that you used with registerAsset()
     * 
     * @return  STRING        The handle name of the asset
     */
    public function getAssetHandle($path)
    {
        $pluginDirName = explode(DIRECTORY_SEPARATOR, ltrim(str_replace(realpath(WP_PLUGIN_DIR), '', realpath(__FILE__)), DIRECTORY_SEPARATOR), 2)[0];
        return preg_replace('/[^a-zA-Z0-9]+/', '-', $pluginDirName . '-' . $path);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.