diff --git a/admin.css b/admin.css
index 5199a48..3c63c93 100644
--- a/admin.css
+++ b/admin.css
@@ -14,3 +14,16 @@
.fixed .column-expires {
width: 12%;
}
+
+.nav-tab-content {
+ display: none;
+}
+.form-table th small {
+ display: block;
+ clear: both;
+ color: #999;
+}
+.form-table td label {
+ display: block;
+ clear:both;
+}
\ No newline at end of file
diff --git a/admin.js b/admin.js
index e69de29..2b6d574 100644
--- a/admin.js
+++ b/admin.js
@@ -0,0 +1,20 @@
+jQuery(function($){
+
+ // Admin page tabs
+ var $tabs = $('.nav-tab-wrapper'),
+ $panels = $('.nav-tab-content'),
+ currentHash = window.location.hash;
+
+ $tabs.on('click', 'a', function(e){
+ var hash = $(this).attr('href').replace('#tab-', '#tab-content-'); // prevents page scrolling if hash is present
+ $panels.hide().filter(hash).show();
+ $tabs.find('a').removeClass('nav-tab-active').filter($(this)).addClass('nav-tab-active');
+ });
+ $tabs.find( currentHash ? 'a[href="'+currentHash+'"]' : ':first').trigger('click');
+
+ // Fix input-inside-label glitch
+ $panels.on('click', 'input[type=text]', function(e){
+ e.preventDefault();
+ })
+
+});
\ No newline at end of file
diff --git a/dependency-minification.php b/dependency-minification.php
index 60c8c91..7f4ae98 100644
--- a/dependency-minification.php
+++ b/dependency-minification.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: Dependency Minification
* Description: Concatenates and minifies scripts and stylesheets. Please install and activate scribu's Proper Network Activation plugin before activating this plugin network-wide.
- * Version: 0.9.7
+ * Version: 1.0
* Author: X-Team
* Author URI: http://x-team.com/wordpress/
* Text Domain: dependency-minification
@@ -28,1010 +28,277 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-class Dependency_Minification {
- static $options = array();
- protected static $minified_count = 0;
- static $admin_page_hook;
-
- const DEFAULT_ENDPOINT = '_minify';
- const CRON_MINIFY_ACTION = 'minify_dependencies';
- const CACHE_KEY_PREFIX = 'depmin_cache_';
- const FILENAME_PATTERN = '([^/]+?)\.([0-9a-f]+)(?:\.([0-9a-f]+))?\.(css|js)';
- const AJAX_ACTION = 'dependency_minification';
- const ADMIN_PAGE_SLUG = 'dependency-minification';
- const ADMIN_PARENT_PAGE = 'tools.php';
-
- static $query_vars = array(
- 'depmin_handles',
- 'depmin_src_hash',
- 'depmin_ver_hash',
- 'depmin_file_ext',
- );
-
- static function setup() {
- self::$options = apply_filters( 'dependency_minification_options', array_merge(
- array(
- 'endpoint' => self::DEFAULT_ENDPOINT,
- 'default_exclude_remote_dependencies' => true,
- 'cache_control_max_age_cache' => 2629743, // 1 month in seconds
- 'cache_control_max_age_error' => 60 * 60, // 1 hour, to try minifying again
- 'allow_not_modified_responses' => true, // only needs to be true if not Akamaized and max-age is short
- 'admin_page_capability' => 'edit_theme_options',
- 'show_error_messages' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
- 'disable_if_wp_debug' => true,
- ),
- self::$options
- ) );
-
- $is_frontend = ! (
- is_admin()
- ||
- in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) )
- );
- if ( $is_frontend ) {
- add_filter( 'print_scripts_array', array( __CLASS__, 'filter_print_scripts_array' ) );
- add_filter( 'print_styles_array', array( __CLASS__, 'filter_print_styles_array' ) );
- }
- add_action( 'init', array( __CLASS__, 'hook_rewrites' ) );
- add_action( self::CRON_MINIFY_ACTION, array( __CLASS__, 'minify' ), 10, 4 );
- add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
- add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
- add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
- add_action( 'wp_ajax_' . self::AJAX_ACTION, array( __CLASS__, 'admin_ajax_handler' ) );
- add_filter( 'plugin_action_links', array( __CLASS__, 'admin_plugin_action_links' ), 10, 2 );
- }
-
- static function hook_rewrites() {
- add_filter( 'query_vars', array( __CLASS__, 'filter_query_vars' ) );
- add_action( 'pre_get_posts', array( __CLASS__, 'handle_request' ) );
- self::add_rewrite_rule();
- }
+// Exit if accessed directly
+if ( ! defined( 'ABSPATH' ) ) exit;
- static function get_rewrite_regex() {
- return sprintf( '^%s/%s', self::$options['endpoint'], self::FILENAME_PATTERN );
- }
+/**
+ * Main Dependency Minification plugin class.
+ *
+ * @since 0.1
+ */
+class Dependency_Minification {
- static function add_rewrite_rule() {
- $regex = self::get_rewrite_regex();
- $redirect = 'index.php?';
- for ( $i = 0; $i < count( self::$query_vars ); $i += 1 ) {
- $redirect .= sprintf( '%s=$matches[%d]&', self::$query_vars[$i], $i + 1 );
- }
- add_rewrite_rule( $regex, $redirect, 'top' );
- }
+ /**
+ * @var float
+ * @since 1.0
+ */
+ const VERSION = '1.0';
- static function remove_rewrite_rule() {
- $regex = self::get_rewrite_regex();
- global $wp_rewrite;
- unset( $wp_rewrite->extra_rules_top[ $regex ] );
- }
-
- protected static $is_footer = array(
- 'scripts' => false,
- 'styles' => false,
- );
+ /*** Properties ***********************************************************/
/**
- * register_activation_hook
+ * @var DepMin_Admin
+ * @since 1.0
*/
- static function activate() {
- self::setup();
- self::add_rewrite_rule();
- flush_rewrite_rules();
- }
+ public static $admin;
/**
- * register_deactivation_hook
+ * @var DepMin_Options
+ * @since 1.0
*/
- static function deactivate() {
- self::remove_rewrite_rule();
- flush_rewrite_rules();
- }
+ public static $options;
/**
- * @filter query_vars
+ * @var DepMin_Handler
+ * @since 1.0
*/
- static function filter_query_vars( $query_vars ) {
- return array_merge( $query_vars, self::$query_vars );
- }
+ public static $handler;
/**
- * @filter print_styles_array
+ * @var DepMin_Collation
+ * @since 1.0
*/
- static function filter_print_styles_array( $handles ) {
- $handles = self::filter_print_dependency_array( $handles, 'styles' );
- return $handles;
- }
+ public static $collation;
/**
- * @filter print_scripts_array
+ * @var array
+ * @since 1.0
*/
- static function filter_print_scripts_array( $handles ) {
- $handles = self::filter_print_dependency_array( $handles, 'scripts' );
- return $handles;
- }
+ public static $query_vars = array(
+ 'depmin_handles',
+ 'depmin_src_hash',
+ 'depmin_ver_hash',
+ 'depmin_file_ext',
+ );
+
+ /*** Methods **************************************************************/
/**
- * @action admin_enqueue_scripts
+ * @access private
+ * @return void
+ * @since 1.0
*/
- static function admin_enqueue_scripts( $hook ) {
- if ( $hook !== self::$admin_page_hook ) {
- return;
- }
- $meta = get_plugin_data( __FILE__ );
- wp_enqueue_script( 'depmin-admin', plugin_dir_url( __FILE__ ) . 'admin.js', array( 'jquery' ), $meta['Version'] );
- wp_enqueue_style( 'depmin-admin', plugin_dir_url( __FILE__ ) . 'admin.css', array(), $meta['Version'] );
+ private function load_includes() {
+
+ // Load the helpers functions.
+ require self::path( 'inc/helpers.php' );
+
}
/**
- * @action admin_menu
+ * @access private
+ * @return void
+ * @since 1.0
*/
- static function admin_menu() {
- self::$admin_page_hook = add_submenu_page(
- self::ADMIN_PARENT_PAGE,
- __( 'Dependency Minification', 'dependency-minification' ),
- __( 'Dep. Minification', 'dependency-minification' ),
- self::$options['admin_page_capability'],
- self::ADMIN_PAGE_SLUG,
- array( __CLASS__, 'admin_page' )
- );
+ private function setup_actions() {
+
+ DepMin_Minify::hook_cron_action();
+ add_action( 'init', array( __CLASS__, 'add_rewrite_rules' ) );
+ add_filter( 'query_vars', array( __CLASS__, 'filter_query_vars' ) );
+
}
/**
- * @action admin_notices
+ * @access private
+ * @return void
+ * @since 1.0
*/
- static function admin_notices() {
- // Show a notice to notify user that pretty urls is disabled, hence the plugin won't work
- if ( empty( $GLOBALS['wp_rewrite']->permalink_structure ) ) {
- ?>
-
-
%1$s: %2$s',
- __( 'Dependency Minification', 'dependency-minification' ),
- sprintf(
- __( 'Pretty permalinks are not enabled in your %1$s, which is required for this plugin to operate. Select something other than Default (e.g. ?p=123)', 'dependency-minification' ),
- sprintf(
- '%2$s',
- admin_url( 'options-permalink.php' ),
- __( 'Permalinks Settings', 'dependency-minification' )
- )
- )
- );
- ?>
-
- id !== self::$admin_page_hook ) {
- return;
- }
- if ( empty( $_GET['updated-count'] ) ) {
- return;
- }
- if ( empty( $_GET['updated-action'] ) ) {
- return;
}
- $updated_count = intval( $_REQUEST['updated-count'] );
- $updated_task = sanitize_title( $_REQUEST['updated-action'] );
- ?>
-
- %s', esc_url( $admin_page_url ), esc_html__( 'Settings', 'dependency-minification' ) );
- array_push( $links, $admin_page_link );
- }
- return $links;
- }
+ public static function add_rewrite_rules() {
- static function admin_page() {
- if ( ! current_user_can( self::$options['admin_page_capability'] ) ) {
- wp_die( __( 'You cannot access this page.', 'dependency-minification' ) );
- }
- $nonce = wp_create_nonce( self::AJAX_ACTION );
- ?>
-
- extra_rules_top[ $regex ] );
}
/**
- * Separate external from internal (local) dependencies and then group the
- * internal resources into maximal groups.
- * @param {array} $handles
- * @param {string} $type (scripts|styles)
+ * @return void
+ * @since 1.0
*/
- static function filter_print_dependency_array( array $handles, $type ) {
- assert( in_array($type, array( 'scripts', 'styles' ) ) );
- assert( isset($GLOBALS["wp_{$type}"]) );
- $wp_deps = &$GLOBALS["wp_{$type}"];
- assert( is_a($wp_deps, 'WP_Dependencies') );
-
- /**
- * Determine if minification is enabled for the provided $handles.
- * Note that we cannot use the $concatenate_scripts global set by script_concat_settings
- * because it is intended to only be used in the WP Admin
- * Plugin is automatically disabled if pretty permalinks is not activated
- */
- $disabled = self::$options['disable_if_wp_debug'] ? ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : false;
- $disabled = $disabled || ( defined( 'DEPENDENCY_MINIFICATION_DEFAULT_DISABLED' ) && DEPENDENCY_MINIFICATION_DEFAULT_DISABLED );
- $disabled = apply_filters( 'dependency_minification_disabled', $disabled, $handles, $type );
- $disabled = apply_filters( "dependency_minification_disabled_{$type}", $disabled, $handles );
- $disabled = $disabled || empty( $GLOBALS['wp_rewrite']->permalink_structure );
- if ( $disabled ) {
- return $handles;
- }
+ public static function autoload( $class_name ) {
- // @todo There should be a better way to determine which group we are in
- $current_group = (int) self::$is_footer[$type]; // false => 0, true => 1
-
- $handles_in_group = array();
- foreach ( $handles as $handle ) {
- $must_process_handle = (
- $wp_deps->groups[$handle] === $current_group
- ||
- // Handle case where script is erroneously enqueued without in_footer=true (here's lookin at you, PollDaddy)
- (
- $wp_deps->groups[$handle] < $current_group
- &&
- !in_array($handle, $wp_deps->done)
- )
- );
-
- if ( $must_process_handle ) {
- $handles_in_group[] = $handle;
- }
- }
+ switch( $class_name ) {
- $filtered_handles = array();
- $groups = self::group_dependencies_by_exclusion( $handles_in_group, $wp_deps );
-
- foreach ( $groups as $group ) {
- // $internal_groups as $extra => $handles_in_group
- if ( empty( $group['handles'] ) ) {
- continue;
- }
-
- if ( $group['excluded'] ) {
- $filtered_handles = array_merge( $filtered_handles, $group['handles'] );
- continue;
- }
- $extra = empty( $group['extra'] ) ? array() : $group['extra'];
-
- $deps = array();
- foreach ( $group['handles'] as $handle ) {
- $deps[] = array(
- 'handle' => $handle,
- 'src' => $wp_deps->registered[$handle]->src,
- 'ver' => $wp_deps->registered[$handle]->ver,
- );
- }
- $srcs = wp_list_pluck( $deps, 'src' );
- $src_hash = self::hash_array( $srcs );
- $ver_hash = self::hash_array( wp_list_pluck( $deps, 'ver' ) );
-
- $cache_option_name = self::get_cache_option_name( $src_hash );
- $cached = get_option( $cache_option_name );
- $cached_ver_hash = null;
- if ( ! empty( $cached['deps'] ) ) {
- $cached_ver_hash = self::hash_array( wp_list_pluck( $cached['deps'], 'ver' ) );
- }
-
- $is_error = (
- ! empty( $cached['error'] )
- &&
- $ver_hash === $cached_ver_hash
- &&
- time() < $cached['expires']
- );
-
- $is_stale = (
- empty( $cached )
- ||
- time() > $cached['expires']
- ||
- $ver_hash !== $cached_ver_hash
- );
-
- if ( $is_error ) {
- if ( self::$options['show_error_messages'] ) {
- print "\n\n" );
- }
- $filtered_handles = array_merge( $filtered_handles, $group['handles'] );
- } elseif ( $is_stale ) {
- printf( "\n\n" );
-
- // @todo We could store the info in the option, and just pass the cache key to the cron; this would allow reliable passing of request_uri
-
- $scheduled = time();
- $args = array_merge(
- array(
- 'expires' => false,
- 'last_modified' => false,
- 'etag' => false,
- 'unminified_size' => false,
- 'contents' => false,
- 'pending' => true,
- ),
- compact( 'type', 'deps', 'scheduled' )
- );
-
- wp_schedule_single_event( $scheduled, self::CRON_MINIFY_ACTION, array( $args ) );
- // The bundle is not ready yet, so re-use the existing dependencies
- $filtered_handles = array_merge( $filtered_handles, $group['handles'] );
- } else {
- self::$minified_count += 1;
- $new_handle = sprintf('minified-%d', self::$minified_count);
- $filtered_handles[] = $new_handle;
- $src = self::get_dependency_minified_url( $deps, $type );
-
- // Deps are registered without versions since the URL includes the version (ver_hash)
- if ( 'scripts' === $type ) {
- $in_footer = !empty( $extra['group'] ); // @todo what if the group is not 0 or 1?
- wp_register_script( $new_handle, $src, array(), null, $in_footer );
- } elseif ( 'styles' === $type ) {
- wp_register_style( $new_handle, $src, array(), null, $extra['media'] );
- }
- $wp_deps->set_group( $new_handle, /*recursive*/false, $current_group );
- $new_dep = $wp_deps->registered[$new_handle];
- $new_extra = array(
- 'data' => '',
- );
- foreach ( $group['handles'] as $handle ) {
-
- // Aggregate data from scripts (e.g. wp_localize_script)
- if ( ! empty( $wp_deps->registered[$handle]->extra ) ) {
-
- foreach ( array_keys( $wp_deps->registered[$handle]->extra ) as $extra_key ) {
- $data = $wp_deps->get_data( $handle, $extra_key );
-
- if ( 'data' === $extra_key ) {
- $new_extra['data'] .= "/* wp_localize_script($handle): */\n";
- $new_extra['data'] .= "$data\n\n";
- } else {
- if ( isset( $new_extra[$extra_key] ) ) {
- // The handles should have been grouped so that they have the same extras
- assert( $new_extra[$extra_key] === $data );
- }
- $new_extra[$extra_key] = $data;
- }
- }
- }
-
- // Mark the handles as done for the resources that have been minified
- $wp_deps->done[] = $handle;
- }
-
- // Add aggregated extra to new dependency
- foreach ( $new_extra as $key => $value ) {
- $new_dep->add_data( $key, $value );
- }
- }
- }
+ case 'DepMin_Admin':
+ require self::path( 'inc/admin.php' );
+ break;
+
+ case 'DepMin_Options':
+ require self::path( 'inc/options.php' );
+ break;
+
+ case 'DepMin_SrcInfo':
+ require self::path( 'inc/helpers.php' );
+ break;
- // @todo Must be a better way to do this
- self::$is_footer[$type] = true; // for the next invocation
+ case 'DepMin_Handler':
+ require self::path( 'inc/handler.php' );
+ break;
+
+ case 'DepMin_Collation':
+ require self::path( 'inc/collation.php' );
+ break;
+
+ case 'DepMin_Minify':
+ case 'DepMin_Minifier':
+ case 'DepMin_Minifier_Default':
+ require self::path( 'inc/minifier.php' );
+ break;
+
+ case 'DepMin_Cache':
+ case 'DepMin_Cache_Default':
+ case 'DepMin_Cache_Interface':
+ require self::path( 'inc/cache.php' );
+ break;
+
+ }
- return $filtered_handles;
}
/**
- * @param string $src
- * @return bool
+ * @return string
+ * @since 1.0
*/
- static function is_self_hosted_src( $src ) {
- $parsed_url = parse_url( $src );
- return (
- (
- empty( $parsed_url['host'] )
- &&
- substr( $parsed_url['path'], 0, 1) === '/'
- )
- ||
- (
- ! empty( $parsed_url['host'] )
- &&
- $parsed_url['host'] === parse_url( get_home_url(), PHP_URL_HOST )
- )
- );
+ public static function url( $path = '' ) {
+ return plugins_url( $path, __FILE__ );
}
/**
- * @return {array} Two members, the 1st containing external handles and the 2nd containing internal handles
+ * @return string
+ * @since 1.0
*/
- static function group_dependencies_by_exclusion( $handles, WP_Dependencies $wp_deps ) {
- $groups = array();
-
- // First create groups based on whether they are excluded from minification
- $last_was_excluded = null;
- foreach ( $handles as $handle ) {
- $src = $wp_deps->registered[$handle]->src;
- $is_local = self::is_self_hosted_src( $src );
- $is_excluded = !$is_local && self::$options['default_exclude_remote_dependencies'];
- $is_excluded = apply_filters( 'dependency_minification_excluded', $is_excluded, $handle, $src );
-
- if ( $last_was_excluded !== $is_excluded ) {
- $groups[] = array(
- 'excluded' => $is_excluded,
- 'handles' => array(),
- );
- }
- $groups[ count( $groups ) - 1 ]['handles'][] = $handle;
- $last_was_excluded = $is_excluded;
- }
+ public static function path( $path = '' ) {
- // Now divide up the groups to create bundles that share the same extras (e.g. stylesheet media or conditional)
- $bundled_groups = array();
- foreach ( $groups as $group ) {
- if ( $group['excluded'] ) {
- $bundled_groups[] = $group;
- } else {
- $handles_bundles = self::group_handles_by_extra( $group['handles'], $wp_deps );
- foreach ( $handles_bundles as $extra => $handles_bundle ) {
- $bundled_groups[] = array(
- 'excluded' => false,
- 'extra' => unserialize( $extra ),
- 'handles' => $handles_bundle,
- );
- }
- }
- }
+ $base = plugin_dir_path( __FILE__ );
+
+ if ( ! empty( $path ) )
+ $path = path_join( $base, $path );
+ else
+ $path = untrailingslashit( $base );
- return $bundled_groups;
+ return $path;
}
/**
- * @todo This is only applicable for styles, right? The media and conditional extras.
- * @param {array} $handles
- * @return {array} Associative array where the keys are the args and extras
+ * @return void
+ * @since 1.0
*/
- static function group_handles_by_extra( array $handles, WP_Dependencies $wp_deps ) {
- $bundles = array();
- foreach ( $handles as $handle ) {
- $dep = &$wp_deps->registered[$handle];
- $extra = (array) $dep->extra;
- if ( is_a($wp_deps, 'WP_Styles') ) {
- $extra['media'] = is_string($dep->args) ? $dep->args : 'all';
- }
- unset($extra['suffix']);
- unset($extra['rtl']);
- unset($extra['data']);
- // Default scripts are not assigned 'group', so we use the original 'deps->args' value
- if ( is_a( $wp_deps, 'WP_Scripts' ) && empty( $extra['group'] ) && is_int( $dep->args ) ) {
- $extra['group'] = $dep->args;
- }
- ksort($extra);
- $key = serialize($extra);
- $bundles[$key][] = $handle;
- }
- return $bundles;
+ public static function activate() {
+ self::add_rewrite_rule();
+ flush_rewrite_rules();
}
/**
- * @action minify_dependencies
- *
+ * @return void
+ * @since 1.0
*/
- static function minify( $cached ) {
- extract( $cached );
- $host_domain = parse_url( home_url(), PHP_URL_HOST );
- $ver_hash = self::hash_array( wp_list_pluck( $deps, 'ver' ) );
- $src_hash = self::hash_array( wp_list_pluck( $deps, 'src' ) );
- $cache_option_name = self::get_cache_option_name( $src_hash );
-
- try {
- $is_css = ( 'styles' === $type );
- if ( 'scripts' === $type ) {
- require_once( dirname(__FILE__) . '/minify/JS/JSMin.php' );
- } elseif ( 'styles' === $type ) {
- require_once( dirname(__FILE__) . '/minify/CSS/UriRewriter.php' );
- require_once( dirname(__FILE__) . '/minify/CSS/Compressor.php' );
- }
-
- $unminified_size = 0;
- $srcs = wp_list_pluck( $deps, 'src' );
-
- // Get the contents of each script
- $contents_for_each_dep = array();
- foreach ( $srcs as $src ) {
-
- if ( ! preg_match( '|^(https?:)?//|', $src ) ) {
- $src = site_url( $src );
- }
-
- // First attempt to get the file from the filesystem
- $contents = false;
- $is_self_hosted = self::is_self_hosted_src( $src );
- if ( $is_self_hosted ) {
- $src_abspath = ltrim( parse_url( $src, PHP_URL_PATH ), '/' );
- $src_abspath = path_join( $_SERVER['DOCUMENT_ROOT'], $src_abspath );
- $contents = file_get_contents( $src_abspath );
- }
-
- // Dependency is not self-hosted or it the filesystem read failed, so do HTTP request
- if ( false === $contents ) {
- $r = wp_remote_get( $src );
- if ( is_wp_error($r) ) {
- throw new Exception("Failed to retrieve $src: " . $r->get_error_message());
- } elseif ( intval( wp_remote_retrieve_response_code( $r ) ) !== 200 ) {
- throw new Dependency_Minification_Exception( sprintf('Request for %s returned with HTTP %d %s', $src, wp_remote_retrieve_response_code( $r ), wp_remote_retrieve_response_message( $r )) );
- }
- $contents = wp_remote_retrieve_body( $r );
- }
- $unminified_size += strlen( $contents );
-
- // Remove the BOM
- $contents = preg_replace("/^\xEF\xBB\xBF/", '', $contents);
-
- // Rewrite relative paths in CSS
- $src_dir_path = dirname( parse_url( $src, PHP_URL_PATH ) );
- if ( 'styles' === $type && is_dir( ABSPATH . $src_dir_path ) ) {
- $contents = Minify_CSS_UriRewriter::rewrite( $contents, ABSPATH . $src_dir_path );
- }
-
- $contents_for_each_dep[$src] = $contents;
- }
-
- $contents = '';
-
- // Print a manifest of the dependencies
- $contents .= sprintf("/*! This minified dependency bundle includes:\n");
- $i = 0;
- foreach ( $srcs as $src ) {
- $i += 1;
- $contents .= sprintf( " * %02d. %s\n", $i, $src );
- }
- $contents .= sprintf(" */\n\n");
-
- // Minify
- // Note: semicolon needed in case a file lacks trailing semicolon
- // like `x = {a:1}` and the next file is IIFE (function(){}),
- // then it would get combined as x={a:1}(function(){}) and attempt
- // to pass the anonymous function into a function {a:1} which
- // is of course an object and not a function. Culprit here
- // is the comment-reply.js in WordPress.
- if ( 'scripts' === $type ) {
- $minified_contents = join( "\n;;\n", $contents_for_each_dep );
- $minified_contents = JSMin::minify($minified_contents);
- if ( false === $minified_contents ) {
- throw new Dependency_Minification_Exception( 'JavaScript parse error' );
- }
- } elseif ( 'styles' === $type ) {
- $minified_contents = join( "\n\n", $contents_for_each_dep );
- $minified_contents = Minify_CSS_Compressor::process($minified_contents);
- }
-
- $contents .= $minified_contents;
- $cached['unminified_size'] = $unminified_size;
- $max_age = apply_filters( 'dependency_minification_cache_control_max_age', (int) self::$options['cache_control_max_age_cache'], $srcs );
- $cached['contents'] = $contents;
- $cached['expires'] = time() + $max_age;
- $cached['error'] = null;
- }
- catch (Exception $e) {
- error_log( sprintf( '%s in %s: %s for srcs %s',
- get_class( $e ),
- __FUNCTION__,
- $e->getMessage(),
- join( ',', $srcs )
- ) );
- $cached['error'] = $e->getMessage();
- $max_age = apply_filters( 'dependency_minification_cache_control_max_age_error', (int) self::$options['cache_control_max_age_error'], $srcs );
- $cached['expires'] = time() + $max_age;
- }
- $cached['etag'] = self::generate_etag( $src_hash, $ver_hash );
- $cached['last_modified'] = time();
- $cached['pending'] = false;
-
- if ( false === get_option( $cache_option_name ) ) {
- add_option( $cache_option_name, $cached, '', 'no' );
- } else {
- update_option( $cache_option_name, $cached );
- }
+ public static function deactivate() {
+ self::remove_rewrite_rule();
+ flush_rewrite_rules();
}
+ /*** SingleTone ***********************************************************/
+
/**
- * Handle a request for the minified resource
+ * @return void
+ * @since 1.0
*/
- static function handle_request() {
- $src_hash = get_query_var( 'depmin_src_hash' );
- $ext = get_query_var( 'depmin_file_ext' );
- if ( empty( $src_hash ) || empty( $ext ) ) {
- return;
- }
+ public static function instance() {
+
+ static $instance;
+
+ if ( is_null( $instance ) ) {
+
+ $instance = new Dependency_Minification();
+ $instance->load_includes();
+ $instance->setup_actions();
+ $instance->setup();
- try {
- ob_start();
-
- if ( 'js' === $ext ) {
- header( 'Content-Type: application/javascript; charset=utf-8' );
- } else {
- header( 'Content-Type: text/css; charset=utf-8' );
- }
-
- $cache_option_name = self::get_cache_option_name( $src_hash );
- $cached = get_option($cache_option_name);
- if ( empty( $cached ) ) {
- throw new Dependency_Minification_Exception( 'Unknown minified dependency bundle.', 404 );
- }
- if ( ! empty( $cached['error'] ) ) {
- throw new Dependency_Minification_Exception( $cached['error'], 500 );
- }
-
- // Send the response headers for caching
- header( 'Expires: ' . str_replace('+0000', 'GMT', gmdate('r', $cached['expires'])) );
- if ( ! empty( $cached['last_modified'] ) ) {
- header( 'Last-Modified: ' . str_replace('+0000', 'GMT', gmdate('r', $cached['last_modified'])) );
- }
- if ( ! empty( $cached['etag'] ) ) {
- header( 'ETag: ' . $cached['etag'] );
- }
-
- $is_not_modified = false;
- if ( time() < $cached['expires'] ) {
- $is_not_modified = self::$options['allow_not_modified_responses'] && (
- (
- ! empty( $_SERVER['HTTP_IF_NONE_MATCH'] )
- &&
- ! empty( $cached['etag'] )
- &&
- trim( $_SERVER['HTTP_IF_NONE_MATCH'] ) === $cached['etag']
- )
- ||
- (
- ! empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] )
- &&
- ! empty( $cached['last_modified'] )
- &&
- strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) <= $cached['last_modified']
- )
- );
- }
-
- if ( $is_not_modified ) {
- status_header(304);
- } else {
- status_header(200);
- $out = $cached['contents'];
-
- global $compress_scripts, $compress_css;
- script_concat_settings();
- $compress = ( 'js' === $ext ? $compress_scripts : $compress_css );
- $force_gzip = ( $compress && defined('ENFORCE_GZIP') && ENFORCE_GZIP );
-
- // Copied from /wp-admin/load-scripts.php
- if ( $compress && ! ini_get('zlib.output_compression') && 'ob_gzhandler' != ini_get('output_handler') && isset($_SERVER['HTTP_ACCEPT_ENCODING']) ) {
- header('Vary: Accept-Encoding'); // Handle proxies
- if ( false !== stripos($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') && function_exists('gzdeflate') && ! $force_gzip ) {
- header('Content-Encoding: deflate');
- $out = gzdeflate( $out, 3 );
- } elseif ( false !== stripos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') && function_exists('gzencode') ) {
- header('Content-Encoding: gzip');
- $out = gzencode( $out, 3 );
- }
- }
-
- print $out; // xss ok
- }
- ob_end_flush();
- }
- catch(Exception $e) {
- ob_end_clean();
- $status = null;
- $message = '';
- if ( $e instanceof Dependency_Minification_Exception || self::$options['show_error_messages'] ) {
- $status = $e->getCode();
- $message = $e->getMessage();
- } else {
- error_log( sprintf('%s: %s via URI %s', __METHOD__, $e->getMessage(), esc_url_raw( $_SERVER['REQUEST_URI'] )) );
- $message = 'Unexpected error occurred.';
- }
- if ( empty($status) ) {
- $status = 500;
- }
- status_header( $status );
- nocache_headers();
- header( 'Content-Type: text/plain' );
- print $message; // xss ok
}
- exit;
+
+ return $instance;
}
}
-add_action( 'plugins_loaded', array( 'Dependency_Minification', 'setup' ), 100 );
-register_activation_hook( __FILE__, array( 'Dependency_Minification', 'activate' ) );
+/**
+ * @since 1.0
+ */
+class DepMin_Exception extends Exception {}
+
+// Register the plugin activation and deactivation hooks.
register_deactivation_hook( __FILE__, array( 'Dependency_Minification', 'deactivate' ) );
+register_activation_hook( __FILE__, array( 'Dependency_Minification', 'activate' ) );
+
+// Hook the plugin early onto the 'plugins_loaded' action.
+add_action( 'plugins_loaded', array( 'Dependency_Minification', 'instance' ), 100 );
-class Dependency_Minification_Exception extends Exception {}
+// Register the plugin classes autoloader.
+spl_autoload_register( array( 'Dependency_Minification', 'autoload' ) );
\ No newline at end of file
diff --git a/inc/admin.php b/inc/admin.php
new file mode 100644
index 0000000..f5758bf
--- /dev/null
+++ b/inc/admin.php
@@ -0,0 +1,596 @@
+page_hook = add_submenu_page(
+ self::PARENT_PAGE,
+ __( 'Dependency Minification', 'dependency-minification' ),
+ __( 'Dep. Minification', 'dependency-minification' ),
+ Dependency_Minification::$options['admin_page_capability'],
+ self::PAGE_SLUG,
+ array( $this, 'page_content' )
+ );
+
+ }
+
+ /**
+ * @return void
+ * @since 1.0
+ */
+ public function page_content() { ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ page_tab_content_status() ?>
+
+ page_tab_content_settings() ?>
+
+
+
+
+
+
+
+
+
+
+ getArrayCopy();
+
+ $options['exclude_dependencies'] = array_filter( preg_split( "#[\n\r]+#", esc_attr( $_POST['options']['exclude_dependencies'] ) ) );
+ $options['disabled_on_conditions'] = ( isset( $_POST['options']['disabled_on_conditions'] ) )
+ ? $_POST['options']['disabled_on_conditions']
+ : array();
+ $options['default_exclude_remote_dependencies'] = isset( $_POST['options']['default_exclude_remote_dependencies'] );
+
+ Dependency_Minification::$options->exchangeArray( $options );
+
+ }
+
+ $redirect_url = add_query_arg( 'page', self::PAGE_SLUG, admin_url( self::PARENT_PAGE ) );
+ $redirect_url = add_query_arg( 'updated', 1, $redirect_url );
+ $redirect_url .= '#tab-settings';
+ wp_redirect( $redirect_url );
+
+ die();
+ }
+
+ /**
+ * @action admin_enqueue_scripts
+ * @since 1.0
+ */
+ public function enqueue_scripts( $hook ) {
+
+ if ( $hook !== $this->page_hook )
+ return;
+
+ wp_enqueue_style( 'depmin-admin', Dependency_Minification::url( 'admin.css' ), array(), Dependency_Minification::VERSION );
+ wp_enqueue_script( 'depmin-admin', Dependency_Minification::url( 'admin.js' ), array( 'jquery' ), Dependency_Minification::VERSION );
+
+ }
+
+ /**
+ * @action admin_notices
+ */
+ public function admin_notices() {
+
+ // Show a notice to notify user that pretty urls is disabled, hence the plugin won't work
+ if ( empty( $GLOBALS['wp_rewrite']->permalink_structure ) ) { ?>
+
+
+
%1$s: %2$s',
+ __( 'Dependency Minification', 'dependency-minification' ),
+ sprintf(
+ __( 'Pretty permalinks are not enabled in your %1$s, which is required for this plugin to operate. Select something other than Default (e.g. ?p=123)', 'dependency-minification' ),
+ sprintf(
+ '%2$s',
+ admin_url( 'options-permalink.php' ),
+ __( 'Permalinks Settings', 'dependency-minification' )
+ )
+ )
+ ); // xss ok
+ ?>
+
+
+ id !== $this->page_hook )
+ return;
+
+ if ( empty( $_GET['updated-action'] ) )
+ return;
+
+ if ( empty( $_GET['updated-count'] ) )
+ return;
+
+ $updated_count = intval( $_GET['updated-count'] );
+ $updated_task = filter_input( INPUT_GET, 'updated-action' );
+ ?>
+
+ %s', esc_url( $admin_page_url ), esc_html__( 'Settings', 'dependency-minification' ) );
+ array_push( $links, $admin_page_link );
+
+ }
+
+ return $links;
+ }
+
+}
\ No newline at end of file
diff --git a/inc/cache.php b/inc/cache.php
new file mode 100644
index 0000000..3c244cf
--- /dev/null
+++ b/inc/cache.php
@@ -0,0 +1,271 @@
+get_all();
+ }
+
+ /**
+ * @return string
+ * @since 1.0
+ */
+ public static function get_key( $key ) {
+ return self::get_object()->get_key( $key );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public static function exists( $key ) {
+ return self::get_object()->exists( $key );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public static function delete( $key ) {
+ return self::get_object()->delete( $key );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public static function set( $key, $value ) {
+ return self::get_object()->set( $key, $value );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public static function add( $key, $value ) {
+ return self::get_object()->add( $key, $value );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public static function replace( $key, $value ) {
+ return self::get_object()->replace( $key, $value );
+ }
+
+ /**
+ * @return mixed
+ * @since 1.0
+ */
+ public static function get( $key, $default = false ) {
+ return self::get_object()->get( $key, $default );
+ }
+
+ /**
+ * @return void
+ * @since 1.0
+ */
+ public static function set_object( $object ) {
+
+ if ( $object instanceof DepMin_Cache_Interface )
+ self::$object = $object;
+
+ }
+
+ /**
+ * @return DepMin_Cache_Interface
+ * @since 1.0
+ */
+ public static function get_object() {
+
+ if ( is_null( self::$object ) )
+ self::$object = new DepMin_Cache_Default();
+
+ return self::$object;
+
+ }
+
+}
+
+/**
+ * @since 1.0
+ */
+class DepMin_Cache_Default implements DepMin_Cache_Interface {
+
+ /**
+ * @return array
+ * @since 1.0
+ */
+ public function get_all() {
+
+ global $wpdb;
+ $list = array();
+
+ foreach ( $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'depmin_cache_%'" ) as $key ) {
+
+ if ( ( $value = get_option( $key ) ) )
+ $list[ $key ] = $value;
+
+ }
+
+ return $list;
+ }
+
+ /**
+ * @return string|bool
+ * @since 1.0
+ */
+ public function get_key( $key ) {
+
+ if ( is_array( $key ) ) {
+ $key = DepMin_hash_array( $key );
+
+ } elseif ( is_object( $key ) ) {
+ $key = spl_object_hash( $key );
+
+ }
+
+ if ( empty( $key ) )
+ return false;
+
+ return 'depmin_cache_' . trim( $key );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function set( $key, $value ) {
+
+ $key = trim( $key );
+
+ if ( empty( $key ) )
+ return false;
+
+ if ( ! $this->exists( $key ) ) {
+ return $this->add( $key, $value );
+
+ } else {
+ return $this->replace( $key, $value );
+
+ }
+
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function add( $key, $value ) {
+
+ if ( empty( $key ) || $this->exists( $key ) )
+ return false;
+
+ return add_option( $key, $value, '', 'no' );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function replace( $key, $value ) {
+
+ if ( empty( $key ) || ! $this->exists( $key ) )
+ return false;
+
+ return update_option( $key, $value );
+ }
+
+ /**
+ * @return mixed
+ * @since 1.0
+ */
+ public function get( $key, $default = false ) {
+ return get_option( $key, $default );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function exists( $key ) {
+ return (bool) $this->get( $key );
+ }
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function delete( $key ) {
+ return delete_option( $key );
+ }
+
+}
+
+/**
+ * @since 1.0
+ */
+interface DepMin_Cache_Interface {
+
+ /**
+ * @return array
+ * @since 1.0
+ */
+ public function get_all();
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function exists( $key );
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function delete( $key );
+
+ /**
+ * @return string
+ * @since 1.0
+ */
+ public function get_key( $key );
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function set( $key, $value );
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function add( $key, $value );
+
+ /**
+ * @return bool
+ * @since 1.0
+ */
+ public function replace( $key, $value );
+
+ /**
+ * @return mixed
+ * @since 1.0
+ */
+ public function get( $key, $default = false );
+
+}
diff --git a/inc/collation.php b/inc/collation.php
new file mode 100644
index 0000000..f9259e2
--- /dev/null
+++ b/inc/collation.php
@@ -0,0 +1,347 @@
+ false,
+ 'styles' => false,
+ );
+
+ /**
+ * @return void
+ * @since 1.0
+ */
+ public function __construct() {
+
+ $disabled = (
+ ( isset( Dependency_Minification::$options['disabled_on_conditions']['all'] ) && ! empty( Dependency_Minification::$options['disabled_on_conditions']['all'] ) )
+ || ( isset( Dependency_Minification::$options['disabled_on_conditions']['loggedin'] ) && ! empty( Dependency_Minification::$options['disabled_on_conditions']['loggedin'] ) && is_user_logged_in() )
+ || ( ! empty( Dependency_Minification::$options['disabled_on_conditions']['admin'] ) && is_user_logged_in() && current_user_can( 'manage_options' ) )
+ || ( ! empty( Dependency_Minification::$options['disabled_on_conditions']['queryvar']['enabled'] )
+ && ! empty( Dependency_Minification::$options['disabled_on_conditions']['queryvar']['enabled'] )
+ && ! empty( $_GET[ Dependency_Minification::$options['disabled_on_conditions']['queryvar']['value'] ] )
+ )
+ );
+
+ if ( DepMin_is_frontend() && ! $disabled ) {
+
+ add_filter( 'print_scripts_array', array( $this, 'filter_print_scripts_array' ) );
+ add_filter( 'print_styles_array', array( $this, 'filter_print_styles_array' ) );
+ }
+
+ }
+
+ /**
+ * @filter print_styles_array
+ * @return array
+ * @since 1.0
+ */
+ public function filter_print_styles_array( $handles ) {
+ $handles = $this->filter_print_dependency_array( $handles, 'styles' );
+ return $handles;
+ }
+
+ /**
+ * @filter print_scripts_array
+ * @return array
+ * @since 1.0
+ */
+ public function filter_print_scripts_array( $handles ) {
+ $handles = $this->filter_print_dependency_array( $handles, 'scripts' );
+ return $handles;
+ }
+
+ /**
+ * Separate external from internal (local) dependencies and then group the
+ * internal resources into maximal groups.
+ * @param array $handles
+ * @param string $type (scripts|styles)
+ * @return array
+ */
+ public function filter_print_dependency_array( array $handles, $type ) {
+ assert( in_array($type, array( 'scripts', 'styles' ) ) );
+ assert( isset($GLOBALS["wp_{$type}"]) );
+ $wp_deps = &$GLOBALS["wp_{$type}"];
+ assert( is_a($wp_deps, 'WP_Dependencies') );
+
+ /**
+ * Determine if minification is enabled for the provided $handles.
+ * Note that we cannot use the $concatenate_scripts global set by script_concat_settings
+ * because it is intended to only be used in the WP Admin
+ * Plugin is automatically disabled if pretty permalinks is not activated
+ */
+ $disabled = Dependency_Minification::$options['disable_if_wp_debug'] ? ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : false;
+ $disabled = $disabled || ( defined( 'DEPENDENCY_MINIFICATION_DEFAULT_DISABLED' ) && DEPENDENCY_MINIFICATION_DEFAULT_DISABLED );
+ $disabled = apply_filters( 'dependency_minification_disabled', $disabled, $handles, $type );
+ $disabled = apply_filters( "dependency_minification_disabled_{$type}", $disabled, $handles );
+ $disabled = $disabled || empty( $GLOBALS['wp_rewrite']->permalink_structure );
+ if ( $disabled ) {
+ return $handles;
+ }
+
+ // @todo There should be a better way to determine which group we are in
+ $current_group = (int) $this->is_footer[$type]; // false => 0, true => 1
+
+ $handles_in_group = array();
+ foreach ( $handles as $handle ) {
+ $must_process_handle = (
+ $wp_deps->groups[$handle] === $current_group
+ ||
+ // Handle case where script is erroneously enqueued without in_footer=true (here's lookin at you, PollDaddy)
+ (
+ $wp_deps->groups[$handle] < $current_group
+ &&
+ ! in_array( $handle, $wp_deps->done )
+ )
+ );
+
+ if ( $must_process_handle ) {
+ $handles_in_group[] = $handle;
+ }
+ }
+
+ $filtered_handles = array();
+ $groups = $this->group_dependencies_by_exclusion( $handles_in_group, $wp_deps );
+
+ foreach ( $groups as $group ) {
+ // $internal_groups as $extra => $handles_in_group
+ if ( empty( $group['handles'] ) ) {
+ continue;
+ }
+
+ if ( $group['excluded'] ) {
+ $filtered_handles = array_merge( $filtered_handles, $group['handles'] );
+ continue;
+ }
+ $extra = empty( $group['extra'] ) ? array() : $group['extra'];
+
+ $deps = array();
+ foreach ( $group['handles'] as $handle ) {
+ $deps[] = array(
+ 'handle' => $handle,
+ 'src' => $wp_deps->registered[$handle]->src,
+ 'ver' => $wp_deps->registered[$handle]->ver,
+ );
+ }
+
+ $srcs = wp_list_pluck( $deps, 'src' );
+ $vers = wp_list_pluck( $deps, 'ver' );
+
+ $src_hash = DepMin_hash_array( $srcs );
+ $ver_hash = DepMin_hash_array( $vers );
+
+ $cached_ver_hash = null;
+ $cached = DepMin_Cache::get( DepMin_Cache::get_key( $src_hash ) );
+
+ if ( ! empty( $cached['deps'] ) ) {
+ $cached_ver_hash = DepMin_hash_array( wp_list_pluck( $cached['deps'], 'ver' ) );
+ }
+
+ $is_error = (
+ ! empty( $cached['error'] )
+ &&
+ $ver_hash === $cached_ver_hash
+ &&
+ time() < $cached['expires']
+ );
+
+ $is_stale = (
+ empty( $cached )
+ ||
+ time() > $cached['expires']
+ ||
+ $ver_hash !== $cached_ver_hash
+ );
+
+ if ( $is_error ) {
+ if ( Dependency_Minification::$options['show_error_messages'] ) {
+ print "\n\n" );
+ }
+ $filtered_handles = array_merge( $filtered_handles, $group['handles'] );
+ } elseif ( $is_stale ) {
+ printf( "\n\n" );
+
+ // @todo We could store the info in the option, and just pass the cache key to the cron; this would allow reliable passing of request_uri
+
+ $scheduled = time();
+ $args = array_merge(
+ array(
+ 'expires' => false,
+ 'last_modified' => false,
+ 'etag' => false,
+ 'unminified_size' => false,
+ 'contents' => false,
+ 'pending' => true,
+ ),
+ compact( 'type', 'deps' )
+ );
+
+ wp_schedule_single_event( $scheduled, DepMin_Minify::CRON_ACTION, array( $args ) );
+ // The bundle is not ready yet, so re-use the existing dependencies
+ $filtered_handles = array_merge( $filtered_handles, $group['handles'] );
+ } else {
+ $this->minified_count += 1;
+ $new_handle = sprintf( 'minified-%d', $this->minified_count );
+ $filtered_handles[] = $new_handle;
+ $src = DepMin_Minify::get_minified_dependency_url( $deps, $type );
+
+ // Deps are registered without versions since the URL includes the version (ver_hash)
+ if ( 'scripts' === $type ) {
+ $in_footer = ! empty( $extra['group'] ); // @todo what if the group is not 0 or 1?
+ wp_register_script( $new_handle, $src, array(), null, $in_footer );
+ } elseif ( 'styles' === $type ) {
+ wp_register_style( $new_handle, $src, array(), null, $extra['media'] );
+ }
+ $wp_deps->set_group( $new_handle, /*recursive*/false, $current_group );
+ $new_dep = $wp_deps->registered[$new_handle];
+ $new_extra = array(
+ 'data' => '',
+ );
+ foreach ( $group['handles'] as $handle ) {
+
+ // Aggregate data from scripts (e.g. wp_localize_script)
+ if ( ! empty( $wp_deps->registered[$handle]->extra ) ) {
+
+ foreach ( array_keys( $wp_deps->registered[$handle]->extra ) as $extra_key ) {
+ $data = $wp_deps->get_data( $handle, $extra_key );
+
+ if ( 'data' === $extra_key ) {
+ $new_extra['data'] .= "/* wp_localize_script($handle): */\n";
+ $new_extra['data'] .= "$data\n\n";
+ } else {
+ if ( isset( $new_extra[$extra_key] ) ) {
+ // The handles should have been grouped so that they have the same extras
+ assert( $new_extra[$extra_key] === $data );
+ }
+ $new_extra[$extra_key] = $data;
+ }
+ }
+ }
+
+ // Mark the handles as done for the resources that have been minified
+ $wp_deps->done[] = $handle;
+ }
+
+ // Add aggregated extra to new dependency
+ foreach ( $new_extra as $key => $value ) {
+ $new_dep->add_data( $key, $value );
+ }
+ }
+ }
+
+ // @todo Must be a better way to do this
+ $this->is_footer[$type] = true; // for the next invocation
+
+ return $filtered_handles;
+
+ }
+
+ /**
+ * @param array $handles
+ * @param WP_Dependencies $wp_deps
+ * @return array Two members, the 1st containing external handles and the 2nd containing internal handles
+ */
+ public function group_dependencies_by_exclusion( $handles, WP_Dependencies $wp_deps ) {
+ $groups = array();
+
+ // First create groups based on whether they are excluded from minification
+ $last_was_excluded = null;
+ foreach ( $handles as $handle ) {
+ $src = $wp_deps->registered[$handle]->src;
+ $is_local = DepMin_is_self_hosted_src( $src );
+ $is_excluded = ! $is_local && Dependency_Minification::$options['default_exclude_remote_dependencies'];
+ $is_excluded = $is_excluded || $this->is_url_included( $src, Dependency_Minification::$options['exclude_dependencies'] );
+ $is_excluded = apply_filters( 'dependency_minification_excluded', $is_excluded, $handle, $src );
+
+ if ( $last_was_excluded !== $is_excluded ) {
+ $groups[] = array(
+ 'excluded' => $is_excluded,
+ 'handles' => array(),
+ );
+ }
+ $groups[ count( $groups ) - 1 ]['handles'][] = $handle;
+ $last_was_excluded = $is_excluded;
+ }
+
+ // Now divide up the groups to create bundles that share the same extras (e.g. stylesheet media or conditional)
+ $bundled_groups = array();
+ foreach ( $groups as $group ) {
+ if ( $group['excluded'] ) {
+ $bundled_groups[] = $group;
+ } else {
+ $handles_bundles = $this->group_handles_by_extra( $group['handles'], $wp_deps );
+ foreach ( $handles_bundles as $extra => $handles_bundle ) {
+ $bundled_groups[] = array(
+ 'excluded' => false,
+ 'extra' => unserialize( $extra ),
+ 'handles' => $handles_bundle,
+ );
+ }
+ }
+ }
+
+ return $bundled_groups;
+ }
+
+ /**
+ * @todo This is only applicable for styles, right? The media and conditional extras.
+ * @param array $handles
+ * @param WP_Dependencies $wp_deps
+ * @return array Associative array where the keys are the args and extras
+ */
+ public function group_handles_by_extra( array $handles, WP_Dependencies $wp_deps ) {
+ $bundles = array();
+ foreach ( $handles as $handle ) {
+ $dep = &$wp_deps->registered[$handle];
+ $extra = (array) $dep->extra;
+ if ( is_a( $wp_deps, 'WP_Styles' ) ) {
+ $extra['media'] = is_string( $dep->args ) ? $dep->args : 'all';
+ }
+ unset($extra['suffix']);
+ unset($extra['rtl']);
+ unset($extra['data']);
+ // Default scripts are not assigned 'group', so we use the original 'deps->args' value
+ if ( is_a( $wp_deps, 'WP_Scripts' ) && empty( $extra['group'] ) && is_int( $dep->args ) ) {
+ $extra['group'] = $dep->args;
+ }
+ ksort( $extra );
+ $key = serialize( $extra );
+ $bundles[$key][] = $handle;
+ }
+ return $bundles;
+ }
+
+ public function is_url_included( $needle, $haystack ) {
+ foreach ( $haystack as $entry ) {
+ if ( strpos( $needle, $entry ) !== false ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/inc/handler.php b/inc/handler.php
new file mode 100644
index 0000000..77b248c
--- /dev/null
+++ b/inc/handler.php
@@ -0,0 +1,132 @@
+getCode();
+ $message = $e->getMessage();
+ } else {
+ error_log(
+ sprintf(
+ '%s: %s via URI %s',
+ __METHOD__,
+ $e->getMessage(),
+ esc_url_raw( $_SERVER['REQUEST_URI'] )
+ )
+ );
+ $message = 'Unexpected error occurred.';
+ }
+ if ( empty($status) ) {
+ $status = 500;
+ }
+ status_header( $status );
+ nocache_headers();
+ header( 'Content-Type: text/plain' );
+ print $message; // xss ok
+ }
+ exit;
+ }
+
+}
\ No newline at end of file
diff --git a/inc/helpers.php b/inc/helpers.php
new file mode 100644
index 0000000..a06d687
--- /dev/null
+++ b/inc/helpers.php
@@ -0,0 +1,220 @@
+set_url( $url );
+
+ if ( ! empty( $path ) )
+ $this->set_path( $path );
+
+ if ( empty( $url ) && empty( $path ) )
+ throw new DepMin_Exception( 'Specific the file path or URL' );
+
+ }
+
+ /**
+ * @throw DepMin_Exception
+ * @return string|bool
+ * @since 1.0
+ */
+ public function is_self_hosted() {
+
+ $url = $this->get_url();
+
+ if ( ! empty( $url ) ) {
+
+ $parsed_url = parse_url( $url );
+
+ if ( empty( $parsed_url['host'] ) && substr( $parsed_url['path'], 0, 1 ) === '/' )
+ return true;
+
+ if ( ! empty( $parsed_url['host'] ) && $parsed_url['host'] === parse_url( get_site_url(), PHP_URL_HOST ) )
+ return true;
+
+ }
+
+ return false;
+ }
+
+ /**
+ * @throw DepMin_Exception
+ * @return string|bool
+ * @since 1.0
+ */
+ public function get_contents() {
+
+ $contents = false;
+ $url = $this->get_url();
+ $path = $this->get_path();
+
+ if ( ! empty( $path ) )
+ $contents = file_get_contents( $path );
+
+ if ( false === $contents && ! empty( $url ) ) {
+
+ $response = wp_remote_get( $url );
+
+ if ( is_wp_error( $response ) ) {
+
+ throw new DepMin_Exception(
+ sprintf( 'Failed to retrieve {%s}: %s',
+ $url,
+ $response->get_error_message()
+ )
+ );
+
+ } elseif ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
+
+ throw new DepMin_Exception(
+ sprintf( 'Request for %s returned with HTTP %d %s',
+ $url,
+ wp_remote_retrieve_response_code( $response ),
+ wp_remote_retrieve_response_message( $response )
+ )
+ );
+
+ }
+
+ $contents = wp_remote_retrieve_body( $response );
+
+ }
+
+ // Remove the BOM
+ if ( ! empty( $contents ) )
+ $contents = preg_replace( "/^\xEF\xBB\xBF/", '', $contents );
+
+ return $contents;
+ }
+
+ /**
+ * @throw DepMin_Exception
+ * @return void
+ * @since 1.0
+ */
+ protected function set_path( $path ) {
+
+ if ( ! file_exists( $path ) )
+ throw new DepMin_Exception( 'Invalid file path' );
+
+ $this->path = $path;
+
+ }
+
+ /**
+ * @throw DepMin_Exception
+ * @return void
+ * @since 1.0
+ */
+ protected function set_url( $url ) {
+
+ if ( ! DepMin_is_vaild_url( $url ) )
+ throw new DepMin_Exception( 'Invalid file URL' );
+
+ $this->url = $url;
+
+ }
+
+ /**
+ * @return string
+ * @since 1.0
+ */
+ public function get_path() {
+
+ if ( empty( $this->path ) && $this->is_self_hosted() ) {
+
+ $this->path = ltrim( parse_url( $this->url, PHP_URL_PATH ), '/' );
+ $this->path = path_join( $_SERVER['DOCUMENT_ROOT'], $this->path );
+
+ }
+
+ return $this->path;
+ }
+
+ /**
+ * @return string
+ * @since 1.0
+ */
+ public function get_url() {
+ return $this->url;
+ }
+
+}
+
+/**
+ * URL Validation.
+ *
+ * @param string URL to be validated
+ * @return bool Validation result
+ */
+function DepMin_is_vaild_url( $url ) {
+ return (bool) filter_var( utf8_uri_encode( $url ), FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED + FILTER_FLAG_HOST_REQUIRED );
+}
+
+/**
+ * @var bool
+ * @since 1.0
+ */
+function DepMin_is_frontend() {
+
+ return ! (
+ is_admin()
+ ||
+ in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) )
+ );
+
+}
+
+/**
+ * @var string
+ * @since 1.0
+ */
+function DepMin_hash_array( array $r ) {
+ return md5( serialize( $r ) );
+}
+
+/**
+ * @var bool
+ * @since 1.0
+ */
+function DepMin_is_self_hosted_src( $src ) {
+
+ $parsed_url = parse_url( $src );
+ return (
+ (
+ empty( $parsed_url['host'] )
+ &&
+ substr( $parsed_url['path'], 0, 1 ) === '/'
+ )
+ ||
+ (
+ ! empty( $parsed_url['host'] )
+ &&
+ $parsed_url['host'] === parse_url( get_home_url(), PHP_URL_HOST )
+ )
+ );
+
+}
\ No newline at end of file
diff --git a/inc/minifier.php b/inc/minifier.php
new file mode 100644
index 0000000..65d0ffb
--- /dev/null
+++ b/inc/minifier.php
@@ -0,0 +1,361 @@
+args = wp_parse_args( $args, array(
+ // @TODO: Add more minification options.
+ 'type' => '', // 'styles' or 'scripts'
+ ) );
+
+ return $this;
+
+ }
+
+ /**
+ * @param DepMin_SrcInfo[] $srcs
+ * @return DepMin_Minifier
+ * @since 1.0
+ */
+ public function set_srcs( array $srcs ) {
+
+ foreach( $srcs as $src ) {
+
+ if ( ! is_a( $src, 'DepMin_SrcInfo' ) )
+ $src = new DepMin_SrcInfo( $src );
+
+ $this->srcs[]= $src;
+
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ * @since 1.0
+ */
+ public function get_args( $key = '' ) {
+
+ if ( ! empty( $key ) ) {
+
+ if ( isset( $this->args[ $key ] ) )
+ return $this->args[ $key ];
+
+ return false;
+
+ }
+
+ return $this->args;
+
+ }
+
+ /**
+ * @return DepMin_SrcInfo[]
+ * @since 1.0
+ */
+ public function get_srcs() {
+ return $this->srcs;
+ }
+
+ /**
+ * @throw DepMin_Exception
+ * @return array
+ * @since 1.0
+ */
+ abstract function minify();
+
+}
+
+/**
+ * @since 1.0
+ */
+class DepMin_Minifier_Default extends DepMin_Minifier {
+
+ /**
+ * @return string
+ * @since 1.0
+ */
+ private function get_unminified_contents() {
+
+ $i = 0;
+ $unminified = '';
+
+ foreach( $this->get_srcs() as $source ) {
+
+ $contents = $source->get_contents();
+
+ if ( 'styles' === $this->get_args( 'type' ) ) {
+
+ $dir_path = dirname( $source->get_path() );
+
+ // Rewrite relative paths in CSS.
+ if ( ! empty( $dir_path ) ) {
+ require_once Dependency_Minification::path( 'minify/CSS/UriRewriter.php' );
+ $contents = Minify_CSS_UriRewriter::rewrite( $contents, $dir_path );
+ }
+
+ }
+
+ $unminified .= $contents;
+
+ if ( $i < ( count( $this->get_srcs() ) - 1 ) ) {
+
+ /*
+ * @note
+ * Semicolon needed in case a file lacks trailing semicolon
+ * like `x = {a:1}` and the next file is IIFE (function(){}),
+ * then it would get combined as x={a:1}(function(){}) and attempt
+ * to pass the anonymous function into a function {a:1} which
+ * is of course an object and not a function. Culprit here
+ * is the comment-reply.js in WordPress.
+ */
+ switch( $this->get_args( 'type' ) ) {
+
+ case 'scripts':
+ $unminified .= "\n;;\n";
+ break;
+
+ case 'styles':
+ $unminified .= "\n\n";
+ break;
+
+ }
+
+ }
+
+ $i++;
+
+ }
+
+ return $unminified;
+ }
+
+ /**
+ * @return array
+ * @since 1.0
+ */
+ public function minify() {
+
+ $minified = '';
+ $unminified = $this->get_unminified_contents();
+
+ switch( $this->get_args( 'type' ) ) {
+
+ case 'styles':
+ require_once Dependency_Minification::path( 'minify/CSS/Compressor.php' );
+ $minified = Minify_CSS_Compressor::process( $unminified );
+ break;
+
+ case 'scripts':
+ require_once Dependency_Minification::path( 'minify/JS/JSMin.php' );
+ $minified = JSMin::minify( $unminified );
+ break;
+
+ }
+
+ return array(
+ 'contents' => $minified,
+ 'minified_size' => strlen( $minified ),
+ 'unminified_size' => strlen( $unminified ),
+ );
+
+ }
+
+}
+
+/**
+ * @since 1.0
+ */
+class DepMin_Minify {
+
+ /**
+ * @var string
+ * @since 1.0
+ */
+ const CRON_ACTION = 'minify_dependencies';
+
+ /**
+ * @return array|bool
+ * @since 1.0
+ */
+ public static function minify( array $srcs, $type ) {
+
+ $minifier = apply_filters( 'DepMin_minifier_class', 'DepMin_Minifier_Default' );
+
+ if ( ! empty( $minifier ) && class_exists( $minifier ) ) {
+
+ $minifier = new $minifier();
+ $minifier->set_srcs( $srcs )
+ ->set_args( array(
+ 'type' => $type,
+ ) );
+
+ return $minifier->minify();
+
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ * @since 1.0
+ */
+ public static function cron_action( $args = '' ) {
+
+ $args = wp_parse_args( $args, array(
+ 'unminified_size' => false,
+ 'minified_size' => false,
+ 'last_modified' => false,
+ 'contents' => false,
+ 'expires' => false,
+ 'pending' => true,
+ 'deps' => array(),
+ 'etag' => false,
+ 'type' => '',
+ ) );
+
+ $srcs = wp_list_pluck( $args['deps'], 'src' );
+ $vers = wp_list_pluck( $args['deps'], 'ver' );
+
+ $src_hash = DepMin_hash_array( $srcs );
+ $ver_hash = DepMin_hash_array( $vers );
+
+ try {
+
+ foreach( $srcs as &$src ) {
+
+ if ( ! preg_match( '|^(https?:)?//|', $src ) )
+ $src = site_url( $src );
+
+ }
+
+ if ( ( $minified = DepMin_Minify::minify( $srcs, $args['type'] ) ) ) {
+
+ $args['contents'] = "/*! This minified dependency bundle includes:\n";
+
+ foreach ( $srcs as $key => $src )
+ $args['contents'] .= sprintf( " * %02d. %s\n", $key + 1, $src );
+
+ $args['contents'] .= " */\n\n" . $minified['contents'];
+ $args['unminified_size'] = $minified['unminified_size'];
+ $args['minified_size'] = $minified['minified_size'];
+
+ }
+
+ $max_age = apply_filters( 'dependency_minification_cache_control_max_age',
+ (int) Dependency_Minification::$options['cache_control_max_age_cache'],
+ $srcs
+ );
+
+ $args['expires'] = time() + $max_age;
+ $args['error'] = null;
+
+ } catch ( Exception $e ) {
+
+ error_log(
+ sprintf(
+ '%s in %s: %s for srcs %s',
+ get_class( $e ),
+ __FUNCTION__,
+ $e->getMessage(),
+ implode( ',', $srcs )
+ )
+ );
+
+ $args['error'] = $e->getMessage();
+
+ $max_age = apply_filters( 'dependency_minification_cache_control_max_age_error',
+ (int) Dependency_Minification::$options['cache_control_max_age_error'],
+ $srcs
+ );
+
+ $args['expires'] = time() + $max_age;
+
+ }
+
+ $args['etag'] = implode( '.', array( $src_hash, $ver_hash ) );
+ $args['last_modified'] = time();
+ $args['pending'] = false;
+
+ DepMin_Cache::set( DepMin_Cache::get_key( $src_hash ), $args );
+
+ }
+
+ /**
+ * @param array $deps
+ * @param string $type (scripts or styles)
+ * @return string
+ * @since 1.0
+ */
+ public static function get_minified_dependency_url( array $deps, $type ) {
+ $srcs = wp_list_pluck( $deps, 'src' );
+ $vars = wp_list_pluck( $deps, 'ver' );
+ $handles = wp_list_pluck( $deps, 'handle' );
+
+ $src = trailingslashit( home_url( Dependency_Minification::$options['endpoint'] ) );
+ $src .= implode( '.', array(
+ implode( ',', $handles ),
+ DepMin_hash_array( $srcs ),
+ DepMin_hash_array( $vars ),
+ $type === 'scripts' ? 'js' : 'css',
+ ) );
+
+ return $src;
+ }
+
+ /**
+ * @return array
+ * @since 1.0
+ */
+ public static function get_pending_dependencies() {
+
+ $list = array();
+ foreach ( _get_cron_array() as $cron ) {
+ if ( isset( $cron[ self::CRON_ACTION ] ) ) {
+ foreach ( $cron[ self::CRON_ACTION ] as $event ) {
+
+ $data = reset( $event['args'] );
+
+ if ( ! empty( $data['pending'] ) )
+ $list[] = $data;
+
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * @return void
+ * @since 1.0
+ */
+ public static function hook_cron_action() {
+ add_action( self::CRON_ACTION, array( __CLASS__, 'cron_action' ) );
+ }
+
+}
\ No newline at end of file
diff --git a/inc/options.php b/inc/options.php
new file mode 100644
index 0000000..8e7d028
--- /dev/null
+++ b/inc/options.php
@@ -0,0 +1,84 @@
+get_options(), array(
+ 'endpoint' => '_minify',
+ 'default_exclude_remote_dependencies' => true,
+ 'cache_control_max_age_cache' => 2629743, // 1 month in seconds
+ 'cache_control_max_age_error' => 60 * 60, // 1 hour, to try minifying again
+ 'allow_not_modified_responses' => true, // only needs to be true if not Akamaized and max-age is short
+ 'admin_page_capability' => 'edit_theme_options',
+ 'show_error_messages' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
+ 'disable_if_wp_debug' => true,
+ 'exclude_dependencies' => array(),
+ 'disabled_on_conditions' => array(
+ 'all' => false,
+ 'loggedin' => false,
+ 'admin' => false,
+ 'queryvar' => false,
+ ),
+ ) );
+
+ $options = apply_filters( 'dependency_minification_options', $options );
+
+ parent::__construct( $options );
+
+ }
+
+ /**
+ * @access protected
+ * @return array
+ * @since 1.0
+ */
+ protected function get_options() {
+ return get_option( 'dependency_minification_options', array() );
+ }
+
+ /**
+ * @access protected
+ * @return bool
+ * @since 1.0
+ */
+ protected function set_options( array $options ) {
+ return update_option( 'dependency_minification_options', $options );
+ }
+
+ /*** ArrayObject Methods **************************************************/
+
+ public function exchangeArray( array $input ) {
+
+ if ( $this->set_options( $input ) )
+ return parent::exchangeArray( $input );
+
+ }
+
+ public function offsetSet( $index, $newval ) {
+
+ $options = $this->get_options();
+ $options[ $index ] = $newval;
+
+ if ( $this->set_options( $options ) )
+ parent::offsetSet( $index, $newval );
+
+ }
+
+ public function offsetUnset( $index ) {
+
+ $options = $this->get_options();
+ unset( $options[ $index ] );
+
+ if ( $this->set_options( $options ) )
+ parent::offsetUnset( $index );
+
+ }
+
+}
diff --git a/ruleset.xml b/ruleset.xml
new file mode 100644
index 0000000..50eddd7
--- /dev/null
+++ b/ruleset.xml
@@ -0,0 +1,8 @@
+
+
+ We want all WordPress standards but we want to exclude the vendor directories
+
+ /minify/*
+
+
+