Skip to content

Instantly share code, notes, and snippets.

@bfiessinger
Last active August 26, 2022 11:29
Show Gist options
  • Save bfiessinger/94c79e3b15f1079ce0c9471feaa471d6 to your computer and use it in GitHub Desktop.
Save bfiessinger/94c79e3b15f1079ce0c9471feaa471d6 to your computer and use it in GitHub Desktop.
Custom WordPress asset loader Class with the ability to add scripts / styles conditionally, add extra attributes and more!
<?php
/**
* Script and Style includes and helper Methods
*/
namespace bf;
if ( ! defined('ABSPATH')) exit('restricted access');
class script_loader {
public $enqueued_scripts = [];
public $enqueued_stylesheets = [];
public $extra_attribute_prefix = 'yourtheme_assets_extra_attributes&#';
function __construct(int $priority = 10) {
add_action( 'wp_enqueue_scripts', [ $this, 'script_enqueue' ], $priority );
add_filter('script_loader_tag',function($tag, $handle, $src){
return $this->scriptAndStyleTagAttributeAdder($tag, $handle, $src, null, false);
},$priority,4);
add_filter('style_loader_tag',function($tag, $handle, $src, $media){
return $this->scriptAndStyleTagAttributeAdder($tag, $handle, $src, $media, true);
},$priority,4);
}
/**
* Small Helper Utility to determine if an array
* is multidimensional or not
*
* @param array $a - The Array to check
*
* @since 2.0.0
*/
function array_is_multi( $a ) {
$rv = array_filter( $a, 'is_array' );
if( count( $rv ) > 0 )
return true;
return false;
}
function check_restrictions( array $restrictions ) {
foreach ( $restrictions as $rule ) {
$allow = true;
// Skip one iteration if $rule matches
if ( true !== $rule )
$allow = false;
yield $allow;
}
}
/**
* Determine if a script or stylesheet is allowed on the current view
*
* @param array $restrictions - Array with callable restrictions.
* Might even be multidimensional to specify the relation between the restrictions as an argument
*
* @return bool
*
* @since 2.0.0
*/
function restrict( array $restrictions ) {
$restriction_relation = ( isset( $restrictions['relation'] ) ) ? $restrictions['relation'] : 'OR';
unset( $restrictions['relation'] );
/**
* Flatten the restrictions Array.
* Works only with 2 dimensional Arrays
*/
if ( $this->array_is_multi( $restrictions ) ) {
$restrictions = call_user_func_array('array_merge', $restrictions);
}
/**
* Check if the current script is allowed to enqueue
* otherwise just return false from this function
*/
if ( $restriction_relation != 'OR' ) {
foreach ( $this->check_restrictions( $restrictions ) as $allowed ) {
if ( ! $allowed )
return false;
}
} elseif ( ! in_array(true, \iterator_to_array( $this->check_restrictions( $restrictions ) ), true) ) {
return false;
}
return true;
}
/**
* Custom Enqueue Script function with the ability to add
* attributes and restrictions
*
* @param string $handle - File handler
* @param string $src - Path to the file - Default: ''
* @param array $deps - Script dependencies - Default: []
* @param mixed $ver - Script version - Default: null
* @param bool $in_footer - Enqueue the script in the header or the footer - Default: true
* @param array $atts - Custom Script Attributes - Default: []
* @param array $restrictions - Restrict enqueueing the script. This array accepts callables. If one returns a value thats falsy the script won't enqueue
* @param array $localize - Setup data for wp_localize_script to pass PHP variables
*
* @since 2.0.0
*/
function add_script( string $handle, string $src = '', array $deps = [], $ver = null, bool $in_footer = true, array $atts = [], array $restrictions = [], array $localize = [] ) {
if ( ! empty( $restrictions ) ) {
if ( $this->restrict( $restrictions ) === false )
return;
}
$this->enqueued_scripts[] = [
'handle' => $handle,
'src' => $src,
'deps' => $deps,
'ver' => $ver,
'in_footer' => $in_footer,
'atts' => $atts,
'localize' => $localize
];
}
/**
* Custom Enqueue Stylesheet function with the ability to add
* attributes and restrictions
*
* @param string $handle - File handler
* @param string $src - Path to the file - Default: ''
* @param array $deps - Stylesheet dependencies - Default: []
* @param mixed $ver - Stylesheet version - Default: null
* @param string $media - The media type for the enqueued stylesheet - Default: 'all'
* @param array $atts - Custom Stylesheet Attributes - Default: []
* @param array $restrictions - Restrict enqueueing the Stylesheet. This array accepts callables. If one returns a value thats falsy the Stylesheet won't enqueue
*
* @since 2.0.0
*/
function add_stylesheet( string $handle, string $src = '', array $deps = [], $ver = null, string $media = 'all', array $atts = [], array $restrictions = [] ) {
if ( ! empty( $restrictions ) ) {
if ( $this->restrict( $restrictions ) === false )
return;
}
$this->enqueued_stylesheets[] = [
'handle' => $handle,
'src' => $src,
'deps' => $deps,
'ver' => $ver,
'media' => $media,
'atts' => $atts
];
}
/**
* Wrapper function that registers, enqueues, localizes and adds custom
* attributes for scripts and styles
*
* @since 2.0.0
*/
function script_enqueue() {
foreach ( $this->enqueued_scripts as $script ) {
wp_register_script( $script['handle'], $script['src'], $script['deps'], $script['ver'], $script['in_footer'] );
wp_enqueue_script( $script['handle'] );
// Add additional Attributes to the script tag if needed
if ( is_array( $script['atts'] ) && ! empty( $script['atts'] ) ) {
foreach ( $script['atts'] as $key => $value ) {
if ( $key == 'conditional' )
wp_script_add_data( $script['handle'], $key, $value );
else
wp_script_add_data( $script['handle'], $this->extra_attribute_prefix . $key, $value );
}
}
if (
is_array( $script['localize'] ) &&
! empty( $script['localize'] ) &&
isset( $script['localize']['handle'] ) &&
isset( $script['localize']['data'] ) &&
\gettype( $script['localize']['handle'] ) == 'string' &&
\gettype( $script['localize']['data'] ) == 'array'
) {
wp_localize_script( $script['handle'], $script['localize']['handle'], $script['localize']['data'] );
}
}
foreach ( $this->enqueued_stylesheets as $stylesheet ) {
wp_register_style( $stylesheet['handle'], $stylesheet['src'], $stylesheet['deps'], $stylesheet['ver'], $stylesheet['media'] );
wp_enqueue_style( $stylesheet['handle'] );
// Add additional Attributes to the script tag if needed
if ( is_array( $stylesheet['atts'] ) && ! empty( $stylesheet['atts'] ) || true ) {
foreach ( $stylesheet['atts'] as $key => $value ) {
if ( $key == 'conditional' )
wp_style_add_data( $stylesheet['handle'], $key, $value );
else
wp_style_add_data( $stylesheet['handle'], $this->extra_attribute_prefix . $key, $value );
}
}
}
}
/**
* Callback for WP to hit before echoing out an enqueued resource. This callback specifically checks for any key-value pairs that have been added
* through `add_data()` and are prefixed with a special value to indicate they should be injected into the final HTML
*
* @param {string} $tag - Will be the full string of the tag (`<link>` or `<script>`)
* @param {string} $handle - The handle that was specified for the resource when enqueuing it
* @param {string} $src - the URI of the resource
* @param {string|null} $media - if resources is style, should be the target media, else null
* @param {boolean} $isStyle - If the resource is a stylesheet
*
* @since 2.0.0
*/
function scriptAndStyleTagAttributeAdder($tag, $handle, $src, $media, $isStyle){
$extra_attribute_pattern = '/' . $this->extra_attribute_prefix . '(.+)/';
$extraAttrs = [];
$nodeName = '';
// Get the WP_Dependency instance for this handle, and grab any extra fields
if ($isStyle) {
$nodeName = 'link';
$extraAttrs = wp_styles()->registered[$handle]->extra;
} else {
$nodeName = 'script';
$extraAttrs = wp_scripts()->registered[$handle]->extra;
}
// Check stored properties on WP resource instance against our pattern
$attribsToAdd = [];
foreach ($extraAttrs as $fullAttrKey => $attrVal) {
$matches = [];
preg_match($extra_attribute_pattern, $fullAttrKey, $matches);
if (count($matches) > 1) {
$attrKey = $matches[1];
$attribsToAdd[$attrKey] = $attrVal;
}
}
$replacementStr = '<' . $nodeName;
foreach ($attribsToAdd as $attrKey => $attrVal) {
$replacementStr .= ' ' . $attrKey . '="' . $attrVal . '"';
}
$tag = preg_replace('/<' . $nodeName . '/', $replacementStr, $tag );
return $tag;
}
}
// Usage Instructions:
/**
* Create an instance
* @param int $priority
*/
$scriptLoaderInstance = new \bf\script_loader(11);
/**
* Add a script to the queue
*
* @param string $handle - File handler
* @param string $src - Path to the file - Default: ''
* @param array $deps - Script dependencies - Default: []
* @param mixed $ver - Script version - Default: null
* @param bool $in_footer - Enqueue the script in the header or the footer - Default: true
* @param array $atts - Custom Script Attributes - Default: []
* @param array $restrictions - Restrict enqueueing the script. This array accepts callables. If one returns a value thats falsy the script won't enqueue
* @param array $localize - Setup data for wp_localize_script to pass PHP variables
*/
$scriptLoaderInstance->add_script(
'your-handle', // Handle
'path/to/your/script.js', // source
[
'lodash',
'wp-i18n'
], // Dependencies
'1.0.0', // Version
true, // in footer
[
'async' => 'async'
], // Custom script attributes
[
'relation' => 'AND',
[
( is_single() || is_page() ),
( comments_open() || get_comments_number() ),
! post_password_required()
]
], // Restrictions
[
'ajaxUrl' => admin_url( 'admin-ajax.php' )
] // localize
);
/**
* Add a stylesheet to the queue
*
* @param string $handle - File handler
* @param string $src - Path to the file - Default: ''
* @param array $deps - Stylesheet dependencies - Default: []
* @param mixed $ver - Stylesheet version - Default: null
* @param string $media - The media type for the enqueued stylesheet - Default: 'all'
* @param array $atts - Custom Stylesheet Attributes - Default: []
* @param array $restrictions - Restrict enqueueing the Stylesheet. This array accepts callables. If one returns a value thats falsy the Stylesheet won't enqueue
*/
$scriptLoaderInstance->add_stylesheet(
'your-handle', // Handle
'path/to/your/script.css', // source
[
'dashicons'
],
'1.0.0', // Version
'all', // media
[
'crossorigin' => 'anonymous' // Custom Stylesheet attributes
],
[
'relation' => 'OR',
[
is_singular(),
is_post_type_archive(['book'])
]
] // Restrictions
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment