diff --git a/include/helpers.php b/include/helpers.php index be813cf..8932eea 100644 --- a/include/helpers.php +++ b/include/helpers.php @@ -4,14 +4,14 @@ add_filter( 'minit-item-css', 'minit_comment_combined', 15, 3 ); add_filter( 'minit-item-js', 'minit_comment_combined', 15, 3 ); -function minit_comment_combined( $content, $object, $script ) { +function minit_comment_combined( $content, $object, $handle ) { if ( ! $content ) return $content; return sprintf( "\n\n/* Minit: %s */\n", - $object->registered[ $script ]->src + $object->registered[ $handle ]->src ) . $content; } @@ -36,75 +36,6 @@ function minit_add_toc( $content, $items ) { } -// Turn all local asset URLs into absolute URLs -add_filter( 'minit-item-css', 'minit_resolve_css_urls', 10, 3 ); - -function minit_resolve_css_urls( $content, $object, $script ) { - - if ( ! $content ) - return $content; - - $src = Minit::get_asset_relative_path( - $object->base_url, - $object->registered[ $script ]->src - ); - - // Make all local asset URLs absolute - $content = preg_replace( - '/url\(["\' ]?+(?!data:|https?:|\/\/)(.*?)["\' ]?\)/i', - sprintf( "url('%s/$1')", $object->base_url . dirname( $src ) ), - $content - ); - - return $content; - -} - - -// Add support for relative CSS imports -add_filter( 'minit-item-css', 'minit_resolve_css_imports', 10, 3 ); - -function minit_resolve_css_imports( $content, $object, $script ) { - - if ( ! $content ) - return $content; - - $src = Minit::get_asset_relative_path( - $object->base_url, - $object->registered[ $script ]->src - ); - - // Make all import asset URLs absolute - $content = preg_replace( - '/@import\s+(url\()?["\'](?!https?:|\/\/)(.*?)["\'](\)?)/i', - sprintf( "@import url('%s/$2')", $object->base_url . dirname( $src ) ), - $content - ); - - return $content; - -} - - -// Exclude styles with media queries from being included in Minit -add_filter( 'minit-item-css', 'minit_exclude_css_with_media_query', 10, 3 ); - -function minit_exclude_css_with_media_query( $content, $object, $script ) { - - if ( ! $content ) - return $content; - - $whitelist = array( '', 'all', 'screen' ); - - // Exclude from Minit if media query specified - if ( ! in_array( $object->registered[ $script ]->args, $whitelist ) ) - return false; - - return $content; - -} - - // Make sure that all Minit files are served from the correct protocol add_filter( 'minit-url-css', 'minit_maybe_ssl_url' ); add_filter( 'minit-url-js', 'minit_maybe_ssl_url' ); @@ -118,6 +49,7 @@ function minit_maybe_ssl_url( $url ) { } + // Exclude handles that are known to cause problems add_filter( 'minit-exclude-js', 'minit_exclude_defaults' ); @@ -127,6 +59,6 @@ function minit_exclude_defaults( $handles ) { 'this-is-a-handle-of-a-script-you-want-to-exclude', ); - return array_merge( $handles, $exclude ); + return array_merge( $exclude, $handles ); } diff --git a/include/minit-assets.php b/include/minit-assets.php new file mode 100644 index 0000000..810d49b --- /dev/null +++ b/include/minit-assets.php @@ -0,0 +1,228 @@ +handler = $handler; + + if ( ! empty( $extension ) ) + $this->extension = $extension; + else + $this->extension = get_class( $handler ); + + } + + + abstract function init(); + + + function register( $todo ) { + + if ( empty( $todo ) ) + return $todo; + + // Allow files to be excluded from Minit + $minit_exclude = apply_filters( 'minit-exclude-' . $this->extension, array() ); + + if ( ! is_array( $minit_exclude ) ) + $minit_exclude = array(); + + $minit_todo = array_diff( $todo, $minit_exclude ); + + if ( empty( $minit_todo ) ) + return $todo; + + foreach ( $minit_todo as $handle ) + if ( ! in_array( $handle, $this->queue ) ) + $this->queue[] = $handle; + + // Mark these as done since we'll take care of them + $this->handler->done = array_merge( $this->handler->done, $this->queue ); + + return $todo; + + } + + + function minit() { + + $done = array(); + + if ( empty( $this->queue ) ) + return false; + + // Build a cache key + $ver = array( + 'is_ssl-' . is_ssl(), // Use different cache key for SSL and non-SSL + 'minit_cache_ver-' . get_option( 'minit_cache_ver' ), // Use a global cache version key to purge cache + ); + + // Include individual scripts versions in the cache key + foreach ( $this->queue as $handle ) + $ver[] = sprintf( '%s-%s', $handle, $this->handler->registered[ $handle ]->ver ); + + $cache_ver = md5( 'minit-' . implode( '-', $ver ) ); + + // Try to get queue from cache + //$cache = get_transient( 'minit-' . $cache_ver ); + + if ( ! empty( $cache ) && isset( $cache['url'] ) ) { + $this->mark_done( $cache['done'] ); + + return $cache['url']; + } + + foreach ( $this->queue as $handle ) { + + // Ignore pseudo packages such as jquery which return src as empty string + if ( empty( $this->handler->registered[ $handle ]->src ) ) + $done[ $handle ] = null; + + // Get the relative URL of the asset + $src = $this->get_asset_relative_path( $handle ); + + // Skip if the file is not hosted locally + if ( empty( $src ) || ! file_exists( ABSPATH . $src ) ) + continue; + + $item = $this->minit_item( file_get_contents( ABSPATH . $src ), $handle, $src ); + + $item = apply_filters( + 'minit-item-' . $this->extension, + $item, + $this->handler, + $handle + ); + + if ( false !== $item ) + $done[ $handle ] = $item; + + } + + if ( empty( $done ) ) + return false; + + $this->mark_done( array_keys( $done ) ); + + $wp_upload_dir = wp_upload_dir(); + + // Try to create the folder for cache + if ( ! is_dir( $wp_upload_dir['basedir'] . '/minit' ) ) + if ( ! mkdir( $wp_upload_dir['basedir'] . '/minit' ) ) + return false; + + $combined_file_path = sprintf( '%s/minit/%s.%s', $wp_upload_dir['basedir'], $cache_ver, $this->extension ); + $combined_file_url = sprintf( '%s/minit/%s.%s', $wp_upload_dir['baseurl'], $cache_ver, $this->extension ); + + // Allow other plugins to do something with the resulting URL + $combined_file_url = apply_filters( 'minit-url-' . $this->extension, $combined_file_url, $done ); + + // Allow other plugins to minify and obfuscate + $done_imploded = apply_filters( 'minit-content-' . $this->extension, implode( "\n\n", $done ), $done ); + + // Store the combined file on the filesystem + if ( ! file_exists( $combined_file_path ) ) + if ( ! file_put_contents( $combined_file_path, $done_imploded ) ) + return false; + + // Cache this set of scripts, by default for 24 hours + $cache_ttl = apply_filters( 'minit-cache-expiration', 24 * 60 * 60 ); + $cache_ttl = apply_filters( 'minit-cache-expiration-' . $this->extension, $cache_ttl ); + + $result = array( + 'done' => array_keys( $done ), + 'url' => $combined_file_url, + 'file' => $combined_file_path, + ); + + set_transient( 'minit-' . $cache_ver, $result, $cache_ttl ); + + return $combined_file_url; + + } + + + abstract function process( $todo ); + + + function minit_item( $source, $handle, $src ) { + + return $source; + + } + + + protected function mark_done( $handles ) { + + // Remove processed items from the queue + $this->queue = array_diff( $this->queue, $handles ); + + // Mark them as processed by Minit + $this->done = array_merge( $this->done, $handles ); + + } + + + protected function get_asset_relative_path( $handle ) { + + if ( ! isset( $this->handler->registered[ $handle ] ) ) + return false; + + $item_url = $this->handler->registered[ $handle ]->src; + + if ( empty( $item_url ) ) + return false; + + // Remove protocol reference from the local base URL + $base_url = preg_replace( '/^(https?:)/i', '', $this->handler->base_url ); + + // Check if this is a local asset which we can include + $src_parts = explode( $base_url, $item_url ); + + if ( empty( $src_parts ) ) + return false; + + // Get the trailing part of the local URL + $maybe_relative = array_pop( $src_parts ); + + if ( file_exists( ABSPATH . $maybe_relative ) ) + return $maybe_relative; + + return false; + + } + + + public static function cache_bump() { + + // Use this as a global cache version number + update_option( 'minit_cache_ver', time() ); + + // Allow other plugins to know that we purged + do_action( 'minit-cache-purged' ); + + } + + + public static function cache_delete() { + + $wp_upload_dir = wp_upload_dir(); + $minit_files = glob( $wp_upload_dir['basedir'] . '/minit/*' ); + + if ( $minit_files ) { + foreach ( $minit_files as $minit_file ) { + unlink( $minit_file ); + } + } + + } + + +} diff --git a/include/minit-css.php b/include/minit-css.php new file mode 100644 index 0000000..8a4ba35 --- /dev/null +++ b/include/minit-css.php @@ -0,0 +1,128 @@ +plugin = $plugin; + + global $wp_styles; + + parent::__construct( $wp_styles, 'css' ); + + } + + + public function init() { + + // Queue all assets + add_filter( 'print_styles_array', array( $this, 'register' ) ); + + // Print our CSS files + add_filter( 'print_styles_array', array( $this, 'process' ), 20 ); + + } + + + function process( $todo ) { + + $handle = 'minit-css'; + $url = $this->minit(); + + if ( empty( $url ) ) { + return $todo; + } + + wp_enqueue_style( $handle, $url, null, null ); + + // Add our Minit style since wp_enqueue_script won't do it at this point + $todo[] = $handle; + + // Add inline styles for all minited styles + foreach ( $this->done as $script ) { + + // Can this return an array instead? + $inline_styles = $this->handler->get_data( $script, 'after' ); + + if ( ! empty( $inline_styles ) ) + $this->handler->add_inline_style( $handle, implode( "\n", $inline_styles ) ); + + } + + return $todo; + + } + + + function minit_item( $content, $handle, $src ) { + + if ( empty( $content ) ) + return $content; + + // Exclude styles with media queries from being included in Minit + $content = $this->exclude_with_media_query( $content, $handle, $src ); + + // Make all asset URLs absolute + $content = $this->resolve_urls( $content, $handle, $src ); + + // Add support for relative CSS imports + $content = $this->resolve_imports( $content, $handle, $src ); + + return $content; + + } + + + private function resolve_urls( $content, $handle, $src ) { + + if ( ! $content ) + return $content; + + // Make all local asset URLs absolute + $content = preg_replace( + '/url\(["\' ]?+(?!data:|https?:|\/\/)(.*?)["\' ]?\)/i', + sprintf( "url('%s/$1')", $this->handler->base_url . dirname( $src ) ), + $content + ); + + return $content; + + } + + + private function resolve_imports( $content, $handle, $src ) { + + if ( ! $content ) + return $content; + + // Make all import asset URLs absolute + $content = preg_replace( + '/@import\s+(url\()?["\'](?!https?:|\/\/)(.*?)["\'](\)?)/i', + sprintf( "@import url('%s/$2')", $this->handler->base_url . dirname( $src ) ), + $content + ); + + return $content; + + } + + + private function exclude_with_media_query( $content, $handle, $src ) { + + if ( ! $content ) + return $content; + + $whitelist = array( '', 'all', 'screen' ); + + // Exclude from Minit if media query specified + if ( ! in_array( $this->handler->registered[ $handle ]->args, $whitelist ) ) + return false; + + return $content; + + } + +} diff --git a/include/minit-js.php b/include/minit-js.php new file mode 100644 index 0000000..d6e0e80 --- /dev/null +++ b/include/minit-js.php @@ -0,0 +1,147 @@ +plugin = $plugin; + + global $wp_scripts; + + parent::__construct( $wp_scripts, 'js' ); + + } + + + function init() { + + // Queue all assets + add_filter( 'print_scripts_array', array( $this, 'register' ) ); + + // Print our JS file + add_filter( 'print_scripts_array', array( $this, 'process' ), 20 ); + + // Print external scripts asynchronously in the footer + add_action( 'wp_print_footer_scripts', array( $this, 'print_async_scripts' ), 20 ); + + // Load our JS files asynchronously + add_filter( 'script_loader_tag', array( $this, 'script_tag_async' ), 20, 3 ); + + } + + + function process( $todo ) { + + // Run this only in the footer + if ( ! did_action( 'wp_print_footer_scripts' ) ) + return $todo; + + $handle = 'minit-js'; + $url = $this->minit(); + + if ( empty( $url ) ) { + return $todo; + } + + // @todo create a fallback for apply_filters( 'minit-js-in-footer', true ) + wp_register_script( $handle, $url, null, null, true ); + + // Add our Minit script since wp_enqueue_script won't do it at this point + $todo[] = $handle; + + $inline_js = array(); + + // Add inline scripts for all minited scripts + foreach ( $this->done as $script ) { + + $extra = $this->handler->get_data( $script, 'data' ); + + if ( ! empty( $extra ) ) + $inline_js[] = $extra; + + } + + if ( ! empty( $inline_js ) ) + $this->handler->add_data( $handle, 'data', implode( "\n", $inline_js ) ); + + return $todo; + + } + + + public function print_async_scripts() { + + $async_queue = array(); + $minit_exclude = (array) apply_filters( 'minit-exclude-js', array() ); + + foreach ( $this->handler->queue as $handle ) { + + // Skip asyncing explicitly excluded script handles + if ( in_array( $handle, $minit_exclude ) ) { + continue; + } + + $script_relative_path = $this->get_asset_relative_path( $handle ); + + if ( ! $script_relative_path ) { + // Add this script to our async queue + $async_queue[] = $handle; + } + + } + + if ( empty( $async_queue ) ) + return; + + ?> + + + array(), - 'js' => array(), - ); - - protected $done = array( - 'css' => array(), - 'js' => array(), - ); - - protected $extension_map = array( - 'WP_Styles' => 'css', - 'WP_Scripts' => 'js', - ); - - - public function __construct( $plugin ) { - - $this->plugin = $plugin; - - } - - - public function init() { - - // Queue all assets - add_filter( 'print_scripts_array', array( $this, 'register_js' ) ); - add_filter( 'print_styles_array', array( $this, 'register_css' ) ); - - // Print our CSS files - add_filter( 'print_styles_array', array( $this, 'minit_css' ), 20 ); - - // Print our JS file - add_filter( 'print_scripts_array', array( $this, 'minit_js' ), 20 ); - - // Print external scripts asynchronously in the footer - add_action( 'wp_print_footer_scripts', array( $this, 'print_async_scripts' ), 20 ); - - // Load our JS files asynchronously - add_filter( 'script_loader_tag', array( $this, 'script_tag_async' ), 20, 3 ); - - } - - - public static function instance() { - - return self::$instance; - - } - - - function register_js( $todo ) { - - global $wp_scripts; - - return $this->register_assets( $wp_scripts, $todo ); - - } - - - function register_css( $todo ) { - - global $wp_styles; - - return $this->register_assets( $wp_styles, $todo ); - - } - - - function register_assets( $object, $todo ) { - - if ( empty( $todo ) ) - return $todo; - - $extension = $this->get_object_extension( $object ); - - // Allow files to be excluded from Minit - $minit_exclude = apply_filters( 'minit-exclude-' . $extension, array() ); - - if ( ! is_array( $minit_exclude ) ) - $minit_exclude = array(); - - $minit_todo = array_diff( $todo, $minit_exclude ); - - if ( empty( $minit_todo ) ) - return $todo; - - foreach ( $minit_todo as $handle ) - if ( ! in_array( $handle, $this->queue[ $extension ] ) ) - $this->queue[ $extension ][] = $handle; - - // Mark these as done since we'll take care of them - $object->done = array_merge( $object->done, $this->queue[ $extension ] ); - - return $todo; - - } - - - function get_object_extension( $object ) { - - $class = get_class( $object ); - - if ( empty( $class ) ) - return false; - - if ( isset( $this->extension_map[ $class ] ) ) - return $this->extension_map[ $class ]; - - return $class; - - } - - - function get_queue( $object ) { - - $extension = $this->get_object_extension( $object ); - - if ( isset( $this->queue[ $extension ] ) ) - return $this->queue[ $extension ]; - - return array(); - - } - - - function get_done( $object ) { - - $extension = $this->get_object_extension( $object ); - - if ( isset( $this->done[ $extension ] ) ) - return $this->done[ $extension ]; - - return array(); - - } - - - function minit_assets( $object ) { - - $done = array(); - $extension = $this->get_object_extension( $object ); - $todo = $this->get_queue( $object ); - - if ( empty( $todo ) ) - return false; - - // Build a cache key - $ver = array( - $this->plugin->version, - $extension, - 'is_ssl-' . is_ssl(), // Use different cache key for SSL and non-SSL - 'minit_cache_ver-' . get_option( 'minit_cache_ver' ), // Use a global cache version key to purge cache - ); - - // Include individual scripts versions in the cache key - foreach ( $todo as $handle ) - $ver[] = sprintf( '%s-%s', $handle, $object->registered[ $handle ]->ver ); - - $cache_ver = md5( 'minit-' . implode( '-', $ver ) ); - - // Try to get queue from cache - $cache = get_transient( 'minit-' . $cache_ver ); - - if ( ! empty( $cache ) && isset( $cache['url'] ) ) { - $this->mark_done( $cache['done'], $object ); - - return $cache['url']; - } - - foreach ( $todo as $script ) { - - // Ignore pseudo packages such as jquery which return src as empty string - if ( empty( $object->registered[ $script ]->src ) ) - $done[ $script ] = null; - - // Get the relative URL of the asset - $src = $this->get_asset_relative_path( - $object->base_url, - $object->registered[ $script ]->src - ); - - // Skip if the file is not hosted locally - if ( empty( $src ) || ! file_exists( ABSPATH . $src ) ) - continue; - - $done[ $script ] = apply_filters( - 'minit-item-' . $extension, - file_get_contents( ABSPATH . $src ), - $object, - $script - ); - - } - - if ( empty( $done ) ) - return false; - - $this->mark_done( array_keys( $done ), $object ); - - $wp_upload_dir = wp_upload_dir(); - - // Try to create the folder for cache - if ( ! is_dir( $wp_upload_dir['basedir'] . '/minit' ) ) - if ( ! mkdir( $wp_upload_dir['basedir'] . '/minit' ) ) - return false; - - $combined_file_path = sprintf( '%s/minit/%s.%s', $wp_upload_dir['basedir'], $cache_ver, $extension ); - $combined_file_url = sprintf( '%s/minit/%s.%s', $wp_upload_dir['baseurl'], $cache_ver, $extension ); - - // Allow other plugins to do something with the resulting URL - $combined_file_url = apply_filters( 'minit-url-' . $extension, $combined_file_url, $done ); - - // Allow other plugins to minify and obfuscate - $done_imploded = apply_filters( 'minit-content-' . $extension, implode( "\n\n", $done ), $done ); - - // Store the combined file on the filesystem - if ( ! file_exists( $combined_file_path ) ) - if ( ! file_put_contents( $combined_file_path, $done_imploded ) ) - return false; - - // Cache this set of scripts, by default for 24 hours - $cache_ttl = apply_filters( 'minit-cache-expiration', 24 * 60 * 60 ); - $cache_ttl = apply_filters( 'minit-cache-expiration-' . $extension, $cache_ttl ); - - $result = array( - 'done' => array_keys( $done ), - 'url' => $combined_file_url, - 'file' => $combined_file_path, - ); - - set_transient( 'minit-' . $cache_ver, $result, $cache_ttl ); - - return $combined_file_url; - - } - - - protected function mark_done( $handles, $object ) { - - $extension = $this->get_object_extension( $object ); - - // Remove processed items from the queue - $this->queue[ $extension ] = array_diff( $this->queue[ $extension ], $handles ); - - // Mark them as processed by Minit - $this->done[ $extension ] = array_merge( $this->done[ $extension ], $handles ); - - } - - - function minit_js( $todo ) { - - global $wp_scripts; - - // Run this only in the footer - if ( ! did_action( 'wp_print_footer_scripts' ) ) - return $todo; - - $handle = 'minit-js'; - $url = $this->minit_assets( $wp_scripts ); - - if ( empty( $url ) ) { - return $todo; - } - - // @todo create a fallback for apply_filters( 'minit-js-in-footer', true ) - wp_register_script( $handle, $url, null, null, true ); - - // Add our Minit script since wp_enqueue_script won't do it at this point - $todo[] = $handle; - - $done = $this->get_done( $wp_scripts ); - $inline_js = array(); - - // Add inline scripts for all minited scripts - foreach ( $done as $script ) { - - $extra = $wp_scripts->get_data( $script, 'data' ); - - if ( ! empty( $extra ) ) - $inline_js[] = $extra; - - } - - if ( ! empty( $inline_js ) ) - $wp_scripts->add_data( $handle, 'data', implode( "\n", $inline_js ) ); - - return $todo; - - } - - - function minit_css( $todo ) { - - global $wp_styles; - - $handle = 'minit-css'; - $url = $this->minit_assets( $wp_styles ); - - if ( empty( $url ) ) { - return $todo; - } - - wp_enqueue_style( $handle, $url, null, null ); - - // Add our Minit style since wp_enqueue_script won't do it at this point - $todo[] = $handle; - - $done = $this->get_done( $wp_styles ); - - // Add inline styles for all minited styles - foreach ( $done as $script ) { - - // Can this return an array instead? - $inline_styles = $wp_styles->get_data( $script, 'after' ); - - if ( ! empty( $inline_styles ) ) - $wp_styles->add_inline_style( $handle, implode( "\n", $inline_styles ) ); - - } - - return $todo; - - } - - - public static function get_asset_relative_path( $base_url, $item_url ) { - - // Remove protocol reference from the local base URL - $base_url = preg_replace( '/^(https?:\/\/|\/\/)/i', '', $base_url ); - - // Check if this is a local asset which we can include - $src_parts = explode( $base_url, $item_url ); - - // Get the trailing part of the local URL - $maybe_relative = end( $src_parts ); - - if ( ! file_exists( ABSPATH . $maybe_relative ) ) - return false; - - return $maybe_relative; - - } - - public function print_async_scripts() { - - global $wp_scripts; - - $async_queue = array(); - $minit_exclude = (array) apply_filters( 'minit-exclude-js', array() ); - - foreach ( $wp_scripts->queue as $handle ) { - - // Skip asyncing explicitly excluded script handles - if ( in_array( $handle, $minit_exclude ) ) { - continue; - } - - $script_relative_path = $this->get_asset_relative_path( - $wp_scripts->base_url, - $wp_scripts->registered[ $handle ]->src - ); - - if ( ! $script_relative_path ) { - // Add this script to our async queue - $async_queue[] = $handle; - } - - } - - if ( empty( $async_queue ) ) - return; - - // Remove this script from being printed the regular way - wp_dequeue_script( $async_queue ); - - ?> - - - plugin_file = __FILE__; - $this->minit = new Minit( $this ); - $this->admin = new Minit_Admin( $this ); - - $this->init(); + add_action( 'init', array( $this, 'init' ) ); } public function init() { - if ( ! is_admin() ) - $this->minit->init(); + include dirname( __FILE__ ) . '/include/minit-assets.php'; + include dirname( __FILE__ ) . '/include/minit-js.php'; + include dirname( __FILE__ ) . '/include/minit-css.php'; + include dirname( __FILE__ ) . '/include/helpers.php'; + include dirname( __FILE__ ) . '/include/admin.php'; + + $minit_js = new Minit_Js( $this ); + $minit_css = new Minit_Css( $this ); + $minit_admin = new Minit_Admin( $this ); - $this->admin->init(); + if ( is_admin() ) { + $minit_admin->init(); + } else { + $minit_js->init(); + $minit_css->init(); + } // This action can used to delete all Minit cache files from cron - add_action( 'minit-cache-purge-delete', array( $this->minit, 'cache_delete' ) ); + add_action( 'minit-cache-purge-delete', array( $minit_js, 'cache_delete' ) ); }