diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh new file mode 100755 index 00000000..ce26ddc8 --- /dev/null +++ b/bin/install-wp-tests.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} + +tmpdir=${TMPDIR-/tmp} +tmpdir=${tmpdir%/} + +WP_TESTS_DIR=${WP_TESTS_DIR-$tmpdir/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$tmpdir/wordpress/} + + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then + WP_TESTS_TAG="tags/$WP_VERSION" +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi + +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + fi + + cd $WP_TESTS_DIR + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # TODO/FIXME: + # In msys/mingw case $WP_TESTS_DIR has the "wrong" style + # of path written in the config file, i.e.: + # + # ```php + # define( 'ABSPATH', '/c/Windows/Temp/wordpress/' ); + # ``` + # + # If that's happening to you, edit that file manually once after + # running this script and change it so it becomes: + # + # ```php + # define( 'ABSPATH', 'C:/Windows/Temp/wordpress/' ); + # ``` + # + # Running `php phpunit.phar` within your plugin directory should + # work as expected after that. + sed $ioption "s|dirname( __FILE__ ) . '/src/'|'$WP_CORE_DIR'|" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi +} + +install_db() { + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/bin/readme-windows.txt b/bin/readme-windows.txt new file mode 100644 index 00000000..cb09d12f --- /dev/null +++ b/bin/readme-windows.txt @@ -0,0 +1,23 @@ +C:\github\autoptimize>C:\PortableGit\bin\bash.exe bin/install-wp-tests.sh wordpress_test webodjel webber localhost 4.3.1 + +C:\PortableGit\bin\bash.exe install-wp-tests.sh + +Open `C:\Windows\Temp\wordpress-tests-lib\wp-tests-config.php` file and: + + # TODO/FIXME: + # In msys/mingw case $WP_TESTS_DIR has the "wrong" style + # of path written in the config file, i.e.: + # + # ```php + # define( 'ABSPATH', '/c/Windows/Temp/wordpress/' ); + # ``` + # + # If that's happening to you, edit that file manually once after + # running this script and change it so it becomes: + # + # ```php + # define( 'ABSPATH', 'C:/Windows/Temp/wordpress/' ); + # ``` + # + # Running `php phpunit.phar` within your plugin directory should + # work as expected after that. diff --git a/classes/autoptimizeCSSmin.php b/classes/autoptimizeCSSmin.php new file mode 100644 index 00000000..7c14c8a5 --- /dev/null +++ b/classes/autoptimizeCSSmin.php @@ -0,0 +1,57 @@ +minifier = new Autoptimize\tubalmartin\CssMin\Minifier( $raise_limits ); + } + + /** + * Runs the minifier on given string of $css. + * Returns the minified css. + * + * @param string $css CSS to minify. + * + * @return string + */ + public function run( $css ) + { + $result = $this->minifier->run( $css ); + + return $result; + } + + /** + * Static helper. + * + * @param string $css CSS to minify. + * + * @return string + */ + public static function minify( $css ) + { + $minifier = new self(); + + return $minifier->run( $css ); + } +} diff --git a/classes/autoptimizeCacheChecker.php b/classes/autoptimizeCacheChecker.php new file mode 100644 index 00000000..3f14c2a0 --- /dev/null +++ b/classes/autoptimizeCacheChecker.php @@ -0,0 +1,84 @@ + 0.5GB (size is filterable), if so, an option is set which controls showing an admin notice. + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +class autoptimizeCacheChecker +{ + const SCHEDULE_HOOK = 'ao_cachechecker'; + + public function __construct() + { + } + + public function run() + { + $this->add_hooks(); + } + + public function add_hooks() + { + if ( is_admin() ) { + add_action( 'plugins_loaded', array( $this, 'setup' ) ); + } + add_action( self::SCHEDULE_HOOK, array( $this, 'cronjob' ) ); + add_action( 'admin_notices', array( $this, 'show_admin_notice' ) ); + } + + public function setup() + { + $do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true ); + $schedule = wp_get_schedule( self::SCHEDULE_HOOK ); + $frequency = apply_filters( 'autoptimize_filter_cachecheck_frequency', 'daily' ); + if ( ! in_array( $frequency, array( 'hourly', 'daily', 'monthly' ) ) ) { + $frequency = 'daily'; + } + if ( $do_cache_check && ( ! $schedule || $schedule !== $frequency ) ) { + wp_schedule_event( time(), $frequency, self::SCHEDULE_HOOK ); + } elseif ( $schedule && ! $do_cache_check ) { + wp_clear_scheduled_hook( self::SCHEDULE_HOOK ); + } + } + + public function cronjob() + { + $max_size = (int) apply_filters( 'autoptimize_filter_cachecheck_maxsize', 536870912 ); + $do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true ); + $stat_array = autoptimizeCache::stats(); + $cache_size = round( $stat_array[1] ); + if ( ( $cache_size > $max_size ) && ( $do_cache_check ) ) { + update_option( 'autoptimize_cachesize_notice', true ); + if ( apply_filters( 'autoptimize_filter_cachecheck_sendmail', true ) ) { + $site_url = esc_url( site_url() ); + $ao_mailto = apply_filters( 'autoptimize_filter_cachecheck_mailto', get_option( 'admin_email', '' ) ); + + $ao_mailsubject = __( 'Autoptimize cache size warning', 'autoptimize' ) . ' (' . $site_url . ')'; + $ao_mailbody = __( 'Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at https://wordpress.org/plugins/autoptimize/faq/ to see how you can keep the cache size under control.', 'autoptimize' ) . ' (site: ' . $site_url . ')'; + + if ( ! empty( $ao_mailto ) ) { + $ao_mailresult = wp_mail( $ao_mailto, $ao_mailsubject, $ao_mailbody ); + if ( ! $ao_mailresult ) { + error_log( 'Autoptimize could not send cache size warning mail.' ); + } + } + } + } + } + + public function show_admin_notice() + { + if ( (bool) get_option( 'autoptimize_cachesize_notice', false ) ) { + echo '

'; + _e( 'Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at the Autoptimize FAQ to see how you can keep the cache size under control.', 'autoptimize' ); + echo '

'; + update_option( 'autoptimize_cachesize_notice', false ); + } + } +} diff --git a/classes/autoptimizeExtra.php b/classes/autoptimizeExtra.php new file mode 100644 index 00000000..8d20bd5a --- /dev/null +++ b/classes/autoptimizeExtra.php @@ -0,0 +1,390 @@ +fetch_options(); + } + + $this->options = $options; + } + + public function run() + { + if ( is_admin() ) { + add_action( 'admin_menu', array( $this, 'admin_menu' ) ); + add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) ); + } else { + $this->run_on_frontend(); + } + } + + protected function fetch_options() + { + $value = get_option( 'autoptimize_extra_settings' ); + if ( empty( $value ) ) { + // Fallback to returning defaults when no stored option exists yet. + $value = autoptimizeConfig::get_ao_extra_default_options(); + } + + return $value; + } + + public function disable_emojis() + { + // Removing all actions related to emojis! + remove_action( 'admin_print_styles', 'print_emoji_styles' ); + remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); + remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); + remove_action( 'wp_print_styles', 'print_emoji_styles' ); + remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); + remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); + remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); + + // Removes TinyMCE emojis. + add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) ); + + // Removes emoji dns-preftech. + add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 ); + } + + public function filter_disable_emojis_tinymce( $plugins ) + { + if ( is_array( $plugins ) ) { + return array_diff( $plugins, array( 'wpemoji' ) ); + } else { + return array(); + } + } + + public function filter_remove_qs( $src ) { + if ( strpos( $src, '?ver=' ) ) { + $src = remove_query_arg( 'ver', $src ); + } + + return $src; + } + + public function extra_async_js( $in ) + { + $exclusions = array(); + if ( ! empty( $in ) ) { + $exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' ); + } + + $settings = $this->options['autoptimize_extra_text_field_3']; + $async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' ); + $attr = apply_filters( 'autoptimize_filter_extra_async', 'async' ); + foreach ( $async as $k => $v ) { + $async[ $k ] = $attr; + } + + // Merge exclusions & asyncs in one array and return to AO API. + $merged = array_merge( $exclusions, $async ); + + return $merged; + } + + protected function run_on_frontend() + { + $options = $this->options; + + // Disable emojis if specified. + if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) { + $this->disable_emojis(); + } + + // Remove version query parameters. + if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) { + add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 ); + add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 ); + } + + // Async JS! + if ( ! empty( $options['autoptimize_extra_text_field_3'] ) ) { + add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 ); + } + + // Optimize google fonts! + if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) { + add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 ); + if ( '2' === $options['autoptimize_extra_radio_field_4'] ) { + add_filter( 'autoptimize_filter_css_removables', array( $this, 'filter_remove_gfonts' ), 10, 1 ); + } else { + add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 ); + add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 ); + } + } + + // Preconnect! + if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) { + add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 ); + } + } + + public function filter_remove_gfonts( $in ) + { + // Adds 'fonts.googleapis.com' to the `autoptimize_filter_css_removables` list. + return $in . ', fonts.googleapis.com'; + } + + public function filter_remove_emoji_dns_prefetch( $urls, $relation_type ) + { + $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' ); + + return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url ); + } + + public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type ) + { + return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' ); + } + + public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove ) + { + if ( 'dns-prefetch' === $relation_type ) { + $cnt = 0; + foreach ( $urls as $url ) { + if ( false !== strpos( $url, $url_to_remove ) ) { + unset( $urls[ $cnt ] ); + } + $cnt++; + } + } + + return $urls; + } + + public function filter_optimize_google_fonts( $in ) + { + // Extract fonts, partly based on wp rocket's extraction code. + $markup = preg_replace( '//Uis', '', $in ); + preg_match_all( '#])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches ); + + $fonts_collection = array(); + if ( ! $matches[2] ) { + return $in; + } + + // Store them in $fonts array. + $i = 0; + foreach ( $matches[2] as $font ) { + if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) { + // Get fonts name. + $font = str_replace( array( '%7C', '%7c' ), '|', $font ); + $font = explode( 'family=', $font ); + $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array(); + // Add font to $fonts[$i] but make sure not to pollute with an empty family! + $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) ); + if ( ! empty( $_thisfont ) ) { + $fonts_collection[ $i ]['fonts'] = $_thisfont; + // And add subset if any! + $subset = ( is_array( $font ) ) ? end( $font ) : ''; + if ( false !== strpos( $subset, 'subset=' ) ) { + $subset = explode( 'subset=', $subset ); + $fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] ); + } + } + // And remove Google Fonts. + $in = str_replace( $matches[0][ $i ], '', $in ); + } + $i++; + } + + $options = $this->options; + $fonts_markup = ''; + if ( '3' === $options['autoptimize_extra_radio_field_4'] ) { + // Aggregate & link! + $fonts_string = ''; + $subset_string = ''; + foreach ( $fonts_collection as $font ) { + $fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' ); + if ( ! empty( $font['subsets'] ) ) { + $subset_string .= implode( ',', $font['subsets'] ); + } + } + + if ( ! empty( $subset_string ) ) { + $fonts_string = $fonts_string . '#038;subset=' . $subset_string; + } + + $fonts_string = str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) ); + + if ( ! empty( $fonts_string ) ) { + $fonts_markup = ''; + } + } elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) { + // Aggregate & load async (webfont.js impl.)! + $fonts_array = array(); + foreach ( $fonts_collection as $_fonts ) { + if ( ! empty( $_fonts['subsets'] ) ) { + $_subset = implode( ',', $_fonts['subsets'] ); + foreach ( $_fonts['fonts'] as $key => $_one_font ) { + $_one_font = $_one_font . ':' . $_subset; + $_fonts['fonts'][ $key ] = $_one_font; + } + } + $fonts_array = array_merge( $fonts_array, $_fonts['fonts'] ); + } + + $fonts_markup = ''; + } + + // Replace back in markup. + $out = substr_replace( $in, $fonts_markup . 'options; + + // Get settings and store in array. + $preconns = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_2'] ) ) ); + $preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns ); + + // Walk array, extract domain and add to new array with crossorigin attribute. + foreach ( $preconns as $preconn ) { + $parsed = parse_url( $preconn ); + + if ( is_array( $parsed ) && empty( $parsed['scheme'] ) ) { + $domain = '//' . $parsed['host']; + } elseif ( is_array( $parsed ) ) { + $domain = $parsed['scheme'] . '://' . $parsed['host']; + } + + if ( ! empty( $domain ) ) { + $hint = array( 'href' => $domain ); + // Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set + // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed. + $crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) ); + if ( in_array( $domain, $crossorigins ) ) { + $hint['crossorigin'] = 'anonymous'; + } + $new_hints[] = $hint; + } + } + + // Merge in WP's preconnect hints. + if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) { + $hints = array_merge( $hints, $new_hints ); + } + + return $hints; + } + + public function filter_preconnect_google_fonts( $in ) + { + $in[] = 'https://fonts.gstatic.com'; + + if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) { + // Preconnect even more hosts for webfont.js! + $in[] = 'https://ajax.googleapis.com'; + $in[] = 'https://fonts.googleapis.com'; + } + + return $in; + } + + public function admin_menu() + { + add_submenu_page( null, 'autoptimize_extra', 'autoptimize_extra', 'manage_options', 'autoptimize_extra', array( $this, 'options_page' ) ); + register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' ); + } + + public function add_extra_tab( $in ) + { + $in = array_merge( $in, array( 'autoptimize_extra' => __( 'Extra', 'autoptimize' ) ) ); + + return $in; + } + + public function options_page() + { + // Working with actual option values from the database here. + // That way any saves are still processed as expected, but we can still + // override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom + // behavior being persisted in the DB even if save is done here. + $options = $this->fetch_options(); + $gfonts = $options['autoptimize_extra_radio_field_4']; + ?> + +
+

+ +
+ +

+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ >
+ >
+ >
+ >webfont.js', 'autoptimize' ); ?>
+
(advanced users)', 'autoptimize' ); ?> + +
(advanced users)', 'autoptimize' ); ?> + '> +
+ async flag. JS-files from your own site will be automatically excluded if added here.', 'autoptimize' ); ?> +
+

+
+ version = $version; + $this->filepath = $filepath; + } + + public function run() + { + $this->add_hooks(); + + // Runs cache size checker. + $checker = new autoptimizeCacheChecker(); + $checker->run(); + } + + protected function add_hooks() + { + add_action( 'plugins_loaded', array( $this, 'setup' ) ); + + add_action( 'autoptimize_setup_done', array( $this, 'version_upgrades_check' ) ); + add_action( 'autoptimize_setup_done', array( $this, 'check_cache_and_run' ) ); + add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_ao_extra' ) ); + add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_partners_tab' ) ); + + add_action( 'init', array( $this, 'load_textdomain' ) ); + + register_activation_hook( $this->filepath, array( $this, 'on_activate' ) ); + } + + public function on_activate() + { + register_uninstall_hook( $this->filepath, array( $this, 'on_uninstall' ) ); + } + + public function load_textdomain() + { + load_plugin_textdomain( 'autoptimize' ); + } + + public function setup() + { + // Do we gzip in php when caching or is the webserver doing it? + define( 'AUTOPTIMIZE_CACHE_NOGZIP', (bool) get_option( 'autoptimize_cache_nogzip' ) ); + + // These can be overridden by specifying them in wp-config.php or such. + if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_NAME' ) ) { + define( 'AUTOPTIMIZE_WP_CONTENT_NAME', '/' . wp_basename( WP_CONTENT_DIR ) ); + } + if ( ! defined( 'AUTOPTIMIZE_CACHE_CHILD_DIR' ) ) { + define( 'AUTOPTIMIZE_CACHE_CHILD_DIR', '/cache/autoptimize/' ); + } + if ( ! defined( 'AUTOPTIMIZE_CACHEFILE_PREFIX' ) ) { + define( 'AUTOPTIMIZE_CACHEFILE_PREFIX', 'autoptimize_' ); + } + if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) { + if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) { + $blog_id = get_current_blog_id(); + define( 'AUTOPTIMIZE_CACHE_DIR', WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR . $blog_id . '/' ); + } else { + define( 'AUTOPTIMIZE_CACHE_DIR', WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR ); + } + } + + define( 'WP_ROOT_DIR', substr( WP_CONTENT_DIR, 0, strlen( WP_CONTENT_DIR ) - strlen( AUTOPTIMIZE_WP_CONTENT_NAME ) ) ); + + if ( ! defined( 'AUTOPTIMIZE_WP_SITE_URL' ) ) { + if ( function_exists( 'domain_mapping_siteurl' ) ) { + define( 'AUTOPTIMIZE_WP_SITE_URL', domain_mapping_siteurl( get_current_blog_id() ) ); + } else { + define( 'AUTOPTIMIZE_WP_SITE_URL', site_url() ); + } + } + if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_URL' ) ) { + if ( function_exists( 'domain_mapping_siteurl' ) ) { + define( 'AUTOPTIMIZE_WP_CONTENT_URL', str_replace( get_original_url( AUTOPTIMIZE_WP_SITE_URL ), AUTOPTIMIZE_WP_SITE_URL, content_url() ) ); + } else { + define( 'AUTOPTIMIZE_WP_CONTENT_URL', content_url() ); + } + } + if ( ! defined( 'AUTOPTIMIZE_CACHE_URL' ) ) { + if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) { + $blog_id = get_current_blog_id(); + define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR . $blog_id . '/' ); + } else { + define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR ); + } + } + if ( ! defined( 'AUTOPTIMIZE_WP_ROOT_URL' ) ) { + define( 'AUTOPTIMIZE_WP_ROOT_URL', str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', AUTOPTIMIZE_WP_CONTENT_URL ) ); + } + if ( ! defined( 'AUTOPTIMIZE_HASH' ) ) { + define( 'AUTOPTIMIZE_HASH', wp_hash( AUTOPTIMIZE_CACHE_URL ) ); + } + + do_action( 'autoptimize_setup_done' ); + } + + /** + * Checks if there's a need to upgrade/update options and whatnot, + * in which case we might need to do stuff and flush the cache + * to avoid old versions of aggregated files lingering around. + */ + public function version_upgrades_check() + { + autoptimizeVersionUpdatesHandler::check_installed_and_update( $this->version ); + } + + public function check_cache_and_run() + { + if ( autoptimizeCache::cacheavail() ) { + $conf = autoptimizeConfig::instance(); + if ( $conf->get( 'autoptimize_html' ) || $conf->get( 'autoptimize_js' ) || $conf->get( 'autoptimize_css' ) ) { + // Hook into WordPress frontend. + if ( defined( 'AUTOPTIMIZE_INIT_EARLIER' ) ) { + add_action( + 'init', + array( $this, 'start_buffering' ), + self::INIT_EARLIER_PRIORITY + ); + } else { + if ( ! defined( 'AUTOPTIMIZE_HOOK_INTO' ) ) { + define( 'AUTOPTIMIZE_HOOK_INTO', 'template_redirect' ); + } + add_action( + constant( 'AUTOPTIMIZE_HOOK_INTO' ), + array( $this, 'start_buffering' ), + self::DEFAULT_HOOK_PRIORITY + ); + } + } + } else { + add_action( 'admin_notices', 'autoptimizeMain::notice_cache_unavailable' ); + } + } + + public function maybe_run_ao_extra() + { + if ( apply_filters( 'autoptimize_filter_extra_activate', true ) ) { + include AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizeExtra.php'; + $ao_extra = new autoptimizeExtra(); + $ao_extra->run(); + } + } + + public function maybe_run_partners_tab() + { + // Loads partners tab code if in admin (and not in admin-ajax.php)! + if ( autoptimizeConfig::is_admin_and_not_ajax() ) { + include AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizePartners.php'; + new autoptimizePartners(); + } + } + + /** + * Setup output buffering if needed. + * + * @return void + */ + public function start_buffering() + { + if ( $this->should_buffer() ) { + + // Load speedupper conditionally (true by default). + if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) { + $ao_speedupper = new autoptimizeSpeedupper(); + } + + $conf = autoptimizeConfig::instance(); + + if ( $conf->get( 'autoptimize_js' ) ) { + if ( ! defined( 'CONCATENATE_SCRIPTS' ) ) { + define( 'CONCATENATE_SCRIPTS', false ); + } + if ( ! defined( 'COMPRESS_SCRIPTS' ) ) { + define( 'COMPRESS_SCRIPTS', false ); + } + } + + if ( $conf->get( 'autoptimize_css' ) ) { + if ( ! defined( 'COMPRESS_CSS' ) ) { + define( 'COMPRESS_CSS', false ); + } + } + + if ( apply_filters( 'autoptimize_filter_obkiller', false ) ) { + while ( ob_get_level() > 0 ) { + ob_end_clean(); + } + } + + // Now, start the real thing! + ob_start( array( $this, 'end_buffering' ) ); + } + } + + /** + * Returns true if all the conditions to start output buffering are satisfied. + * + * @param bool $doing_tests Allows overriding the optimization of only + * deciding once per request (for use in tests). + * @return bool + */ + public function should_buffer( $doing_tests = false ) + { + static $do_buffering = null; + + // Only check once in case we're called multiple times by others but + // still allows multiple calls when doing tests. + if ( null === $do_buffering || $doing_tests ) { + + $ao_noptimize = false; + + // Checking for DONOTMINIFY constant as used by e.g. WooCommerce POS. + if ( defined( 'DONOTMINIFY' ) && ( constant( 'DONOTMINIFY' ) === true || constant( 'DONOTMINIFY' ) === 'true' ) ) { + $ao_noptimize = true; + } + + // Skip checking query strings if they're disabled. + if ( apply_filters( 'autoptimize_filter_honor_qs_noptimize', true ) ) { + // Check for `ao_noptimize` (and other) keys in the query string + // to get non-optimized page for debugging. + $keys = array( + 'ao_noptimize', + 'ao_noptirocket', + ); + foreach ( $keys as $key ) { + if ( array_key_exists( $key, $_GET ) && '1' === $_GET[ $key ] ) { + $ao_noptimize = true; + break; + } + } + } + + // If setting says not to optimize logged in user and user is logged in... + if ( 'on' !== get_option( 'autoptimize_optimize_logged', 'on' ) && is_user_logged_in() && current_user_can( 'edit_posts' ) ) { + $ao_noptimize = true; + } + + // If setting says not to optimize cart/checkout. + if ( 'on' !== get_option( 'autoptimize_optimize_checkout', 'on' ) ) { + // Checking for woocommerce, easy digital downloads and wp ecommerce... + foreach ( array( 'is_checkout', 'is_cart', 'edd_is_checkout', 'wpsc_is_cart', 'wpsc_is_checkout' ) as $func ) { + if ( function_exists( $func ) && $func() ) { + $ao_noptimize = true; + break; + } + } + } + + // Allows blocking of autoptimization on your own terms regardless of above decisions. + $ao_noptimize = (bool) apply_filters( 'autoptimize_filter_noptimize', $ao_noptimize ); + + // Check for site being previewed in the Customizer (available since WP 4.0). + $is_customize_preview = false; + if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) { + $is_customize_preview = is_customize_preview(); + } + + /** + * We only buffer the frontend requests (and then only if not a feed + * and not turned off explicitly and not when being previewed in Customizer)! + * NOTE: Tests throw a notice here due to is_feed() being called + * while the main query hasn't been ran yet. Thats why we use + * AUTOPTIMIZE_INIT_EARLIER in tests. + */ + $do_buffering = ( ! is_admin() && ! is_feed() && ! $ao_noptimize && ! $is_customize_preview ); + } + + return $do_buffering; + } + + /** + * Returns true if given markup is considered valid/processable/optimizable. + * + * @param string $content Markup. + * + * @return bool + */ + public function is_valid_buffer( $content ) + { + // Defaults to true. + $valid = true; + + $has_no_html_tag = ( false === stripos( $content, '/i', $content ) > 0 ); + + if ( $has_no_html_tag ) { + // Can't be valid amp markup without an html tag preceding it. + $is_amp_markup = false; + } else { + $is_amp_markup = self::is_amp_markup( $content ); + } + + // If it's not html, or if it's amp or contains xsl stylesheets we don't touch it. + if ( $has_no_html_tag && ! $has_html5_doctype || $is_amp_markup || $has_xsl_stylesheet ) { + $valid = false; + } + + return $valid; + } + + /** + * Returns true if given $content is considered to be AMP markup. + * This is far from actual validation against AMP spec, but it'll do for now. + * + * @param string $content Markup to check. + * + * @return bool + */ + public static function is_amp_markup( $content ) + { + $is_amp_markup = preg_match( '/]*(?:amp|⚡)/i', $content ); + + return (bool) $is_amp_markup; + } + + /** + * Processes/optimizes the output-buffered content and returns it. + * If the content is not processable, it is returned unmodified. + * + * @param string $content Buffered content. + * + * @return string + */ + public function end_buffering( $content ) + { + // Bail early without modifying anything if we can't handle the content. + if ( ! $this->is_valid_buffer( $content ) ) { + return $content; + } + + $conf = autoptimizeConfig::instance(); + + // Determine what needs to be ran. + $classes = array(); + if ( $conf->get( 'autoptimize_js' ) ) { + $classes[] = 'autoptimizeScripts'; + } + if ( $conf->get( 'autoptimize_css' ) ) { + $classes[] = 'autoptimizeStyles'; + } + if ( $conf->get( 'autoptimize_html' ) ) { + $classes[] = 'autoptimizeHTML'; + } + + $classoptions = array( + 'autoptimizeScripts' => array( + 'aggregate' => $conf->get( 'autoptimize_js_aggregate' ), + 'justhead' => $conf->get( 'autoptimize_js_justhead' ), + 'forcehead' => $conf->get( 'autoptimize_js_forcehead' ), + 'trycatch' => $conf->get( 'autoptimize_js_trycatch' ), + 'js_exclude' => $conf->get( 'autoptimize_js_exclude' ), + 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ), + 'include_inline' => $conf->get( 'autoptimize_js_include_inline' ), + ), + 'autoptimizeStyles' => array( + 'aggregate' => $conf->get( 'autoptimize_css_aggregate' ), + 'justhead' => $conf->get( 'autoptimize_css_justhead' ), + 'datauris' => $conf->get( 'autoptimize_css_datauris' ), + 'defer' => $conf->get( 'autoptimize_css_defer' ), + 'defer_inline' => $conf->get( 'autoptimize_css_defer_inline' ), + 'inline' => $conf->get( 'autoptimize_css_inline' ), + 'css_exclude' => $conf->get( 'autoptimize_css_exclude' ), + 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ), + 'include_inline' => $conf->get( 'autoptimize_css_include_inline' ), + 'nogooglefont' => $conf->get( 'autoptimize_css_nogooglefont' ), + ), + 'autoptimizeHTML' => array( + 'keepcomments' => $conf->get( 'autoptimize_html_keepcomments' ), + ), + ); + + $content = apply_filters( 'autoptimize_filter_html_before_minify', $content ); + + // Run the classes! + foreach ( $classes as $name ) { + $instance = new $name( $content ); + if ( $instance->read( $classoptions[ $name ] ) ) { + $instance->minify(); + $instance->cache(); + $content = $instance->getcontent(); + } + unset( $instance ); + } + + $content = apply_filters( 'autoptimize_html_after_minify', $content ); + + return $content; + } + + public function on_uninstall() + { + autoptimizeCache::clearall(); + + $delete_options = array( + 'autoptimize_cache_clean', + 'autoptimize_cache_nogzip', + 'autoptimize_css', + 'autoptimize_css_datauris', + 'autoptimize_css_justhead', + 'autoptimize_css_defer', + 'autoptimize_css_defer_inline', + 'autoptimize_css_inline', + 'autoptimize_css_exclude', + 'autoptimize_html', + 'autoptimize_html_keepcomments', + 'autoptimize_js', + 'autoptimize_js_exclude', + 'autoptimize_js_forcehead', + 'autoptimize_js_justhead', + 'autoptimize_js_trycatch', + 'autoptimize_version', + 'autoptimize_show_adv', + 'autoptimize_cdn_url', + 'autoptimize_cachesize_notice', + 'autoptimize_css_include_inline', + 'autoptimize_js_include_inline', + 'autoptimize_optimize_logged', + 'autoptimize_optimize_checkout', + 'autoptimize_extra_settings', + ); + + if ( ! is_multisite() ) { + foreach ( $delete_options as $del_opt ) { + delete_option( $del_opt ); + } + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + foreach ( $delete_options as $del_opt ) { + delete_option( $del_opt ); + } + } + switch_to_blog( $original_blog_id ); + } + + if ( wp_get_schedule( 'ao_cachechecker' ) ) { + wp_clear_scheduled_hook( 'ao_cachechecker' ); + } + } + + public static function notice_cache_unavailable() + { + echo '

'; + // Translators: %s is the cache directory location. + printf( __( 'Autoptimize cannot write to the cache directory (%s), please fix to enable CSS/ JS optimization!', 'autoptimize' ), AUTOPTIMIZE_CACHE_DIR ); + echo '

'; + } + + public static function notice_installed() + { + echo '

'; + _e( 'Thank you for installing and activating Autoptimize. Please configure it under "Settings" -> "Autoptimize" to start improving your site\'s performance.', 'autoptimize' ); + echo '

'; + } + + public static function notice_updated() + { + echo '

'; + _e( 'Autoptimize has just been updated. Please test your site now and adapt Autoptimize config if needed.', 'autoptimize' ); + echo '

'; + } + +} diff --git a/classes/autoptimizePartners.php b/classes/autoptimizePartners.php new file mode 100644 index 00000000..9304a5f7 --- /dev/null +++ b/classes/autoptimizePartners.php @@ -0,0 +1,144 @@ +run(); + } + + public function run() + { + if ( $this->enabled() ) { + add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_partner_tabs' ), 10, 1 ); + } + add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); + } + + protected function enabled() + { + return apply_filters( 'autoptimize_filter_show_partner_tabs', true ); + } + + public function add_partner_tabs( $in ) + { + $in = array_merge( $in, array( + 'ao_partners' => __( 'Optimize More!', 'autoptimize' ), + ) ); + + return $in; + } + + public function add_admin_menu() + { + if ( $this->enabled() ) { + add_submenu_page( null, 'AO partner', 'AO partner', 'manage_options', 'ao_partners', array( $this, 'ao_partners_page' ) ); + } + } + + protected function get_ao_partner_feed_markup() { + $no_feed_text = __( 'Have a look at optimizingmatters.com for Autoptimize power-ups!', 'autoptimize' ); + $output = ''; + if ( apply_filters( 'autoptimize_settingsscreen_remotehttp', true ) ) { + $rss = fetch_feed( 'http://feeds.feedburner.com/OptimizingMattersDownloads' ); + $maxitems = 0; + + if ( ! is_wp_error( $rss ) ) { + $maxitems = $rss->get_item_quantity( 20 ); + $rss_items = $rss->get_items( 0, $maxitems ); + } + + if ( 0 == $maxitems ) { + $output .= $no_feed_text; + } else { + $output .= '
    '; + foreach ( $rss_items as $item ) { + $item_url = esc_url( $item->get_permalink() ); + $enclosure = $item->get_enclosure(); + + $output .= '
  • '; + $output .= '

    ' . esc_html( $item->get_title() ) . '

    '; + + if ( $enclosure && ( false !== strpos( $enclosure->get_type(), 'image' ) ) ) { + $img_url = esc_url( $enclosure->get_link() ); + $output .= '
    '; + } + + $output .= '
    ' . wp_kses_post( $item->get_description() ) . '
    '; + $output .= ''; + $output .= '
  • '; + } + $output .= '
'; + } + } else { + $output .= $no_feed_text; + } + + return $output; + } + + public function ao_partners_page() + { +?> + +
+

+ + ' . __( "These Autoptimize power-ups and related services will improve your site's performance even more!", 'autoptimize' ) . ''; ?> +
+ get_ao_partner_feed_markup(); ?> +
+
+add_hooks(); + } + + public function add_hooks() + { + if ( apply_filters( 'autoptimize_js_do_minify', true ) ) { + add_filter( 'autoptimize_js_individual_script', array( $this, 'js_snippetcacher' ), 10, 2 ); + add_filter( 'autoptimize_js_after_minify', array( $this, 'js_cleanup' ), 10, 1 ); + } + if ( apply_filters( 'autoptimize_css_do_minify', true ) ) { + add_filter( 'autoptimize_css_individual_style', array( $this, 'css_snippetcacher' ), 10, 2 ); + add_filter( 'autoptimize_css_after_minify', array( $this, 'css_cleanup' ), 10, 1 ); + } + } + + public function js_snippetcacher( $jsin, $jsfilename ) + { + $md5hash = 'snippet_' . md5( $jsin ); + $ccheck = new autoptimizeCache( $md5hash, 'js' ); + if ( $ccheck->check() ) { + $scriptsrc = $ccheck->retrieve(); + } else { + if ( false === ( strpos( $jsfilename, 'min.js' ) ) && ( false === strpos( $jsfilename, 'js/jquery/jquery.js' ) ) && ( str_replace( apply_filters( 'autoptimize_filter_js_consider_minified', false ), '', $jsfilename ) === $jsfilename ) ) { + $tmp_jscode = trim( JSMin::minify( $jsin ) ); + if ( ! empty( $tmp_jscode ) ) { + $scriptsrc = $tmp_jscode; + unset( $tmp_jscode ); + } else { + $scriptsrc = $jsin; + } + } else { + // Removing comments, linebreaks and stuff! + $scriptsrc = preg_replace( '#^\s*\/\/.*$#Um', '', $jsin ); + $scriptsrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $scriptsrc ); + $scriptsrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $scriptsrc ); + } + + $last_char = substr( $scriptsrc, -1, 1 ); + if ( ';' !== $last_char && '}' !== $last_char ) { + $scriptsrc .= ';'; + } + + if ( ! empty( $jsfilename ) && str_replace( apply_filters( 'autoptimize_filter_js_speedup_cache', false ), '', $jsfilename ) === $jsfilename ) { + // Don't cache inline CSS or if filter says no! + $ccheck->cache( $scriptsrc, 'text/javascript' ); + } + } + unset( $ccheck ); + + return $scriptsrc; + } + + public function css_snippetcacher( $cssin, $cssfilename ) + { + $md5hash = 'snippet_' . md5( $cssin ); + $ccheck = new autoptimizeCache( $md5hash, 'css' ); + if ( $ccheck->check() ) { + $stylesrc = $ccheck->retrieve(); + } else { + if ( ( false === strpos( $cssfilename, 'min.css' ) ) && ( str_replace( apply_filters( 'autoptimize_filter_css_consider_minified', false ), '', $cssfilename ) === $cssfilename ) ) { + $cssmin = new autoptimizeCSSmin(); + $tmp_code = trim( $cssmin->run( $cssin ) ); + + if ( ! empty( $tmp_code ) ) { + $stylesrc = $tmp_code; + unset( $tmp_code ); + } else { + $stylesrc = $cssin; + } + } else { + // .min.css -> no heavy-lifting, just some cleanup! + $stylesrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $cssin ); + $stylesrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $stylesrc ); + $stylesrc = autoptimizeStyles::fixurls( $cssfilename, $stylesrc ); + } + if ( ! empty( $cssfilename ) && ( str_replace( apply_filters( 'autoptimize_filter_css_speedup_cache', false ), '', $cssfilename ) === $cssfilename ) ) { + // Only caching CSS if it's not inline and is allowed by filter! + $ccheck->cache( $stylesrc, 'text/css' ); + } + } + unset( $ccheck ); + + return $stylesrc; + } + + public function css_cleanup( $cssin ) + { + // Speedupper results in aggregated CSS not being minified, so the filestart-marker AO adds when aggregating needs to be removed. + return trim( str_replace( array( '/*FILESTART*/', '/*FILESTART2*/' ), '', $cssin ) ); + } + + public function js_cleanup( $jsin ) + { + return trim( $jsin ); + } +} diff --git a/classes/autoptimizeVersionUpdatesHandler.php b/classes/autoptimizeVersionUpdatesHandler.php new file mode 100644 index 00000000..5c1bc4c8 --- /dev/null +++ b/classes/autoptimizeVersionUpdatesHandler.php @@ -0,0 +1,206 @@ +current_major_version = substr( $current_version, 0, 3 ); + } + + /** + * Runs all needed upgrade procedures (depending on the + * current major version specified during class instantiation) + */ + public function run_needed_major_upgrades() + { + $major_update = false; + + switch ( $this->current_major_version ) { + case '1.6': + $this->upgrade_from_1_6(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + case '1.7': + $this->upgrade_from_1_7(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + case '1.9': + $this->upgrade_from_1_9(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + case '2.2': + $this->upgrade_from_2_2(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + } + + if ( true === $major_update ) { + $this->on_major_version_update(); + } + } + + /** + * Checks specified version against the one stored in the database under `autoptimize_version` and performs + * any major upgrade routines if needed. + * Updates the database version to the specified $target if it's different to the one currently stored there. + * + * @param string $target Target version to check against (ie., the currently running one). + */ + public static function check_installed_and_update( $target ) + { + $db_version = get_option( 'autoptimize_version', 'none' ); + if ( $db_version !== $target ) { + if ( 'none' === $db_version ) { + add_action( 'admin_notices', 'autoptimizeMain::notice_installed' ); + } else { + $updater = new self( $db_version ); + $updater->run_needed_major_upgrades(); + } + + // Versions differed, upgrades happened if needed, store the new version. + update_option( 'autoptimize_version', $target ); + } + } + + /** + * Called after any major version update (and it's accompanying upgrade procedure) + * has happened. Clears cache and sets an admin notice. + */ + protected function on_major_version_update() + { + // The transients guard here prevents stale object caches from busting the cache on every request. + if ( false == get_transient( 'autoptimize_stale_option_buster' ) ) { + set_transient( 'autoptimize_stale_option_buster', 'Mamsie & Liessie zehhe: ZWIJH!', HOUR_IN_SECONDS ); + autoptimizeCache::clearall(); + add_action( 'admin_notices', 'autoptimizeMain::notice_updated' ); + } + } + + /** + * From back in the days when I did not yet consider multisite. + */ + private function upgrade_from_1_6() + { + // If user was on version 1.6.x, force advanced options to be shown by default. + update_option( 'autoptimize_show_adv', '1' ); + + // And remove old options. + $to_delete_options = array( + 'autoptimize_cdn_css', + 'autoptimize_cdn_css_url', + 'autoptimize_cdn_js', + 'autoptimize_cdn_js_url', + 'autoptimize_cdn_img', + 'autoptimize_cdn_img_url', + 'autoptimize_css_yui', + ); + foreach ( $to_delete_options as $del_opt ) { + delete_option( $del_opt ); + } + } + + /** + * Forces WP 3.8 dashicons in CSS exclude options when upgrading from 1.7 to 1.8 + * + * @global $wpdb + */ + private function upgrade_from_1_7() + { + if ( ! is_multisite() ) { + $css_exclude = get_option( 'autoptimize_css_exclude' ); + if ( empty( $css_exclude ) ) { + $css_exclude = 'admin-bar.min.css, dashicons.min.css'; + } elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) { + $css_exclude .= ', dashicons.min.css'; + } + update_option( 'autoptimize_css_exclude', $css_exclude ); + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + $css_exclude = get_option( 'autoptimize_css_exclude' ); + if ( empty( $css_exclude ) ) { + $css_exclude = 'admin-bar.min.css, dashicons.min.css'; + } elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) { + $css_exclude .= ', dashicons.min.css'; + } + update_option( 'autoptimize_css_exclude', $css_exclude ); + } + switch_to_blog( $original_blog_id ); + } + } + + /** + * 2.0 will not aggregate inline CSS/JS by default, but we want users + * upgrading from 1.9 to keep their inline code aggregated by default. + * + * @global $wpdb + */ + private function upgrade_from_1_9() + { + if ( ! is_multisite() ) { + update_option( 'autoptimize_css_include_inline', 'on' ); + update_option( 'autoptimize_js_include_inline', 'on' ); + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + update_option( 'autoptimize_css_include_inline', 'on' ); + update_option( 'autoptimize_js_include_inline', 'on' ); + } + switch_to_blog( $original_blog_id ); + } + } + + /** + * 2.3 has no "remove google fonts" in main screen, moved to "extra" + * + * @global $wpdb + */ + private function upgrade_from_2_2() + { + if ( ! is_multisite() ) { + $this->do_2_2_settings_update(); + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + $this->do_2_2_settings_update(); + } + switch_to_blog( $original_blog_id ); + } + } + + /** + * Helper for 2.2 autoptimize_extra_settings upgrade to avoid duplicate code + */ + private function do_2_2_settings_update() + { + $nogooglefont = get_option( 'autoptimize_css_nogooglefont', '' ); + $ao_extrasetting = get_option( 'autoptimize_extra_settings', '' ); + if ( ( $nogooglefont ) && ( empty( $ao_extrasetting ) ) ) { + update_option( 'autoptimize_extra_settings', autoptimizeConfig::get_ao_extra_default_options() ); + } + delete_option( 'autoptimize_css_nogooglefont' ); + } +} diff --git a/classes/external/php/jsmin.php b/classes/external/php/jsmin.php new file mode 100644 index 00000000..bb9e4e75 --- /dev/null +++ b/classes/external/php/jsmin.php @@ -0,0 +1,458 @@ + + * $minifiedJs = JSMin::minify($js); + * + * + * This is a modified port of jsmin.c. Improvements: + * + * Does not choke on some regexp literals containing quote characters. E.g. /'/ + * + * Spaces are preserved after some add/sub operators, so they are not mistakenly + * converted to post-inc/dec. E.g. a + ++b -> a+ ++b + * + * Preserves multi-line comments that begin with /*! + * + * PHP 5 or higher is required. + * + * Permission is hereby granted to use this version of the library under the + * same terms as jsmin.c, which has the following license: + * + * -- + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * -- + * + * @package JSMin + * @author Ryan Grove (PHP port) + * @author Steve Clay (modifications + cleanup) + * @author Andrea Giammarchi (spaceBeforeRegExp) + * @copyright 2002 Douglas Crockford (jsmin.c) + * @copyright 2008 Ryan Grove (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @link http://code.google.com/p/jsmin-php/ + */ + +// This is from https://github.com/mrclay/jsmin-php 2.3.2 + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + const ACTION_KEEP_A = 1; + const ACTION_DELETE_A = 2; + const ACTION_DELETE_A_B = 3; + + protected $a = "\n"; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; + protected $lastByteOut = ''; + protected $keptComment = ''; + + /** + * Minify Javascript. + * + * @param string $js Javascript to be minified + * + * @return string + */ + public static function minify($js) + { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + /** + * @param string $input + */ + public function __construct($input) + { + $this->input = $input; + } + + /** + * Perform minification, return result + * + * @return string + */ + public function min() + { + if ($this->output !== '') { // min already run + return $this->output; + } + + $mbIntEnc = null; + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('8bit'); + } + + if (isset($this->input[0]) && $this->input[0] === "\xef") { + $this->input = substr($this->input, 3); + } + + $this->input = str_replace("\r\n", "\n", $this->input); + $this->inputLength = strlen($this->input); + + $this->action(self::ACTION_DELETE_A_B); + + while ($this->a !== null) { + // determine next command + $command = self::ACTION_KEEP_A; // default + if ($this->a === ' ') { + if (($this->lastByteOut === '+' || $this->lastByteOut === '-') + && ($this->b === $this->lastByteOut)) { + // Don't delete this space. If we do, the addition/subtraction + // could be parsed as a post-increment + } elseif (! $this->isAlphaNum($this->b)) { + $command = self::ACTION_DELETE_A; + } + } elseif ($this->a === "\n") { + if ($this->b === ' ') { + $command = self::ACTION_DELETE_A_B; + + // in case of mbstring.func_overload & 2, must check for null b, + // otherwise mb_strpos will give WARNING + } elseif ($this->b === null + || (false === strpos('{[(+-!~', $this->b) + && ! $this->isAlphaNum($this->b))) { + $command = self::ACTION_DELETE_A; + } + } elseif (! $this->isAlphaNum($this->a)) { + if ($this->b === ' ' + || ($this->b === "\n" + && (false === strpos('}])+-"\'', $this->a)))) { + $command = self::ACTION_DELETE_A_B; + } + } + $this->action($command); + } + $this->output = trim($this->output); + + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } + return $this->output; + } + + /** + * ACTION_KEEP_A = Output A. Copy B to A. Get the next B. + * ACTION_DELETE_A = Copy B to A. Get the next B. + * ACTION_DELETE_A_B = Get the next B. + * + * @param int $command + * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException + */ + protected function action($command) + { + // make sure we don't compress "a + ++b" to "a+++b", etc. + if ($command === self::ACTION_DELETE_A_B + && $this->b === ' ' + && ($this->a === '+' || $this->a === '-')) { + // Note: we're at an addition/substraction operator; the inputIndex + // will certainly be a valid index + if ($this->input[$this->inputIndex] === $this->a) { + // This is "+ +" or "- -". Don't delete the space. + $command = self::ACTION_KEEP_A; + } + } + + switch ($command) { + case self::ACTION_KEEP_A: // 1 + $this->output .= $this->a; + + if ($this->keptComment) { + $this->output = rtrim($this->output, "\n"); + $this->output .= $this->keptComment; + $this->keptComment = ''; + } + + $this->lastByteOut = $this->a; + + // fallthrough intentional + case self::ACTION_DELETE_A: // 2 + $this->a = $this->b; + if ($this->a === "'" || $this->a === '"') { // string literal + $str = $this->a; // in case needed for exception + for(;;) { + $this->output .= $this->a; + $this->lastByteOut = $this->a; + + $this->a = $this->get(); + if ($this->a === $this->b) { // end quote + break; + } + if ($this->isEOF($this->a)) { + $byte = $this->inputIndex - 1; + throw new JSMin_UnterminatedStringException( + "JSMin: Unterminated String at byte {$byte}: {$str}"); + } + $str .= $this->a; + if ($this->a === '\\') { + $this->output .= $this->a; + $this->lastByteOut = $this->a; + + $this->a = $this->get(); + $str .= $this->a; + } + } + } + + // fallthrough intentional + case self::ACTION_DELETE_A_B: // 3 + $this->b = $this->next(); + if ($this->b === '/' && $this->isRegexpLiteral()) { + $this->output .= $this->a . $this->b; + $pattern = '/'; // keep entire pattern in case we need to report it in the exception + for(;;) { + $this->a = $this->get(); + $pattern .= $this->a; + if ($this->a === '[') { + for(;;) { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + if ($this->a === ']') { + break; + } + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + } + if ($this->isEOF($this->a)) { + throw new JSMin_UnterminatedRegExpException( + "JSMin: Unterminated set in RegExp at byte " + . $this->inputIndex .": {$pattern}"); + } + } + } + + if ($this->a === '/') { // end pattern + break; // while (true) + } elseif ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + } elseif ($this->isEOF($this->a)) { + $byte = $this->inputIndex - 1; + throw new JSMin_UnterminatedRegExpException( + "JSMin: Unterminated RegExp at byte {$byte}: {$pattern}"); + } + $this->output .= $this->a; + $this->lastByteOut = $this->a; + } + $this->b = $this->next(); + } + // end case ACTION_DELETE_A_B + } + } + + /** + * @return bool + */ + protected function isRegexpLiteral() + { + if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { + // we can't divide after these tokens + return true; + } + + // check if first non-ws token is "/" (see starts-regex.js) + $length = strlen($this->output); + if ($this->a === ' ' || $this->a === "\n") { + if ($length < 2) { // weird edge case + return true; + } + } + + // if the "/" follows a keyword, it must be a regexp, otherwise it's best to assume division + + $subject = $this->output . trim($this->a); + if (!preg_match('/(?:case|else|in|return|typeof)$/', $subject, $m)) { + // not a keyword + return false; + } + + // can't be sure it's a keyword yet (see not-regexp.js) + $charBeforeKeyword = substr($subject, 0 - strlen($m[0]) - 1, 1); + if ($this->isAlphaNum($charBeforeKeyword)) { + // this is really an identifier ending in a keyword, e.g. "xreturn" + return false; + } + + // it's a regexp. Remove unneeded whitespace after keyword + if ($this->a === ' ' || $this->a === "\n") { + $this->a = ''; + } + + return true; + } + + /** + * Return the next character from stdin. Watch out for lookahead. If the character is a control character, + * translate it to a space or linefeed. + * + * @return string + */ + protected function get() + { + $c = $this->lookAhead; + $this->lookAhead = null; + if ($c === null) { + // getc(stdin) + if ($this->inputIndex < $this->inputLength) { + $c = $this->input[$this->inputIndex]; + $this->inputIndex += 1; + } else { + $c = null; + } + } + if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { + return $c; + } + if ($c === "\r") { + return "\n"; + } + return ' '; + } + + /** + * Does $a indicate end of input? + * + * @param string $a + * @return bool + */ + protected function isEOF($a) + { + return ord($a) <= self::ORD_LF; + } + + /** + * Get next char (without getting it). If is ctrl character, translate to a space or newline. + * + * @return string + */ + protected function peek() + { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } + + /** + * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. + * + * @param string $c + * + * @return bool + */ + protected function isAlphaNum($c) + { + return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126); + } + + /** + * Consume a single line comment from input (possibly retaining it) + */ + protected function consumeSingleLineComment() + { + $comment = ''; + while (true) { + $get = $this->get(); + $comment .= $get; + if (ord($get) <= self::ORD_LF) { // end of line reached + // if IE conditional comment + if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { + $this->keptComment .= "/{$comment}"; + } + return; + } + } + } + + /** + * Consume a multiple line comment from input (possibly retaining it) + * + * @throws JSMin_UnterminatedCommentException + */ + protected function consumeMultipleLineComment() + { + $this->get(); + $comment = ''; + for(;;) { + $get = $this->get(); + if ($get === '*') { + if ($this->peek() === '/') { // end of comment reached + $this->get(); + if (0 === strpos($comment, '!')) { + // preserved by YUI Compressor + if (!$this->keptComment) { + // don't prepend a newline if two comments right after one another + $this->keptComment = "\n"; + } + $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; + } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { + // IE conditional + $this->keptComment .= "/*{$comment}*/"; + } + return; + } + } elseif ($get === null) { + throw new JSMin_UnterminatedCommentException( + "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); + } + $comment .= $get; + } + } + + /** + * Get the next character, skipping over comments. Some comments may be preserved. + * + * @return string + */ + protected function next() + { + $get = $this->get(); + if ($get === '/') { + switch ($this->peek()) { + case '/': + $this->consumeSingleLineComment(); + $get = "\n"; + break; + case '*': + $this->consumeMultipleLineComment(); + $get = ' '; + break; + } + } + return $get; + } +} + +class JSMin_UnterminatedStringException extends Exception {} +class JSMin_UnterminatedCommentException extends Exception {} +class JSMin_UnterminatedRegExpException extends Exception {} diff --git a/classes/external/php/yui-php-cssmin-bundled/Colors.php b/classes/external/php/yui-php-cssmin-bundled/Colors.php new file mode 100644 index 00000000..d37115a3 --- /dev/null +++ b/classes/external/php/yui-php-cssmin-bundled/Colors.php @@ -0,0 +1,155 @@ + 'azure', + '#f5f5dc' => 'beige', + '#ffe4c4' => 'bisque', + '#a52a2a' => 'brown', + '#ff7f50' => 'coral', + '#ffd700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4b0082' => 'indigo', + '#fffff0' => 'ivory', + '#f0e68c' => 'khaki', + '#faf0e6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#fdf5e6' => 'oldlace', + '#808000' => 'olive', + '#ffa500' => 'orange', + '#da70d6' => 'orchid', + '#cd853f' => 'peru', + '#ffc0cb' => 'pink', + '#dda0dd' => 'plum', + '#800080' => 'purple', + '#f00' => 'red', + '#fa8072' => 'salmon', + '#a0522d' => 'sienna', + '#c0c0c0' => 'silver', + '#fffafa' => 'snow', + '#d2b48c' => 'tan', + '#008080' => 'teal', + '#ff6347' => 'tomato', + '#ee82ee' => 'violet', + '#f5deb3' => 'wheat' + ); + } + + public static function getNamedToHexMap() + { + // Named colors longer than hex counterpart + return array( + 'aliceblue' => '#f0f8ff', + 'antiquewhite' => '#faebd7', + 'aquamarine' => '#7fffd4', + 'black' => '#000', + 'blanchedalmond' => '#ffebcd', + 'blueviolet' => '#8a2be2', + 'burlywood' => '#deb887', + 'cadetblue' => '#5f9ea0', + 'chartreuse' => '#7fff00', + 'chocolate' => '#d2691e', + 'cornflowerblue' => '#6495ed', + 'cornsilk' => '#fff8dc', + 'darkblue' => '#00008b', + 'darkcyan' => '#008b8b', + 'darkgoldenrod' => '#b8860b', + 'darkgray' => '#a9a9a9', + 'darkgreen' => '#006400', + 'darkgrey' => '#a9a9a9', + 'darkkhaki' => '#bdb76b', + 'darkmagenta' => '#8b008b', + 'darkolivegreen' => '#556b2f', + 'darkorange' => '#ff8c00', + 'darkorchid' => '#9932cc', + 'darksalmon' => '#e9967a', + 'darkseagreen' => '#8fbc8f', + 'darkslateblue' => '#483d8b', + 'darkslategray' => '#2f4f4f', + 'darkslategrey' => '#2f4f4f', + 'darkturquoise' => '#00ced1', + 'darkviolet' => '#9400d3', + 'deeppink' => '#ff1493', + 'deepskyblue' => '#00bfff', + 'dodgerblue' => '#1e90ff', + 'firebrick' => '#b22222', + 'floralwhite' => '#fffaf0', + 'forestgreen' => '#228b22', + 'fuchsia' => '#f0f', + 'gainsboro' => '#dcdcdc', + 'ghostwhite' => '#f8f8ff', + 'goldenrod' => '#daa520', + 'greenyellow' => '#adff2f', + 'honeydew' => '#f0fff0', + 'indianred' => '#cd5c5c', + 'lavender' => '#e6e6fa', + 'lavenderblush' => '#fff0f5', + 'lawngreen' => '#7cfc00', + 'lemonchiffon' => '#fffacd', + 'lightblue' => '#add8e6', + 'lightcoral' => '#f08080', + 'lightcyan' => '#e0ffff', + 'lightgoldenrodyellow' => '#fafad2', + 'lightgray' => '#d3d3d3', + 'lightgreen' => '#90ee90', + 'lightgrey' => '#d3d3d3', + 'lightpink' => '#ffb6c1', + 'lightsalmon' => '#ffa07a', + 'lightseagreen' => '#20b2aa', + 'lightskyblue' => '#87cefa', + 'lightslategray' => '#778899', + 'lightslategrey' => '#778899', + 'lightsteelblue' => '#b0c4de', + 'lightyellow' => '#ffffe0', + 'limegreen' => '#32cd32', + 'mediumaquamarine' => '#66cdaa', + 'mediumblue' => '#0000cd', + 'mediumorchid' => '#ba55d3', + 'mediumpurple' => '#9370db', + 'mediumseagreen' => '#3cb371', + 'mediumslateblue' => '#7b68ee', + 'mediumspringgreen' => '#00fa9a', + 'mediumturquoise' => '#48d1cc', + 'mediumvioletred' => '#c71585', + 'midnightblue' => '#191970', + 'mintcream' => '#f5fffa', + 'mistyrose' => '#ffe4e1', + 'moccasin' => '#ffe4b5', + 'navajowhite' => '#ffdead', + 'olivedrab' => '#6b8e23', + 'orangered' => '#ff4500', + 'palegoldenrod' => '#eee8aa', + 'palegreen' => '#98fb98', + 'paleturquoise' => '#afeeee', + 'palevioletred' => '#db7093', + 'papayawhip' => '#ffefd5', + 'peachpuff' => '#ffdab9', + 'powderblue' => '#b0e0e6', + 'rebeccapurple' => '#663399', + 'rosybrown' => '#bc8f8f', + 'royalblue' => '#4169e1', + 'saddlebrown' => '#8b4513', + 'sandybrown' => '#f4a460', + 'seagreen' => '#2e8b57', + 'seashell' => '#fff5ee', + 'slateblue' => '#6a5acd', + 'slategray' => '#708090', + 'slategrey' => '#708090', + 'springgreen' => '#00ff7f', + 'steelblue' => '#4682b4', + 'turquoise' => '#40e0d0', + 'white' => '#fff', + 'whitesmoke' => '#f5f5f5', + 'yellow' => '#ff0', + 'yellowgreen' => '#9acd32' + ); + } +} diff --git a/classes/external/php/yui-php-cssmin-bundled/Minifier.php b/classes/external/php/yui-php-cssmin-bundled/Minifier.php new file mode 100644 index 00000000..1b92d6d6 --- /dev/null +++ b/classes/external/php/yui-php-cssmin-bundled/Minifier.php @@ -0,0 +1,895 @@ +raisePhpLimits = (bool) $raisePhpLimits; + $this->memoryLimit = 128 * 1048576; // 128MB in bytes + $this->pcreBacktrackLimit = 1000 * 1000; + $this->pcreRecursionLimit = 500 * 1000; + $this->hexToNamedColorsMap = Colors::getHexToNamedMap(); + $this->namedToHexColorsMap = Colors::getNamedToHexMap(); + $this->namedToHexColorsRegex = sprintf( + '/([:,( ])(%s)( |,|\)|;|$)/Si', + implode('|', array_keys($this->namedToHexColorsMap)) + ); + $this->numRegex = sprintf('-?\d*\.?\d+%s?', $this->unitsGroupRegex); + $this->setShortenZeroValuesRegexes(); + } + + /** + * Parses & minifies the given input CSS string + * @param string $css + * @return string + */ + public function run($css = '') + { + if (empty($css) || !is_string($css)) { + return ''; + } + + $this->resetRunProperties(); + + if ($this->raisePhpLimits) { + $this->doRaisePhpLimits(); + } + + return $this->minify($css); + } + + /** + * Sets whether to keep or remove sourcemap special comment. + * Sourcemap comments are removed by default. + * @param bool $keepSourceMapComment + */ + public function keepSourceMapComment($keepSourceMapComment = true) + { + $this->keepSourceMapComment = (bool) $keepSourceMapComment; + } + + /** + * Sets whether to keep or remove important comments. + * Important comments outside of a declaration block are kept by default. + * @param bool $removeImportantComments + */ + public function removeImportantComments($removeImportantComments = true) + { + $this->keepImportantComments = !(bool) $removeImportantComments; + } + + /** + * Sets the approximate column after which long lines will be splitted in the output + * with a linebreak. + * @param int $position + */ + public function setLineBreakPosition($position) + { + $this->linebreakPosition = (int) $position; + } + + /** + * Sets the memory limit for this script + * @param int|string $limit + */ + public function setMemoryLimit($limit) + { + $this->memoryLimit = Utils::normalizeInt($limit); + } + + /** + * Sets the maximum execution time for this script + * @param int|string $seconds + */ + public function setMaxExecutionTime($seconds) + { + $this->maxExecutionTime = (int) $seconds; + } + + /** + * Sets the PCRE backtrack limit for this script + * @param int $limit + */ + public function setPcreBacktrackLimit($limit) + { + $this->pcreBacktrackLimit = (int) $limit; + } + + /** + * Sets the PCRE recursion limit for this script + * @param int $limit + */ + public function setPcreRecursionLimit($limit) + { + $this->pcreRecursionLimit = (int) $limit; + } + + /** + * Builds regular expressions needed for shortening zero values + */ + private function setShortenZeroValuesRegexes() + { + $zeroRegex = '0'. $this->unitsGroupRegex; + $numOrPosRegex = '('. $this->numRegex .'|top|left|bottom|right|center) '; + $oneZeroSafeProperties = array( + '(?:line-)?height', + '(?:(?:min|max)-)?width', + 'top', + 'left', + 'background-position', + 'bottom', + 'right', + 'border(?:-(?:top|left|bottom|right))?(?:-width)?', + 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', + 'column-(?:gap|width)', + 'margin(?:-(?:top|left|bottom|right))?', + 'outline-width', + 'padding(?:-(?:top|left|bottom|right))?' + ); + + // First zero regex + $regex = '/(^|;)('. implode('|', $oneZeroSafeProperties) .'):%s/Si'; + $this->shortenOneZeroesRegex = sprintf($regex, $zeroRegex); + + // Multiple zeroes regexes + $regex = '/(^|;)(margin|padding|border-(?:width|radius)|background-position):%s/Si'; + $this->shortenTwoZeroesRegex = sprintf($regex, $numOrPosRegex . $zeroRegex); + $this->shortenThreeZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $zeroRegex); + $this->shortenFourZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $numOrPosRegex . $zeroRegex); + } + + /** + * Resets properties whose value may change between runs + */ + private function resetRunProperties() + { + $this->comments = array(); + $this->ruleBodies = array(); + $this->preservedTokens = array(); + } + + /** + * Tries to configure PHP to use at least the suggested minimum settings + * @return void + */ + private function doRaisePhpLimits() + { + $phpLimits = array( + 'memory_limit' => $this->memoryLimit, + 'max_execution_time' => $this->maxExecutionTime, + 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, + 'pcre.recursion_limit' => $this->pcreRecursionLimit + ); + + // If current settings are higher respect them. + foreach ($phpLimits as $name => $suggested) { + $current = Utils::normalizeInt(ini_get($name)); + + if ($current >= $suggested) { + continue; + } + + // memoryLimit exception: allow -1 for "no memory limit". + if ($name === 'memory_limit' && $current === -1) { + continue; + } + + // maxExecutionTime exception: allow 0 for "no memory limit". + if ($name === 'max_execution_time' && $current === 0) { + continue; + } + + ini_set($name, $suggested); + } + } + + /** + * Registers a preserved token + * @param string $token + * @return string The token ID string + */ + private function registerPreservedToken($token) + { + $tokenId = sprintf(self::PRESERVED_TOKEN, count($this->preservedTokens)); + $this->preservedTokens[$tokenId] = $token; + return $tokenId; + } + + /** + * Registers a candidate comment token + * @param string $comment + * @return string The comment token ID string + */ + private function registerCommentToken($comment) + { + $tokenId = sprintf(self::COMMENT_TOKEN, count($this->comments)); + $this->comments[$tokenId] = $comment; + return $tokenId; + } + + /** + * Registers a rule body token + * @param string $body the minified rule body + * @return string The rule body token ID string + */ + private function registerRuleBodyToken($body) + { + if (empty($body)) { + return ''; + } + + $tokenId = sprintf(self::RULE_BODY_TOKEN, count($this->ruleBodies)); + $this->ruleBodies[$tokenId] = $body; + return $tokenId; + } + + /** + * Parses & minifies the given input CSS string + * @param string $css + * @return string + */ + private function minify($css) + { + // Process data urls + $css = $this->processDataUrls($css); + + // Process comments + $css = preg_replace_callback( + '/(?processComments($css); + + // Process rule bodies + $css = $this->processRuleBodies($css); + + // Process at-rules and selectors + $css = $this->processAtRulesAndSelectors($css); + + // Restore preserved rule bodies before splitting + $css = strtr($css, $this->ruleBodies); + + // Split long lines in output if required + $css = $this->processLongLineSplitting($css); + + // Restore preserved comments and strings + $css = strtr($css, $this->preservedTokens); + + return trim($css); + } + + /** + * Searches & replaces all data urls with tokens before we start compressing, + * to avoid performance issues running some of the subsequent regexes against large string chunks. + * @param string $css + * @return string + */ + private function processDataUrls($css) + { + $ret = ''; + $searchOffset = $substrOffset = 0; + + // Since we need to account for non-base64 data urls, we need to handle + // ' and ) being part of the data string. + while (preg_match('/url\(\s*(["\']?)data:/Si', $css, $m, PREG_OFFSET_CAPTURE, $searchOffset)) { + $matchStartIndex = $m[0][1]; + $dataStartIndex = $matchStartIndex + 4; // url( length + $searchOffset = $matchStartIndex + strlen($m[0][0]); + $terminator = $m[1][0]; // ', " or empty (not quoted) + $terminatorRegex = '/(?registerPreservedToken(trim($token)) .')'; + // No end terminator found, re-add the whole match. Should we throw/warn here? + } else { + $ret .= substr($css, $matchStartIndex, $searchOffset - $matchStartIndex); + } + + $substrOffset = $searchOffset; + } + + $ret .= substr($css, $substrOffset); + + return $ret; + } + + /** + * Registers all comments found as candidates to be preserved. + * @param array $matches + * @return string + */ + private function processCommentsCallback($matches) + { + return '/*'. $this->registerCommentToken($matches[1]) .'*/'; + } + + /** + * Preserves old IE Matrix string definition + * @param array $matches + * @return string + */ + private function processOldIeSpecificMatrixDefinitionCallback($matches) + { + return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $this->registerPreservedToken($matches[1]) .')'; + } + + /** + * Preserves strings found + * @param array $matches + * @return string + */ + private function processStringsCallback($matches) + { + $match = $matches[0]; + $quote = substr($match, 0, 1); + $match = substr($match, 1, -1); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (strpos($match, self::COMMENT_TOKEN_START) !== false) { + $match = strtr($match, $this->comments); + } + + // minify alpha opacity in filter strings + $match = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $match); + + return $quote . $this->registerPreservedToken($match) . $quote; + } + + /** + * Searches & replaces all import at-rule unquoted urls with tokens so URI reserved characters such as a semicolon + * may be used safely in a URL. + * @param array $matches + * @return string + */ + private function processImportUnquotedUrlAtRulesCallback($matches) + { + return '@import url('. $this->registerPreservedToken($matches[1]) .')'. $matches[2]; + } + + /** + * Preserves or removes comments found. + * @param string $css + * @return string + */ + private function processComments($css) + { + foreach ($this->comments as $commentId => $comment) { + $commentIdString = '/*'. $commentId .'*/'; + + // ! in the first position of the comment means preserve + // so push to the preserved tokens keeping the ! + if ($this->keepImportantComments && strpos($comment, '!') === 0) { + $preservedTokenId = $this->registerPreservedToken($comment); + // Put new lines before and after /*! important comments + $css = str_replace($commentIdString, "\n/*$preservedTokenId*/\n", $css); + continue; + } + + // # sourceMappingURL= in the first position of the comment means sourcemap + // so push to the preserved tokens if {$this->keepSourceMapComment} is truthy. + if ($this->keepSourceMapComment && strpos($comment, '# sourceMappingURL=') === 0) { + $preservedTokenId = $this->registerPreservedToken($comment); + // Add new line before the sourcemap comment + $css = str_replace($commentIdString, "\n/*$preservedTokenId*/", $css); + continue; + } + + // Keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (strlen($comment) === 0 && strpos($css, '>/*'.$commentId) !== false) { + $css = str_replace($commentId, $this->registerPreservedToken(''), $css); + continue; + } + + // in all other cases kill the comment + $css = str_replace($commentIdString, '', $css); + } + + // Normalize whitespace again + $css = preg_replace('/ +/S', ' ', $css); + + return $css; + } + + /** + * Finds, minifies & preserves all rule bodies. + * @param string $css the whole stylesheet. + * @return string + */ + private function processRuleBodies($css) + { + $ret = ''; + $searchOffset = $substrOffset = 0; + + while (($blockStartPos = strpos($css, '{', $searchOffset)) !== false) { + $blockEndPos = strpos($css, '}', $blockStartPos); + $nextBlockStartPos = strpos($css, '{', $blockStartPos + 1); + $ret .= substr($css, $substrOffset, $blockStartPos - $substrOffset); + + if ($nextBlockStartPos !== false && $nextBlockStartPos < $blockEndPos) { + $ret .= substr($css, $blockStartPos, $nextBlockStartPos - $blockStartPos); + $searchOffset = $nextBlockStartPos; + } else { + $ruleBody = substr($css, $blockStartPos + 1, $blockEndPos - $blockStartPos - 1); + $ruleBodyToken = $this->registerRuleBodyToken($this->processRuleBody($ruleBody)); + $ret .= '{'. $ruleBodyToken .'}'; + $searchOffset = $blockEndPos + 1; + } + + $substrOffset = $searchOffset; + } + + $ret .= substr($css, $substrOffset); + + return $ret; + } + + /** + * Compresses non-group rule bodies. + * @param string $body The rule body without curly braces + * @return string + */ + private function processRuleBody($body) + { + $body = trim($body); + + // Remove spaces before the things that should not have spaces before them. + $body = preg_replace('/ ([:=,)*\/;\n])/S', '$1', $body); + + // Remove the spaces after the things that should not have spaces after them. + $body = preg_replace('/([:=,(*\/!;\n]) /S', '$1', $body); + + // Replace multiple semi-colons in a row by a single one + $body = preg_replace('/;;+/S', ';', $body); + + // Remove semicolon before closing brace except when: + // - The last property is prefixed with a `*` (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers. + if (!preg_match('/\*[a-z0-9-]+:[^;]+;$/Si', $body)) { + $body = rtrim($body, ';'); + } + + // Remove important comments inside a rule body (because they make no sense here). + if (strpos($body, '/*') !== false) { + $body = preg_replace('/\n?\/\*[A-Z0-9_]+\*\/\n?/S', '', $body); + } + + // Empty rule body? Exit :) + if (empty($body)) { + return ''; + } + + // Shorten font-weight values + $body = preg_replace( + array('/(font-weight:)bold\b/Si', '/(font-weight:)normal\b/Si'), + array('${1}700', '${1}400'), + $body + ); + + // Shorten background property + $body = preg_replace('/(background:)(?:none|transparent)( !|;|$)/Si', '${1}0 0$2', $body); + + // Shorten opacity IE filter + $body = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $body); + + // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space) + // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space) + // This makes it more likely that it'll get further compressed in the next step. + $body = preg_replace_callback( + '/(rgb|hsl)\(([0-9,.% -]+)\)(.|$)/Si', + array($this, 'shortenHslAndRgbToHexCallback'), + $body + ); + + // Shorten colors from #AABBCC to #ABC or shorter color name: + // - Look for hex colors which don't have a "=" in front of them (to avoid MSIE filters) + $body = preg_replace_callback( + '/(? #fff. + // Run at least 2 times to cover most cases + $body = preg_replace_callback( + array($this->namedToHexColorsRegex, $this->namedToHexColorsRegex), + array($this, 'shortenNamedColorsCallback'), + $body + ); + + // Replace positive sign from numbers before the leading space is removed. + // +1.2em to 1.2em, +.8px to .8px, +2% to 2% + $body = preg_replace('/([ :,(])\+(\.?\d+)/S', '$1$2', $body); + + // shorten ms to s + $body = preg_replace_callback('/([ :,(])(-?)(\d{3,})ms/Si', function ($matches) { + return $matches[1] . $matches[2] . ((int) $matches[3] / 1000) .'s'; + }, $body); + + // Remove leading zeros from integer and float numbers. + // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05 + $body = preg_replace('/([ :,(])(-?)0+([1-9]?\.?\d+)/S', '$1$2$3', $body); + + // Remove trailing zeros from float numbers. + // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px + $body = preg_replace('/([ :,(])(-?\d?\.\d+?)0+([^\d])/S', '$1$2$3', $body); + + // Remove trailing .0 -> -9.0 to -9 + $body = preg_replace('/([ :,(])(-?\d+)\.0([^\d])/S', '$1$2$3', $body); + + // Replace 0 length numbers with 0 + $body = preg_replace('/([ :,(])-?\.?0+([^\d])/S', '${1}0$2', $body); + + // Shorten zero values for safe properties only + $body = preg_replace( + array( + $this->shortenOneZeroesRegex, + $this->shortenTwoZeroesRegex, + $this->shortenThreeZeroesRegex, + $this->shortenFourZeroesRegex + ), + array( + '$1$2:0', + '$1$2:$3 0', + '$1$2:$3 $4 0', + '$1$2:$3 $4 $5 0' + ), + $body + ); + + // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. + $body = preg_replace('/(background-position):0(?: 0){2,3}( !|;|$)/Si', '$1:0 0$2', $body); + + // Shorten suitable shorthand properties with repeated values + $body = preg_replace( + array( + '/(margin|padding|border-(?:width|radius)):('.$this->numRegex.')(?: \2)+( !|;|$)/Si', + '/(border-(?:style|color)):([#a-z0-9]+)(?: \2)+( !|;|$)/Si' + ), + '$1:$2$3', + $body + ); + $body = preg_replace( + array( + '/(margin|padding|border-(?:width|radius)):'. + '('.$this->numRegex.') ('.$this->numRegex.') \2 \3( !|;|$)/Si', + '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) \2 \3( !|;|$)/Si' + ), + '$1:$2 $3$4', + $body + ); + $body = preg_replace( + array( + '/(margin|padding|border-(?:width|radius)):'. + '('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') \3( !|;|$)/Si', + '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) ([#a-z0-9]+) \3( !|;|$)/Si' + ), + '$1:$2 $3 $4$5', + $body + ); + + // Lowercase some common functions that can be values + $body = preg_replace_callback( + '/(?:attr|blur|brightness|circle|contrast|cubic-bezier|drop-shadow|ellipse|from|grayscale|'. + 'hsla?|hue-rotate|inset|invert|local|minmax|opacity|perspective|polygon|rgba?|rect|repeat|saturate|sepia|'. + 'steps|to|url|var|-webkit-gradient|'. + '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|(?:repeating-)?(?:linear|radial)-gradient))\(/Si', + array($this, 'strtolowerCallback'), + $body + ); + + // Lowercase all uppercase properties + $body = preg_replace_callback('/(?:^|;)[A-Z-]+:/S', array($this, 'strtolowerCallback'), $body); + + return $body; + } + + /** + * Compresses At-rules and selectors. + * @param string $css the whole stylesheet with rule bodies tokenized. + * @return string + */ + private function processAtRulesAndSelectors($css) + { + $charset = ''; + $imports = ''; + $namespaces = ''; + + // Remove spaces before the things that should not have spaces before them. + $css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css); + + // Remove the spaces after the things that should not have spaces after them. + $css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css); + + // Shorten shortable double colon (CSS3) pseudo-elements to single colon (CSS2) + $css = preg_replace('/::(before|after|first-(?:line|letter))(\{|,)/Si', ':$1$2', $css); + + // Retain space for special IE6 cases + $css = preg_replace_callback('/:first-(line|letter)(\{|,)/Si', function ($matches) { + return ':first-'. strtolower($matches[1]) .' '. $matches[2]; + }, $css); + + // Find a fraction that may used in some @media queries such as: (min-aspect-ratio: 1/1) + // Add token to add the "/" back in later + $css = preg_replace('/\(([a-z-]+):([0-9]+)\/([0-9]+)\)/Si', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); + + // Remove empty rule blocks up to 2 levels deep. + $css = preg_replace(array_fill(0, 2, '/(\{)[^{};\/\n]+\{\}/S'), '$1', $css); + $css = preg_replace('/[^{};\/\n]+\{\}/S', '', $css); + + // Two important comments next to each other? Remove extra newline. + if ($this->keepImportantComments) { + $css = str_replace("\n\n", "\n", $css); + } + + // Restore fraction + $css = str_replace(self::QUERY_FRACTION, '/', $css); + + // Lowercase some popular @directives + $css = preg_replace_callback( + '/(?charsetRegex, $css, $matches)) { + // Keep the first @charset at-rule found + $charset = $matches[0]; + // Delete all @charset at-rules + $css = preg_replace($this->charsetRegex, '', $css); + } + + // @import handling + $css = preg_replace_callback($this->importRegex, function ($matches) use (&$imports) { + // Keep all @import at-rules found for later + $imports .= $matches[0]; + // Delete all @import at-rules + return ''; + }, $css); + + // @namespace handling + $css = preg_replace_callback($this->namespaceRegex, function ($matches) use (&$namespaces) { + // Keep all @namespace at-rules found for later + $namespaces .= $matches[0]; + // Delete all @namespace at-rules + return ''; + }, $css); + + // Order critical at-rules: + // 1. @charset first + // 2. @imports below @charset + // 3. @namespaces below @imports + $css = $charset . $imports . $namespaces . $css; + + return $css; + } + + /** + * Splits long lines after a specific column. + * + * Some source control tools don't like it when files containing lines longer + * than, say 8000 characters, are checked in. The linebreak option is used in + * that case to split long lines after a specific column. + * + * @param string $css the whole stylesheet. + * @return string + */ + private function processLongLineSplitting($css) + { + if ($this->linebreakPosition > 0) { + $l = strlen($css); + $offset = $this->linebreakPosition; + while (preg_match('/(?linebreakPosition; + $l += 1; + if ($offset > $l) { + break; + } + } + } + + return $css; + } + + /** + * Converts hsl() & rgb() colors to HEX format. + * @param $matches + * @return string + */ + private function shortenHslAndRgbToHexCallback($matches) + { + $type = $matches[1]; + $values = explode(',', $matches[2]); + $terminator = $matches[3]; + + if ($type === 'hsl') { + $values = Utils::hslToRgb($values); + } + + $hexColors = Utils::rgbToHex($values); + + // Restore space after rgb() or hsl() function in some cases such as: + // background-image: linear-gradient(to bottom, rgb(210,180,140) 10%, rgb(255,0,0) 90%); + if (!empty($terminator) && !preg_match('/[ ,);]/S', $terminator)) { + $terminator = ' '. $terminator; + } + + return '#'. implode('', $hexColors) . $terminator; + } + + /** + * Compresses HEX color values of the form #AABBCC to #ABC or short color name. + * @param $matches + * @return string + */ + private function shortenHexColorsCallback($matches) + { + $hex = $matches[1]; + + // Shorten suitable 6 chars HEX colors + if (strlen($hex) === 6 && preg_match('/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/Si', $hex, $m)) { + $hex = $m[1] . $m[2] . $m[3]; + } + + // Lowercase + $hex = '#'. strtolower($hex); + + // Replace Hex colors with shorter color names + $color = array_key_exists($hex, $this->hexToNamedColorsMap) ? $this->hexToNamedColorsMap[$hex] : $hex; + + return $color . $matches[2]; + } + + /** + * Shortens all named colors with a shorter HEX counterpart for a set of safe properties + * e.g. white -> #fff + * @param array $matches + * @return string + */ + private function shortenNamedColorsCallback($matches) + { + return $matches[1] . $this->namedToHexColorsMap[strtolower($matches[2])] . $matches[3]; + } + + /** + * Makes a string lowercase + * @param array $matches + * @return string + */ + private function strtolowerCallback($matches) + { + return strtolower($matches[0]); + } +} diff --git a/classes/external/php/yui-php-cssmin-bundled/Utils.php b/classes/external/php/yui-php-cssmin-bundled/Utils.php new file mode 100644 index 00000000..6afdb63d --- /dev/null +++ b/classes/external/php/yui-php-cssmin-bundled/Utils.php @@ -0,0 +1,149 @@ + 1 ? $vh - 1 : $vh); + + if ($vh * 6 < 1) { + return $v1 + ($v2 - $v1) * 6 * $vh; + } + + if ($vh * 2 < 1) { + return $v2; + } + + if ($vh * 3 < 2) { + return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; + } + + return $v1; + } + + /** + * Convert strings like "64M" or "30" to int values + * @param mixed $size + * @return int + */ + public static function normalizeInt($size) + { + if (is_string($size)) { + $letter = substr($size, -1); + $size = intval($size); + switch ($letter) { + case 'M': + case 'm': + return (int) $size * 1048576; + case 'K': + case 'k': + return (int) $size * 1024; + case 'G': + case 'g': + return (int) $size * 1073741824; + } + } + return (int) $size; + } + + /** + * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 + * @param $rgbPercentage + * @return int + */ + public static function rgbPercentageToRgbInteger($rgbPercentage) + { + if (strpos($rgbPercentage, '%') !== false) { + $rgbPercentage = self::roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); + } + + return intval($rgbPercentage, 10); + } + + /** + * Converts a RGB color into a HEX color + * @param array $rgbColors + * @return array + */ + public static function rgbToHex($rgbColors) + { + $hexColors = array(); + + // Values outside the sRGB color space should be clipped (0-255) + for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { + $hexColors[$i] = sprintf("%02x", self::clampNumberSrgb(self::rgbPercentageToRgbInteger($rgbColors[$i]))); + } + + return $hexColors; + } + + /** + * Rounds a number to its closest integer + * @param $n + * @return int + */ + public static function roundNumber($n) + { + return intval(round(floatval($n)), 10); + } +} diff --git a/classes/external/php/yui-php-cssmin-bundled/index.html b/classes/external/php/yui-php-cssmin-bundled/index.html new file mode 100644 index 00000000..fe7267f7 --- /dev/null +++ b/classes/external/php/yui-php-cssmin-bundled/index.html @@ -0,0 +1 @@ +Generated by Autoptimize diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..ba269b4b --- /dev/null +++ b/composer.json @@ -0,0 +1,37 @@ +{ + "name": "futtta/autoptimize", + "description": "Optimizes your WordPress website, concatenating the CSS and JavaScript code, and compressing it.", + "type": "wordpress-plugin", + "homepage": "https://autoptimize.com/", + "keywords": [ + "async", + "minify", + "optimize", + "pagespeed", + "performance" + ], + "license": "GPL", + "minimum-stability": "dev", + "prefer-stable": true, + "support": { + "issues": "https://wordpress.org/support/plugin/autoptimize" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php": ">=5.6", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.2", + "squizlabs/php_codesniffer": "^3.1", + "phpunit/phpunit": "^5.7.27", + "wimg/php-compatibility": "*", + "wp-coding-standards/wpcs": "*" + }, + "scripts": { + "test": [ + "@phpunit" + ], + "phpunit": "phpunit", + "phpcs": "phpcs" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..1f10017e --- /dev/null +++ b/composer.lock @@ -0,0 +1,1552 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "339a3a6488d0c1cfeaf9fb3517b768e6", + "packages": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.4.4", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": "^5.3|^7", + "squizlabs/php_codesniffer": "*" + }, + "require-dev": { + "composer/composer": "*", + "wimg/php-compatibility": "^8.0" + }, + "suggest": { + "dealerdirect/qa-tools": "All the PHP QA tools you'll need" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "f.nijhof@dealerdirect.nl", + "homepage": "http://workingatdealerdirect.eu", + "role": "Developer" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://workingatdealerdirect.eu", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "time": "2017-12-06T16:27:17+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-10T14:09:06+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "9f901e29c93dae4aa77c0bb161df4276f9c9a1be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9f901e29c93dae4aa77c0bb161df4276f9c9a1be", + "reference": "9f901e29c93dae4aa77c0bb161df4276f9c9a1be", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-02-11T18:49:29+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-12-19T21:44:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "eab73b6c21d27ae4cd037c417618dfd4befb0bfe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/eab73b6c21d27ae4cd037c417618dfd4befb0bfe", + "reference": "eab73b6c21d27ae4cd037c417618dfd4befb0bfe", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-01-21T19:05:02+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-01-29T19:49:41+00:00" + }, + { + "name": "wimg/php-compatibility", + "version": "8.1.0", + "source": { + "type": "git", + "url": "https://github.com/wimg/PHPCompatibility.git", + "reference": "4ac01e4fe8faaa4f8d3b3cd06ea92e5418ce472e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wimg/PHPCompatibility/zipball/4ac01e4fe8faaa4f8d3b3cd06ea92e5418ce472e", + "reference": "4ac01e4fe8faaa4f8d3b3cd06ea92e5418ce472e", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.2 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "PHPCompatibility\\": "PHPCompatibility/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2017-12-27T21:58:38+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "0.14.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git", + "reference": "8cadf48fa1c70b2381988e0a79e029e011a8f41c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/8cadf48fa1c70b2381988e0a79e029e011a8f41c", + "reference": "8cadf48fa1c70b2381988e0a79e029e011a8f41c", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "time": "2017-11-01T15:10:46+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=5.3" + }, + "platform-dev": { + "php": ">=5.6" + } +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..e982bc1e --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,37 @@ + + + Modified from WordPress Coding Standards for Plugins + + + + + + + + + + + + + + + + + + + + + autoptimize.php + classes + tests + + + + + + + + + */vendor/* + */external/* + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..44f0fdb6 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + ./tests/ + + + diff --git a/tests/LICENSE b/tests/LICENSE new file mode 100644 index 00000000..d6a93266 --- /dev/null +++ b/tests/LICENSE @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..c34ea1de --- /dev/null +++ b/tests/README.md @@ -0,0 +1,13 @@ +Autoptimize +=========== + +The [official Autoptimize repo on Github can be found here](https://github.com/futtta/autoptimize/). + +## Installing/running the tests +* Install wp test suite by running `bin/install-wp-tests.sh` +* Run `composer install` +* Now you should be able to run either `composer test` or `phpunit` + +Have a read through `tests/test-ao.php` and `tests/bootstrap.php` if you'd like to know more. + +Ideally, this should be switched to a more modern setup using https://github.com/Brain-WP/BrainMonkey -- once the AO codebase allows for easier testing. One day, maybe. diff --git a/tests/autoptimize.php b/tests/autoptimize.php new file mode 100644 index 00000000..ee5c96f0 --- /dev/null +++ b/tests/autoptimize.php @@ -0,0 +1,91 @@ +

' . __( 'Autoptimize requires PHP 5.3 (or higher) to function properly. Please upgrade PHP. The Plugin has been auto-deactivated.', 'autoptimize' ) . '

'; + if ( isset( $_GET['activate'] ) ) { + unset( $_GET['activate'] ); + } + } + function autoptimize_deactivate_self() { + deactivate_plugins( plugin_basename( AUTOPTIMIZE_PLUGIN_FILE ) ); + } + add_action( 'admin_notices', 'autoptimize_incompatible_admin_notice' ); + add_action( 'admin_init', 'autoptimize_deactivate_self' ); + return; +} + +function autoptimize_autoload( $class_name ) { + if ( in_array( $class_name, array( 'Minify_HTML', 'JSMin' ) ) ) { + $file = strtolower( $class_name ); + $file = str_replace( '_', '-', $file ); + $path = dirname( __FILE__ ) . '/classes/external/php/'; + $filepath = $path . $file . '.php'; + } elseif ( false !== strpos( $class_name, 'Autoptimize\\tubalmartin\\CssMin' ) ) { + $file = str_replace( 'Autoptimize\\tubalmartin\\CssMin\\', '', $class_name ); + $path = dirname( __FILE__ ) . '/classes/external/php/yui-php-cssmin-bundled/'; + $filepath = $path . $file . '.php'; + } elseif ( 'autoptimize' === substr( $class_name, 0, 11 ) ) { + // One of our "old" classes. + $file = $class_name; + $path = dirname( __FILE__ ) . '/classes/'; + $filepath = $path . $file . '.php'; + } + + // If we didn't match one of our rules, bail! + if ( ! isset( $filepath ) ) { + return; + } + + require $filepath; +} + +spl_autoload_register( 'autoptimize_autoload' ); + +// Load WP CLI command(s) on demand. +if ( defined( 'WP_CLI' ) && WP_CLI ) { + require AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizeCLI.php'; +} + +/** + * Retrieve the instance of the main plugin class. + * + * @return autoptimizeMain + */ +function autoptimize() { + static $plugin = null; + + if ( null === $plugin ) { + $plugin = new autoptimizeMain( AUTOPTIMIZE_PLUGIN_VERSION, AUTOPTIMIZE_PLUGIN_FILE ); + } + + return $plugin; +} + +autoptimize()->run(); diff --git a/tests/autoptimize_helper.php_example b/tests/autoptimize_helper.php_example new file mode 100644 index 00000000..09a10657 --- /dev/null +++ b/tests/autoptimize_helper.php_example @@ -0,0 +1,143 @@ +","after"); + } + +/* autoptimize_filter_js_replacetag: where in the HTML is optimized JS injected + +@param array $replacetag, containing the html-tag and the method (inject "before", "after" or "replace") +@return array with updated values */ +// add_filter('autoptimize_filter_js_replacetag','my_ao_override_js_replacetag',10,1); +function my_ao_override_js_replacetag($replacetag) { + return array("","replace"); + } + +/* autoptimize_js_do_minify: do we want to minify? if set to false autoptimize effectively only aggregates, but does not minify + +@return: boolean true or false */ +// add_filter('autoptimize_js_do_minify','my_ao_js_minify',10,1); +function my_ao_js_minify() { + return false; + } + +/* autoptimize_css_do_minify: do we want to minify? if set to false autoptimize effectively only aggregates, but does not minify + +@return: boolean true or false */ +// add_filter('autoptimize_css_do_minify','my_ao_css_minify',10,1); +function my_ao_css_minify() { + return false; + } + +/* autoptimize_js_include_inline: do we want AO to also aggregate inline JS? + +@return: boolean true or false */ +// add_filter('autoptimize_js_include_inline','my_ao_js_include_inline',10,1); +function my_ao_js_include_inline() { + return false; + } + +/* autoptimize_css_include_inline: do we want AO to also aggregate inline CSS? + +@return: boolean true or false */ +// add_filter('autoptimize_css_include_inline','my_ao_css_include_inline',10,1); +function my_ao_css_include_inline() { + return false; + } + +/* autoptimize_filter_css_defer_inline: what CSS to inline when "defer and inline" is activated + +@param $inlined: string with above the fold CSS as configured in admin +@return: updated string with above the fold CSS */ +// add_filter('autoptimize_filter_css_defer_inline','my_ao_css_defer_inline',10,1); +function my_ao_css_defer_inline($inlined) { + return $inlined."h2,h1{color:red !important;}"; + } + +/* autoptimize_filter_css_fonts_cdn: do we want to move fonts to the CDN-url as well + +@return: false (default) or true */ +// add_filter('autoptimize_filter_css_fonts_cdn','my_css_cdnfont',10,0); +function my_css_cdnfont(){ + return true; +} diff --git a/tests/bin/install-wp-tests.sh b/tests/bin/install-wp-tests.sh new file mode 100755 index 00000000..ce26ddc8 --- /dev/null +++ b/tests/bin/install-wp-tests.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} + +tmpdir=${TMPDIR-/tmp} +tmpdir=${tmpdir%/} + +WP_TESTS_DIR=${WP_TESTS_DIR-$tmpdir/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$tmpdir/wordpress/} + + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then + WP_TESTS_TAG="tags/$WP_VERSION" +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi + +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + fi + + cd $WP_TESTS_DIR + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # TODO/FIXME: + # In msys/mingw case $WP_TESTS_DIR has the "wrong" style + # of path written in the config file, i.e.: + # + # ```php + # define( 'ABSPATH', '/c/Windows/Temp/wordpress/' ); + # ``` + # + # If that's happening to you, edit that file manually once after + # running this script and change it so it becomes: + # + # ```php + # define( 'ABSPATH', 'C:/Windows/Temp/wordpress/' ); + # ``` + # + # Running `php phpunit.phar` within your plugin directory should + # work as expected after that. + sed $ioption "s|dirname( __FILE__ ) . '/src/'|'$WP_CORE_DIR'|" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi +} + +install_db() { + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/tests/bin/readme-windows.txt b/tests/bin/readme-windows.txt new file mode 100644 index 00000000..cb09d12f --- /dev/null +++ b/tests/bin/readme-windows.txt @@ -0,0 +1,23 @@ +C:\github\autoptimize>C:\PortableGit\bin\bash.exe bin/install-wp-tests.sh wordpress_test webodjel webber localhost 4.3.1 + +C:\PortableGit\bin\bash.exe install-wp-tests.sh + +Open `C:\Windows\Temp\wordpress-tests-lib\wp-tests-config.php` file and: + + # TODO/FIXME: + # In msys/mingw case $WP_TESTS_DIR has the "wrong" style + # of path written in the config file, i.e.: + # + # ```php + # define( 'ABSPATH', '/c/Windows/Temp/wordpress/' ); + # ``` + # + # If that's happening to you, edit that file manually once after + # running this script and change it so it becomes: + # + # ```php + # define( 'ABSPATH', 'C:/Windows/Temp/wordpress/' ); + # ``` + # + # Running `php phpunit.phar` within your plugin directory should + # work as expected after that. diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..6194e6ae --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,45 @@ +content = $content; + } + + /** + * Reads the page and collects tags. + * + * @param array $options Options. + * + * @return bool + */ + abstract public function read( $options ); + + /** + * Joins and optimizes collected things. + * + * @return bool + */ + abstract public function minify(); + + /** + * Caches the things. + * + * @return void + */ + abstract public function cache(); + + /** + * Returns the content + * + * @return string + */ + abstract public function getcontent(); + + /** + * Tranfsorms a given URL to a full local filepath if possible. + * Returns local filepath or false. + * + * @param string $url URL to transform. + * + * @return bool|string + */ + public function getpath( $url ) + { + $url = apply_filters( 'autoptimize_filter_cssjs_alter_url', $url ); + + if ( false !== strpos( $url, '%' ) ) { + $url = urldecode( $url ); + } + + $site_host = parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ); + $content_host = parse_url( AUTOPTIMIZE_WP_ROOT_URL, PHP_URL_HOST ); + + // Normalizing attempts... + if ( 0 === strpos( $url, '//' ) ) { + if ( is_ssl() ) { + $url = 'https:' . $url; + } else { + $url = 'http:' . $url; + } + } elseif ( ( false === strpos( $url, '//' ) ) && ( false === strpos( $url, $site_host ) ) ) { + if ( AUTOPTIMIZE_WP_SITE_URL === $site_host ) { + $url = AUTOPTIMIZE_WP_SITE_URL . $url; + } else { + $subdir_levels = substr_count( preg_replace( '/https?:\/\//', '', AUTOPTIMIZE_WP_SITE_URL ), '/' ); + $url = AUTOPTIMIZE_WP_SITE_URL . str_repeat( '/..', $subdir_levels ) . $url; + } + } + + if ( $site_host !== $content_host ) { + $url = str_replace( AUTOPTIMIZE_WP_CONTENT_URL, AUTOPTIMIZE_WP_SITE_URL . AUTOPTIMIZE_WP_CONTENT_NAME, $url ); + } + + // First check; hostname wp site should be hostname of url! + $url_host = @parse_url( $url, PHP_URL_HOST ); // @codingStandardsIgnoreLine + if ( $url_host !== $site_host ) { + /** + * First try to get all domains from WPML (if available) + * then explicitely declare $this->cdn_url as OK as well + * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames + * each item in that array will be considered part of the same WP multisite installation + */ + $multidomains = array(); + + $multidomains_wpml = apply_filters( 'wpml_setting', array(), 'language_domains' ); + if ( ! empty( $multidomains_wpml ) ) { + $multidomains = array_map( array( $this, 'get_url_hostname' ), $multidomains_wpml ); + } + + if ( ! empty( $this->cdn_url ) ) { + $multidomains[] = parse_url( $this->cdn_url, PHP_URL_HOST ); + } + + $multidomains = apply_filters( 'autoptimize_filter_cssjs_multidomain', $multidomains ); + + if ( ! empty( $multidomains ) ) { + if ( in_array( $url_host, $multidomains ) ) { + $url = str_replace( $url_host, $site_host, $url ); + } else { + return false; + } + } else { + return false; + } + } + + // Try to remove "wp root url" from url while not minding http<>https. + $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_ROOT_URL ); + + if ( $site_host !== $content_host ) { + // As we replaced the content-domain with the site-domain, we should match against that. + $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_SITE_URL ); + } + + $tmp_url = preg_replace( '/https?:/', '', $url ); + $path = str_replace( $tmp_ao_root, '', $tmp_url ); + + // If path starts with :// or //, this is not a URL in the WP context and + // we have to assume we can't aggregate. + if ( preg_match( '#^:?//#', $path ) ) { + // External script/css (adsense, etc). + return false; + } + + // Prepend with WP_ROOT_DIR to have full path to file. + $path = str_replace( '//', '/', WP_ROOT_DIR . $path ); + + // Final check: does file exist and is it readable? + if ( file_exists( $path ) && is_file( $path ) && is_readable( $path ) ) { + return $path; + } else { + return false; + } + } + + /** + * Returns the hostname part of a given $url if we're able to parse it. + * If not, it returns the original url (prefixed with http:// scheme in case + * it was missing). + * Used as callback for WPML multidomains filter. + * + * @param string $url URL. + * + * @return string + */ + protected function get_url_hostname( $url ) + { + // Checking that the url starts with something vaguely resembling a protocol. + if ( ( 0 !== strpos( $url, 'http' ) ) && ( 0 !== strpos( $url, '//' ) ) ) { + $url = 'http://' . $url; + } + + // Grab the hostname. + $hostname = parse_url( $url, PHP_URL_HOST ); + + // Fallback when parse_url() fails. + if ( empty( $hostname ) ) { + $hostname = $url; + } + + return $hostname; + } + + /** + * Hides everything between noptimize-comment tags. + * + * @param string $markup Markup to process. + * + * @return string + */ + protected function hide_noptimize( $markup ) + { + return $this->replace_contents_with_marker_if_exists( + 'NOPTIMIZE', + '//', + '#.*?#is', + $markup + ); + } + + /** + * Unhide noptimize-tags. + * + * @param string $markup Markup to process. + * + * @return string + */ + protected function restore_noptimize( $markup ) + { + return $this->restore_marked_content( 'NOPTIMIZE', $markup ); + } + + /** + * Hides "iehacks" content. + * + * @param string $markup Markup to process. + * + * @return string + */ + protected function hide_iehacks( $markup ) + { + return $this->replace_contents_with_marker_if_exists( + 'IEHACK', // Marker name... + '#is', // Replacement regex... + $markup + ); + } + + /** + * Restores "hidden" iehacks content. + * + * @param string $markup Markup to process. + * + * @return string + */ + protected function restore_iehacks( $markup ) + { + return $this->restore_marked_content( 'IEHACK', $markup ); + } + + /** + * "Hides" content within HTML comments using a regex-based replacement + * if HTML comment markers are found. + * `` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%` + * + * @param string $markup Markup to process. + * + * @return string + */ + protected function hide_comments( $markup ) + { + return $this->replace_contents_with_marker_if_exists( + 'COMMENTS', + '#is', + $markup + ); + } + + /** + * Restores original HTML comment markers inside a string whose HTML + * comments have been "hidden" by using `hide_comments()`. + * + * @param string $markup Markup to process. + * + * @return string + */ + protected function restore_comments( $markup ) + { + return $this->restore_marked_content( 'COMMENTS', $markup ); + } + + /** + * Replaces the given URL with the CDN-version of it when CDN replacement + * is supposed to be done. + * + * @param string $url URL to process. + * + * @return string + */ + public function url_replace_cdn( $url ) + { + $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $this->cdn_url ); + if ( ! empty( $cdn_url ) ) { + $this->debug_log( 'before=' . $url ); + + // Simple str_replace-based approach fails when $url is protocol-or-host-relative. + $is_protocol_relative = ( '/' === $url{1} ); // second char is `/`. + $is_host_relative = ( ! $is_protocol_relative && ( '/' === $url{0} ) ); + $cdn_url = rtrim( $cdn_url, '/' ); + + if ( $is_host_relative ) { + // Prepending host-relative urls with the cdn url. + $url = $cdn_url . $url; + } else { + // Either a protocol-relative or "regular" url, replacing it either way. + if ( $is_protocol_relative ) { + // Massage $site_url so that simple str_replace() still "works" by + // searching for the protocol-relative version of AUTOPTIMIZE_WP_SITE_URL. + $site_url = str_replace( array( 'http:', 'https:' ), '', AUTOPTIMIZE_WP_SITE_URL ); + } else { + $site_url = AUTOPTIMIZE_WP_SITE_URL; + } + $this->debug_log( '`' . $site_url . '` -> `' . $cdn_url . '` in `' . $url . '`' ); + $url = str_replace( $site_url, $cdn_url, $url ); + } + + $this->debug_log( 'after=' . $url ); + } + + // Allow API filter to take further care of CDN replacement. + $url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url ); + + return $url; + } + + /** + * Injects/replaces the given payload markup into `$this->content` + * at the specified location. + * If the specified tag cannot be found, the payload is appended into + * $this->content along with a warning wrapped inside tags. + * + * @param string $payload Markup to inject. + * @param array $where Array specifying the tag name and method of injection. + * Index 0 is the tag name (i.e., ``). + * Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'. + * + * @return void + */ + protected function inject_in_html( $payload, $where ) + { + $warned = false; + if ( false !== strpos( $this->content, $where[0] ) ) { + // Found the tag, setup content/injection as specified. + if ( 'after' === $where[1] ) { + $content = $where[0] . $payload; + } elseif ( 'replace' === $where[1] ) { + $content = $payload; + } else { + $content = $payload . $where[0]; + } + // Place where specified. + $this->content = substr_replace( + $this->content, + $content, + strpos( $this->content, $where[0] ), + strlen( $where[0] ) + ); + } else { + // Couldn't find what was specified, just append and add a warning. + $this->content .= $payload; + if ( ! $warned ) { + $tag_display = str_replace( array( '<', '>' ), '', $where[0] ); + $this->content .= ''; + $warned = true; + } + } + } + + /** + * Returns true if given `$tag` is found in the list of `$removables`. + * + * @param string $tag Tag to search for. + * @param array $removables List of things considered completely removable. + * + * @return bool + */ + protected function isremovable( $tag, $removables ) + { + foreach ( $removables as $match ) { + if ( false !== strpos( $tag, $match ) ) { + return true; + } + } + + return false; + } + + /** + * Callback used in `self::inject_minified()`. + * + * @param array $matches Regex matches. + * + * @return string + */ + public function inject_minified_callback( $matches ) + { + static $conf = null; + if ( null === $conf ) { + $conf = autoptimizeConfig::instance(); + } + + /** + * $matches[1] holds the whole match caught by regex in self::inject_minified(), + * so we take that and split the string on `|`. + * First element is the filepath, second is the md5 hash of contents + * the filepath had when it was being processed. + * If we don't have those, we'll bail out early. + */ + $filepath = null; + $filehash = null; + + // Grab the parts we need. + $parts = explode( $matches[1], '|' ); + if ( ! empty( $parts ) ) { + $filepath = isset( $parts[0] ) ? $parts[0] : null; + $filehash = isset( $parts[1] ) ? $parts[1] : null; + } + + // Bail early if something's not right... + if ( ! $filepath || ! $filehash ) { + return "\n"; + } + + $filecontent = file_get_contents( $filepath ); + + // Some things are differently handled for css/js... + $is_js_file = ( '.js' === substr( $filepath, -3, 3 ) ); + + $is_css_file = false; + if ( ! $is_js_file ) { + $is_css_file = ( '.css' === substr( $filepath, -4, 4 ) ); + } + + // BOMs being nuked here unconditionally (regardless of where they are)! + $filecontent = preg_replace( "#\x{EF}\x{BB}\x{BF}#", '', $filecontent ); + + // Remove comments and blank lines. + if ( $is_js_file ) { + $filecontent = preg_replace( '#^\s*\/\/.*$#Um', '', $filecontent ); + } + + // Nuke un-important comments. + $filecontent = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent ); + + // Normalize newlines. + $filecontent = preg_replace( '#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent ); + + // JS specifics. + if ( $is_js_file ) { + // Append a semicolon at the end of js files if it's missing. + $last_char = substr( $filecontent, -1, 1 ); + if ( ';' !== $last_char && '}' !== $last_char ) { + $filecontent .= ';'; + } + // Check if try/catch should be used. + $opt_js_try_catch = $conf->get( 'autoptimize_js_trycatch' ); + if ( 'on' === $opt_js_try_catch ) { + // It should, wrap in try/catch. + $filecontent = 'try{' . $filecontent . '}catch(e){}'; + } + } elseif ( $is_css_file ) { + $filecontent = autoptimizeStyles::fixurls( $filepath, $filecontent ); + } else { + $filecontent = ''; + } + + // Return modified (or empty!) code/content. + return "\n" . $filecontent; + } + + /** + * Inject already minified code in optimized JS/CSS. + * + * @param string $in Markup. + * + * @return string + */ + protected function inject_minified( $in ) + { + $out = $in; + + if ( false !== strpos( $in, '%%INJECTLATER%%' ) ) { + $out = preg_replace_callback( + '#\/\*\!%%INJECTLATER' . AUTOPTIMIZE_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is', + array( $this, 'inject_minified_callback' ), + $in + ); + } + + return $out; + } + + /** + * Specialized method to create the INJECTLATER marker. + * These are somewhat "special", in the sense that they're additionally wrapped + * within an "exclamation mark style" comment, so that they're not stripped + * out by minifiers. + * They also currently contain the hash of the file's contents too (unlike other markers). + * + * @param string $filepath Filepath. + * @param string $hash Hash. + * + * @return string + */ + public static function build_injectlater_marker( $filepath, $hash ) + { + $contents = '/*!' . self::build_marker( 'INJECTLATER', $filepath, $hash ) . '*/'; + + return $contents; + } + + /** + * Creates and returns a `%%`-style named marker which holds + * the base64 encoded `$data`. + * If `$hash` is provided, it's appended to the base64 encoded string + * using `|` as the separator (in order to support building the + * somewhat special/different INJECTLATER marker). + * + * @param string $name Marker name. + * @param string $data Marker data which will be base64-encoded. + * @param string|null $hash Optional. + * + * @return string + */ + public static function build_marker( $name, $data, $hash = null ) + { + // Start the marker, add the data. + $marker = '%%' . $name . AUTOPTIMIZE_HASH . '%%' . base64_encode( $data ); + + // Add the hash if provided. + if ( null !== $hash ) { + $marker .= '|' . $hash; + } + + // Close the marker. + $marker .= '%%' . $name . '%%'; + + return $marker; + } + + /** + * Returns true if the string is a valid regex. + * + * @param string $string String, duh. + * + * @return bool + */ + protected function str_is_valid_regex( $string ) + { + set_error_handler( function() {}, E_WARNING ); + $is_regex = ( false !== preg_match( $string, '' ) ); + restore_error_handler(); + + return $is_regex; + } + + /** + * Searches for `$search` in `$content` (using either `preg_match()` + * or `strpos()`, depending on whether `$search` is a valid regex pattern or not). + * If something is found, it replaces `$content` using `$re_replace_pattern`, + * effectively creating our named markers (`%%{$marker}%%`. + * These are then at some point replaced back to their actual/original/modified + * contents using `autoptimizeBase::restore_marked_content()`. + * + * @param string $marker Marker name (without percent characters). + * @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()`. + * @param string $re_replace_pattern Regex pattern to use when replacing contents. + * @param string $content Content to work on. + * + * @return string + */ + protected function replace_contents_with_marker_if_exists( $marker, $search, $re_replace_pattern, $content ) + { + $found = false; + + $is_regex = $this->str_is_valid_regex( $search ); + if ( $is_regex ) { + $found = preg_match( $search, $content ); + } else { + $found = ( false !== strpos( $content, $search ) ); + } + + if ( $found ) { + $content = preg_replace_callback( + $re_replace_pattern, + function( $matches ) use ( $marker ) { + return autoptimizeBase::build_marker( $marker, $matches[0] ); + }, + $content + ); + } + + return $content; + } + + /** + * Complements `autoptimizeBase::replace_contents_with_marker_if_exists()`. + * + * @param string $marker Marker. + * @param string $content Markup. + * + * @return string + */ + protected function restore_marked_content( $marker, $content ) + { + if ( false !== strpos( $content, $marker ) ) { + $content = preg_replace_callback( + '#%%' . $marker . AUTOPTIMIZE_HASH . '%%(.*?)%%' . $marker . '%%#is', + function ( $matches ) { + return base64_decode( $matches[1] ); + }, + $content + ); + } + + return $content; + } + + /** + * Logs given `$data` for debugging purposes (when debug logging is on). + * + * @param mixed $data Data to log. + * + * @return void + */ + protected function debug_log( $data ) + { + if ( ! isset( $this->debug_log ) || ! $this->debug_log ) { + return; + } + + if ( ! is_string( $data ) && ! is_resource( $data ) ) { + $data = var_export( $data, true ); + } + + error_log( $data ); + } + + /** + * Minifies a single local css/js file and returns its (cached) url. + * + * @param string $filepath Filepath. + * + * @return bool|string Url pointing to the minified css/js file or false. + */ + protected function minify_single( $filepath ) + { + // Decide what we're dealing with, return false if we don't know. + if ( $this->str_ends_in( $filepath, '.js' ) ) { + $type = 'js'; + $mime = 'text/javascript'; + } elseif ( $this->str_ends_in( $filepath, '.css' ) ) { + $type = 'css'; + $mime = 'text/css'; + } else { + return false; + } + + // Bail if it looks like its already minifed (by having -min or .min + // in filename) or if it looks like WP jquery.js (which is minified). + $minified_variants = array( + '-min.' . $type, + '.min.' . $type, + 'js/jquery/jquery.js', + ); + foreach ( $minified_variants as $ending ) { + if ( $this->str_ends_in( $filepath, $ending ) ) { + return false; + } + } + + // Get file contents, bail if empty. + $contents = file_get_contents( $filepath ); + if ( empty( $contents ) ) { + return false; + } + + // Check cache. + $hash = 'single_' . md5( $contents ); + $cache = new autoptimizeCache( $hash, $type ); + + // If not in cache already, minify... + if ( ! $cache->check() ) { + if ( 'js' === $type ) { + $contents = trim( JSMin::minify( $contents ) ); + } elseif ( 'css' === $type ) { + $cssmin = new autoptimizeCSSmin(); + $contents = trim( $cssmin->run( $contents ) ); + } + // Store in cache. + $cache->cache( $contents, $mime ); + } + $url = AUTOPTIMIZE_CACHE_URL . $cache->getname(); + unset( $cache ); + + // CDN-replace if needed... + $url = $this->url_replace_cdn( $url ); + + return $url; + } + + /** + * Returns true if given $str ends with given $test. + * + * @param string $str String to check. + * @param string $test Ending to match. + * + * @return bool + */ + protected function str_ends_in( $str, $test ) + { + // @codingStandardsIgnoreStart + // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH + // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) ); + // @codingStandardsIgnoreEnd + + $length = strlen( $test ); + + return ( substr( $str, -$length, $length ) === $test ); + } +} diff --git a/tests/classes/autoptimizeCLI.php b/tests/classes/autoptimizeCLI.php new file mode 100644 index 00000000..6d0adade --- /dev/null +++ b/tests/classes/autoptimizeCLI.php @@ -0,0 +1,34 @@ +minifier = new Autoptimize\tubalmartin\CssMin\Minifier( $raise_limits ); + } + + /** + * Runs the minifier on given string of $css. + * Returns the minified css. + * + * @param string $css CSS to minify. + * + * @return string + */ + public function run( $css ) + { + $result = $this->minifier->run( $css ); + + return $result; + } + + /** + * Static helper. + * + * @param string $css CSS to minify. + * + * @return string + */ + public static function minify( $css ) + { + $minifier = new self(); + + return $minifier->run( $css ); + } +} diff --git a/tests/classes/autoptimizeCache.php b/tests/classes/autoptimizeCache.php new file mode 100644 index 00000000..3657f739 --- /dev/null +++ b/tests/classes/autoptimizeCache.php @@ -0,0 +1,469 @@ + we don't gzip, the web server does it. + * False => we do it ourselves. + * + * @var bool + */ + private $nogzip; + + /** + * Ctor. + * + * @param string $md5 Hash. + * @param string $ext Extension. + */ + public function __construct( $md5, $ext = 'php' ) + { + $this->cachedir = AUTOPTIMIZE_CACHE_DIR; + $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP; + if ( ! $this->nogzip ) { + $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.php'; + } else { + if ( in_array( $ext, array( 'js', 'css' ) ) ) { + $this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.' . $ext; + } else { + $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.' . $ext; + } + } + } + + /** + * Returns true if the cached file exists on disk. + * + * @return bool + */ + public function check() + { + return file_exists( $this->cachedir . $this->filename ); + } + + /** + * Returns cache contents if they exist, false otherwise. + * + * @return string|false + */ + public function retrieve() + { + if ( $this->check() ) { + if ( false == $this->nogzip ) { + return file_get_contents( $this->cachedir . $this->filename . '.none' ); + } else { + return file_get_contents( $this->cachedir . $this->filename ); + } + } + return false; + } + + /** + * Stores given $data in cache. + * + * @param string $data Data to cache. + * @param string $mime Mimetype. + * + * @return void + */ + public function cache( $data, $mime ) + { + if ( false === $this->nogzip ) { + // We handle gzipping ourselves. + $file = 'default.php'; + $phpcode = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $file ); + $phpcode = str_replace( array( '%%CONTENT%%', 'exit;' ), array( $mime, '' ), $phpcode ); + + file_put_contents( $this->cachedir . $this->filename, $phpcode, LOCK_EX ); + file_put_contents( $this->cachedir . $this->filename . '.none', $data, LOCK_EX ); + } else { + // Write code to cache without doing anything else. + file_put_contents( $this->cachedir . $this->filename, $data, LOCK_EX ); + if ( apply_filters( 'autoptimize_filter_cache_create_static_gzip', false ) ) { + // Create an additional cached gzip file. + file_put_contents( $this->cachedir . $this->filename . '.gz', gzencode( $data, 9, FORCE_GZIP ), LOCK_EX ); + } + } + } + + /** + * Get cache filename. + * + * @return string + */ + public function getname() + { + // NOTE: This could've maybe been a do_action() instead, however, + // that ship has sailed. + // The original idea here was to provide 3rd party code a hook so that + // it can "listen" to all the complete autoptimized-urls that the page + // will emit... Or something to that effect I think? + apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename ); + + return $this->filename; + } + + /** + * Returns true if given `$file` is considered a valid Autoptimize cache file, + * false otherwise. + * + * @param string $dir Directory name (with a trailing slash). + * @param string $file Filename. + * @return bool + */ + protected static function is_valid_cache_file( $dir, $file ) + { + if ( '.' !== $file && '..' !== $file && + false !== strpos( $file, AUTOPTIMIZE_CACHEFILE_PREFIX ) && + is_file( $dir . $file ) ) { + + // It's a valid file! + return true; + } + + // Everything else is considered invalid! + return false; + } + + /** + * Deletes everything from the cache directories. + * + * @return bool + */ + public static function clearall() + { + if ( ! autoptimizeCache::cacheavail() ) { + return false; + } + + $contents = self::get_cache_contents(); + foreach ( $contents as $name => $files ) { + $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/'; + foreach ( $files as $file ) { + if ( self::is_valid_cache_file( $dir, $file ) ) { + @unlink( $dir . $file ); // @codingStandardsIgnoreLine + } + } + } + + @unlink( AUTOPTIMIZE_CACHE_DIR . '/.htaccess' ); // @codingStandardsIgnoreLine + delete_transient( 'autoptimize_stats' ); + + // Cache was just purged! + if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) { + function autoptimize_do_cachepurged_action() { + do_action( 'autoptimize_action_cachepurged' ); + } + } + add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 ); + add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 ); + + // Warm cache (part of speedupper)! + if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) { + $url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 ); + $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine + unset( $cache ); + } + + return true; + } + + /** + * Returns the contents of our cache dirs. + * + * @return array + */ + protected static function get_cache_contents() + { + $contents = array(); + + foreach ( array( '', 'js', 'css' ) as $dir ) { + $contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir ); + } + + return $contents; + } + + /** + * Returns stats about cached contents. + * + * @return array + */ + public static function stats() + { + $stats = get_transient( 'autoptimize_stats' ); + + // If no transient, do the actual scan! + if ( ! is_array( $stats ) ) { + if ( ! autoptimizeCache::cacheavail() ) { + return 0; + } + $stats = self::stats_scan(); + $count = $stats[0]; + if ( $count > 100 ) { + // Store results in transient. + set_transient( + 'autoptimize_stats', + $stats, + apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS ) + ); + } + } + + return $stats; + } + + /** + * Performs a scan of cache directory contents and returns an array + * with 3 values: count, size, timestamp. + * count = total number of found files + * size = total filesize (in bytes) of found files + * timestamp = unix timestamp when the scan was last performed/finished. + * + * @return array + */ + protected static function stats_scan() + { + $count = 0; + $size = 0; + + // Scan everything in our cache directories. + foreach ( self::get_cache_contents() as $name => $files ) { + $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/'; + foreach ( $files as $file ) { + if ( self::is_valid_cache_file( $dir, $file ) ) { + if ( AUTOPTIMIZE_CACHE_NOGZIP && + ( + false !== strpos( $file, '.js' ) || + false !== strpos( $file, '.css' ) || + false !== strpos( $file, '.img' ) || + false !== strpos( $file, '.txt' ) + ) + ) { + // Web server is gzipping, we count .js|.css|.img|.txt files. + $count++; + } elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) { + // We are gzipping ourselves via php, counting only .none files. + $count++; + } + $size += filesize( $dir . $file ); + } + } + } + + $stats = array( $count, $size, time() ); + + return $stats; + } + + /** + * Ensures the cache directory exists, is writeable and contains the + * required .htaccess files. + * Returns false in case it fails to ensure any of those things. + * + * @return bool + */ + public static function cacheavail() + { + if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) { + // We didn't set a cache. + return false; + } + + foreach ( array( '', 'js', 'css' ) as $dir ) { + if ( ! autoptimizeCache::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) { + return false; + } + } + + // Using .htaccess inside our cache folder to overrule wp-super-cache. + $htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess'; + if ( ! is_file( $htaccess ) ) { + /** + * Create `wp-content/AO_htaccess_tmpl` file with + * whatever htaccess rules you might need + * if you want to override default AO htaccess + */ + $htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl'; + if ( is_file( $htaccess_tmpl ) ) { + $content = file_get_contents( $htaccess_tmpl ); + } elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) { + $content = ' + ExpiresActive On + ExpiresByType text/css A30672000 + ExpiresByType text/javascript A30672000 + ExpiresByType application/javascript A30672000 + + + Header append Cache-Control "public, immutable" + + + + SetOutputFilter DEFLATE + + + + + Require all granted + + + + + Order allow,deny + Allow from all + +'; + } else { + $content = ' + ExpiresActive On + ExpiresByType text/css A30672000 + ExpiresByType text/javascript A30672000 + ExpiresByType application/javascript A30672000 + + + Header append Cache-Control "public, immutable" + + + + SetOutputFilter DEFLATE + + + + + Require all denied + + + + + Order deny,allow + Deny from all + +'; + } + @file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine + } + + // All OK! + return true; + } + + /** + * Ensures the specified `$dir` exists and is writeable. + * Returns false if that's not the case. + * + * @param string $dir Directory to check/create. + * + * @return bool + */ + protected static function check_cache_dir( $dir ) + { + // Try creating the dir if it doesn't exist. + if ( ! file_exists( $dir ) ) { + @mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine + if ( ! file_exists( $dir ) ) { + return false; + } + } + + // If we still cannot write, bail. + if ( ! is_writable( $dir ) ) { + return false; + } + + // Create an index.html in there to avoid prying eyes! + $idx_file = rtrim( $dir, '/\\' ) . '/index.html'; + if ( ! is_file( $idx_file ) ) { + @file_put_contents( $idx_file, 'Generated by Autoptimize' ); // @codingStandardsIgnoreLine + } + + return true; + } + + /** + * Flushes as many page cache plugin's caches as possible. + * + * @return void + */ + // @codingStandardsIgnoreStart + public static function flushPageCache() + { + if ( function_exists( 'wp_cache_clear_cache' ) ) { + if ( is_multisite() ) { + $blog_id = get_current_blog_id(); + wp_cache_clear_cache( $blog_id ); + } else { + wp_cache_clear_cache(); + } + } elseif ( has_action( 'cachify_flush_cache' ) ) { + do_action( 'cachify_flush_cache' ); + } elseif ( function_exists( 'w3tc_pgcache_flush' ) ) { + w3tc_pgcache_flush(); + } elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) { + wp_fast_cache_bulk_delete_all(); + } elseif ( class_exists( 'WpFastestCache' ) ) { + $wpfc = new WpFastestCache(); + $wpfc->deleteCache(); + } elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) { + c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache + } elseif ( class_exists( 'zencache' ) ) { + zencache::clear(); + } elseif ( class_exists( 'comet_cache' ) ) { + comet_cache::clear(); + } elseif ( class_exists( 'WpeCommon' ) ) { + // WPEngine cache purge/flush methods to call by default + $wpe_methods = array( + 'purge_varnish_cache', + ); + + // More agressive clear/flush/purge behind a filter + if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) { + $wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) ); + } + + // Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing) + $wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods ); + + foreach ( $wpe_methods as $wpe_method ) { + if ( method_exists( 'WpeCommon', $wpe_method ) ) { + WpeCommon::$wpe_method(); + } + } + } elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) { + sg_cachepress_purge_cache(); + } elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) { + // fallback for WP-Super-Cache + global $cache_path; + if ( is_multisite() ) { + $blog_id = get_current_blog_id(); + prune_super_cache( get_supercache_dir( $blog_id ), true ); + prune_super_cache( $cache_path . 'blogs/', true ); + } else { + prune_super_cache( $cache_path . 'supercache/', true ); + prune_super_cache( $cache_path, true ); + } + } + } + // @codingStandardsIgnoreEnd +} diff --git a/tests/classes/autoptimizeCacheChecker.php b/tests/classes/autoptimizeCacheChecker.php new file mode 100644 index 00000000..3f14c2a0 --- /dev/null +++ b/tests/classes/autoptimizeCacheChecker.php @@ -0,0 +1,84 @@ + 0.5GB (size is filterable), if so, an option is set which controls showing an admin notice. + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +class autoptimizeCacheChecker +{ + const SCHEDULE_HOOK = 'ao_cachechecker'; + + public function __construct() + { + } + + public function run() + { + $this->add_hooks(); + } + + public function add_hooks() + { + if ( is_admin() ) { + add_action( 'plugins_loaded', array( $this, 'setup' ) ); + } + add_action( self::SCHEDULE_HOOK, array( $this, 'cronjob' ) ); + add_action( 'admin_notices', array( $this, 'show_admin_notice' ) ); + } + + public function setup() + { + $do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true ); + $schedule = wp_get_schedule( self::SCHEDULE_HOOK ); + $frequency = apply_filters( 'autoptimize_filter_cachecheck_frequency', 'daily' ); + if ( ! in_array( $frequency, array( 'hourly', 'daily', 'monthly' ) ) ) { + $frequency = 'daily'; + } + if ( $do_cache_check && ( ! $schedule || $schedule !== $frequency ) ) { + wp_schedule_event( time(), $frequency, self::SCHEDULE_HOOK ); + } elseif ( $schedule && ! $do_cache_check ) { + wp_clear_scheduled_hook( self::SCHEDULE_HOOK ); + } + } + + public function cronjob() + { + $max_size = (int) apply_filters( 'autoptimize_filter_cachecheck_maxsize', 536870912 ); + $do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true ); + $stat_array = autoptimizeCache::stats(); + $cache_size = round( $stat_array[1] ); + if ( ( $cache_size > $max_size ) && ( $do_cache_check ) ) { + update_option( 'autoptimize_cachesize_notice', true ); + if ( apply_filters( 'autoptimize_filter_cachecheck_sendmail', true ) ) { + $site_url = esc_url( site_url() ); + $ao_mailto = apply_filters( 'autoptimize_filter_cachecheck_mailto', get_option( 'admin_email', '' ) ); + + $ao_mailsubject = __( 'Autoptimize cache size warning', 'autoptimize' ) . ' (' . $site_url . ')'; + $ao_mailbody = __( 'Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at https://wordpress.org/plugins/autoptimize/faq/ to see how you can keep the cache size under control.', 'autoptimize' ) . ' (site: ' . $site_url . ')'; + + if ( ! empty( $ao_mailto ) ) { + $ao_mailresult = wp_mail( $ao_mailto, $ao_mailsubject, $ao_mailbody ); + if ( ! $ao_mailresult ) { + error_log( 'Autoptimize could not send cache size warning mail.' ); + } + } + } + } + } + + public function show_admin_notice() + { + if ( (bool) get_option( 'autoptimize_cachesize_notice', false ) ) { + echo '

'; + _e( 'Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at the Autoptimize FAQ to see how you can keep the cache size under control.', 'autoptimize' ); + echo '

'; + update_option( 'autoptimize_cachesize_notice', false ); + } + } +} diff --git a/tests/classes/autoptimizeConfig.php b/tests/classes/autoptimizeConfig.php new file mode 100644 index 00000000..7b3f2b8a --- /dev/null +++ b/tests/classes/autoptimizeConfig.php @@ -0,0 +1,807 @@ +settings_screen_do_remote_http = apply_filters( 'autoptimize_settingsscreen_remotehttp', $this->settings_screen_do_remote_http ); + } + + // Adds the Autoptimize Toolbar to the Admin bar. + // (we load outside the is_admin check so it's also displayed on the frontend toolbar) + $toolbar = new autoptimizeToolbar(); + } + + /** + * @return autoptimizeConfig + */ + static public function instance() + { + // Only one instance + if ( null === self::$instance ) { + self::$instance = new autoptimizeConfig(); + } + + return self::$instance; + } + + public function show() + { + $conf = self::instance(); +?> + + +
+ + +
' . sprintf( __( 'You are using a very old version of PHP (5.2.x or older) which has serious security and performance issues. Support for PHP 5.5 and below will be removed in one of the next AO released, please ask your hoster to provide you with an upgrade path to 7.x.', 'autoptimize' ), '"http://blog.futtta.be/2016/03/15/why-would-you-still-be-on-php-5-2/" target="_blank"' ) . '

'; ?>
+ + + +

+ +

+ + +
+
+

+ + + + + + + + + + + + +

+
+ + ao_admin_tabs(); ?> + +
+ + +
    + +
  • +

    + + + + + + + + + +
    />
    +
  • + +
  • +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    />
    '. __('(deprecated)','autoptimize') . ''; ?>
    +
  • + +
  • +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    />
    '. __('(deprecated)','autoptimize') . ''; ?>
    +
  • + +
  • +

    + + + + + +
    +
  • + +
  • +

    + + + + + + + + + + + + + +
    0 ) { + $details = ', ~' . $AOcacheSize . ' total'; + } + printf( __( '%1$s files, totalling %2$s Kbytes (calculated at %3$s)', 'autoptimize' ), $AOstatArr[0], $AOcacheSize, date( 'H:i e', $AOstatArr[2] ) ); + } +?>
    +
  • + +
  • +

    + + + + + + + + + + + + + + + +
    +
    +
  • + +
+ + + +

+ + +

+ +
+
+ + + +
+ +%s', __( 'Settings' ) ); + array_unshift( $links, $settings_link ); + } else { + //2.8 + //If it's us, add the link + if ( $file === $plugin ) { + $newlink = array( sprintf( '%s', __( 'Settings' ) ) ); + $links = array_merge( $links, $newlink ); + } + } + + return $links; + } + + /** + * @return array + */ + public static function get_defaults() + { + static $config = array( + 'autoptimize_html' => 0, + 'autoptimize_html_keepcomments' => 0, + 'autoptimize_js' => 0, + 'autoptimize_js_aggregate' => 1, + 'autoptimize_js_exclude' => 'seal.js, js/jquery/jquery.js', + 'autoptimize_js_trycatch' => 0, + 'autoptimize_js_justhead' => 0, + 'autoptimize_js_include_inline' => 0, + 'autoptimize_js_forcehead' => 0, + 'autoptimize_css' => 0, + 'autoptimize_css_aggregate' => 1, + 'autoptimize_css_exclude' => 'admin-bar.min.css, dashicons.min.css, wp-content/cache/, wp-content/uploads/', + 'autoptimize_css_justhead' => 0, + 'autoptimize_css_include_inline' => 1, + 'autoptimize_css_defer' => 0, + 'autoptimize_css_defer_inline' => '', + 'autoptimize_css_inline' => 0, + 'autoptimize_css_datauris' => 0, + 'autoptimize_cdn_url' => '', + 'autoptimize_cache_nogzip' => 1, + 'autoptimize_show_adv' => 0, + 'autoptimize_optimize_logged' => 1, + 'autoptimize_optimize_checkout' => 1 + ); + + return $config; + } + + /** + * Returns default option values for autoptimizeExtra. + * + * @return array + */ + public static function get_ao_extra_default_options() + { + $defaults = array( + 'autoptimize_extra_checkbox_field_1' => '0', + 'autoptimize_extra_checkbox_field_0' => '0', + 'autoptimize_extra_radio_field_4' => '1', + 'autoptimize_extra_text_field_2' => '', + 'autoptimize_extra_text_field_3' => '', + ); + + return $defaults; + } + + public function get($key) + { + if ( ! is_array( $this->config ) ) { + // Default config + $config = self::get_defaults(); + + // Override with user settings + foreach ( array_keys( $config ) as $name ) { + $conf = get_option( $name ); + if ( false !== $conf ) { + // It was set before! + $config[ $name ] = $conf; + } + } + + // Save for next call + $this->config = apply_filters( 'autoptimize_filter_get_config', $config ); + } + + if ( isset( $this->config[ $key ] ) ) { + return $this->config[ $key ]; + } + + return false; + } + + private function getFutttaFeeds($url) { + if ( $this->settings_screen_do_remote_http ) { + $rss = fetch_feed( $url ); + $maxitems = 0; + + if ( ! is_wp_error( $rss ) ) { + $maxitems = $rss->get_item_quantity( 7 ); + $rss_items = $rss->get_items( 0, $maxitems ); + } + ?> + + __( 'Main', 'autoptimize' ) ) ); + $tabContent = ''; + $tabs_count = count($tabs); + if ( $tabs_count > 1 ) { + if ( isset( $_GET['page'] ) ) { + $currentId = $_GET['page']; + } else { + $currentId = "autoptimize"; + } + $tabContent .= ''; + } else { + $tabContent = '
'; + } + + return $tabContent; + } + + /** + * Returns true if in admin (and not in admin-ajax.php!) + * + * @return bool + */ + public static function is_admin_and_not_ajax() + { + return ( is_admin() && ! self::doing_ajax() ); + } + + /** + * Returns true if doing ajax. + * + * @return type + */ + protected static function doing_ajax() + { + if ( function_exists( 'wp_doing_ajax' ) ) { + return wp_doing_ajax(); + } else { + return ( defined( 'DOING_AJAX' ) && DOING_AJAX ); + } + } +} diff --git a/tests/classes/autoptimizeExtra.php b/tests/classes/autoptimizeExtra.php new file mode 100644 index 00000000..8d20bd5a --- /dev/null +++ b/tests/classes/autoptimizeExtra.php @@ -0,0 +1,390 @@ +fetch_options(); + } + + $this->options = $options; + } + + public function run() + { + if ( is_admin() ) { + add_action( 'admin_menu', array( $this, 'admin_menu' ) ); + add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) ); + } else { + $this->run_on_frontend(); + } + } + + protected function fetch_options() + { + $value = get_option( 'autoptimize_extra_settings' ); + if ( empty( $value ) ) { + // Fallback to returning defaults when no stored option exists yet. + $value = autoptimizeConfig::get_ao_extra_default_options(); + } + + return $value; + } + + public function disable_emojis() + { + // Removing all actions related to emojis! + remove_action( 'admin_print_styles', 'print_emoji_styles' ); + remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); + remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); + remove_action( 'wp_print_styles', 'print_emoji_styles' ); + remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); + remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); + remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); + + // Removes TinyMCE emojis. + add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) ); + + // Removes emoji dns-preftech. + add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 ); + } + + public function filter_disable_emojis_tinymce( $plugins ) + { + if ( is_array( $plugins ) ) { + return array_diff( $plugins, array( 'wpemoji' ) ); + } else { + return array(); + } + } + + public function filter_remove_qs( $src ) { + if ( strpos( $src, '?ver=' ) ) { + $src = remove_query_arg( 'ver', $src ); + } + + return $src; + } + + public function extra_async_js( $in ) + { + $exclusions = array(); + if ( ! empty( $in ) ) { + $exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' ); + } + + $settings = $this->options['autoptimize_extra_text_field_3']; + $async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' ); + $attr = apply_filters( 'autoptimize_filter_extra_async', 'async' ); + foreach ( $async as $k => $v ) { + $async[ $k ] = $attr; + } + + // Merge exclusions & asyncs in one array and return to AO API. + $merged = array_merge( $exclusions, $async ); + + return $merged; + } + + protected function run_on_frontend() + { + $options = $this->options; + + // Disable emojis if specified. + if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) { + $this->disable_emojis(); + } + + // Remove version query parameters. + if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) { + add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 ); + add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 ); + } + + // Async JS! + if ( ! empty( $options['autoptimize_extra_text_field_3'] ) ) { + add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 ); + } + + // Optimize google fonts! + if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) { + add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 ); + if ( '2' === $options['autoptimize_extra_radio_field_4'] ) { + add_filter( 'autoptimize_filter_css_removables', array( $this, 'filter_remove_gfonts' ), 10, 1 ); + } else { + add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 ); + add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 ); + } + } + + // Preconnect! + if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) { + add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 ); + } + } + + public function filter_remove_gfonts( $in ) + { + // Adds 'fonts.googleapis.com' to the `autoptimize_filter_css_removables` list. + return $in . ', fonts.googleapis.com'; + } + + public function filter_remove_emoji_dns_prefetch( $urls, $relation_type ) + { + $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' ); + + return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url ); + } + + public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type ) + { + return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' ); + } + + public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove ) + { + if ( 'dns-prefetch' === $relation_type ) { + $cnt = 0; + foreach ( $urls as $url ) { + if ( false !== strpos( $url, $url_to_remove ) ) { + unset( $urls[ $cnt ] ); + } + $cnt++; + } + } + + return $urls; + } + + public function filter_optimize_google_fonts( $in ) + { + // Extract fonts, partly based on wp rocket's extraction code. + $markup = preg_replace( '//Uis', '', $in ); + preg_match_all( '#])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches ); + + $fonts_collection = array(); + if ( ! $matches[2] ) { + return $in; + } + + // Store them in $fonts array. + $i = 0; + foreach ( $matches[2] as $font ) { + if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) { + // Get fonts name. + $font = str_replace( array( '%7C', '%7c' ), '|', $font ); + $font = explode( 'family=', $font ); + $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array(); + // Add font to $fonts[$i] but make sure not to pollute with an empty family! + $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) ); + if ( ! empty( $_thisfont ) ) { + $fonts_collection[ $i ]['fonts'] = $_thisfont; + // And add subset if any! + $subset = ( is_array( $font ) ) ? end( $font ) : ''; + if ( false !== strpos( $subset, 'subset=' ) ) { + $subset = explode( 'subset=', $subset ); + $fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] ); + } + } + // And remove Google Fonts. + $in = str_replace( $matches[0][ $i ], '', $in ); + } + $i++; + } + + $options = $this->options; + $fonts_markup = ''; + if ( '3' === $options['autoptimize_extra_radio_field_4'] ) { + // Aggregate & link! + $fonts_string = ''; + $subset_string = ''; + foreach ( $fonts_collection as $font ) { + $fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' ); + if ( ! empty( $font['subsets'] ) ) { + $subset_string .= implode( ',', $font['subsets'] ); + } + } + + if ( ! empty( $subset_string ) ) { + $fonts_string = $fonts_string . '#038;subset=' . $subset_string; + } + + $fonts_string = str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) ); + + if ( ! empty( $fonts_string ) ) { + $fonts_markup = ''; + } + } elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) { + // Aggregate & load async (webfont.js impl.)! + $fonts_array = array(); + foreach ( $fonts_collection as $_fonts ) { + if ( ! empty( $_fonts['subsets'] ) ) { + $_subset = implode( ',', $_fonts['subsets'] ); + foreach ( $_fonts['fonts'] as $key => $_one_font ) { + $_one_font = $_one_font . ':' . $_subset; + $_fonts['fonts'][ $key ] = $_one_font; + } + } + $fonts_array = array_merge( $fonts_array, $_fonts['fonts'] ); + } + + $fonts_markup = ''; + } + + // Replace back in markup. + $out = substr_replace( $in, $fonts_markup . 'options; + + // Get settings and store in array. + $preconns = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_2'] ) ) ); + $preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns ); + + // Walk array, extract domain and add to new array with crossorigin attribute. + foreach ( $preconns as $preconn ) { + $parsed = parse_url( $preconn ); + + if ( is_array( $parsed ) && empty( $parsed['scheme'] ) ) { + $domain = '//' . $parsed['host']; + } elseif ( is_array( $parsed ) ) { + $domain = $parsed['scheme'] . '://' . $parsed['host']; + } + + if ( ! empty( $domain ) ) { + $hint = array( 'href' => $domain ); + // Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set + // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed. + $crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) ); + if ( in_array( $domain, $crossorigins ) ) { + $hint['crossorigin'] = 'anonymous'; + } + $new_hints[] = $hint; + } + } + + // Merge in WP's preconnect hints. + if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) { + $hints = array_merge( $hints, $new_hints ); + } + + return $hints; + } + + public function filter_preconnect_google_fonts( $in ) + { + $in[] = 'https://fonts.gstatic.com'; + + if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) { + // Preconnect even more hosts for webfont.js! + $in[] = 'https://ajax.googleapis.com'; + $in[] = 'https://fonts.googleapis.com'; + } + + return $in; + } + + public function admin_menu() + { + add_submenu_page( null, 'autoptimize_extra', 'autoptimize_extra', 'manage_options', 'autoptimize_extra', array( $this, 'options_page' ) ); + register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' ); + } + + public function add_extra_tab( $in ) + { + $in = array_merge( $in, array( 'autoptimize_extra' => __( 'Extra', 'autoptimize' ) ) ); + + return $in; + } + + public function options_page() + { + // Working with actual option values from the database here. + // That way any saves are still processed as expected, but we can still + // override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom + // behavior being persisted in the DB even if save is done here. + $options = $this->fetch_options(); + $gfonts = $options['autoptimize_extra_radio_field_4']; + ?> + +
+

+ +
+ +

+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ >
+ >
+ >
+ >webfont.js', 'autoptimize' ); ?>
+
(advanced users)', 'autoptimize' ); ?> + +
(advanced users)', 'autoptimize' ); ?> + '> +
+ async flag. JS-files from your own site will be automatically excluded if added here.', 'autoptimize' ); ?> +
+

+
+ ', + '', + '', + ); + + public function read( $options ) + { + // Remove the HTML comments? + $this->keepcomments = (bool) $options['keepcomments']; + + // Filter to force xhtml. + $this->forcexhtml = (bool) apply_filters( 'autoptimize_filter_html_forcexhtml', false ); + + // Filterable strings to be excluded from HTML minification. + $exclude = apply_filters( 'autoptimize_filter_html_exclude', '' ); + if ( '' !== $exclude ) { + $exclude_arr = array_filter( array_map( 'trim', explode( ',', $exclude ) ) ); + $this->exclude = array_merge( $exclude_arr, $this->exclude ); + } + + // Nothing else for HTML! + return true; + } + + /** + * Minifies HTML. + * + * @return bool + */ + public function minify() + { + $noptimize = apply_filters( 'autoptimize_filter_html_noptimize', false, $this->content ); + if ( $noptimize ) { + return false; + } + + // Wrap the to-be-excluded strings in noptimize tags. + foreach ( $this->exclude as $str ) { + if ( false !== strpos( $this->content, $str ) ) { + $replacement = '' . $str . ''; + $this->content = str_replace( $str, $replacement, $this->content ); + } + } + + // Noptimize. + $this->content = $this->hide_noptimize( $this->content ); + + // Preparing options for Minify_HTML. + $options = array( 'keepComments' => $this->keepcomments ); + if ( $this->forcexhtml ) { + $options['xhtml'] = true; + } + + $tmp_content = Minify_HTML::minify( $this->content, $options ); + if ( ! empty( $tmp_content ) ) { + $this->content = $tmp_content; + unset( $tmp_content ); + } + + // Restore noptimize. + $this->content = $this->restore_noptimize( $this->content ); + + // Remove the noptimize-wrapper from around the excluded strings. + foreach ( $this->exclude as $str ) { + $replacement = '' . $str . ''; + if ( false !== strpos( $this->content, $replacement ) ) { + $this->content = str_replace( $replacement, $str, $this->content ); + } + } + + // Revslider data attribs somehow suffer from HTML optimization, this fixes that! + if ( class_exists( 'RevSlider' ) && apply_filters( 'autoptimize_filter_html_dataattrib_cleanup', false ) ) { + $this->content = preg_replace( '#\n(data-.*$)\n#Um', ' $1 ', $this->content ); + $this->content = preg_replace( '#<[^>]*(=\"[^"\'<>\s]*\")(\w)#', '$1 $2', $this->content ); + } + + return true; + } + + /** + * Doesn't do much in case of HTML (no cache in css/js sense there) + * + * @return true + */ + public function cache() + { + return true; + } + + /** + * Returns the HTML markup. + * + * @return string + */ + public function getcontent() + { + return $this->content; + } +} diff --git a/tests/classes/autoptimizeMain.php b/tests/classes/autoptimizeMain.php new file mode 100644 index 00000000..a1156bc8 --- /dev/null +++ b/tests/classes/autoptimizeMain.php @@ -0,0 +1,503 @@ +version = $version; + $this->filepath = $filepath; + } + + public function run() + { + $this->add_hooks(); + + // Runs cache size checker. + $checker = new autoptimizeCacheChecker(); + $checker->run(); + } + + protected function add_hooks() + { + add_action( 'plugins_loaded', array( $this, 'setup' ) ); + + add_action( 'autoptimize_setup_done', array( $this, 'version_upgrades_check' ) ); + add_action( 'autoptimize_setup_done', array( $this, 'check_cache_and_run' ) ); + add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_ao_extra' ) ); + add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_partners_tab' ) ); + + add_action( 'init', array( $this, 'load_textdomain' ) ); + + register_activation_hook( $this->filepath, array( $this, 'on_activate' ) ); + } + + public function on_activate() + { + register_uninstall_hook( $this->filepath, array( $this, 'on_uninstall' ) ); + } + + public function load_textdomain() + { + load_plugin_textdomain( 'autoptimize' ); + } + + public function setup() + { + // Do we gzip in php when caching or is the webserver doing it? + define( 'AUTOPTIMIZE_CACHE_NOGZIP', (bool) get_option( 'autoptimize_cache_nogzip' ) ); + + // These can be overridden by specifying them in wp-config.php or such. + if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_NAME' ) ) { + define( 'AUTOPTIMIZE_WP_CONTENT_NAME', '/' . wp_basename( WP_CONTENT_DIR ) ); + } + if ( ! defined( 'AUTOPTIMIZE_CACHE_CHILD_DIR' ) ) { + define( 'AUTOPTIMIZE_CACHE_CHILD_DIR', '/cache/autoptimize/' ); + } + if ( ! defined( 'AUTOPTIMIZE_CACHEFILE_PREFIX' ) ) { + define( 'AUTOPTIMIZE_CACHEFILE_PREFIX', 'autoptimize_' ); + } + if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) { + if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) { + $blog_id = get_current_blog_id(); + define( 'AUTOPTIMIZE_CACHE_DIR', WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR . $blog_id . '/' ); + } else { + define( 'AUTOPTIMIZE_CACHE_DIR', WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR ); + } + } + + define( 'WP_ROOT_DIR', substr( WP_CONTENT_DIR, 0, strlen( WP_CONTENT_DIR ) - strlen( AUTOPTIMIZE_WP_CONTENT_NAME ) ) ); + + if ( ! defined( 'AUTOPTIMIZE_WP_SITE_URL' ) ) { + if ( function_exists( 'domain_mapping_siteurl' ) ) { + define( 'AUTOPTIMIZE_WP_SITE_URL', domain_mapping_siteurl( get_current_blog_id() ) ); + } else { + define( 'AUTOPTIMIZE_WP_SITE_URL', site_url() ); + } + } + if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_URL' ) ) { + if ( function_exists( 'domain_mapping_siteurl' ) ) { + define( 'AUTOPTIMIZE_WP_CONTENT_URL', str_replace( get_original_url( AUTOPTIMIZE_WP_SITE_URL ), AUTOPTIMIZE_WP_SITE_URL, content_url() ) ); + } else { + define( 'AUTOPTIMIZE_WP_CONTENT_URL', content_url() ); + } + } + if ( ! defined( 'AUTOPTIMIZE_CACHE_URL' ) ) { + if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) { + $blog_id = get_current_blog_id(); + define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR . $blog_id . '/' ); + } else { + define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR ); + } + } + if ( ! defined( 'AUTOPTIMIZE_WP_ROOT_URL' ) ) { + define( 'AUTOPTIMIZE_WP_ROOT_URL', str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', AUTOPTIMIZE_WP_CONTENT_URL ) ); + } + if ( ! defined( 'AUTOPTIMIZE_HASH' ) ) { + define( 'AUTOPTIMIZE_HASH', wp_hash( AUTOPTIMIZE_CACHE_URL ) ); + } + + do_action( 'autoptimize_setup_done' ); + } + + /** + * Checks if there's a need to upgrade/update options and whatnot, + * in which case we might need to do stuff and flush the cache + * to avoid old versions of aggregated files lingering around. + */ + public function version_upgrades_check() + { + autoptimizeVersionUpdatesHandler::check_installed_and_update( $this->version ); + } + + public function check_cache_and_run() + { + if ( autoptimizeCache::cacheavail() ) { + $conf = autoptimizeConfig::instance(); + if ( $conf->get( 'autoptimize_html' ) || $conf->get( 'autoptimize_js' ) || $conf->get( 'autoptimize_css' ) ) { + // Hook into WordPress frontend. + if ( defined( 'AUTOPTIMIZE_INIT_EARLIER' ) ) { + add_action( + 'init', + array( $this, 'start_buffering' ), + self::INIT_EARLIER_PRIORITY + ); + } else { + if ( ! defined( 'AUTOPTIMIZE_HOOK_INTO' ) ) { + define( 'AUTOPTIMIZE_HOOK_INTO', 'template_redirect' ); + } + add_action( + constant( 'AUTOPTIMIZE_HOOK_INTO' ), + array( $this, 'start_buffering' ), + self::DEFAULT_HOOK_PRIORITY + ); + } + } + } else { + add_action( 'admin_notices', 'autoptimizeMain::notice_cache_unavailable' ); + } + } + + public function maybe_run_ao_extra() + { + if ( apply_filters( 'autoptimize_filter_extra_activate', true ) ) { + include AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizeExtra.php'; + $ao_extra = new autoptimizeExtra(); + $ao_extra->run(); + } + } + + public function maybe_run_partners_tab() + { + // Loads partners tab code if in admin (and not in admin-ajax.php)! + if ( autoptimizeConfig::is_admin_and_not_ajax() ) { + include AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizePartners.php'; + new autoptimizePartners(); + } + } + + /** + * Setup output buffering if needed. + * + * @return void + */ + public function start_buffering() + { + if ( $this->should_buffer() ) { + + // Load speedupper conditionally (true by default). + if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) { + $ao_speedupper = new autoptimizeSpeedupper(); + } + + $conf = autoptimizeConfig::instance(); + + if ( $conf->get( 'autoptimize_js' ) ) { + if ( ! defined( 'CONCATENATE_SCRIPTS' ) ) { + define( 'CONCATENATE_SCRIPTS', false ); + } + if ( ! defined( 'COMPRESS_SCRIPTS' ) ) { + define( 'COMPRESS_SCRIPTS', false ); + } + } + + if ( $conf->get( 'autoptimize_css' ) ) { + if ( ! defined( 'COMPRESS_CSS' ) ) { + define( 'COMPRESS_CSS', false ); + } + } + + if ( apply_filters( 'autoptimize_filter_obkiller', false ) ) { + while ( ob_get_level() > 0 ) { + ob_end_clean(); + } + } + + // Now, start the real thing! + ob_start( array( $this, 'end_buffering' ) ); + } + } + + /** + * Returns true if all the conditions to start output buffering are satisfied. + * + * @param bool $doing_tests Allows overriding the optimization of only + * deciding once per request (for use in tests). + * @return bool + */ + public function should_buffer( $doing_tests = false ) + { + static $do_buffering = null; + + // Only check once in case we're called multiple times by others but + // still allows multiple calls when doing tests. + if ( null === $do_buffering || $doing_tests ) { + + $ao_noptimize = false; + + // Checking for DONOTMINIFY constant as used by e.g. WooCommerce POS. + if ( defined( 'DONOTMINIFY' ) && ( constant( 'DONOTMINIFY' ) === true || constant( 'DONOTMINIFY' ) === 'true' ) ) { + $ao_noptimize = true; + } + + // Skip checking query strings if they're disabled. + if ( apply_filters( 'autoptimize_filter_honor_qs_noptimize', true ) ) { + // Check for `ao_noptimize` (and other) keys in the query string + // to get non-optimized page for debugging. + $keys = array( + 'ao_noptimize', + 'ao_noptirocket', + ); + foreach ( $keys as $key ) { + if ( array_key_exists( $key, $_GET ) && '1' === $_GET[ $key ] ) { + $ao_noptimize = true; + break; + } + } + } + + // If setting says not to optimize logged in user and user is logged in... + if ( 'on' !== get_option( 'autoptimize_optimize_logged', 'on' ) && is_user_logged_in() && current_user_can( 'edit_posts' ) ) { + $ao_noptimize = true; + } + + // If setting says not to optimize cart/checkout. + if ( 'on' !== get_option( 'autoptimize_optimize_checkout', 'on' ) ) { + // Checking for woocommerce, easy digital downloads and wp ecommerce... + foreach ( array( 'is_checkout', 'is_cart', 'edd_is_checkout', 'wpsc_is_cart', 'wpsc_is_checkout' ) as $func ) { + if ( function_exists( $func ) && $func() ) { + $ao_noptimize = true; + break; + } + } + } + + // Allows blocking of autoptimization on your own terms regardless of above decisions. + $ao_noptimize = (bool) apply_filters( 'autoptimize_filter_noptimize', $ao_noptimize ); + + // Check for site being previewed in the Customizer (available since WP 4.0). + $is_customize_preview = false; + if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) { + $is_customize_preview = is_customize_preview(); + } + + /** + * We only buffer the frontend requests (and then only if not a feed + * and not turned off explicitly and not when being previewed in Customizer)! + * NOTE: Tests throw a notice here due to is_feed() being called + * while the main query hasn't been ran yet. Thats why we use + * AUTOPTIMIZE_INIT_EARLIER in tests. + */ + $do_buffering = ( ! is_admin() && ! is_feed() && ! $ao_noptimize && ! $is_customize_preview ); + } + + return $do_buffering; + } + + /** + * Returns true if given markup is considered valid/processable/optimizable. + * + * @param string $content Markup. + * + * @return bool + */ + public function is_valid_buffer( $content ) + { + // Defaults to true. + $valid = true; + + $has_no_html_tag = ( false === stripos( $content, '/i', $content ) > 0 ); + + if ( $has_no_html_tag ) { + // Can't be valid amp markup without an html tag preceding it. + $is_amp_markup = false; + } else { + $is_amp_markup = self::is_amp_markup( $content ); + } + + // If it's not html, or if it's amp or contains xsl stylesheets we don't touch it. + if ( $has_no_html_tag && ! $has_html5_doctype || $is_amp_markup || $has_xsl_stylesheet ) { + $valid = false; + } + + return $valid; + } + + /** + * Returns true if given $content is considered to be AMP markup. + * This is far from actual validation against AMP spec, but it'll do for now. + * + * @param string $content Markup to check. + * + * @return bool + */ + public static function is_amp_markup( $content ) + { + $is_amp_markup = preg_match( '/]*(?:amp|⚡)/i', $content ); + + return (bool) $is_amp_markup; + } + + /** + * Processes/optimizes the output-buffered content and returns it. + * If the content is not processable, it is returned unmodified. + * + * @param string $content Buffered content. + * + * @return string + */ + public function end_buffering( $content ) + { + // Bail early without modifying anything if we can't handle the content. + if ( ! $this->is_valid_buffer( $content ) ) { + return $content; + } + + $conf = autoptimizeConfig::instance(); + + // Determine what needs to be ran. + $classes = array(); + if ( $conf->get( 'autoptimize_js' ) ) { + $classes[] = 'autoptimizeScripts'; + } + if ( $conf->get( 'autoptimize_css' ) ) { + $classes[] = 'autoptimizeStyles'; + } + if ( $conf->get( 'autoptimize_html' ) ) { + $classes[] = 'autoptimizeHTML'; + } + + $classoptions = array( + 'autoptimizeScripts' => array( + 'aggregate' => $conf->get( 'autoptimize_js_aggregate' ), + 'justhead' => $conf->get( 'autoptimize_js_justhead' ), + 'forcehead' => $conf->get( 'autoptimize_js_forcehead' ), + 'trycatch' => $conf->get( 'autoptimize_js_trycatch' ), + 'js_exclude' => $conf->get( 'autoptimize_js_exclude' ), + 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ), + 'include_inline' => $conf->get( 'autoptimize_js_include_inline' ), + ), + 'autoptimizeStyles' => array( + 'aggregate' => $conf->get( 'autoptimize_css_aggregate' ), + 'justhead' => $conf->get( 'autoptimize_css_justhead' ), + 'datauris' => $conf->get( 'autoptimize_css_datauris' ), + 'defer' => $conf->get( 'autoptimize_css_defer' ), + 'defer_inline' => $conf->get( 'autoptimize_css_defer_inline' ), + 'inline' => $conf->get( 'autoptimize_css_inline' ), + 'css_exclude' => $conf->get( 'autoptimize_css_exclude' ), + 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ), + 'include_inline' => $conf->get( 'autoptimize_css_include_inline' ), + 'nogooglefont' => $conf->get( 'autoptimize_css_nogooglefont' ), + ), + 'autoptimizeHTML' => array( + 'keepcomments' => $conf->get( 'autoptimize_html_keepcomments' ), + ), + ); + + $content = apply_filters( 'autoptimize_filter_html_before_minify', $content ); + + // Run the classes! + foreach ( $classes as $name ) { + $instance = new $name( $content ); + if ( $instance->read( $classoptions[ $name ] ) ) { + $instance->minify(); + $instance->cache(); + $content = $instance->getcontent(); + } + unset( $instance ); + } + + $content = apply_filters( 'autoptimize_html_after_minify', $content ); + + return $content; + } + + public function on_uninstall() + { + autoptimizeCache::clearall(); + + $delete_options = array( + 'autoptimize_cache_clean', + 'autoptimize_cache_nogzip', + 'autoptimize_css', + 'autoptimize_css_datauris', + 'autoptimize_css_justhead', + 'autoptimize_css_defer', + 'autoptimize_css_defer_inline', + 'autoptimize_css_inline', + 'autoptimize_css_exclude', + 'autoptimize_html', + 'autoptimize_html_keepcomments', + 'autoptimize_js', + 'autoptimize_js_exclude', + 'autoptimize_js_forcehead', + 'autoptimize_js_justhead', + 'autoptimize_js_trycatch', + 'autoptimize_version', + 'autoptimize_show_adv', + 'autoptimize_cdn_url', + 'autoptimize_cachesize_notice', + 'autoptimize_css_include_inline', + 'autoptimize_js_include_inline', + 'autoptimize_optimize_logged', + 'autoptimize_optimize_checkout', + 'autoptimize_extra_settings', + ); + + if ( ! is_multisite() ) { + foreach ( $delete_options as $del_opt ) { + delete_option( $del_opt ); + } + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + foreach ( $delete_options as $del_opt ) { + delete_option( $del_opt ); + } + } + switch_to_blog( $original_blog_id ); + } + + if ( wp_get_schedule( 'ao_cachechecker' ) ) { + wp_clear_scheduled_hook( 'ao_cachechecker' ); + } + } + + public static function notice_cache_unavailable() + { + echo '

'; + // Translators: %s is the cache directory location. + printf( __( 'Autoptimize cannot write to the cache directory (%s), please fix to enable CSS/ JS optimization!', 'autoptimize' ), AUTOPTIMIZE_CACHE_DIR ); + echo '

'; + } + + public static function notice_installed() + { + echo '

'; + _e( 'Thank you for installing and activating Autoptimize. Please configure it under "Settings" -> "Autoptimize" to start improving your site\'s performance.', 'autoptimize' ); + echo '

'; + } + + public static function notice_updated() + { + echo '

'; + _e( 'Autoptimize has just been updated. Please test your site now and adapt Autoptimize config if needed.', 'autoptimize' ); + echo '

'; + } + +} diff --git a/tests/classes/autoptimizePartners.php b/tests/classes/autoptimizePartners.php new file mode 100644 index 00000000..9304a5f7 --- /dev/null +++ b/tests/classes/autoptimizePartners.php @@ -0,0 +1,144 @@ +run(); + } + + public function run() + { + if ( $this->enabled() ) { + add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_partner_tabs' ), 10, 1 ); + } + add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); + } + + protected function enabled() + { + return apply_filters( 'autoptimize_filter_show_partner_tabs', true ); + } + + public function add_partner_tabs( $in ) + { + $in = array_merge( $in, array( + 'ao_partners' => __( 'Optimize More!', 'autoptimize' ), + ) ); + + return $in; + } + + public function add_admin_menu() + { + if ( $this->enabled() ) { + add_submenu_page( null, 'AO partner', 'AO partner', 'manage_options', 'ao_partners', array( $this, 'ao_partners_page' ) ); + } + } + + protected function get_ao_partner_feed_markup() { + $no_feed_text = __( 'Have a look at optimizingmatters.com for Autoptimize power-ups!', 'autoptimize' ); + $output = ''; + if ( apply_filters( 'autoptimize_settingsscreen_remotehttp', true ) ) { + $rss = fetch_feed( 'http://feeds.feedburner.com/OptimizingMattersDownloads' ); + $maxitems = 0; + + if ( ! is_wp_error( $rss ) ) { + $maxitems = $rss->get_item_quantity( 20 ); + $rss_items = $rss->get_items( 0, $maxitems ); + } + + if ( 0 == $maxitems ) { + $output .= $no_feed_text; + } else { + $output .= '
    '; + foreach ( $rss_items as $item ) { + $item_url = esc_url( $item->get_permalink() ); + $enclosure = $item->get_enclosure(); + + $output .= '
  • '; + $output .= '

    ' . esc_html( $item->get_title() ) . '

    '; + + if ( $enclosure && ( false !== strpos( $enclosure->get_type(), 'image' ) ) ) { + $img_url = esc_url( $enclosure->get_link() ); + $output .= '
    '; + } + + $output .= '
    ' . wp_kses_post( $item->get_description() ) . '
    '; + $output .= ''; + $output .= '
  • '; + } + $output .= '
'; + } + } else { + $output .= $no_feed_text; + } + + return $output; + } + + public function ao_partners_page() + { +?> + +
+

+ + ' . __( "These Autoptimize power-ups and related services will improve your site's performance even more!", 'autoptimize' ) . ''; ?> +
+ get_ao_partner_feed_markup(); ?> +
+
+ array(), + 'last' => array() + ); + + private $dontmove = array( + 'document.write','html5.js','show_ads.js','google_ad','histats.com/js','statcounter.com/counter/counter.js', + 'ws.amazon.com/widgets','media.fastclick.net','/ads/','comment-form-quicktags/quicktags.php','edToolbar', + 'intensedebate.com','scripts.chitika.net/','_gaq.push','jotform.com/','admin-bar.min.js','GoogleAnalyticsObject', + 'plupload.full.min.js','syntaxhighlighter','adsbygoogle','gist.github.com','_stq','nonce','post_id','data-noptimize' + ,'wordfence_logHuman' + ); + private $domove = array( + 'gaJsHost','load_cmc','jd.gallery.transitions.js','swfobject.embedSWF(','tiny_mce.js','tinyMCEPreInit.go' + ); + private $domovelast = array( + 'addthis.com','/afsonline/show_afs_search.js','disqus.js','networkedblogs.com/getnetworkwidget','infolinks.com/js/', + 'jd.gallery.js.php','jd.gallery.transitions.js','swfobject.embedSWF(','linkwithin.com/widget.js','tiny_mce.js','tinyMCEPreInit.go' + ); + + private $aggregate = true; + private $trycatch = false; + private $alreadyminified = false; + private $forcehead = true; + private $include_inline = false; + private $jscode = ''; + private $url = ''; + private $restofcontent = ''; + private $md5hash = ''; + private $whitelist = ''; + private $jsremovables = array(); + private $inject_min_late = ''; + + // Reads the page and collects script tags + public function read($options) + { + $noptimizeJS = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content ); + if ( $noptimizeJS ) { + return false; + } + + // only optimize known good JS? + $whitelistJS = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content ); + if ( ! empty( $whitelistJS ) ) { + $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistJS ) ) ); + } + + // is there JS we should simply remove + $removableJS = apply_filters( 'autoptimize_filter_js_removables', '', $this->content ); + if (!empty($removableJS)) { + $this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removableJS ) ) ); + } + + // only header? + if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) { + $content = explode( '', $this->content, 2 ); + $this->content = $content[0] . ''; + $this->restofcontent = $content[1]; + } + + // Determine whether we're doing JS-files aggregation or not. + if ( ! $options['aggregate'] ) { + $this->aggregate = false; + } + // Returning true for "dontaggregate" turns off aggregation. + if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) { + $this->aggregate = false; + } + + // include inline? + if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) { + $this->include_inline = true; + } + + // filter to "late inject minified JS", default to true for now (it is faster) + $this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true ); + + // filters to override hardcoded do(nt)move(last) array contents (array in, array out!) + $this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove ); + $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast ); + $this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove ); + + // get extra exclusions settings or filter + $excludeJS = $options['js_exclude']; + $excludeJS = apply_filters( 'autoptimize_filter_js_exclude', $excludeJS, $this->content ); + + if ( '' !== $excludeJS ) { + if ( is_array( $excludeJS ) ) { + if ( ( $removeKeys = array_keys( $excludeJS, 'remove' ) ) !== false ) { + foreach ( $removeKeys as $removeKey ) { + unset( $excludeJS[$removeKey] ); + $this->jsremovables[] = $removeKey; + } + } + $exclJSArr = array_keys( $excludeJS ); + } else { + $exclJSArr = array_filter( array_map( 'trim', explode( ',', $excludeJS ) ) ); + } + $this->dontmove = array_merge( $exclJSArr, $this->dontmove ); + } + + // Should we add try-catch? + if ( $options['trycatch'] ) { + $this->trycatch = true; + } + + // force js in head? + if ( $options['forcehead'] ) { + $this->forcehead = true; + } else { + $this->forcehead = false; + } + + $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead ); + + // get cdn url + $this->cdn_url = $options['cdn_url']; + + // noptimize me + $this->content = $this->hide_noptimize($this->content); + + // Save IE hacks + $this->content = $this->hide_iehacks($this->content); + + // comments + $this->content = $this->hide_comments($this->content); + + // Get script files + if ( preg_match_all( '##Usmi', $this->content, $matches ) ) { + foreach( $matches[0] as $tag ) { + // only consider script aggregation for types whitelisted in should_aggregate-function + $should_aggregate = $this->should_aggregate($tag); + if ( ! $should_aggregate ) { + $tag = ''; + continue; + } + + if ( preg_match( '#]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) { + // non-inline script + if ( $this->isremovable($tag, $this->jsremovables) ) { + $this->content = str_replace( $tag, '', $this->content ); + continue; + } + + $origTag = null; + $url = current( explode( '?', $source[2], 2 ) ); + $path = $this->getpath($url); + if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable($tag) ) { + // ok to optimize, add to array + $this->scripts[] = $path; + } else { + $origTag = $tag; + $newTag = $tag; + + // non-mergeable script (excluded or dynamic or external) + if ( is_array( $excludeJS ) ) { + // should we add flags? + foreach ( $excludeJS as $exclTag => $exclFlags) { + if ( false !== strpos( $origTag, $exclTag ) && in_array( $exclFlags, array( 'async', 'defer' ) ) ) { + $newTag = str_replace( '#Usmi', $tag , $code ); + $code = preg_replace('#.*.*#sm', '$1', $code[1] ); + $code = preg_replace('/(?:^\\s*\\s*$)/', '', $code ); + $this->scripts[] = 'INLINE;' . $code; + } else { + // Can we move this? + $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag ); + if ( $this->ismovable($tag) || '' !== $autoptimize_js_moveable ) { + if ( $this->movetolast($tag) || 'last' === $autoptimize_js_moveable ) { + $this->move['last'][] = $tag; + } else { + $this->move['first'][] = $tag; + } + } else { + // We shouldn't touch this + $tag = ''; + } + } + // Re-hide comments to be able to do the removal based on tag from $this->content + $tag = $this->hide_comments($tag); + } + + //Remove the original script tag + $this->content = str_replace( $tag, '', $this->content ); + } + + return true; + } + + // No script files, great ;-) + return false; + } + + /** + * Determines wheter a certain `'; + $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload ); + + $bodyreplacement = implode( '',$this->move['first'] ); + $bodyreplacement .= $bodyreplacementpayload; + $bodyreplacement .= implode( '', $this->move['last'] ); + + $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag ); + + if ( strlen( $this->jscode ) > 0 ) { + $this->inject_in_html($bodyreplacement, $replaceTag); + } + + // restore comments + $this->content = $this->restore_comments($this->content); + + // Restore IE hacks + $this->content = $this->restore_iehacks($this->content); + + // Restore noptimize + $this->content = $this->restore_noptimize($this->content); + + // Return the modified HTML + return $this->content; + } + + // Checks against the white- and blacklists + private function ismergeable($tag) + { + if ( ! $this->aggregate ) { + return false; + } + + if ( ! empty( $this->whitelist ) ) { + foreach ( $this->whitelist as $match ) { + if (false !== strpos( $tag, $match ) ) { + return true; + } + } + // no match with whitelist + return false; + } else { + foreach($this->domove as $match) { + if ( false !== strpos( $tag, $match ) ) { + // Matched something + return false; + } + } + + if ( $this->movetolast($tag) ) { + return false; + } + + foreach( $this->dontmove as $match ) { + if ( false !== strpos( $tag, $match ) ) { + // Matched something + return false; + } + } + + // If we're here it's safe to merge + return true; + } + } + + // Checks agains the blacklist + private function ismovable($tag) + { + if ( true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) { + return false; + } + + foreach ( $this->domove as $match ) { + if ( false !== strpos( $tag, $match ) ) { + // Matched something + return true; + } + } + + if ( $this->movetolast($tag) ) { + return true; + } + + foreach ( $this->dontmove as $match ) { + if ( false !== strpos( $tag, $match ) ) { + // Matched something + return false; + } + } + + // If we're here it's safe to move + return true; + } + + private function movetolast($tag) + { + foreach ( $this->domovelast as $match ) { + if ( false !== strpos( $tag, $match ) ) { + // Matched, return true + return true; + } + } + + // Should be in 'first' + return false; + } + + /** + * Determines wheter a '; + $noScriptCssBlock .= ''; + $this->inject_in_html($preloadCssBlock . $noScriptCssBlock, $replaceTag); + + // Adds preload polyfill at end of body tag + $this->inject_in_html( + apply_filters( 'autoptimize_css_preload_polyfill', $preloadPolyfill ), + array( '', 'before' ) + ); + } + } + + // Return the modified stylesheet + return $this->content; + } + + static function fixurls($file, $code) + { + // Switch all imports to the url() syntax + $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code ); + + if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) { + $file = str_replace( WP_ROOT_DIR, '/', $file ); + // rollback as per https://github.com/futtta/autoptimize/issues/94 + // $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file ); + $dir = dirname( $file ); // Like /themes/expound/css + + // $dir should not contain backslashes, since it's used to replace + // urls, but it can contain them when running on Windows because + // fixurls() is sometimes called with `ABSPATH . 'index.php'` + $dir = str_replace( '\\', '/', $dir ); + unset( $file ); // not used below at all + + $replace = array(); + foreach ( $matches[1] as $k => $url ) { + // Remove quotes + $url = trim( $url," \t\n\r\0\x0B\"'" ); + $noQurl = trim( $url, "\"'" ); + if ( $url !== $noQurl ) { + $removedQuotes = true; + } else { + $removedQuotes = false; + } + + if ( '' === $noQurl ) { + continue; + } + + $url = $noQurl; + if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) { + // URL is protocol-relative, host-relative or something we don't touch + continue; + } else { + // Relative URL + // rollback as per https://github.com/futtta/autoptimize/issues/94 + // $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) ); + $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) ); + + // Hash the url + whatever was behind potentially for replacement + // We must do this, or different css classes referencing the same bg image (but + // different parts of it, say, in sprites and such) loose their stuff... + $hash = md5( $url . $matches[2][$k] ); + $code = str_replace( $matches[0][$k], $hash, $code ); + + if ( $removedQuotes ) { + $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k]; + } else { + $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k]; + } + } + } + + if ( ! empty( $replace ) ) { + // Sort the replacements array by key length in desc order (so that the longest strings are replaced first) + $keys = array_map( 'strlen', array_keys( $replace ) ); + array_multisort( $keys, SORT_DESC, $replace ); + + // Replace URLs found within $code + $code = str_replace( array_keys( $replace ), array_values( $replace ), $code ); + } + } + + return $code; + } + + private function ismovable($tag) + { + if ( ! $this->aggregate ) { + return false; + } + + if ( ! empty( $this->whitelist ) ) { + foreach ( $this->whitelist as $match ) { + if ( false !== strpos( $tag, $match ) ) { + return true; + } + } + // no match with whitelist + return false; + } else { + if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) { + foreach ( $this->dontmove as $match ) { + if ( false !== strpos( $tag, $match ) ) { + //Matched something + return false; + } + } + } + + //If we're here it's safe to move + return true; + } + } + + private function can_inject_late($cssPath, $css) + { + $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath ); + if ( true !== $this->inject_min_late ) { + // late-inject turned off + return false; + } else if ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) { + // file not minified based on filename & filter + return false; + } else if ( false !== strpos( $css, '@import' ) ) { + // can't late-inject files with imports as those need to be aggregated + return false; + } else if ( ( false !== strpos( $css, '@font-face') ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) { + // don't late-inject CSS with font-src's if fonts are set to be CDN'ed + return false; + } else if ( ( ( $this->datauris == true ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) { + // don't late-inject CSS with images if CDN is set OR if image inlining is on + return false; + } else { + // phew, all is safe, we can late-inject + return true; + } + } + + /** + * Returns whether we're doing aggregation or not. + * + * @return bool + */ + public function aggregating() + { + return $this->aggregate; + } + + public function getOptions() + { + return $this->options; + } + + public function replaceOptions($options) + { + $this->options = $options; + } + + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->$name = $value; + } + + public function getOption($name) + { + return $this->options[$name]; + } +} diff --git a/tests/classes/autoptimizeToolbar.php b/tests/classes/autoptimizeToolbar.php new file mode 100644 index 00000000..4209d467 --- /dev/null +++ b/tests/classes/autoptimizeToolbar.php @@ -0,0 +1,150 @@ +format_filesize( $bytes ); + + // Calculate the percentage of cache used. + $percentage = ceil( $bytes / $max_size * 100 ); + if ( $percentage > 100 ) { + $percentage = 100; + } + + /** + * We define the type of color indicator for the current state of cache size: + * - "green" if the size is less than 80% of the total recommended. + * - "orange" if over 80%. + * - "red" if over 100%. + */ + $color = ( 100 == $percentage ) ? 'red' : ( ( $percentage > 80 ) ? 'orange' : 'green' ); + + // Create or add new items into the Admin Toolbar. + // Main "Autoptimize" node. + $wp_admin_bar->add_node( array( + 'id' => 'autoptimize', + 'title' => '' . __( 'Autoptimize', 'autoptimize' ) . '', + 'href' => admin_url( 'options-general.php?page=autoptimize' ), + 'meta' => array( 'class' => 'bullet-' . $color ), + )); + + // "Cache Info" node. + $wp_admin_bar->add_node( array( + 'id' => 'autoptimize-cache-info', + 'title' => '

' . __( 'Cache Info', 'autoptimize' ) . '

' . + '
' . + '
' . + '
' . + '
' . + '
' . + '
' . + '
' . $percentage . '%
' . + '
' . + '' . + '' . + '' . + '
' . __( 'Size', 'autoptimize' ) . ':' . $size . '
' . __( 'Files', 'autoptimize' ) . ':' . $files . '
', + 'parent' => 'autoptimize', + )); + + // "Delete Cache" node. + $wp_admin_bar->add_node( array( + 'id' => 'autoptimize-delete-cache', + 'title' => __( 'Delete Cache', 'autoptimize' ), + 'parent' => 'autoptimize', + )); + } + + public function delete_cache() + { + check_ajax_referer( 'ao_delcache_nonce', 'nonce' ); + + $result = false; + if ( current_user_can( 'manage_options' ) ) { + // We call the function for cleaning the Autoptimize cache. + $result = autoptimizeCache::clearall(); + } + + wp_send_json( $result ); + } + + public function enqueue_scripts() + { + // Autoptimize Toolbar Styles. + wp_enqueue_style( 'autoptimize-toolbar', plugins_url( '/static/toolbar.css', __FILE__ ), array(), time(), 'all' ); + + // Autoptimize Toolbar Javascript. + wp_enqueue_script( 'autoptimize-toolbar', plugins_url( '/static/toolbar.js', __FILE__ ), array( 'jquery' ), time(), true ); + + // Localizes a registered script with data for a JavaScript variable. + // Needed for the AJAX to work properly on the frontend. + wp_localize_script( 'autoptimize-toolbar', 'autoptimize_ajax_object', array( + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'error_msg' => sprintf( __( 'Your Autoptimize cache might not have been purged successfully, please check on the Autoptimize settings page.', 'autoptimize' ), admin_url( 'options-general.php?page=autoptimize' ) . ' style="white-space:nowrap;"' ), + 'dismiss_msg' => __( 'Dismiss this notice.' ), + 'nonce' => wp_create_nonce( 'ao_delcache_nonce' ), + ) ); + } + + public function format_filesize( $bytes, $decimals = 2 ) + { + $units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ); + + for ( $i = 0; ( $bytes / 1024) > 0.9; $i++, $bytes /= 1024 ) {} // @codingStandardsIgnoreLine + + return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[ $i ] ); + } +} diff --git a/tests/classes/autoptimizeVersionUpdatesHandler.php b/tests/classes/autoptimizeVersionUpdatesHandler.php new file mode 100644 index 00000000..5c1bc4c8 --- /dev/null +++ b/tests/classes/autoptimizeVersionUpdatesHandler.php @@ -0,0 +1,206 @@ +current_major_version = substr( $current_version, 0, 3 ); + } + + /** + * Runs all needed upgrade procedures (depending on the + * current major version specified during class instantiation) + */ + public function run_needed_major_upgrades() + { + $major_update = false; + + switch ( $this->current_major_version ) { + case '1.6': + $this->upgrade_from_1_6(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + case '1.7': + $this->upgrade_from_1_7(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + case '1.9': + $this->upgrade_from_1_9(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + case '2.2': + $this->upgrade_from_2_2(); + $major_update = true; + // No break, intentionally, so all upgrades are ran during a single request... + } + + if ( true === $major_update ) { + $this->on_major_version_update(); + } + } + + /** + * Checks specified version against the one stored in the database under `autoptimize_version` and performs + * any major upgrade routines if needed. + * Updates the database version to the specified $target if it's different to the one currently stored there. + * + * @param string $target Target version to check against (ie., the currently running one). + */ + public static function check_installed_and_update( $target ) + { + $db_version = get_option( 'autoptimize_version', 'none' ); + if ( $db_version !== $target ) { + if ( 'none' === $db_version ) { + add_action( 'admin_notices', 'autoptimizeMain::notice_installed' ); + } else { + $updater = new self( $db_version ); + $updater->run_needed_major_upgrades(); + } + + // Versions differed, upgrades happened if needed, store the new version. + update_option( 'autoptimize_version', $target ); + } + } + + /** + * Called after any major version update (and it's accompanying upgrade procedure) + * has happened. Clears cache and sets an admin notice. + */ + protected function on_major_version_update() + { + // The transients guard here prevents stale object caches from busting the cache on every request. + if ( false == get_transient( 'autoptimize_stale_option_buster' ) ) { + set_transient( 'autoptimize_stale_option_buster', 'Mamsie & Liessie zehhe: ZWIJH!', HOUR_IN_SECONDS ); + autoptimizeCache::clearall(); + add_action( 'admin_notices', 'autoptimizeMain::notice_updated' ); + } + } + + /** + * From back in the days when I did not yet consider multisite. + */ + private function upgrade_from_1_6() + { + // If user was on version 1.6.x, force advanced options to be shown by default. + update_option( 'autoptimize_show_adv', '1' ); + + // And remove old options. + $to_delete_options = array( + 'autoptimize_cdn_css', + 'autoptimize_cdn_css_url', + 'autoptimize_cdn_js', + 'autoptimize_cdn_js_url', + 'autoptimize_cdn_img', + 'autoptimize_cdn_img_url', + 'autoptimize_css_yui', + ); + foreach ( $to_delete_options as $del_opt ) { + delete_option( $del_opt ); + } + } + + /** + * Forces WP 3.8 dashicons in CSS exclude options when upgrading from 1.7 to 1.8 + * + * @global $wpdb + */ + private function upgrade_from_1_7() + { + if ( ! is_multisite() ) { + $css_exclude = get_option( 'autoptimize_css_exclude' ); + if ( empty( $css_exclude ) ) { + $css_exclude = 'admin-bar.min.css, dashicons.min.css'; + } elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) { + $css_exclude .= ', dashicons.min.css'; + } + update_option( 'autoptimize_css_exclude', $css_exclude ); + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + $css_exclude = get_option( 'autoptimize_css_exclude' ); + if ( empty( $css_exclude ) ) { + $css_exclude = 'admin-bar.min.css, dashicons.min.css'; + } elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) { + $css_exclude .= ', dashicons.min.css'; + } + update_option( 'autoptimize_css_exclude', $css_exclude ); + } + switch_to_blog( $original_blog_id ); + } + } + + /** + * 2.0 will not aggregate inline CSS/JS by default, but we want users + * upgrading from 1.9 to keep their inline code aggregated by default. + * + * @global $wpdb + */ + private function upgrade_from_1_9() + { + if ( ! is_multisite() ) { + update_option( 'autoptimize_css_include_inline', 'on' ); + update_option( 'autoptimize_js_include_inline', 'on' ); + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + update_option( 'autoptimize_css_include_inline', 'on' ); + update_option( 'autoptimize_js_include_inline', 'on' ); + } + switch_to_blog( $original_blog_id ); + } + } + + /** + * 2.3 has no "remove google fonts" in main screen, moved to "extra" + * + * @global $wpdb + */ + private function upgrade_from_2_2() + { + if ( ! is_multisite() ) { + $this->do_2_2_settings_update(); + } else { + global $wpdb; + $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ); + $original_blog_id = get_current_blog_id(); + foreach ( $blog_ids as $blog_id ) { + switch_to_blog( $blog_id ); + $this->do_2_2_settings_update(); + } + switch_to_blog( $original_blog_id ); + } + } + + /** + * Helper for 2.2 autoptimize_extra_settings upgrade to avoid duplicate code + */ + private function do_2_2_settings_update() + { + $nogooglefont = get_option( 'autoptimize_css_nogooglefont', '' ); + $ao_extrasetting = get_option( 'autoptimize_extra_settings', '' ); + if ( ( $nogooglefont ) && ( empty( $ao_extrasetting ) ) ) { + update_option( 'autoptimize_extra_settings', autoptimizeConfig::get_ao_extra_default_options() ); + } + delete_option( 'autoptimize_css_nogooglefont' ); + } +} diff --git a/tests/classes/external/do_not_donate_smallest.png b/tests/classes/external/do_not_donate_smallest.png new file mode 100644 index 00000000..f8c977ef Binary files /dev/null and b/tests/classes/external/do_not_donate_smallest.png differ diff --git a/tests/classes/external/index.html b/tests/classes/external/index.html new file mode 100644 index 00000000..fe7267f7 --- /dev/null +++ b/tests/classes/external/index.html @@ -0,0 +1 @@ +Generated by Autoptimize diff --git a/tests/classes/external/js/index.html b/tests/classes/external/js/index.html new file mode 100644 index 00000000..fe7267f7 --- /dev/null +++ b/tests/classes/external/js/index.html @@ -0,0 +1 @@ +Generated by Autoptimize diff --git a/tests/classes/external/js/jquery.cookie.js b/tests/classes/external/js/jquery.cookie.js new file mode 100644 index 00000000..7b3e7012 --- /dev/null +++ b/tests/classes/external/js/jquery.cookie.js @@ -0,0 +1,89 @@ +/*jslint browser: true */ /*global jQuery: true */ + +/** + * jQuery Cookie plugin + * + * Copyright (c) 2010 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +// TODO JsDoc + +/** + * Create a cookie with the given key and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String key The key of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given key. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String key The key of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function (key, value, options) { + + // key and value given, set cookie... + if (arguments.length > 1 && (value === null || typeof value !== "object")) { + options = jQuery.extend({}, options); + + if (value === null) { + options.expires = -1; + } + + if (typeof options.expires === 'number') { + var days = options.expires, t = options.expires = new Date(); + t.setDate(t.getDate() + days); + } + + return (document.cookie = [ + encodeURIComponent(key), '=', + options.raw ? String(value) : encodeURIComponent(String(value)), + options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + options.path ? '; path=' + options.path : '', + options.domain ? '; domain=' + options.domain : '', + options.secure ? '; secure' : '' + ].join('')); + } + + // key and possibly options given, get cookie... + options = value || {}; + var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent; + return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null; +}; diff --git a/tests/classes/external/js/jquery.cookie.min.js b/tests/classes/external/js/jquery.cookie.min.js new file mode 100644 index 00000000..927fc7c6 --- /dev/null +++ b/tests/classes/external/js/jquery.cookie.min.js @@ -0,0 +1 @@ +jQuery.cookie=function(key,value,options){if(arguments.length>1&&(value===null||typeof value!=="object")){options=jQuery.extend({},options);if(value===null){options.expires=-1;}if(typeof options.expires==='number'){var days=options.expires,t=options.expires=new Date();t.setDate(t.getDate()+days);}return(document.cookie=[encodeURIComponent(key),'=',options.raw?String(value):encodeURIComponent(String(value)),options.expires?'; expires='+options.expires.toUTCString():'',options.path?'; path='+options.path:'',options.domain?'; domain='+options.domain:'',options.secure?'; secure':''].join(''));}options=value||{};var result,decode=options.raw?function(s){return s;}:decodeURIComponent;return(result=new RegExp('(?:^|; )'+encodeURIComponent(key)+'=([^;]*)').exec(document.cookie))?decode(result[1]):null;}; diff --git a/tests/classes/external/js/unslider-dots.css b/tests/classes/external/js/unslider-dots.css new file mode 100644 index 00000000..65327c00 --- /dev/null +++ b/tests/classes/external/js/unslider-dots.css @@ -0,0 +1,33 @@ +/** + * Here's where everything gets included. You don't need + * to change anything here, and doing so might break + * stuff. Here be dragons and all that. + */ +/** + * Default variables + * + * While these can be set with JavaScript, it's probably + * better and faster to just set them here, compile to + * CSS and include that instead to use some of that + * hardware-accelerated goodness. + */ +.unslider-nav ol { + list-style: none; + text-align: center; +} +.unslider-nav ol li { + display: inline-block; + width: 6px; + height: 6px; + margin: 0 4px; + background: transparent; + border-radius: 5px; + overflow: hidden; + text-indent: -999em; + border: 2px solid #fff; + cursor: pointer; +} +.unslider-nav ol li.unslider-active { + background: #fff; + cursor: default; +} diff --git a/tests/classes/external/js/unslider-min.js b/tests/classes/external/js/unslider-min.js new file mode 100644 index 00000000..600c9d15 --- /dev/null +++ b/tests/classes/external/js/unslider-min.js @@ -0,0 +1 @@ +!function($){return $?($.Unslider=function(t,n){var e=this;return e._="unslider",e.defaults={autoplay:!1,delay:3e3,speed:750,easing:"swing",keys:{prev:37,next:39},nav:!0,arrows:{prev:'',next:''},animation:"horizontal",selectors:{container:"ul:first",slides:"li"},animateHeight:!1,activeClass:e._+"-active",swipe:!0,swipeThreshold:.2},e.$context=t,e.options={},e.$parent=null,e.$container=null,e.$slides=null,e.$nav=null,e.$arrows=[],e.total=0,e.current=0,e.prefix=e._+"-",e.eventSuffix="."+e.prefix+~~(2e3*Math.random()),e.interval=null,e.init=function(t){return e.options=$.extend({},e.defaults,t),e.$container=e.$context.find(e.options.selectors.container).addClass(e.prefix+"wrap"),e.$slides=e.$container.children(e.options.selectors.slides),e.setup(),$.each(["nav","arrows","keys","infinite"],function(t,n){e.options[n]&&e["init"+$._ucfirst(n)]()}),jQuery.event.special.swipe&&e.options.swipe&&e.initSwipe(),e.options.autoplay&&e.start(),e.calculateSlides(),e.$context.trigger(e._+".ready"),e.animate(e.options.index||e.current,"init")},e.setup=function(){e.$context.addClass(e.prefix+e.options.animation).wrap('
'),e.$parent=e.$context.parent("."+e._);var t=e.$context.css("position");"static"===t&&e.$context.css("position","relative"),e.$context.css("overflow","hidden")},e.calculateSlides=function(){if(e.total=e.$slides.length,"fade"!==e.options.animation){var t="width";"vertical"===e.options.animation&&(t="height"),e.$container.css(t,100*e.total+"%").addClass(e.prefix+"carousel"),e.$slides.css(t,100/e.total+"%")}},e.start=function(){return e.interval=setTimeout(function(){e.next()},e.options.delay),e},e.stop=function(){return clearTimeout(e.interval),e},e.initNav=function(){var t=$('');e.$slides.each(function(n){var i=this.getAttribute("data-nav")||n+1;$.isFunction(e.options.nav)&&(i=e.options.nav.call(e.$slides.eq(n),n,i)),t.children("ol").append('
  • '+i+"
  • ")}),e.$nav=t.insertAfter(e.$context),e.$nav.find("li").on("click"+e.eventSuffix,function(){var t=$(this).addClass(e.options.activeClass);t.siblings().removeClass(e.options.activeClass),e.animate(t.attr("data-slide"))})},e.initArrows=function(){e.options.arrows===!0&&(e.options.arrows=e.defaults.arrows),$.each(e.options.arrows,function(t,n){e.$arrows.push($(n).insertAfter(e.$context).on("click"+e.eventSuffix,e[t]))})},e.initKeys=function(){e.options.keys===!0&&(e.options.keys=e.defaults.keys),$(document).on("keyup"+e.eventSuffix,function(t){$.each(e.options.keys,function(n,i){t.which===i&&$.isFunction(e[n])&&e[n].call(e)})})},e.initSwipe=function(){var t=e.$slides.width();"fade"!==e.options.animation&&e.$container.on({movestart:function(t){return t.distX>t.distY&&t.distX<-t.distY||t.distX-t.distY?!!t.preventDefault():void e.$container.css("position","relative")},move:function(n){e.$container.css("left",-(100*e.current)+100*n.distX/t+"%")},moveend:function(n){Math.abs(n.distX)/t>e.options.swipeThreshold?e[n.distX<0?"next":"prev"]():e.$container.animate({left:-(100*e.current)+"%"},e.options.speed/2)}})},e.initInfinite=function(){var t=["first","last"];$.each(t,function(n,i){e.$slides.push.apply(e.$slides,e.$slides.filter(':not(".'+e._+'-clone")')[i]().clone().addClass(e._+"-clone")["insert"+(0===n?"After":"Before")](e.$slides[t[~~!n]]()))})},e.destroyArrows=function(){$.each(e.$arrows,function(t,n){n.remove()})},e.destroySwipe=function(){e.$container.off("movestart move moveend")},e.destroyKeys=function(){$(document).off("keyup"+e.eventSuffix)},e.setIndex=function(t){return 0>t&&(t=e.total-1),e.current=Math.min(Math.max(0,t),e.total-1),e.options.nav&&e.$nav.find('[data-slide="'+e.current+'"]')._active(e.options.activeClass),e.$slides.eq(e.current)._active(e.options.activeClass),e},e.animate=function(t,n){if("first"===t&&(t=0),"last"===t&&(t=e.total),isNaN(t))return e;e.options.autoplay&&e.stop().start(),e.setIndex(t),e.$context.trigger(e._+".change",[t,e.$slides.eq(t)]);var i="animate"+$._ucfirst(e.options.animation);return $.isFunction(e[i])&&e[i](e.current,n),e},e.next=function(){var t=e.current+1;return t>=e.total&&(t=0),e.animate(t,"next")},e.prev=function(){return e.animate(e.current-1,"prev")},e.animateHorizontal=function(t){var n="left";return"rtl"===e.$context.attr("dir")&&(n="right"),e.options.infinite&&e.$container.css("margin-"+n,"-100%"),e.slide(n,t)},e.animateVertical=function(t){return e.options.animateHeight=!0,e.options.infinite&&e.$container.css("margin-top",-e.$slides.outerHeight()),e.slide("top",t)},e.slide=function(t,n){if(e.options.animateHeight&&e._move(e.$context,{height:e.$slides.eq(n).outerHeight()},!1),e.options.infinite){var i;n===e.total-1&&(i=e.total-3,n=-1),n===e.total-2&&(i=0,n=e.total-2),"number"==typeof i&&(e.setIndex(i),e.$context.on(e._+".moved",function(){e.current===i&&e.$container.css(t,-(100*i)+"%").off(e._+".moved")}))}var o={};return o[t]=-(100*n)+"%",e._move(e.$container,o)},e.animateFade=function(t){var n=e.$slides.eq(t).addClass(e.options.activeClass);e._move(n.siblings().removeClass(e.options.activeClass),{opacity:0}),e._move(n,{opacity:1},!1)},e._move=function(t,n,i,o){return i!==!1&&(i=function(){e.$context.trigger(e._+".moved")}),t._move(n,o||e.options.speed,e.options.easing,i)},e.init(n)},$.fn._active=function(t){return this.addClass(t).siblings().removeClass(t)},$._ucfirst=function(t){return(t+"").toLowerCase().replace(/^./,function(t){return t.toUpperCase()})},$.fn._move=function(){return this.stop(!0,!0),$.fn[$.fn.velocity?"velocity":"animate"].apply(this,arguments)},void($.fn.unslider=function(t){return this.each(function(){var n=$(this);if("string"==typeof t&&n.data("unslider")){t=t.split(":");var e=n.data("unslider")[t[0]];if($.isFunction(e))return e.apply(n,t[1]?t[1].split(","):null)}return n.data("unslider",new $.Unslider(n,t))})})):console.warn("Unslider needs jQuery")}(window.jQuery); diff --git a/tests/classes/external/js/unslider.css b/tests/classes/external/js/unslider.css new file mode 100644 index 00000000..249b9b86 --- /dev/null +++ b/tests/classes/external/js/unslider.css @@ -0,0 +1 @@ +.unslider{overflow:auto;margin:0;padding:0}.unslider-wrap{position:relative}.unslider-wrap.unslider-carousel>li{float:left}.unslider-vertical>ul{height:100%}.unslider-vertical li{float:none;width:100%}.unslider-fade{position:relative}.unslider-fade .unslider-wrap li{position:absolute;left:0;top:0;right:0;z-index:8}.unslider-fade .unslider-wrap li.unslider-active{z-index:10}.unslider li,.unslider ol,.unslider ul{list-style:none;margin:0;padding:0;border:none}.unslider-arrow{position:absolute;left:20px;z-index:2;cursor:pointer}.unslider-arrow.next{left:auto;right:20px} diff --git a/tests/classes/external/php/index.html b/tests/classes/external/php/index.html new file mode 100644 index 00000000..fe7267f7 --- /dev/null +++ b/tests/classes/external/php/index.html @@ -0,0 +1 @@ +Generated by Autoptimize diff --git a/tests/classes/external/php/jsmin.php b/tests/classes/external/php/jsmin.php new file mode 100644 index 00000000..bb9e4e75 --- /dev/null +++ b/tests/classes/external/php/jsmin.php @@ -0,0 +1,458 @@ + + * $minifiedJs = JSMin::minify($js); + * + * + * This is a modified port of jsmin.c. Improvements: + * + * Does not choke on some regexp literals containing quote characters. E.g. /'/ + * + * Spaces are preserved after some add/sub operators, so they are not mistakenly + * converted to post-inc/dec. E.g. a + ++b -> a+ ++b + * + * Preserves multi-line comments that begin with /*! + * + * PHP 5 or higher is required. + * + * Permission is hereby granted to use this version of the library under the + * same terms as jsmin.c, which has the following license: + * + * -- + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * -- + * + * @package JSMin + * @author Ryan Grove (PHP port) + * @author Steve Clay (modifications + cleanup) + * @author Andrea Giammarchi (spaceBeforeRegExp) + * @copyright 2002 Douglas Crockford (jsmin.c) + * @copyright 2008 Ryan Grove (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @link http://code.google.com/p/jsmin-php/ + */ + +// This is from https://github.com/mrclay/jsmin-php 2.3.2 + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + const ACTION_KEEP_A = 1; + const ACTION_DELETE_A = 2; + const ACTION_DELETE_A_B = 3; + + protected $a = "\n"; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; + protected $lastByteOut = ''; + protected $keptComment = ''; + + /** + * Minify Javascript. + * + * @param string $js Javascript to be minified + * + * @return string + */ + public static function minify($js) + { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + /** + * @param string $input + */ + public function __construct($input) + { + $this->input = $input; + } + + /** + * Perform minification, return result + * + * @return string + */ + public function min() + { + if ($this->output !== '') { // min already run + return $this->output; + } + + $mbIntEnc = null; + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('8bit'); + } + + if (isset($this->input[0]) && $this->input[0] === "\xef") { + $this->input = substr($this->input, 3); + } + + $this->input = str_replace("\r\n", "\n", $this->input); + $this->inputLength = strlen($this->input); + + $this->action(self::ACTION_DELETE_A_B); + + while ($this->a !== null) { + // determine next command + $command = self::ACTION_KEEP_A; // default + if ($this->a === ' ') { + if (($this->lastByteOut === '+' || $this->lastByteOut === '-') + && ($this->b === $this->lastByteOut)) { + // Don't delete this space. If we do, the addition/subtraction + // could be parsed as a post-increment + } elseif (! $this->isAlphaNum($this->b)) { + $command = self::ACTION_DELETE_A; + } + } elseif ($this->a === "\n") { + if ($this->b === ' ') { + $command = self::ACTION_DELETE_A_B; + + // in case of mbstring.func_overload & 2, must check for null b, + // otherwise mb_strpos will give WARNING + } elseif ($this->b === null + || (false === strpos('{[(+-!~', $this->b) + && ! $this->isAlphaNum($this->b))) { + $command = self::ACTION_DELETE_A; + } + } elseif (! $this->isAlphaNum($this->a)) { + if ($this->b === ' ' + || ($this->b === "\n" + && (false === strpos('}])+-"\'', $this->a)))) { + $command = self::ACTION_DELETE_A_B; + } + } + $this->action($command); + } + $this->output = trim($this->output); + + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } + return $this->output; + } + + /** + * ACTION_KEEP_A = Output A. Copy B to A. Get the next B. + * ACTION_DELETE_A = Copy B to A. Get the next B. + * ACTION_DELETE_A_B = Get the next B. + * + * @param int $command + * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException + */ + protected function action($command) + { + // make sure we don't compress "a + ++b" to "a+++b", etc. + if ($command === self::ACTION_DELETE_A_B + && $this->b === ' ' + && ($this->a === '+' || $this->a === '-')) { + // Note: we're at an addition/substraction operator; the inputIndex + // will certainly be a valid index + if ($this->input[$this->inputIndex] === $this->a) { + // This is "+ +" or "- -". Don't delete the space. + $command = self::ACTION_KEEP_A; + } + } + + switch ($command) { + case self::ACTION_KEEP_A: // 1 + $this->output .= $this->a; + + if ($this->keptComment) { + $this->output = rtrim($this->output, "\n"); + $this->output .= $this->keptComment; + $this->keptComment = ''; + } + + $this->lastByteOut = $this->a; + + // fallthrough intentional + case self::ACTION_DELETE_A: // 2 + $this->a = $this->b; + if ($this->a === "'" || $this->a === '"') { // string literal + $str = $this->a; // in case needed for exception + for(;;) { + $this->output .= $this->a; + $this->lastByteOut = $this->a; + + $this->a = $this->get(); + if ($this->a === $this->b) { // end quote + break; + } + if ($this->isEOF($this->a)) { + $byte = $this->inputIndex - 1; + throw new JSMin_UnterminatedStringException( + "JSMin: Unterminated String at byte {$byte}: {$str}"); + } + $str .= $this->a; + if ($this->a === '\\') { + $this->output .= $this->a; + $this->lastByteOut = $this->a; + + $this->a = $this->get(); + $str .= $this->a; + } + } + } + + // fallthrough intentional + case self::ACTION_DELETE_A_B: // 3 + $this->b = $this->next(); + if ($this->b === '/' && $this->isRegexpLiteral()) { + $this->output .= $this->a . $this->b; + $pattern = '/'; // keep entire pattern in case we need to report it in the exception + for(;;) { + $this->a = $this->get(); + $pattern .= $this->a; + if ($this->a === '[') { + for(;;) { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + if ($this->a === ']') { + break; + } + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + } + if ($this->isEOF($this->a)) { + throw new JSMin_UnterminatedRegExpException( + "JSMin: Unterminated set in RegExp at byte " + . $this->inputIndex .": {$pattern}"); + } + } + } + + if ($this->a === '/') { // end pattern + break; // while (true) + } elseif ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + } elseif ($this->isEOF($this->a)) { + $byte = $this->inputIndex - 1; + throw new JSMin_UnterminatedRegExpException( + "JSMin: Unterminated RegExp at byte {$byte}: {$pattern}"); + } + $this->output .= $this->a; + $this->lastByteOut = $this->a; + } + $this->b = $this->next(); + } + // end case ACTION_DELETE_A_B + } + } + + /** + * @return bool + */ + protected function isRegexpLiteral() + { + if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { + // we can't divide after these tokens + return true; + } + + // check if first non-ws token is "/" (see starts-regex.js) + $length = strlen($this->output); + if ($this->a === ' ' || $this->a === "\n") { + if ($length < 2) { // weird edge case + return true; + } + } + + // if the "/" follows a keyword, it must be a regexp, otherwise it's best to assume division + + $subject = $this->output . trim($this->a); + if (!preg_match('/(?:case|else|in|return|typeof)$/', $subject, $m)) { + // not a keyword + return false; + } + + // can't be sure it's a keyword yet (see not-regexp.js) + $charBeforeKeyword = substr($subject, 0 - strlen($m[0]) - 1, 1); + if ($this->isAlphaNum($charBeforeKeyword)) { + // this is really an identifier ending in a keyword, e.g. "xreturn" + return false; + } + + // it's a regexp. Remove unneeded whitespace after keyword + if ($this->a === ' ' || $this->a === "\n") { + $this->a = ''; + } + + return true; + } + + /** + * Return the next character from stdin. Watch out for lookahead. If the character is a control character, + * translate it to a space or linefeed. + * + * @return string + */ + protected function get() + { + $c = $this->lookAhead; + $this->lookAhead = null; + if ($c === null) { + // getc(stdin) + if ($this->inputIndex < $this->inputLength) { + $c = $this->input[$this->inputIndex]; + $this->inputIndex += 1; + } else { + $c = null; + } + } + if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { + return $c; + } + if ($c === "\r") { + return "\n"; + } + return ' '; + } + + /** + * Does $a indicate end of input? + * + * @param string $a + * @return bool + */ + protected function isEOF($a) + { + return ord($a) <= self::ORD_LF; + } + + /** + * Get next char (without getting it). If is ctrl character, translate to a space or newline. + * + * @return string + */ + protected function peek() + { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } + + /** + * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. + * + * @param string $c + * + * @return bool + */ + protected function isAlphaNum($c) + { + return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126); + } + + /** + * Consume a single line comment from input (possibly retaining it) + */ + protected function consumeSingleLineComment() + { + $comment = ''; + while (true) { + $get = $this->get(); + $comment .= $get; + if (ord($get) <= self::ORD_LF) { // end of line reached + // if IE conditional comment + if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { + $this->keptComment .= "/{$comment}"; + } + return; + } + } + } + + /** + * Consume a multiple line comment from input (possibly retaining it) + * + * @throws JSMin_UnterminatedCommentException + */ + protected function consumeMultipleLineComment() + { + $this->get(); + $comment = ''; + for(;;) { + $get = $this->get(); + if ($get === '*') { + if ($this->peek() === '/') { // end of comment reached + $this->get(); + if (0 === strpos($comment, '!')) { + // preserved by YUI Compressor + if (!$this->keptComment) { + // don't prepend a newline if two comments right after one another + $this->keptComment = "\n"; + } + $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; + } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { + // IE conditional + $this->keptComment .= "/*{$comment}*/"; + } + return; + } + } elseif ($get === null) { + throw new JSMin_UnterminatedCommentException( + "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); + } + $comment .= $get; + } + } + + /** + * Get the next character, skipping over comments. Some comments may be preserved. + * + * @return string + */ + protected function next() + { + $get = $this->get(); + if ($get === '/') { + switch ($this->peek()) { + case '/': + $this->consumeSingleLineComment(); + $get = "\n"; + break; + case '*': + $this->consumeMultipleLineComment(); + $get = ' '; + break; + } + } + return $get; + } +} + +class JSMin_UnterminatedStringException extends Exception {} +class JSMin_UnterminatedCommentException extends Exception {} +class JSMin_UnterminatedRegExpException extends Exception {} diff --git a/tests/classes/external/php/minify-html.php b/tests/classes/external/php/minify-html.php new file mode 100644 index 00000000..45f9f09c --- /dev/null +++ b/tests/classes/external/php/minify-html.php @@ -0,0 +1,268 @@ + + */ +class Minify_HTML { + + /** + * "Minify" an HTML page + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * 'keepComments' : (optional boolean) should the HTML comments be kept + * in the HTML Code? + * + * @return string + */ + public static function minify($html, $options = array()) { + $min = new Minify_HTML($html, $options); + return $min->process(); + } + + + /** + * Create a minifier object + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * @return null + */ + public function __construct($html, $options = array()) + { + $this->_html = str_replace("\r\n", "\n", trim($html)); + if (isset($options['xhtml'])) { + $this->_isXhtml = (bool)$options['xhtml']; + } + if (isset($options['cssMinifier'])) { + $this->_cssMinifier = $options['cssMinifier']; + } + if (isset($options['jsMinifier'])) { + $this->_jsMinifier = $options['jsMinifier']; + } + if (isset($options['keepComments'])) { + $this->_keepComments = $options['keepComments']; + } + } + + + /** + * Minify the markeup given in the constructor + * + * @return string + */ + public function process() + { + if ($this->_isXhtml === null) { + $this->_isXhtml = (false !== strpos($this->_html, '_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']); + $this->_placeholders = array(); + + // replace SCRIPTs (and minify) with placeholders + $this->_html = preg_replace_callback( + '/(\\s*)(]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i' + ,array($this, '_removeScriptCB') + ,$this->_html); + + // replace STYLEs (and minify) with placeholders + $this->_html = preg_replace_callback( + '/\\s*(]*?>)([\\s\\S]*?)<\\/style>\\s*/i' + ,array($this, '_removeStyleCB') + ,$this->_html); + + // remove HTML comments (not containing IE conditional comments). + if ($this->_keepComments == false) { + $this->_html = preg_replace_callback( + '//' + ,array($this, '_commentCB') + ,$this->_html); + } + + // replace PREs with placeholders + $this->_html = preg_replace_callback('/\\s*(]*?>[\\s\\S]*?<\\/pre>)\\s*/i' + ,array($this, '_removePreCB') + ,$this->_html); + + // replace TEXTAREAs with placeholders + $this->_html = preg_replace_callback( + '/\\s*(]*?>[\\s\\S]*?<\\/textarea>)\\s*/i' + ,array($this, '_removeTextareaCB') + ,$this->_html); + + // replace data: URIs with placeholders + $this->_html = preg_replace_callback( + '/(=("|\')data:.*\\2)/Ui' + ,array($this, '_removeDataURICB') + ,$this->_html); + + // trim each line. + // replace by space instead of '' to avoid newline after opening tag getting zapped + $this->_html = preg_replace('/^\s+|\s+$/m', ' ', $this->_html); + + // remove ws around block/undisplayed elements + $this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body' + .'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form' + .'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav' + .'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)' + .'|ul|video)\\b[^>]*>)/i', '$1', $this->_html); + + // remove ws outside of all elements + $this->_html = preg_replace_callback( + '/>([^<]+)_html); + + // use newlines before 1st attribute in open tags (to limit line lengths) + //$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html); + + // fill placeholders + $this->_html = str_replace( + array_keys($this->_placeholders) + ,array_values($this->_placeholders) + ,$this->_html + ); + return $this->_html; + } + + protected function _commentCB($m) + { + return (0 === strpos($m[1], '[') || false !== strpos($m[1], '_replacementHash . count($this->_placeholders) . '%'; + $this->_placeholders[$placeholder] = $content; + return $placeholder; + } + + protected $_isXhtml = null; + protected $_replacementHash = null; + protected $_placeholders = array(); + protected $_cssMinifier = null; + protected $_jsMinifier = null; + protected $_keepComments = false; + + protected function _outsideTagCB($m) + { + return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<'; + } + + protected function _removePreCB($m) + { + return $this->_reservePlace($m[1]); + } + + protected function _removeTextareaCB($m) + { + return $this->_reservePlace($m[1]); + } + + protected function _removeDataURICB($m) + { + return $this->_reservePlace($m[1]); + } + + protected function _removeStyleCB($m) + { + $openStyle = $m[1]; + $css = $m[2]; + // remove HTML comments + $css = preg_replace('/(?:^\\s*\\s*$)/', '', $css); + + // remove CDATA section markers + $css = $this->_removeCdata($css); + + // minify + $minifier = $this->_cssMinifier + ? $this->_cssMinifier + : 'trim'; + $css = call_user_func($minifier, $css); + + return $this->_reservePlace($this->_needsCdata($css) + ? "{$openStyle}/**/" + : "{$openStyle}{$css}" + ); + } + + protected function _removeScriptCB($m) + { + $openScript = $m[2]; + $js = $m[3]; + + // whitespace surrounding? preserve at least one space + $ws1 = ($m[1] === '') ? '' : ' '; + $ws2 = ($m[4] === '') ? '' : ' '; + + // remove HTML comments (and ending "//" if present) + $js = preg_replace('/(?:^\\s*\\s*$)/', '', $js); + + // remove CDATA section markers + $js = $this->_removeCdata($js); + + // minify + $minifier = $this->_jsMinifier + ? $this->_jsMinifier + : 'trim'; + $js = call_user_func($minifier, $js); + + return $this->_reservePlace($this->_needsCdata($js) + ? "{$ws1}{$openScript}/**/{$ws2}" + : "{$ws1}{$openScript}{$js}{$ws2}" + ); + } + + protected function _removeCdata($str) + { + return (false !== strpos($str, ' */','/**/',''), '', $str) + : $str; + } + + protected function _needsCdata($str) + { + return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str)); + } +} diff --git a/tests/classes/external/php/yui-php-cssmin-bundled/Colors.php b/tests/classes/external/php/yui-php-cssmin-bundled/Colors.php new file mode 100644 index 00000000..d37115a3 --- /dev/null +++ b/tests/classes/external/php/yui-php-cssmin-bundled/Colors.php @@ -0,0 +1,155 @@ + 'azure', + '#f5f5dc' => 'beige', + '#ffe4c4' => 'bisque', + '#a52a2a' => 'brown', + '#ff7f50' => 'coral', + '#ffd700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4b0082' => 'indigo', + '#fffff0' => 'ivory', + '#f0e68c' => 'khaki', + '#faf0e6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#fdf5e6' => 'oldlace', + '#808000' => 'olive', + '#ffa500' => 'orange', + '#da70d6' => 'orchid', + '#cd853f' => 'peru', + '#ffc0cb' => 'pink', + '#dda0dd' => 'plum', + '#800080' => 'purple', + '#f00' => 'red', + '#fa8072' => 'salmon', + '#a0522d' => 'sienna', + '#c0c0c0' => 'silver', + '#fffafa' => 'snow', + '#d2b48c' => 'tan', + '#008080' => 'teal', + '#ff6347' => 'tomato', + '#ee82ee' => 'violet', + '#f5deb3' => 'wheat' + ); + } + + public static function getNamedToHexMap() + { + // Named colors longer than hex counterpart + return array( + 'aliceblue' => '#f0f8ff', + 'antiquewhite' => '#faebd7', + 'aquamarine' => '#7fffd4', + 'black' => '#000', + 'blanchedalmond' => '#ffebcd', + 'blueviolet' => '#8a2be2', + 'burlywood' => '#deb887', + 'cadetblue' => '#5f9ea0', + 'chartreuse' => '#7fff00', + 'chocolate' => '#d2691e', + 'cornflowerblue' => '#6495ed', + 'cornsilk' => '#fff8dc', + 'darkblue' => '#00008b', + 'darkcyan' => '#008b8b', + 'darkgoldenrod' => '#b8860b', + 'darkgray' => '#a9a9a9', + 'darkgreen' => '#006400', + 'darkgrey' => '#a9a9a9', + 'darkkhaki' => '#bdb76b', + 'darkmagenta' => '#8b008b', + 'darkolivegreen' => '#556b2f', + 'darkorange' => '#ff8c00', + 'darkorchid' => '#9932cc', + 'darksalmon' => '#e9967a', + 'darkseagreen' => '#8fbc8f', + 'darkslateblue' => '#483d8b', + 'darkslategray' => '#2f4f4f', + 'darkslategrey' => '#2f4f4f', + 'darkturquoise' => '#00ced1', + 'darkviolet' => '#9400d3', + 'deeppink' => '#ff1493', + 'deepskyblue' => '#00bfff', + 'dodgerblue' => '#1e90ff', + 'firebrick' => '#b22222', + 'floralwhite' => '#fffaf0', + 'forestgreen' => '#228b22', + 'fuchsia' => '#f0f', + 'gainsboro' => '#dcdcdc', + 'ghostwhite' => '#f8f8ff', + 'goldenrod' => '#daa520', + 'greenyellow' => '#adff2f', + 'honeydew' => '#f0fff0', + 'indianred' => '#cd5c5c', + 'lavender' => '#e6e6fa', + 'lavenderblush' => '#fff0f5', + 'lawngreen' => '#7cfc00', + 'lemonchiffon' => '#fffacd', + 'lightblue' => '#add8e6', + 'lightcoral' => '#f08080', + 'lightcyan' => '#e0ffff', + 'lightgoldenrodyellow' => '#fafad2', + 'lightgray' => '#d3d3d3', + 'lightgreen' => '#90ee90', + 'lightgrey' => '#d3d3d3', + 'lightpink' => '#ffb6c1', + 'lightsalmon' => '#ffa07a', + 'lightseagreen' => '#20b2aa', + 'lightskyblue' => '#87cefa', + 'lightslategray' => '#778899', + 'lightslategrey' => '#778899', + 'lightsteelblue' => '#b0c4de', + 'lightyellow' => '#ffffe0', + 'limegreen' => '#32cd32', + 'mediumaquamarine' => '#66cdaa', + 'mediumblue' => '#0000cd', + 'mediumorchid' => '#ba55d3', + 'mediumpurple' => '#9370db', + 'mediumseagreen' => '#3cb371', + 'mediumslateblue' => '#7b68ee', + 'mediumspringgreen' => '#00fa9a', + 'mediumturquoise' => '#48d1cc', + 'mediumvioletred' => '#c71585', + 'midnightblue' => '#191970', + 'mintcream' => '#f5fffa', + 'mistyrose' => '#ffe4e1', + 'moccasin' => '#ffe4b5', + 'navajowhite' => '#ffdead', + 'olivedrab' => '#6b8e23', + 'orangered' => '#ff4500', + 'palegoldenrod' => '#eee8aa', + 'palegreen' => '#98fb98', + 'paleturquoise' => '#afeeee', + 'palevioletred' => '#db7093', + 'papayawhip' => '#ffefd5', + 'peachpuff' => '#ffdab9', + 'powderblue' => '#b0e0e6', + 'rebeccapurple' => '#663399', + 'rosybrown' => '#bc8f8f', + 'royalblue' => '#4169e1', + 'saddlebrown' => '#8b4513', + 'sandybrown' => '#f4a460', + 'seagreen' => '#2e8b57', + 'seashell' => '#fff5ee', + 'slateblue' => '#6a5acd', + 'slategray' => '#708090', + 'slategrey' => '#708090', + 'springgreen' => '#00ff7f', + 'steelblue' => '#4682b4', + 'turquoise' => '#40e0d0', + 'white' => '#fff', + 'whitesmoke' => '#f5f5f5', + 'yellow' => '#ff0', + 'yellowgreen' => '#9acd32' + ); + } +} diff --git a/tests/classes/external/php/yui-php-cssmin-bundled/Minifier.php b/tests/classes/external/php/yui-php-cssmin-bundled/Minifier.php new file mode 100644 index 00000000..1b92d6d6 --- /dev/null +++ b/tests/classes/external/php/yui-php-cssmin-bundled/Minifier.php @@ -0,0 +1,895 @@ +raisePhpLimits = (bool) $raisePhpLimits; + $this->memoryLimit = 128 * 1048576; // 128MB in bytes + $this->pcreBacktrackLimit = 1000 * 1000; + $this->pcreRecursionLimit = 500 * 1000; + $this->hexToNamedColorsMap = Colors::getHexToNamedMap(); + $this->namedToHexColorsMap = Colors::getNamedToHexMap(); + $this->namedToHexColorsRegex = sprintf( + '/([:,( ])(%s)( |,|\)|;|$)/Si', + implode('|', array_keys($this->namedToHexColorsMap)) + ); + $this->numRegex = sprintf('-?\d*\.?\d+%s?', $this->unitsGroupRegex); + $this->setShortenZeroValuesRegexes(); + } + + /** + * Parses & minifies the given input CSS string + * @param string $css + * @return string + */ + public function run($css = '') + { + if (empty($css) || !is_string($css)) { + return ''; + } + + $this->resetRunProperties(); + + if ($this->raisePhpLimits) { + $this->doRaisePhpLimits(); + } + + return $this->minify($css); + } + + /** + * Sets whether to keep or remove sourcemap special comment. + * Sourcemap comments are removed by default. + * @param bool $keepSourceMapComment + */ + public function keepSourceMapComment($keepSourceMapComment = true) + { + $this->keepSourceMapComment = (bool) $keepSourceMapComment; + } + + /** + * Sets whether to keep or remove important comments. + * Important comments outside of a declaration block are kept by default. + * @param bool $removeImportantComments + */ + public function removeImportantComments($removeImportantComments = true) + { + $this->keepImportantComments = !(bool) $removeImportantComments; + } + + /** + * Sets the approximate column after which long lines will be splitted in the output + * with a linebreak. + * @param int $position + */ + public function setLineBreakPosition($position) + { + $this->linebreakPosition = (int) $position; + } + + /** + * Sets the memory limit for this script + * @param int|string $limit + */ + public function setMemoryLimit($limit) + { + $this->memoryLimit = Utils::normalizeInt($limit); + } + + /** + * Sets the maximum execution time for this script + * @param int|string $seconds + */ + public function setMaxExecutionTime($seconds) + { + $this->maxExecutionTime = (int) $seconds; + } + + /** + * Sets the PCRE backtrack limit for this script + * @param int $limit + */ + public function setPcreBacktrackLimit($limit) + { + $this->pcreBacktrackLimit = (int) $limit; + } + + /** + * Sets the PCRE recursion limit for this script + * @param int $limit + */ + public function setPcreRecursionLimit($limit) + { + $this->pcreRecursionLimit = (int) $limit; + } + + /** + * Builds regular expressions needed for shortening zero values + */ + private function setShortenZeroValuesRegexes() + { + $zeroRegex = '0'. $this->unitsGroupRegex; + $numOrPosRegex = '('. $this->numRegex .'|top|left|bottom|right|center) '; + $oneZeroSafeProperties = array( + '(?:line-)?height', + '(?:(?:min|max)-)?width', + 'top', + 'left', + 'background-position', + 'bottom', + 'right', + 'border(?:-(?:top|left|bottom|right))?(?:-width)?', + 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', + 'column-(?:gap|width)', + 'margin(?:-(?:top|left|bottom|right))?', + 'outline-width', + 'padding(?:-(?:top|left|bottom|right))?' + ); + + // First zero regex + $regex = '/(^|;)('. implode('|', $oneZeroSafeProperties) .'):%s/Si'; + $this->shortenOneZeroesRegex = sprintf($regex, $zeroRegex); + + // Multiple zeroes regexes + $regex = '/(^|;)(margin|padding|border-(?:width|radius)|background-position):%s/Si'; + $this->shortenTwoZeroesRegex = sprintf($regex, $numOrPosRegex . $zeroRegex); + $this->shortenThreeZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $zeroRegex); + $this->shortenFourZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $numOrPosRegex . $zeroRegex); + } + + /** + * Resets properties whose value may change between runs + */ + private function resetRunProperties() + { + $this->comments = array(); + $this->ruleBodies = array(); + $this->preservedTokens = array(); + } + + /** + * Tries to configure PHP to use at least the suggested minimum settings + * @return void + */ + private function doRaisePhpLimits() + { + $phpLimits = array( + 'memory_limit' => $this->memoryLimit, + 'max_execution_time' => $this->maxExecutionTime, + 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, + 'pcre.recursion_limit' => $this->pcreRecursionLimit + ); + + // If current settings are higher respect them. + foreach ($phpLimits as $name => $suggested) { + $current = Utils::normalizeInt(ini_get($name)); + + if ($current >= $suggested) { + continue; + } + + // memoryLimit exception: allow -1 for "no memory limit". + if ($name === 'memory_limit' && $current === -1) { + continue; + } + + // maxExecutionTime exception: allow 0 for "no memory limit". + if ($name === 'max_execution_time' && $current === 0) { + continue; + } + + ini_set($name, $suggested); + } + } + + /** + * Registers a preserved token + * @param string $token + * @return string The token ID string + */ + private function registerPreservedToken($token) + { + $tokenId = sprintf(self::PRESERVED_TOKEN, count($this->preservedTokens)); + $this->preservedTokens[$tokenId] = $token; + return $tokenId; + } + + /** + * Registers a candidate comment token + * @param string $comment + * @return string The comment token ID string + */ + private function registerCommentToken($comment) + { + $tokenId = sprintf(self::COMMENT_TOKEN, count($this->comments)); + $this->comments[$tokenId] = $comment; + return $tokenId; + } + + /** + * Registers a rule body token + * @param string $body the minified rule body + * @return string The rule body token ID string + */ + private function registerRuleBodyToken($body) + { + if (empty($body)) { + return ''; + } + + $tokenId = sprintf(self::RULE_BODY_TOKEN, count($this->ruleBodies)); + $this->ruleBodies[$tokenId] = $body; + return $tokenId; + } + + /** + * Parses & minifies the given input CSS string + * @param string $css + * @return string + */ + private function minify($css) + { + // Process data urls + $css = $this->processDataUrls($css); + + // Process comments + $css = preg_replace_callback( + '/(?processComments($css); + + // Process rule bodies + $css = $this->processRuleBodies($css); + + // Process at-rules and selectors + $css = $this->processAtRulesAndSelectors($css); + + // Restore preserved rule bodies before splitting + $css = strtr($css, $this->ruleBodies); + + // Split long lines in output if required + $css = $this->processLongLineSplitting($css); + + // Restore preserved comments and strings + $css = strtr($css, $this->preservedTokens); + + return trim($css); + } + + /** + * Searches & replaces all data urls with tokens before we start compressing, + * to avoid performance issues running some of the subsequent regexes against large string chunks. + * @param string $css + * @return string + */ + private function processDataUrls($css) + { + $ret = ''; + $searchOffset = $substrOffset = 0; + + // Since we need to account for non-base64 data urls, we need to handle + // ' and ) being part of the data string. + while (preg_match('/url\(\s*(["\']?)data:/Si', $css, $m, PREG_OFFSET_CAPTURE, $searchOffset)) { + $matchStartIndex = $m[0][1]; + $dataStartIndex = $matchStartIndex + 4; // url( length + $searchOffset = $matchStartIndex + strlen($m[0][0]); + $terminator = $m[1][0]; // ', " or empty (not quoted) + $terminatorRegex = '/(?registerPreservedToken(trim($token)) .')'; + // No end terminator found, re-add the whole match. Should we throw/warn here? + } else { + $ret .= substr($css, $matchStartIndex, $searchOffset - $matchStartIndex); + } + + $substrOffset = $searchOffset; + } + + $ret .= substr($css, $substrOffset); + + return $ret; + } + + /** + * Registers all comments found as candidates to be preserved. + * @param array $matches + * @return string + */ + private function processCommentsCallback($matches) + { + return '/*'. $this->registerCommentToken($matches[1]) .'*/'; + } + + /** + * Preserves old IE Matrix string definition + * @param array $matches + * @return string + */ + private function processOldIeSpecificMatrixDefinitionCallback($matches) + { + return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $this->registerPreservedToken($matches[1]) .')'; + } + + /** + * Preserves strings found + * @param array $matches + * @return string + */ + private function processStringsCallback($matches) + { + $match = $matches[0]; + $quote = substr($match, 0, 1); + $match = substr($match, 1, -1); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (strpos($match, self::COMMENT_TOKEN_START) !== false) { + $match = strtr($match, $this->comments); + } + + // minify alpha opacity in filter strings + $match = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $match); + + return $quote . $this->registerPreservedToken($match) . $quote; + } + + /** + * Searches & replaces all import at-rule unquoted urls with tokens so URI reserved characters such as a semicolon + * may be used safely in a URL. + * @param array $matches + * @return string + */ + private function processImportUnquotedUrlAtRulesCallback($matches) + { + return '@import url('. $this->registerPreservedToken($matches[1]) .')'. $matches[2]; + } + + /** + * Preserves or removes comments found. + * @param string $css + * @return string + */ + private function processComments($css) + { + foreach ($this->comments as $commentId => $comment) { + $commentIdString = '/*'. $commentId .'*/'; + + // ! in the first position of the comment means preserve + // so push to the preserved tokens keeping the ! + if ($this->keepImportantComments && strpos($comment, '!') === 0) { + $preservedTokenId = $this->registerPreservedToken($comment); + // Put new lines before and after /*! important comments + $css = str_replace($commentIdString, "\n/*$preservedTokenId*/\n", $css); + continue; + } + + // # sourceMappingURL= in the first position of the comment means sourcemap + // so push to the preserved tokens if {$this->keepSourceMapComment} is truthy. + if ($this->keepSourceMapComment && strpos($comment, '# sourceMappingURL=') === 0) { + $preservedTokenId = $this->registerPreservedToken($comment); + // Add new line before the sourcemap comment + $css = str_replace($commentIdString, "\n/*$preservedTokenId*/", $css); + continue; + } + + // Keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (strlen($comment) === 0 && strpos($css, '>/*'.$commentId) !== false) { + $css = str_replace($commentId, $this->registerPreservedToken(''), $css); + continue; + } + + // in all other cases kill the comment + $css = str_replace($commentIdString, '', $css); + } + + // Normalize whitespace again + $css = preg_replace('/ +/S', ' ', $css); + + return $css; + } + + /** + * Finds, minifies & preserves all rule bodies. + * @param string $css the whole stylesheet. + * @return string + */ + private function processRuleBodies($css) + { + $ret = ''; + $searchOffset = $substrOffset = 0; + + while (($blockStartPos = strpos($css, '{', $searchOffset)) !== false) { + $blockEndPos = strpos($css, '}', $blockStartPos); + $nextBlockStartPos = strpos($css, '{', $blockStartPos + 1); + $ret .= substr($css, $substrOffset, $blockStartPos - $substrOffset); + + if ($nextBlockStartPos !== false && $nextBlockStartPos < $blockEndPos) { + $ret .= substr($css, $blockStartPos, $nextBlockStartPos - $blockStartPos); + $searchOffset = $nextBlockStartPos; + } else { + $ruleBody = substr($css, $blockStartPos + 1, $blockEndPos - $blockStartPos - 1); + $ruleBodyToken = $this->registerRuleBodyToken($this->processRuleBody($ruleBody)); + $ret .= '{'. $ruleBodyToken .'}'; + $searchOffset = $blockEndPos + 1; + } + + $substrOffset = $searchOffset; + } + + $ret .= substr($css, $substrOffset); + + return $ret; + } + + /** + * Compresses non-group rule bodies. + * @param string $body The rule body without curly braces + * @return string + */ + private function processRuleBody($body) + { + $body = trim($body); + + // Remove spaces before the things that should not have spaces before them. + $body = preg_replace('/ ([:=,)*\/;\n])/S', '$1', $body); + + // Remove the spaces after the things that should not have spaces after them. + $body = preg_replace('/([:=,(*\/!;\n]) /S', '$1', $body); + + // Replace multiple semi-colons in a row by a single one + $body = preg_replace('/;;+/S', ';', $body); + + // Remove semicolon before closing brace except when: + // - The last property is prefixed with a `*` (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers. + if (!preg_match('/\*[a-z0-9-]+:[^;]+;$/Si', $body)) { + $body = rtrim($body, ';'); + } + + // Remove important comments inside a rule body (because they make no sense here). + if (strpos($body, '/*') !== false) { + $body = preg_replace('/\n?\/\*[A-Z0-9_]+\*\/\n?/S', '', $body); + } + + // Empty rule body? Exit :) + if (empty($body)) { + return ''; + } + + // Shorten font-weight values + $body = preg_replace( + array('/(font-weight:)bold\b/Si', '/(font-weight:)normal\b/Si'), + array('${1}700', '${1}400'), + $body + ); + + // Shorten background property + $body = preg_replace('/(background:)(?:none|transparent)( !|;|$)/Si', '${1}0 0$2', $body); + + // Shorten opacity IE filter + $body = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $body); + + // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space) + // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space) + // This makes it more likely that it'll get further compressed in the next step. + $body = preg_replace_callback( + '/(rgb|hsl)\(([0-9,.% -]+)\)(.|$)/Si', + array($this, 'shortenHslAndRgbToHexCallback'), + $body + ); + + // Shorten colors from #AABBCC to #ABC or shorter color name: + // - Look for hex colors which don't have a "=" in front of them (to avoid MSIE filters) + $body = preg_replace_callback( + '/(? #fff. + // Run at least 2 times to cover most cases + $body = preg_replace_callback( + array($this->namedToHexColorsRegex, $this->namedToHexColorsRegex), + array($this, 'shortenNamedColorsCallback'), + $body + ); + + // Replace positive sign from numbers before the leading space is removed. + // +1.2em to 1.2em, +.8px to .8px, +2% to 2% + $body = preg_replace('/([ :,(])\+(\.?\d+)/S', '$1$2', $body); + + // shorten ms to s + $body = preg_replace_callback('/([ :,(])(-?)(\d{3,})ms/Si', function ($matches) { + return $matches[1] . $matches[2] . ((int) $matches[3] / 1000) .'s'; + }, $body); + + // Remove leading zeros from integer and float numbers. + // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05 + $body = preg_replace('/([ :,(])(-?)0+([1-9]?\.?\d+)/S', '$1$2$3', $body); + + // Remove trailing zeros from float numbers. + // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px + $body = preg_replace('/([ :,(])(-?\d?\.\d+?)0+([^\d])/S', '$1$2$3', $body); + + // Remove trailing .0 -> -9.0 to -9 + $body = preg_replace('/([ :,(])(-?\d+)\.0([^\d])/S', '$1$2$3', $body); + + // Replace 0 length numbers with 0 + $body = preg_replace('/([ :,(])-?\.?0+([^\d])/S', '${1}0$2', $body); + + // Shorten zero values for safe properties only + $body = preg_replace( + array( + $this->shortenOneZeroesRegex, + $this->shortenTwoZeroesRegex, + $this->shortenThreeZeroesRegex, + $this->shortenFourZeroesRegex + ), + array( + '$1$2:0', + '$1$2:$3 0', + '$1$2:$3 $4 0', + '$1$2:$3 $4 $5 0' + ), + $body + ); + + // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. + $body = preg_replace('/(background-position):0(?: 0){2,3}( !|;|$)/Si', '$1:0 0$2', $body); + + // Shorten suitable shorthand properties with repeated values + $body = preg_replace( + array( + '/(margin|padding|border-(?:width|radius)):('.$this->numRegex.')(?: \2)+( !|;|$)/Si', + '/(border-(?:style|color)):([#a-z0-9]+)(?: \2)+( !|;|$)/Si' + ), + '$1:$2$3', + $body + ); + $body = preg_replace( + array( + '/(margin|padding|border-(?:width|radius)):'. + '('.$this->numRegex.') ('.$this->numRegex.') \2 \3( !|;|$)/Si', + '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) \2 \3( !|;|$)/Si' + ), + '$1:$2 $3$4', + $body + ); + $body = preg_replace( + array( + '/(margin|padding|border-(?:width|radius)):'. + '('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') \3( !|;|$)/Si', + '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) ([#a-z0-9]+) \3( !|;|$)/Si' + ), + '$1:$2 $3 $4$5', + $body + ); + + // Lowercase some common functions that can be values + $body = preg_replace_callback( + '/(?:attr|blur|brightness|circle|contrast|cubic-bezier|drop-shadow|ellipse|from|grayscale|'. + 'hsla?|hue-rotate|inset|invert|local|minmax|opacity|perspective|polygon|rgba?|rect|repeat|saturate|sepia|'. + 'steps|to|url|var|-webkit-gradient|'. + '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|(?:repeating-)?(?:linear|radial)-gradient))\(/Si', + array($this, 'strtolowerCallback'), + $body + ); + + // Lowercase all uppercase properties + $body = preg_replace_callback('/(?:^|;)[A-Z-]+:/S', array($this, 'strtolowerCallback'), $body); + + return $body; + } + + /** + * Compresses At-rules and selectors. + * @param string $css the whole stylesheet with rule bodies tokenized. + * @return string + */ + private function processAtRulesAndSelectors($css) + { + $charset = ''; + $imports = ''; + $namespaces = ''; + + // Remove spaces before the things that should not have spaces before them. + $css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css); + + // Remove the spaces after the things that should not have spaces after them. + $css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css); + + // Shorten shortable double colon (CSS3) pseudo-elements to single colon (CSS2) + $css = preg_replace('/::(before|after|first-(?:line|letter))(\{|,)/Si', ':$1$2', $css); + + // Retain space for special IE6 cases + $css = preg_replace_callback('/:first-(line|letter)(\{|,)/Si', function ($matches) { + return ':first-'. strtolower($matches[1]) .' '. $matches[2]; + }, $css); + + // Find a fraction that may used in some @media queries such as: (min-aspect-ratio: 1/1) + // Add token to add the "/" back in later + $css = preg_replace('/\(([a-z-]+):([0-9]+)\/([0-9]+)\)/Si', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); + + // Remove empty rule blocks up to 2 levels deep. + $css = preg_replace(array_fill(0, 2, '/(\{)[^{};\/\n]+\{\}/S'), '$1', $css); + $css = preg_replace('/[^{};\/\n]+\{\}/S', '', $css); + + // Two important comments next to each other? Remove extra newline. + if ($this->keepImportantComments) { + $css = str_replace("\n\n", "\n", $css); + } + + // Restore fraction + $css = str_replace(self::QUERY_FRACTION, '/', $css); + + // Lowercase some popular @directives + $css = preg_replace_callback( + '/(?charsetRegex, $css, $matches)) { + // Keep the first @charset at-rule found + $charset = $matches[0]; + // Delete all @charset at-rules + $css = preg_replace($this->charsetRegex, '', $css); + } + + // @import handling + $css = preg_replace_callback($this->importRegex, function ($matches) use (&$imports) { + // Keep all @import at-rules found for later + $imports .= $matches[0]; + // Delete all @import at-rules + return ''; + }, $css); + + // @namespace handling + $css = preg_replace_callback($this->namespaceRegex, function ($matches) use (&$namespaces) { + // Keep all @namespace at-rules found for later + $namespaces .= $matches[0]; + // Delete all @namespace at-rules + return ''; + }, $css); + + // Order critical at-rules: + // 1. @charset first + // 2. @imports below @charset + // 3. @namespaces below @imports + $css = $charset . $imports . $namespaces . $css; + + return $css; + } + + /** + * Splits long lines after a specific column. + * + * Some source control tools don't like it when files containing lines longer + * than, say 8000 characters, are checked in. The linebreak option is used in + * that case to split long lines after a specific column. + * + * @param string $css the whole stylesheet. + * @return string + */ + private function processLongLineSplitting($css) + { + if ($this->linebreakPosition > 0) { + $l = strlen($css); + $offset = $this->linebreakPosition; + while (preg_match('/(?linebreakPosition; + $l += 1; + if ($offset > $l) { + break; + } + } + } + + return $css; + } + + /** + * Converts hsl() & rgb() colors to HEX format. + * @param $matches + * @return string + */ + private function shortenHslAndRgbToHexCallback($matches) + { + $type = $matches[1]; + $values = explode(',', $matches[2]); + $terminator = $matches[3]; + + if ($type === 'hsl') { + $values = Utils::hslToRgb($values); + } + + $hexColors = Utils::rgbToHex($values); + + // Restore space after rgb() or hsl() function in some cases such as: + // background-image: linear-gradient(to bottom, rgb(210,180,140) 10%, rgb(255,0,0) 90%); + if (!empty($terminator) && !preg_match('/[ ,);]/S', $terminator)) { + $terminator = ' '. $terminator; + } + + return '#'. implode('', $hexColors) . $terminator; + } + + /** + * Compresses HEX color values of the form #AABBCC to #ABC or short color name. + * @param $matches + * @return string + */ + private function shortenHexColorsCallback($matches) + { + $hex = $matches[1]; + + // Shorten suitable 6 chars HEX colors + if (strlen($hex) === 6 && preg_match('/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/Si', $hex, $m)) { + $hex = $m[1] . $m[2] . $m[3]; + } + + // Lowercase + $hex = '#'. strtolower($hex); + + // Replace Hex colors with shorter color names + $color = array_key_exists($hex, $this->hexToNamedColorsMap) ? $this->hexToNamedColorsMap[$hex] : $hex; + + return $color . $matches[2]; + } + + /** + * Shortens all named colors with a shorter HEX counterpart for a set of safe properties + * e.g. white -> #fff + * @param array $matches + * @return string + */ + private function shortenNamedColorsCallback($matches) + { + return $matches[1] . $this->namedToHexColorsMap[strtolower($matches[2])] . $matches[3]; + } + + /** + * Makes a string lowercase + * @param array $matches + * @return string + */ + private function strtolowerCallback($matches) + { + return strtolower($matches[0]); + } +} diff --git a/tests/classes/external/php/yui-php-cssmin-bundled/Utils.php b/tests/classes/external/php/yui-php-cssmin-bundled/Utils.php new file mode 100644 index 00000000..6afdb63d --- /dev/null +++ b/tests/classes/external/php/yui-php-cssmin-bundled/Utils.php @@ -0,0 +1,149 @@ + 1 ? $vh - 1 : $vh); + + if ($vh * 6 < 1) { + return $v1 + ($v2 - $v1) * 6 * $vh; + } + + if ($vh * 2 < 1) { + return $v2; + } + + if ($vh * 3 < 2) { + return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; + } + + return $v1; + } + + /** + * Convert strings like "64M" or "30" to int values + * @param mixed $size + * @return int + */ + public static function normalizeInt($size) + { + if (is_string($size)) { + $letter = substr($size, -1); + $size = intval($size); + switch ($letter) { + case 'M': + case 'm': + return (int) $size * 1048576; + case 'K': + case 'k': + return (int) $size * 1024; + case 'G': + case 'g': + return (int) $size * 1073741824; + } + } + return (int) $size; + } + + /** + * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 + * @param $rgbPercentage + * @return int + */ + public static function rgbPercentageToRgbInteger($rgbPercentage) + { + if (strpos($rgbPercentage, '%') !== false) { + $rgbPercentage = self::roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); + } + + return intval($rgbPercentage, 10); + } + + /** + * Converts a RGB color into a HEX color + * @param array $rgbColors + * @return array + */ + public static function rgbToHex($rgbColors) + { + $hexColors = array(); + + // Values outside the sRGB color space should be clipped (0-255) + for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { + $hexColors[$i] = sprintf("%02x", self::clampNumberSrgb(self::rgbPercentageToRgbInteger($rgbColors[$i]))); + } + + return $hexColors; + } + + /** + * Rounds a number to its closest integer + * @param $n + * @return int + */ + public static function roundNumber($n) + { + return intval(round(floatval($n)), 10); + } +} diff --git a/tests/classes/external/php/yui-php-cssmin-bundled/index.html b/tests/classes/external/php/yui-php-cssmin-bundled/index.html new file mode 100644 index 00000000..fe7267f7 --- /dev/null +++ b/tests/classes/external/php/yui-php-cssmin-bundled/index.html @@ -0,0 +1 @@ +Generated by Autoptimize diff --git a/tests/classes/index.html b/tests/classes/index.html new file mode 100644 index 00000000..e5346ce1 --- /dev/null +++ b/tests/classes/index.html @@ -0,0 +1 @@ +Generated by Autoptimize \ No newline at end of file diff --git a/tests/classes/static/loading.gif b/tests/classes/static/loading.gif new file mode 100644 index 00000000..176577b0 Binary files /dev/null and b/tests/classes/static/loading.gif differ diff --git a/tests/classes/static/toolbar.css b/tests/classes/static/toolbar.css new file mode 100644 index 00000000..6334d396 --- /dev/null +++ b/tests/classes/static/toolbar.css @@ -0,0 +1,246 @@ +/* Loading Modal */ +.autoptimize-loading +{ + display: none; + position: fixed; + background-color: rgba(102, 102, 102, 0.8); + background-image: url('loading.gif'); + background-position: center; + background-repeat: no-repeat; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9000000000; +} + +/* Toolbar Font Colors */ +#wp-admin-bar-autoptimize .white +{ + color: #EEE; +} + +#wp-admin-bar-autoptimize .green +{ + color: #26BD26; +} + +#wp-admin-bar-autoptimize .orange +{ + color: #EC9103; +} + +#wp-admin-bar-autoptimize .red +{ + color: #EA1919; +} + +#wp-admin-bar-autoptimize .bg-green +{ + background: #26BD26; +} + +#wp-admin-bar-autoptimize .bg-orange +{ + background: #EC9103; +} + +#wp-admin-bar-autoptimize .bg-red +{ + background: #EA1919; +} + +/* Toolbar Bullet Icons */ +#wp-admin-bar-autoptimize.bullet-green .ab-icon::before, +#wp-admin-bar-autoptimize.bullet-green:hover .ab-icon::before +{ + content: "\f159"; + color: #02CA02; + font-size: 14px; +} + +#wp-admin-bar-autoptimize.bullet-orange .ab-icon::before, +#wp-admin-bar-autoptimize.bullet-orange:hover .ab-icon::before +{ + content: "\f159"; + color: #EC9103; + font-size: 14px; +} + +#wp-admin-bar-autoptimize.bullet-red .ab-icon::before, +#wp-admin-bar-autoptimize.bullet-red:hover .ab-icon::before +{ + content: "\f159"; + color: #EA1919; + font-size: 14px; + -webkit-animation: blink 1s step-end infinite; + animation: blink 1s step-end infinite; +} + +@-webkit-keyframes blink { 50% { visibility: hidden; }} + @keyframes blink { 50% { visibility: hidden; }} + + +/* Some cosmetic Toolbar things */ +#wp-admin-bar-autoptimize table, #wp-admin-bar-autoptimize th, #wp-admin-bar-autoptimize td +{ + border: 0px !important; +} + +#wp-admin-bar-autoptimize-default +{ + padding-top: 0 !important; +} + +#wp-admin-bar-autoptimize-delete-cache .ab-item +{ + cursor: pointer !important; + background: #464b50; +} + +#wp-admin-bar-autoptimize-delete-cache .ab-item:hover +{ + color: rgba(240,245,250,0.85) !important; + background: #B57373 !important; +} + +#wp-admin-bar-autoptimize-cache-info +{ + padding-top: 8px !important; + padding-bottom: 8px !important; +} + +#wp-admin-bar-autoptimize-cache-info, +#wp-admin-bar-autoptimize-cache-info .ab-item +{ + height: auto !important; + cursor: default !important; +} + +#wp-admin-bar-autoptimize-cache-info td + td +{ + padding-left: 3px; +} + +#wp-admin-bar-autoptimize-cache-info .ab-item, +#wp-admin-bar-autoptimize-cache-info .ab-item:hover +{ + color: #b4b9be !important; +} + +#wp-admin-bar-autoptimize-cache-info .ab-item > p +{ + display: block; +} + +#wp-admin-bar-autoptimize-cache-info .ab-item p, +#wp-admin-bar-autoptimize-cache-info .ab-item td +{ + font-size: 11px !important; + line-height: 16px !important; +} + +#wp-admin-bar-autoptimize-cache-info .ab-item table +{ + display: inline-block !important; + margin-left: 10px !important; +} + +/* Radial Bar */ +.autoptimize-radial-bar +{ + display: inline-block !important; + margin-top: 5px !important; +} +.autoptimize-radial-bar, +.autoptimize-radial-bar .mask, +.autoptimize-radial-bar .fill, +.autoptimize-radial-bar .shadow +{ + width : 36px !important; + height : 36px !important; +} +.autoptimize-radial-bar +{ + background-color : #d6dadc; +} +.autoptimize-radial-bar .fill +{ + background-color : #02ca02; +} +.autoptimize-radial-bar .numbers +{ + color : #02ca02; +} +.autoptimize-radial-bar .mask +{ + clip : rect(0px, 36px, 36px, 18px); +} +.autoptimize-radial-bar .fill +{ + clip : rect(0px, 18px, 36px, 0px); +} +.autoptimize-radial-bar .inset +{ + width : 26px !important; + height : 26px !important; + + margin-left : 5px !important; + margin-top : 5px !important; + + background-color : #32373c; +} +.autoptimize-radial-bar .percentage +{ + width : 26px !important; + height : 16px !important; + line-height : 11px !important; + + top : 7px !important; + left : 0px !important; + + overflow : hidden; +} +.autoptimize-radial-bar .numbers +{ + width : 26px !important; + font-weight : 600 !important; + font-size : 9px !important; + + margin-top : -10px !important; + + display : inline-block; + vertical-align : top; + text-align : center; +} + +.autoptimize-radial-bar .inset +{ + box-shadow : 3px 3px 5px rgba(0,0,0,0.3) !important; +} +.autoptimize-radial-bar .shadow +{ + box-shadow : 3px 3px 5px rgba(0,0,0,0.3) inset !important; +} + +.autoptimize-radial-bar .mask, +.autoptimize-radial-bar .fill, +.autoptimize-radial-bar .shadow, +.autoptimize-radial-bar .inset, +.autoptimize-radial-bar .percentage +{ + position : absolute !important; +} + +.autoptimize-radial-bar, +.autoptimize-radial-bar .mask, +.autoptimize-radial-bar .fill, +.autoptimize-radial-bar .shadow, +.autoptimize-radial-bar .inset +{ + border-radius : 50% !important; +} + +/* fixes for toolbar on frontend for other themes messing things up */ +#wp-admin-bar-autoptimize tr{border:0 !important} +#wp-admin-bar-autoptimize td{background-color:#32373c !important} diff --git a/tests/classes/static/toolbar.js b/tests/classes/static/toolbar.js new file mode 100644 index 00000000..9217537c --- /dev/null +++ b/tests/classes/static/toolbar.js @@ -0,0 +1,82 @@ +jQuery( document ).ready(function() +{ + var percentage = jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar' ).attr('percentage'); + var rotate = percentage * 1.8; + + jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .mask.full, #wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill' ).css({ + '-webkit-transform' : 'rotate(' + rotate + 'deg)', + '-ms-transform' : 'rotate(' + rotate + 'deg)', + 'transform' : 'rotate(' + rotate + 'deg)' + }); + + // Fix Background color of circle percentage & delete cache to fit with the current color theme + jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .inset' ).css( 'background-color', jQuery( '#wp-admin-bar-autoptimize .ab-sub-wrapper' ).css( 'background-color') ); + jQuery( '#wp-admin-bar-autoptimize-delete-cache .ab-item' ).css( 'background-color', jQuery( '#wpadminbar' ).css( 'background-color') ); + + jQuery( '#wp-admin-bar-autoptimize-default li' ).click(function(e) + { + var id = ( typeof e.target.id != 'undefined' && e.target.id ) ? e.target.id : jQuery( e.target ).parent( 'li' ).attr( 'id' ); + var action = ''; + + if( id == 'wp-admin-bar-autoptimize-delete-cache' ){ + action = 'autoptimize_delete_cache'; + } else { + return; + } + + // Remove the class "hover" from drop-down Autoptimize menu to hide it. + jQuery( '#wp-admin-bar-autoptimize' ).removeClass( 'hover' ); + + // Create and Show the Autoptimize Loading Modal + var modal_loading = jQuery( '
    ' ).appendTo( 'body' ).show(); + + var success = function() { + // Reset output values & class names of cache info + jQuery( '#wp-admin-bar-autoptimize-cache-info .size' ).attr( 'class', 'size green' ).html( '0.00 B' ); + jQuery( '#wp-admin-bar-autoptimize-cache-info .files' ).html( '0' ); + jQuery( '#wp-admin-bar-autoptimize-cache-info .percentage .numbers' ).attr( 'class', 'numbers green' ).html( '0%' ); + jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill' ).attr( 'class', 'fill bg-green' ); + + // Reset the class names of bullet icon + jQuery( '#wp-admin-bar-autoptimize' ).attr( 'class', 'menupop bullet-green' ); + + // Reset the Radial Bar progress + jQuery( '#wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .mask.full, #wp-admin-bar-autoptimize-cache-info .autoptimize-radial-bar .fill' ).css({ + '-webkit-transform' : 'rotate(0deg)', + '-ms-transform' : 'rotate(0deg)', + 'transform' : 'rotate(0deg)' + }); + }; + + var notice = function() { + jQuery( '

    ' + autoptimize_ajax_object.error_msg + '


    ' ).insertAfter( '#wpbody .wrap h1:first-of-type' ).show(); + }; + + jQuery.ajax({ + type : 'GET', + url : autoptimize_ajax_object.ajaxurl, + data : {'action':action, 'nonce':autoptimize_ajax_object.nonce}, + dataType : 'json', + cache : false, + timeout : 9000, + success : function( cleared ) + { + // Remove the Autoptimize Loading Modal + modal_loading.remove(); + if ( cleared ) { + success(); + } else { + notice(); + } + }, + error: function( jqXHR, textStatus ) + { + // Remove the Autoptimize Loading Modal + modal_loading.remove(); + + // WordPress Admin Notice + notice(); + } + }); + }); +}); diff --git a/tests/composer.json b/tests/composer.json new file mode 100644 index 00000000..ba269b4b --- /dev/null +++ b/tests/composer.json @@ -0,0 +1,37 @@ +{ + "name": "futtta/autoptimize", + "description": "Optimizes your WordPress website, concatenating the CSS and JavaScript code, and compressing it.", + "type": "wordpress-plugin", + "homepage": "https://autoptimize.com/", + "keywords": [ + "async", + "minify", + "optimize", + "pagespeed", + "performance" + ], + "license": "GPL", + "minimum-stability": "dev", + "prefer-stable": true, + "support": { + "issues": "https://wordpress.org/support/plugin/autoptimize" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php": ">=5.6", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.2", + "squizlabs/php_codesniffer": "^3.1", + "phpunit/phpunit": "^5.7.27", + "wimg/php-compatibility": "*", + "wp-coding-standards/wpcs": "*" + }, + "scripts": { + "test": [ + "@phpunit" + ], + "phpunit": "phpunit", + "phpcs": "phpcs" + } +} diff --git a/tests/composer.lock b/tests/composer.lock new file mode 100644 index 00000000..1f10017e --- /dev/null +++ b/tests/composer.lock @@ -0,0 +1,1552 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "339a3a6488d0c1cfeaf9fb3517b768e6", + "packages": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.4.4", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": "^5.3|^7", + "squizlabs/php_codesniffer": "*" + }, + "require-dev": { + "composer/composer": "*", + "wimg/php-compatibility": "^8.0" + }, + "suggest": { + "dealerdirect/qa-tools": "All the PHP QA tools you'll need" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "f.nijhof@dealerdirect.nl", + "homepage": "http://workingatdealerdirect.eu", + "role": "Developer" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://workingatdealerdirect.eu", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "time": "2017-12-06T16:27:17+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", + "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-10T14:09:06+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "9f901e29c93dae4aa77c0bb161df4276f9c9a1be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9f901e29c93dae4aa77c0bb161df4276f9c9a1be", + "reference": "9f901e29c93dae4aa77c0bb161df4276f9c9a1be", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-02-11T18:49:29+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-12-19T21:44:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "eab73b6c21d27ae4cd037c417618dfd4befb0bfe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/eab73b6c21d27ae4cd037c417618dfd4befb0bfe", + "reference": "eab73b6c21d27ae4cd037c417618dfd4befb0bfe", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-01-21T19:05:02+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-01-29T19:49:41+00:00" + }, + { + "name": "wimg/php-compatibility", + "version": "8.1.0", + "source": { + "type": "git", + "url": "https://github.com/wimg/PHPCompatibility.git", + "reference": "4ac01e4fe8faaa4f8d3b3cd06ea92e5418ce472e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wimg/PHPCompatibility/zipball/4ac01e4fe8faaa4f8d3b3cd06ea92e5418ce472e", + "reference": "4ac01e4fe8faaa4f8d3b3cd06ea92e5418ce472e", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.2 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "PHPCompatibility\\": "PHPCompatibility/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2017-12-27T21:58:38+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "0.14.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git", + "reference": "8cadf48fa1c70b2381988e0a79e029e011a8f41c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/8cadf48fa1c70b2381988e0a79e029e011a8f41c", + "reference": "8cadf48fa1c70b2381988e0a79e029e011a8f41c", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "time": "2017-11-01T15:10:46+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=5.3" + }, + "platform-dev": { + "php": ">=5.6" + } +} diff --git a/tests/config/default.php b/tests/config/default.php new file mode 100644 index 00000000..7080855f --- /dev/null +++ b/tests/config/default.php @@ -0,0 +1,87 @@ +Generated by Autoptimize \ No newline at end of file diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 00000000..e5346ce1 --- /dev/null +++ b/tests/index.html @@ -0,0 +1 @@ +Generated by Autoptimize \ No newline at end of file diff --git a/tests/phpcs.xml b/tests/phpcs.xml new file mode 100644 index 00000000..e982bc1e --- /dev/null +++ b/tests/phpcs.xml @@ -0,0 +1,37 @@ + + + Modified from WordPress Coding Standards for Plugins + + + + + + + + + + + + + + + + + + + + + autoptimize.php + classes + tests + + + + + + + + + */vendor/* + */external/* + diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 00000000..44f0fdb6 --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,14 @@ + + + + ./tests/ + + + diff --git a/tests/readme.txt b/tests/readme.txt new file mode 100644 index 00000000..24a127b3 --- /dev/null +++ b/tests/readme.txt @@ -0,0 +1,594 @@ +=== Autoptimize === +Contributors: futtta, optimizingmatters, turl +Tags: optimize, minify, performance, pagespeed, async +Donate link: http://blog.futtta.be/2013/10/21/do-not-donate-to-me/ +Requires at least: 4.0 +Tested up to: 4.9 +Requires PHP: 5.3 +Stable tag: 2.3.2 + +Autoptimize speeds up your website by optimizing JS, CSS and HTML, async-ing JavaScript, removing emoji cruft, optimizing Google Fonts and more. + +== Description == + +Autoptimize makes optimizing your site really easy. It can aggregate, minify and cache scripts and styles, injects CSS in the page head by default (but can also defer), moves and defers scripts to the footer and minifies HTML. The "Extra" options allow you to async non-aggregated JavaScript, remove WordPress core emoji cruft, optimize Google Fonts and more. As such it can improve your site's performance even when already on HTTP/2! There is extensive API available to enable you to tailor Autoptimize to each and every site's specific needs. + +If you consider performance important, you really should use one of the many caching plugins to do page caching. Some good candidates to complement Autoptimize that way are e.g. [WP Super Cache](http://wordpress.org/plugins/wp-super-cache/), [HyperCache](http://wordpress.org/plugins/hyper-cache/), [Comet Cache](https://wordpress.org/plugins/comet-cache/) or [KeyCDN's Cache Enabler](https://wordpress.org/plugins/cache-enabler). + +> Premium Support
    +> We provide great [Autoptimize Pro Support and Web Performance Optimization services](http://autoptimize.com/), check out our offering on (http://autoptimize.com/)! + +(Speed-surfing image under creative commons [by LL Twistiti](https://www.flickr.com/photos/twistiti/818552808/)) + +== Installation == + +Just install from your WordPress "Plugins > Add New" screen and all will be well. Manual installation is very straightforward as well: + +1. Upload the zip file and unzip it in the `/wp-content/plugins/` directory +1. Activate the plugin through the 'Plugins' menu in WordPress +1. Go to `Settings > Autoptimize` and enable the options you want. Generally this means "Optimize HTML/ CSS/ JavaScript". + +== Frequently Asked Questions == + += What does the plugin do to help speed up my site? = + +It concatenates all scripts and styles, minifies and compresses them, adds expires headers, caches them, and moves styles to the page head, and scripts (optionally) to the footer. It also minifies the HTML code itself, making your page really lightweight. + += But I'm on HTTP/2, so I don't need Autoptimize? = + +HTTP/2 is a great step forward for sure, reducing the impact of multiple requests from the same server significantly by using the same connection to perform several concurrent requests. That being said, [concatenation of CSS/ JS can still make a lot of sense](http://engineering.khanacademy.org/posts/js-packaging-http2.htm), as described in [this css-tricks.com article](https://css-tricks.com/http2-real-world-performance-test-analysis/) and this [blogpost from one of the Ebay engineers](http://calendar.perfplanet.com/2015/packaging-for-performance/). The conclusion; configure, test, reconfigure, retest, tweak and look what works best in your context. Maybe it's just HTTP/2, maybe it's HTTP/2 + aggregation and minification, maybe it's HTTP/2 + minification (which AO can do as well). + += Will this work with my blog? = + +Although Autoptimize comes without any warranties, it will in general work flawlessly if you configure it correctly. See "Troubleshooting" below for info on how to configure in case of problems. + += Why is jquery.js not optimized = + +Starting from AO 2.1 WordPress core's jquery.js is not optimized for the simple reason a lot of popular plugins inject inline JS that is not aggregated either (due to possible cache size issues with unique code in inline JS) which relies on jquery being available, so excluding jquery.js ensures that most sites will work out of the box. If you want optimize jquery as well, you can remove it from the JS optimization exclusion-list (you might have to enable "also aggregate inline JS" as well or switch to "force JS in head"). + += Why is Autoptimized JS render blocking? = + +If not "forced in head", Autoptimized JS is not render blocking as it has the "defer" flag added. It is however possible another plugin removes the "defer"-flag. Speed Booster Pack was reported doing this, but [the behavior has not been confirmed yet](https://wordpress.org/support/topic/speed-booster-pack-autoptimized-js-defer-flag/). + += Why is the autoptimized CSS still called out as render blocking? = + +With the default Autoptimize configuration the CSS is linked in the head, which is a safe default but has Google PageSpeed Insights complaining. You can look into "inline all CSS" (easy) or "inline and defer CSS" (better) which are explained in this FAQ as well. + += What is the use of "inline and defer CSS"? = + +CSS in general should go in the head of the document. Recently a.o. Google started promoting deferring non-essential CSS, while inlining those styles needed to build the page above the fold. This is especially important to render pages as quickly as possible on mobile devices. As from Autoptimize 1.9.0 this is easy; select "inline and defer CSS", paste the block of "above the fold CSS" in the input field (text area) and you're good to go! + += But how can one find out what the "above the fold CSS" is? = + +There's no easy solution for that as "above the fold" depends on where the fold is, which in turn depends on screensize. There are some tools available however, which try to identify just what is "above the fold". [This list of tools](https://github.com/addyosmani/above-the-fold-css-tools) is a great starting point. The [Sitelocity critical CSS generator](https://www.sitelocity.com/critical-path-css-generator) and [Jonas Ohlsson's criticalpathcssgenerator](http://jonassebastianohlsson.com/criticalpathcssgenerator/) are nice basic solutions and [http://criticalcss.com/](http://misc.optimizingmatters.com/partners/?from=faq&partner=critcss) is a premium solution by the same Jonas Ohlsson. Alternatively [this bookmarklet](https://gist.github.com/PaulKinlan/6284142) (Chrome-only) can be helpful as well. + += Or should you inline all CSS? = + +The short answer: probably not. + +Back in the days CSS optimization was easy; put all CSS in your head, aggregating everything in one CSS-file per media-type and you were good to go. But ever since Google included mobile in PageSpeed Insights and started complaining about render blocking CSS, things got messy (see "deferring CSS" elsewhere in this FAQ). One of the solutions is inlining all your CSS, which as of Autoptimize 1.8.0 is supported. + +Inlining all CSS has one clear advantage (better PageSpeed score) and one big disadvantage; your base HTML-page gets significantly bigger and if the amount of CSS is big, Pagespeed Insights will complain of "roundtrip times". Also when looking at a test that includes multiple requests (let's say 5 pages), performance will be worse, as the CSS-payload is sent over again and again whereas normally the separate CSS-files would not need to be sent any more as they would be in cache. + +So the choice should be based on your answer to some site-specific questions; how much CSS do you have? How many pages per visit do your visitors request? If you have a lot of CSS or a high number of pages/ visit, it's probably not a good idea to inline all CSS. + +You can find more information on this topic [in this blog post](http://blog.futtta.be/2014/02/13/should-you-inline-or-defer-blocking-css/). + += My cache is getting huge, doesn't Autoptimize purge the cache? = + +Autoptimize does not have its proper cache purging mechanism, as this could remove optimized CSS/JS which is still referred to in other caches, which would break your site. Moreover a fast growing cache is an indication of [other problems you should avoid](http://blog.futtta.be/2016/09/15/autoptimize-cache-size-the-canary-in-the-coal-mine/). + +Instead you can keep the cache size at an acceptable level by either: + +* disactivating the "aggregate inline JS" and/ or "aggregate inline CSS" options +* excluding JS-variables (or sometimes CSS-selectors) that change on a per page (or per pageload) basis. You can read how you can do that [in this blogpost](http://blog.futtta.be/2014/03/19/how-to-keep-autoptimizes-cache-size-under-control-and-improve-visitor-experience/). + +Despite above objections, there are 3rd party solutions to automatically purge the AO cache, e.g. using [this code](https://wordpress.org/support/topic/contribution-autoptimize-cache-size-under-control-by-schedule-auto-cache-purge/) or [this plugin](https://wordpress.org/plugins/bi-clean-cache/), but for reasons above these are to be used only if you really know what you're doing. + += "Clear cache" doesn't seem to work? = + +When clicking the "Delete Cache" link in the Autoptimize dropdown in the admin toolbar, you might to get a "Your cache might not have been purged successfully". In that case go to Autoptimizes setting page and click the "Save changes & clear cache"-button. + +Moreover don't worry if your cache never is down to 0 files/ 0KB, as Autoptimize (as from version 2.2) will automatically preload the cache immediately after it has been cleared to speed further minification significantly up. + += Can I still use Cloudflare's Rocket Loader? = + +Cloudflare Rocket Loader is a pretty advanced but invasive way to make JavaScript non-render-blocking, which [Cloudflare still considers Beta](https://wordpress.org/support/topic/rocket-loader-breaking-onload-js-on-linked-css/#post-9263738). Sometimes Autoptimize & Rocket Loader work together, sometimes they don't. The best approach is to disable Rocket Loader, configure Autoptimize and re-enable Rocket Loader (if you think it can help) after that and test if everything still works. + +At the moment (June 2017) it seems RocketLoader might break AO's "inline & defer CSS", which is based on [Filamentgroup’s loadCSS](https://github.com/filamentgroup/loadCSS), resulting in the deferred CSS not loading. + += I tried Autoptimize but my Google Pagespeed Scored barely improved = + +Autoptimize is not a simple "fix my Pagespeed-problems" plugin; it "only" aggregates & minifies (local) JS & CSS and allows for some nice extra's as removing Google Fonts and deferring the loading of the CSS. As such Autoptimize will allow you to improve your performance (load time measured in seconds) and will probably also help you tackle some specific Pagespeed warnings. If you want to improve further, you will probably also have to look into e.g. page caching, image optimization and your webserver configuration, which will improve real performance (again, load time as measured by e.g. https://webpagetest.org) and your "performance best practise" pagespeed ratings. + += What can I do with the API? = + +A whole lot; there are filters you can use to conditionally disable Autoptimize per request, to change the CSS- and JS-excludes, to change the limit for CSS background-images to be inlined in the CSS, to define what JS-files are moved behind the aggregated one, to change the defer-attribute on the aggregated JS script-tag, ... There are examples for some filters in autoptimize_helper.php_example and in this FAQ. + += How does CDN work? = + +Starting from version 1.7.0, CDN is activated upon entering the CDN blog root directory (e.g. http://cdn.example.net/wordpress/). If that URL is present, it will used for all Autoptimize-generated files (i.e. aggregated CSS and JS), including background-images in the CSS (when not using data-uri's). + +If you want your uploaded images to be on the CDN as well, you can change the upload_url_path in your WordPress configuration (/wp-admin/options.php) to the target CDN upload directory (e.g. http://cdn.example.net/wordpress/wp-content/uploads/). Do take into consideration this only works for images uploaded from that point onwards, not for images that already were uploaded. Thanks to [BeautyPirate for the tip](http://wordpress.org/support/topic/please-don%c2%b4t-remove-cdn?replies=15#post-4720048)! + += Why aren't my fonts put on the CDN as well? = + +Autoptimize supports this, but it is not enabled by default because [non-local fonts might require some extra configuration](http://davidwalsh.name/cdn-fonts). But if you have your cross-origin request policy in order, you can tell Autoptimize to put your fonts on the CDN by hooking into the API, setting `autoptimize_filter_css_fonts_cdn` to `true` this way; + +`add_filter('autoptimize_filter_css_fonts_cdn',__return_true);` + += I'm using Cloudflare, what should I enter as CDN root directory = + +Nothing, when on Cloudflare your autoptimized CSS/ JS is on the Cloudflare's CDN automatically. + += How can I force the aggregated files to be static CSS or JS instead of PHP? = + +If your webserver is properly configured to handle compression (gzip or deflate) and cache expiry (expires and cache-control with sufficient cacheability), you don't need Autoptimize to handle that for you. In that case you can check the "Save aggregated script/css as static files?"-option, which will force Autoptimize to save the aggregated files as .css and .js-files (meaning no PHP is needed to serve these files). This setting is default as of Autoptimize 1.8. + += How does "exclude from optimizing" work? = + +Both CSS and JS optimization can skip code from being aggregated and minimized by adding "identifiers" to the comma-separated exclusion list. The exact identifier string to use can be determined this way: + +* if you want to exclude a specific file, e.g. wp-content/plugins/funkyplugin/css/style.css, you could simply exclude "funkyplugin/css/style.css" +* if you want to exclude all files of a specific plugin, e.g. wp-content/plugins/funkyplugin/js/*, you can exclude for example "funkyplugin/js/" or "plugins/funkyplugin" +* if you want to exclude inline code, you'll have to find a specific, unique string in that block of code and add that to the exclusion list. Example: to exclude ``, the identifier is "funky_data". + += Configuring & Troubleshooting Autoptimize = + +After having installed and activated the plugin, you'll have access to an admin page where you can to enable HTML, CSS and JavaScript optimization. According to your liking, you can start of just enabling all of them, or if you're more cautious one at a time. + +If your blog doesn't function normally after having turned on Autoptimize, here are some pointers to identify & solve such issues using "advanced settings": + +* If all works but you notice your blog is slower, ensure you have a page caching plugin installed (WP Super Cache or similar) and check the info on cache size (the solution for that problem also impacts performance for uncached pages) in this FAQ as well. +* In case your blog looks weird, i.e. when the layout gets messed up, there is problem with CSS optimization. In this case you can turn on the option "Look for styles on just head?" and see if that solves the problem. You can also force CSS not to be aggregated by wrapping it in noptimize-tags in your theme or widget or by adding filename (for external stylesheets) or string (for inline styles) to the exclude-list. +* In case some functionality on your site stops working (a carroussel, a menu, the search input, ...) you're likely hitting JavaScript optimization trouble. Change the "Aggregate inline JS" and/ or "Force JavaScript in head?" settings and try again. Excluding 'js/jquery/jquery.js' from optimization (see below) and optionally activating "[Add try/catch wrapping](http://blog.futtta.be/2014/08/18/when-should-you-trycatch-javascript/)") can also help. Alternatively -for the technically savvy- you can exclude specific scripts from being treated (moved and/ or aggregated) by Autoptimize by adding a string that will match the offending Javascript or excluding it from within your template files or widgets by wrapping the code between noptimize-tags. Identifying the offending JavaScript and choosing the correct exclusion-string can be trial and error, but in the majority of cases JavaScript optimization issues can be solved this way. When debugging JavaScript issues, your browsers error console is the most important tool to help you understand what is going on. +* If your theme or plugin require jQuery, you can try either forcing all in head and/ or excluding jquery.js (and jQuery-plugins if needed). +* If you can't get either CSS or JS optimization working, you can off course always continue using the other two optimization-techniques. +* If you tried the troubleshooting tips above and you still can't get CSS and JS working at all, you can ask for support on the [WordPress Autoptimize support forum](http://wordpress.org/support/plugin/autoptimize). See below for a description of what information you should provide in your "trouble ticket" + += Help, I have a blank page or an internal server error after enabling Autoptimize!! = + +First of all make sure you're not running other HTML, CSS or JS minification plugins (BWP minify, WP minify, ...) simultaneously with Autoptimize or disable that functionality your page caching plugin (W3 Total Cache, WP Fastest Cache, ...). + +In some rare cases the [CSS minification component](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/) currently used by Autoptimize crashes due to a lack of resources (see [detailed technical explanation here](http://blog.futtta.be/2014/01/14/irregular-expressions-have-your-stack-for-lunch/)). You can in that case either disable CSS optimization, try to exclude specific CSS from being aggregated or activate the legacy minifiers which don't have that problem. The latter can be accomplished by adding this to your wp-config.php: + +`define("AUTOPTIMIZE_LEGACY_MINIFIERS","true");` + +The "legacy minifiers" will remain in Autoptimize "for ever" and changes to wp-config.php are not affected by core-, theme- or plugin-upgrades so you should be good to go. + += But I still have blank autoptimized CSS or JS-files! = + +If you are running Apache, the htaccess file written by Autoptimize can in some cases conflict with the AllowOverrides settings of your Apache configuration (as is the case with the default configuration of some Ubuntu installations), which results in "internal server errors" on the autoptimize CSS- and JS-files. This can be solved by [setting AllowOverrides to All](http://httpd.apache.org/docs/2.4/mod/core.html#allowoverride). + += I get no error, but my pages are not optimized at all? = + +Autoptimize does a number of checks before actually optimizing. When one of the following is true, your pages won't be optimized: + +* when in the customizer +* if there is no opening `<script>alert('this will not get autoptimized');</script><!--/noptimize-->` + +You can do this in your page/ post content, in widgets and in your theme files (consider creating [a child theme](http://codex.wordpress.org/Child_Themes) to avoid your work being overwritten by theme updates). + += Can I change the directory & filename of cached autoptimize files? = + +Yes, if you want to serve files from e.g. /wp-content/resources/aggregated_12345.css instead of the default /wp-content/cache/autoptimize/autoptimize_12345.css, then add this to wp-config.php: +` +define('AUTOPTIMIZE_CACHE_CHILD_DIR','/resources/'); +define('AUTOPTIMIZE_CACHEFILE_PREFIX','aggregated_'); +` + += Can the generated JS/ CSS be pre-gzipped? = + +Yes, but this is off by default. You can enable this by passing ´true´ to ´autoptimize_filter_cache_create_static_gzip´. You'll obviously still have to configure your webserver to use these files instead of the non-gzipped ones to avoid the overhead of on-the-fly compression. + += What does "remove emoji's" do? = + +This new option in Autoptimize 2.3 removes the inline CSS, inline JS and linked JS-file added by WordPress core. As such is can have a small positive impact on your site's performance. + += Is "remove query strings" useful? = + +Although some online performance assessement tools will single out "query strings for static files" as an issue for performance, in general the impact of these is almost non-existant. As such Autoptimize, since version 2.3, allows you to have the query string (or more precisely the "ver"-parameter) removed, but ticking "remove query strings from static resources" will have little or no impact of on your site's performance as measured in (milli-)seconds. + += (How) should I optimize Google Fonts? = + +Google Fonts are typically loaded by a "render blocking" linked CSS-file. If you have a theme and plugins that use Google Fonts, you might end up with multiple such CSS-files. Autoptimize (since version 2.3) now let's you lessen the impact of Google Fonts by either removing them alltogether or by optimizing the way they are loaded. There are two optimization-flavors; the first one is "combine and link", which replaces all requests for Google Fonts into one request, which will still be render-blocking but will allow the fonts to be loaded immediately (meaning you won't see fonts change while the page is loading). The alternative is "combine and load async" which uses JavaScript to load the fonts in a non-render blocking manner but which might cause a "flash of unstyled text". + += Should I use "preconnect" = + +Preconnect is a somewhat advanced feature to instruct browsers ([if they support it](https://caniuse.com/#feat=link-rel-preconnect)) to make a connection to specific domains even if the connection is not immediately needed. This can be used e.g. to lessen the impact of 3rd party resources on HTTPS (as DNS-request, TCP-connection and SSL/TLS negotiation are executed early). Use with care, as preconnecting to too many domains can be counter-productive. + += When can('t) I async JS? = + +JavaScript files that are not autoptimized (because they were excluded or because they are hosted elsewhere) are typically render-blocking. By adding them in the comma-separated "async JS" field, Autoptimize will add the async flag causing the browser to load those files asynchronously (i.e. non-render blocking). This can however break your site (page), e.g. if you async "js/jquery/jquery.js" you will very likely get "jQuery is not defined"-errors. Use with care. + += Where can I get help? = + +You can get help on the [wordpress.org support forum](http://wordpress.org/support/plugin/autoptimize). If you are 100% sure this your problem cannot be solved using Autoptimize configuration and that you in fact discovered a bug in the code, you can [create an issue on GitHub](https://github.com/futtta/autoptimize/issues). If you're looking for premium support, check out our [Autoptimize Pro Support and Web Performance Optimization services](http://autoptimize.com/). + += I want out, how should I remove Autoptimize? = + +* Disable the plugin (this will remove options and cache) +* Remove the plugin +* Clear any cache that might still have pages which reference Autoptimized CSS/JS (e.g. of a page caching plugin such as WP Super Cache) + += How can I help/ contribute? = + +Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and code away! + +== Changelog == + += 2.3.2 = +* workaround for [stale options-data in external object cache such as Redis, Memcached (core bug)](https://core.trac.wordpress.org/ticket/31245) resulting in Autoptimize continuously executing the upgrade-procedure including clearing the cache and trying to preload it with HTTP-requests with "cachebuster" in the query string, thanks to [Haroon Q. Raja](https://hqraja.com/) and [Tomas Trkulja](https://twitter.com/zytzagoo) for their great assistance! +* fixes for "undefined index" notices on Extra settings page +* now removing respective dns-prefetch resource hints when "remove emojis" or when Google Fonts are optimized or removed. +* changed JS code to load webfont.js deferred instead of asynced to make sure the js-file or fonts are not consider render blocking. + += 2.3.1 = +* fix for issue with update-code in some circumstances, thanks to [Rajendra Zore](https://rajendrazore.com/) to report & help fix! + += 2.3.0 = +* new: optimize Google fonts with “combine & link” and “combine and load async” (with webload.js), intelligently preconnecting to Google’s domains to limit performance impact even further +* new: Async JS, can be applied to local or 3rd party JS (if local it will be auto-excluded from autoptimization) +* new: support to tell browsers to preconnect (= dns lookup + tcp/ip connection + ssl negotiation) to 3rd party domains (depends on browser support, works in Chrome & Firefox) +* new: remove WordPress’ Core’s emoji CSS & JS +* new: remove (version parameter from) Querystring +* new: support to clear cache through WP CLI thanks to [junaidbhura](https://junaidbhura.com) +* lots of [bugfixes and small improvements done by some seriously smart people via GitHub](https://github.com/futtta/autoptimize/commits/master) (thanks all!!), including [a fix for AO 2.2 which saw the HTML minifier go PacMan on spaces](https://github.com/futtta/autoptimize/commit/0f6ac683c35bc82d1ac2d496ae3b66bb53e49f88) in some circumstances. + += 2.2.2 = +* roll-back to previous battle-tested version of the CSS minifier +* tweaks to Autoptimize toolbar menu (visual + timeout of "delete cache" AJAX call) +* readme update + += 2.2.1 = +* fix for images being referenced in CSS not all being translated to correct path, leading to 404’s as reported by Jeff Inho +* fix for "[] operator not supported for strings" error in PHP7.1 as reported by falk-wussow.de +* fix for security hash busting AO's cache in some cases (esp. in 2.1.1) + += 2.2.0 = +* new: Autoptimize minifies first (caching the individual snippets) and aggregrates the minified snippets, resulting in huge performance improvements for uncached JS/ CSS. +* new: option to enable/ disable AO for logged in users (on by default) +* new: option to enable/ disable AO on WooCommerce, Easy Digital Downloads or WP eCommerce cart/ checkout page (on by default) +* improvement: switched to [rel=preload + Filamentgroup’s loadCSS for CSS deferring](http://blog.futtta.be/2017/02/24/autoptimize-css-defer-switching-to-loadcss-soon/) +* improvement: switched to YUI CSS minifier PHP-port 2.8.4-p10 (so not to the 3.x branch yet) +* improvements to the logic of which JS/ CSS can be optimized (getPath function) increasing reliability of the aggregation process +* security: made placeholder replacement less naive to protect against XSS and LFI vulnerability as reported by Matthew Barry and fixed with great help from Matthew and Tomas Trkulja. Thanks guys!! +* API: Lots of extra filters, making AO (even) more flexible. +* Lots of bugfixes and smaller improvements (see [GitHub commit log](https://github.com/futtta/autoptimize/commits/master)) +* tested and confirmed working in WordPress 4.8 + += 2.1.2 = +* fix for security hash busting AO's cache in some cases (esp. in 2.1.1) +* identical to 2.1.0 except for the security fix backported from 2.2.0 + += 2.1.1 = +* identical to 2.1.0 except for the security fix backported from 2.2.0 + += 2.1.0 = +* new: Autoptimize now appears in admin-toolbar with an easy view on cache size and the possibility to purge the cache (pass `false` to `autoptimize_filter_toolbar_show` filter to disable), a big thanks to [Pablo Custo](https://github.com/pablocusto) for his hard work on this nice feature! +* new: An extra "More Optimization"-tab is shown (can be hidden with ´autoptimize_filter_show_partner_tabs´-filter) with information about related optimization tools- and services. +* new: If cache size becomes too big, a mail will be sent to the site admin (pass `false` to `autoptimize_filter_cachecheck_sendmail` filter to disable or pass alternative email to the `autoptimize_filter_cachecheck_mailto` filter to change email-address) +* new: power-users can enable Autoptimize to pre-gzip the autoptimized files by passing `true` to `autoptimize_filter_cache_create_static_gzip`, kudo's to (Draikin)[https://github.com/Draikin] for this! +* improvement: admin GUI updated (again; thanks Pablo!) with some responsiveness added in the mix (not showing the right hand column on smaller screen-sizes) +* improvement: settings-screen now accepts protocol-relative URL for CDN base URL +* improvement: new (smarter) defaults for JS (don't force in head + exclude jquery.js) and CSS optimization (include inline CSS) +* Misc. bugfixes & small improvements (see [commit-log on GitHub](https://github.com/futtta/autoptimize/commits/master)) +* Minimal version updated from 2.7 (!) to 4.0 +* Tested and confirmed working on WordPress 4.6 + += 2.0.2 = +* bugfix: disallow moving non-aggregated JS by default (can be re-enabled by passing false to the `autoptimize_filter_js_unmovable`) +* bugfix: hook autoptimize_action_cachepurged into init to avoid ugly error-message for ZenCache (Comet Cache) users +* bugfix to allow for Autoptimize to work with PHP 5.2 (although [you really should upgrade](http://blog.futtta.be/2016/03/15/why-would-you-still-be-on-php-5-2/)) + += 2.0.1 = +* Improvement: Autoptimize now also tries to purge WP Engine cache when AO's cache is cleared +* Improvement: for AMP pages (which are pretty optimized anyway) Autoptimize will not optimize to avoid issues with e.g. "inline & defer" and with AO adding attributes to link-tags that are not allowed in the subset of HTML that AMP is +* Improvement: refactored the page cache purging mechanism (removing duplicate code, now nicely hooking into AO's own `autoptimize_action_cachepurged` action) +* Improvement: Re-enable functionality to move non-aggregated JS if "also aggregate inline JS" is active (can be disabled with `autoptimize_filter_js_unmovable` filter) +* Improvement: script tags with `data-noptimize` attribute will be excluded from optimization +* Bugfix: Better support for renamed wp-content directories +* Bugfix: Multiple fixes for late-injected CSS/ JS (changes in those files were not always picked up, fonts or background images were not being CDN'ed, ...) +* Misc. other fixes & improvements, go read [the commit-log on GitHub](https://github.com/futtta/autoptimize/commits/master) if you're that curious +* Tested & confirmed working with WordPress 4.5 (beta 3) + += 2.0.0 = +* On average 30% faster minification (more info [in this blogpost](http://blog.futtta.be/2015/12/22/making-autoptimize-faster/))! +* New: Option to (de-)activate aggregation of inline JS and CSS. +* New: Option to remove Google Fonts. +* New: Cache-size will be checked daily and a notice will be shown on wp-admin if cache size goes over 512 MB (can be changed by filter). +* New: Small autoptimized CSS (less then 256 characters, can be changed by filter) will be inlined instead of linked. +* New in API: filters to declare a JS and CSS whitelist, where only files in that whitelist are autoptimized and all others are left untouched. +* New in API: filters to declare removable CSS and JS, upon which Autoptimize will simply delete that code (emoji CSS/JS for example, if you prefer not to dequeue them). +* New in API: filter to move fonts to CDN as well. +* lots of small and bigger bugfixes, I won't bother you with a full list but have a look at [the commmit log on GitHub](https://github.com/futtta/autoptimize/commits/master). +* tested and confirmed working with PHP7 + += 1.9.4 = +* bugfix: make sure non-AO CSSmin doesn't get fed 2 parameters (as some only expect one, which resulted in an internal server error), based on [feedback from zerooverture and zamba](https://wordpress.org/support/topic/error-code-500internal-server-error?replies=7) +* bugfix: make default add_action hook back into "template_redirect" instead of "init" to fix multiple problems as reported by [schecteracademicservices, bond138, rickenbacker](https://wordpress.org/support/topic/192-concatenated-js-but-193-does-not-for-me?replies=11), [Rick Sportel](https://wordpress.org/support/topic/version-193-made-plugin-wp-cdn-rewrite-crash?replies=3#post-6833159) and [wizray](https://wordpress.org/support/topic/the-page-loads-both-the-auto-combined-css-file-and-origin-raw-file?replies=11#post-6833146). If you do need Autoptimize to initialize earlier (e.g. when using Nextgen Galleries), then add this to your wp-config.php: +`define("AUTOPTIMIZE_INIT_EARLIER","true");` + += 1.9.3 = +* improvement: more intelligent CDN-replacement logic, thanks [Squazz for reporting and testing](https://wordpress.org/support/topic/enable-cdn-for-images-referenced-in-the-css?replies=9) +* improvement: allow strings (comments) to be excluded from HTML-optimization (comment removal) +* improvement: changed priority with which AO gets triggered by WordPress, solving JS not being aggregated when NextGen Galleries is active, with great [help from msebald](https://wordpress.org/support/topic/js-options-dont-work-if-html-disabled/) +* improvement: extra JS exclude-strings: gist.github.com, text/html, text/template, wp-slimstat.min.js, _stq, nonce, post_id (the latter two were removed from the "manual" exclude list on the settings-page) +* new in API: autoptimize_filter_html_exclude, autoptimize_filter_css_defer, autoptimize_filter_css_inline, autoptimize_filter_base_replace_cdn, autopitmize_filter_js_noptimize, autopitmize_filter_css_noptimize, autopitmize_filter_html_noptimize +* bugfix: remove some PHP notices, as [reported by dimitrov.adrian](https://wordpress.org/support/topic/php-errors-39) +* bugfix: make sure HTML-optimalization does not gobble a space before a cite [as proposed by ecdltf](https://wordpress.org/support/topic/%E2%80%9Coptimize-html%E2%80%9D-is-gobbling-whitespace-before-cite-tag) +* bugfix: cleaning the cache did not work on non-default directories as [encountered by NoahJ Champion](https://wordpress.org/support/topic/changing-the-wp-content-path-to-top-level?replies=10#post-6573657) +* upgraded to [yui compressor php port 2.4.8-4](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port) +* added arabic translation, thanks to the [ekleel team](http://www.ekleel.net) +* tested with WordPress 4.2 beta 3 + += 1.9.2 = +First of all; Happy holidays, all the best for 2015!! + +* New: support for alternative cache-directory and file-prefix as requested by a.o. [Jassi Bacha](https://wordpress.org/support/topic/requesthelp-add-ability-to-specify-cache-folder?replies=1#post-6300128), [Cluster666](https://wordpress.org/support/topic/rewrite-js-path?replies=6#post-6363535) and Baris Unver. +* Improvement: hard-exclude all linked-data json objects (script type=application/ld+json) +* Improvement: several filters added to the API, e.g. to alter optimized HTML, CSS or JS +* Bugfix: set Autoptimize priority back from 11 to 2 (as previously) to avoid some pages not being optimized (thanks to [CaveatLector for investigating & reporting](https://wordpress.org/support/topic/wp-property-plugin-add_action-priority-incompatibility?replies=1)) +* Bugfix (in YUI-CSS-compressor-PHP-port): don't convert bools to percentages in rotate3D-transforms (cfr. [bugreport on Github](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/issues/17)) +* Bugfix: background images with a space in the path didn't load, [reported by johnh10](https://wordpress.org/support/topic/optimize-css-code-error-with-background-image-elements?replies=6#post-6201582). +* Bugfix: SVG image with fill:url broken after CSS optimization as [reported by Tkama](https://wordpress.org/support/topic/one-more-broblem-with-plugin?replies=2) +* Updated translation for Swedish, new translation for Ukrainian by [Zanatoly of SebWeo.com](http://SebWeo.com) +* Updated readme.txt +* Confirmed working with WordPress 4.1 + += 1.9.1 = +* hard-exclude [the sidelink-search-box introduced in WP SEO v1.6](http://wordpress.org/plugins/wordpress-seo/changelog/) from JS optimization (this [broke some JS-optimization badly](http://wordpress.org/support/topic/190-breaks-js?replies=4)) +* bugfix: first add semi-colon to inline script, only then add try-catch if required instead of the other way around. + += 1.9.0 = +* "Inline and defer CSS" allows one to specify which "above the fold CSS" should be inlined, while the normal optimized CSS is deferred. +* Inlined Base64-encoded background Images will now be cached as well and the threshold for inlining these images has been bumped up to 4096 bytes (from 2560). +* Separate cache-directories for CSS and JS in /wp-content/cache/autoptimize, which should result in faster cache pruning (and in some cases possibly faster serving of individual aggregated files). +* Autoptimized CSS is now injected before the -tag, JS before </body> (and after when forced in head). This can be overridden in the API. +* Some usability improvements of the administration-page +* Multiple hooks added to the API a.o. filters to not aggregate inline CSS or JS and filters to aggregate but not minify CSS or JS. +* Updated translations for Dutch, French, German, Persian and Polish and new translations for Brazilian Portuguese (thanks to [Leonardo Antonioli](http://tobeguarany.com/)) and Turkish (kudo's [Baris Unver](http://beyn.org/)) +* Multiple bugfixes & improvements +* Tested with WordPress 4.0 rc3 + += 1.8.5 = +* Updated to lastest version of [CSS minification component](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/) +* Improvement: for multi-sites the cache is now written to separate directories, avoiding one site to clean out the cache for the entire installation. Code [contributed by Joern Lund](http://wordpress.org/support/topic/multisite-blog-admin-can-delete-entire-network-cache), kudo's Joern!! +* Improvement: add WordPress plugin header to autoptimize_helper.php_example to make it easier to enable it as a module +* Improvement: nonce and post_id are added to default configuration for JS exclusion +* Improvement: explicitely exclude wp-admin from being Autoptimized +* Bugfix: plupload.min.js, syntaxhighlighter and "adsbygoogle" are excluded from JS aggregation. +* Bugfix: avoid double closing body-tags when Autoptimize adds JS to HTML as [reported by Can](http://wordpress.org/support/topic/works-like-a-charm-but-i-have-two-problems) +* Bugfix: make .htaccess compatible with both Apache 2.2 and 2.4 (http://wordpress.org/support/topic/feature-request-support-generating-htaccess-files-for-apache-24?replies=3) + += 1.8.4 = +* Bugfix: code in inline JS (or CSS) can be wrapped inside HTML-comments, but these got removed since 1.8.2 as part of a bugfix. + += 1.8.3 = +* Bugfix: avoid useless warnings on is_callable to flood php error log as [reported by Praveen Kumar](http://wordpress.org/support/topic/182-breaks-css-and-js?replies=14#post-5377604) + += 1.8.2 = +* Improvement: more graceful failure when minifier classes exist but method does not, based on [bug-report by Franck160](http://wordpress.org/support/topic/confict-with-dynamic-to-top) +* Improvement: deferred CSS is also outputted in noscript-tags +* Improvement: differentiate between Apache version in .htaccess file as suggested by [iMadalin](http://www.imadalin.ro/) +* Improvement: also aggregate protocol-less CSS/JS URI's (as [suggested by Ross](http://wordpress.org/support/topic/protocol-less-url-support)) +* Improvement: disable autoptimization based on parameter in querystring (for debugging) +* Bugfix: some CSS-imports were not being aggregated/ minified +* Bugfix: add CSS before to avoid breakage when title includes other attributes (e.g. itemscope) +* Bugfix: make sure javascript or css between comments is not aggregated as reported by [Milap Gajjar](http://wordpress.org/support/topic/the-optimized-css-contains-duplicate-classes) +* Tested with WordPress 3.9 (beta 1) +* Updates in FAQ + += 1.8.1 = +* bugfix: CSS in conditional comments was not excluded from aggregation as reported by [Rolf](http://www.finkbeiner-holz.de/) and [bottapress](http://www.wordpress-hebergement.fr/) + += 1.8.0 = +* New: Option to inline all CSS [as suggested by Hamed](http://wordpress.org/support/topic/make-style-sheet-inline) +* New: set of filters to provide a simple API to change Autoptimize behavior (e.g. replace "defer" with "async", disabling Autoptimization on certain pages, specificy non-aggregatable script to be moved after aggregated one (cfr. http://wordpress.org/support/topic/feature-request-some-extra-options?replies=14), size of image to be data-urized). More info in the included autoptimize_helper.php_example. +* Improvement: exclude (css in) noscript-tags as [proposed by belg4mit](http://wordpress.org/support/topic/feature-suggestion-noscript-for-css) +* Improvement: switch default delivery of optimized CSS/JS-files from PHP to static files +* Updated [upstream CSS minifier](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/commit/fb33d2ffd0963692747101330b175a80173ce21b) +* Improvement (force gzip of static files) and Bugfix (force expiry for dynamic files, thanks to [Willem Razenberg](http://www.column-razenberg.nl/) in .htaccess +* Improvement: fail gracefully when things go wrong (e.g. CSS import resulting in empty aggregated CSS-files [reported by Danka](http://wordpress.org/support/topic/very-good-332) or when the theme is broken [as seen by Prateek Gupta](http://wordpress.org/support/topic/js-optimization-break-site-white-page-issue?replies=14#post-5038941)) +* Updated translations and Polish added (thanks to [Jakub Sierpinski](http://www.sierpinski.pl/)). +* Bugfix: stop import-statements in CSS comments to be taken into acccount [hat tip to Josef from blog-it-solutions.de](http://www.blog-it-solutions.de/) +* Bugfix: fix for blur in CSS breakeage as [reported by Chris of clickpanic.com](http://blog.clickpanic.com/) + += 1.7.3 = +* improvement: remove cache + options on uninstall as [requested by Gingerbreadmen](http://wordpress.org/support/topic/wp_options-entries) +* improvement: set .htaccess to allow PHP execution in wp-content/cache/autoptimize when saving optimized files as PHP, as suggested by (David Mottershead of bermuda4u.com)[http://www.bermuda4u.com/] but forbid PHP execution when saving aggregated script/css as static files (except for multisite). +* bugfix: avoid Yoast SEO sitemaps going blank (due optimization of Yoast's dynamically built XML/XSL) as reported by [Vance Hallman](http://www.icefishing.co) and [Armand Hadife](http://solar-flag-pole-lights.com/). More info on this issue [can be found on my blog](http://blog.futtta.be/2013/12/09/blank-yoast-seo-sitemaps-no-more/). +* smaller changes to readme.txt + += 1.7.2 = +* improvement: extra checks in CSS @import-handling + move import rules to top of CSS if not imported successfully, based a.o. on bug reports [by ozum](http://wordpress.org/support/topic/zero-lenght-file-with-css-optimization) and by [Peter Stolwijk](http://wordpress.org/support/topic/cant-activate-plugin-22?replies=13#post-4891377) +* improvement: check if JS and CSS minifier classes exist and only load if they don't to avoid possible conflicts with other themes or plugins that already loaded minifiers +* tested and approved for WordPress 3.8 (beta1) + += 1.7.1 = +* New: support for mapped domains as suggested by [Michael for tiremoni.com](http://tiremoni.com/) +* Added an .htaccess to wp-content/cache/autoptimize to overwrite other caching directives (fixing a problem with WP Super Cache's .htaccess really, [as reported](http://wordpress.org/support/topic/expiresmax-age-compatibility-with-supercache) by [Hugh of www.unitedworldschools.org](http://www.unitedworldschools.org/)) +* bugfix: Autoptimize broke data:uri's in CSS in some cases as reported by [Josef from blog-it-solutions.de](http://www.blog-it-solutions.de/) +* bugfix: avoid PHP notice if CSS exclusion list is empty +* moved "do not donate"-image into plugin + += 1.7.0 = +* New: exclude CSS +* New: defer CSS +* Updated minimizing components (JSMin & YUI PHP CSSMin) +* Updated admin-page, hiding advanced configuration options +* Updated CDN-support for added simplicity (code & UI-wise), including changing background image url in CSS +* Updated/ new translations provided for [French: wordpress-hebergement.fr](http://www.wordpress-hebergement.fr/), [Persian: Hamed Irani](http://basics.ir/), [Swedish: Jonathan Sulo](http://sulo.se/), [German: blog-it-solutions.de](http://www.blog-it-solutions.de/) and Dutch +* Removed support for YUI +* Flush HTML caching plugin's cache when flushing Autoptimize's one +* fix for BOM marker in CSS-files [as seen in Frontier theme](http://wordpress.org/support/topic/sidebar-problem-42), kudo's to [Download Converter](http://convertertoolz.com/) for reporting! +* fix for [protocol-less 3rd party scripts disappearing](http://wordpress.org/support/topic/javascript-optimize-breaks-twentythirteen-mobile-menu), thanks for reporting p33t3r! +* fix for stylesheets without type="text/css" not being autoptimized as reported by [renzo](http://cocobeanproductions.com/) +* tested with WordPress 3.7 beta2 (admin-bar.min.js added to automatically excluded scripts) + += 1.6.6 = +* New: disable autoptimizatoin by putting part of your HTML, JS or CSS in between noptimize-tags, e.g.; +`<!--noptimize--><script>alert('this will not get autoptimized');</script><!--/noptimize-->` +* Added extra check to prevent plugin-files being called outside of WordPress as suggested in [this good article on security](http://mikejolley.com/2013/08/keeping-your-shit-secure-whilst-developing-for-wordpress/). +* Added small notice to be displayed after installation/ activation to ask user to configure the plugin as well. +* Added Persian translation, thanks to [Hamed T.](http://basics.ir/) + += 1.6.5 = +* new javascript-debug option to force the aggregated javascript file in the head-section of the HTML instead of at the bottom +* YUI compression & CDN are now deprecated functionality that will be removed in 1.7.0 + += 1.6.4 = +* fix for PHP notice about mfunc_functions +* fix for strpos warnings due to empty values from the "Exclude scripts from autoptimize" configuration as [reported by CandleFOREX](http://wordpress.org/support/topic/empty-needle-warning) +* fix for broken feeds as [reported by Dinata and talgalili](http://wordpress.org/support/topic/feed-issue-5) + += 1.6.3 = +* fix for IE-hacks with javascript inside, causing javascript breakage (as seen in Sampression theme) as reported by [Takahiro of hiskip.com](http://www.hiskip.com/wp/) +* fix for escaping problem of imported css causing css breakage (as seen in Sampression theme) as reported by Takahiro as well +* fix to parse imports with syntax @import 'custom.css' not being parsed (as seen in Arras theme), again as reported by Takahiro +* fix for complex media types in media-attribute [as reported by jvwisssen](http://wordpress.org/support/topic/autoptimize-and-media-queries) +* fix for disappearing background-images that were already datauri's [as reported by will.blaschko](http://wordpress.org/support/topic/data-uris) +* fix not to strip out comments in HTML needed by WP Super Cache or W3 Total Cache (e.g. mfunc) +* added check to clean cache on upgrade +* updated FAQ in readme with information on troubleshooting and support +* tested with WordPress 3.6 beta + += 1.6.2 = +* Yet another emergency bugfix I'm afraid: apache_request_headers (again in config/delayed.php) is only available on ... Apache (duh), breaking non-Apache systems such as ngnix, Lighttpd and MS IIS badly. Reported by multiple users, thanks all! + += 1.6.1 = +* fixed stupid typo in config/delayed.php which broke things badly (april fools-wise); strpos instead of str_pos as reported by Takahiro. + += 1.6.0 = +* You can now specify scripts that should not be Autoptimized in the admin page. Just add the names (or part of the path) of the scripts in a comma-separated list and that JavaScript-file will remain untouched by Autoptimize. +* Added support for ETag and LastModified (essentially for a better pagespeed score, as the files are explicitely cacheable for 1 year) +* Autoptimizing for logged in users is enabled again +* Autoptimize now creates an index.html in wp-content/cache/autoptimize to prevent snooping (as [proposed by Chris](http://blog.futtta.be/2013/01/07/adopting-an-oss-orphan-autoptimize/#li-comment-36292)) +* bugfix: removed all deprecated functions ([reported by Hypolythe](http://wordpress.org/support/topic/many-deprecated-errors) and diff by Heiko Adams, thanks guys!) +* bugfix for HTTPS-problem as [reported by dbs121](http://wordpress.org/support/topic/woocommerce-autoptimizer-https-issue) +* bugfix for breakage with unusual WordPress directory layout as reported by [Josef from blog-it-solutions.de](http://www.blog-it-solutions.de/). + += 1.5.1 = +* bugfix: add CSS before opening title-tag instead of after closing title, to avoid CSS being loaded in wrong order, as reported by [fotofashion](http://fotoandfashion.de/) and [blogitsolutions](http://www.blog-it-solutions.de) (thanks guys) + += 1.5 = +* first bugfix release by [futtta](http://blog.futtta.be/2013/01/07/adopting-an-oss-orphan-autoptimize/), thanks for a great plugin Turl! +* misc bug fixes, a.o. support for Twenty Twelve theme, admin bar problem in WP3.5, data-uri breaking CSS file naming + += 1.4 = +* Add support for inline style tags with CSS media +* Fix Wordpress top bar + += 1.3 = +* Add workaround for TinyMCEComments +* Add workaround for asynchronous Google Analytics + += 1.2 = +* Add workaround for Chitika ads. +* Add workaround for LinkWithin widget. +* Belorussian translation + += 1.1 = +* Add workarounds for amazon and fastclick +* Add workaround for Comment Form Quicktags +* Fix issue with Vipers Video Quicktags +* Fix a bug in where some scripts that shouldn't be moved were moved +* Fix a bug in where the config page wouldn't appear +* Fix @import handling +* Implement an option to disable js/css gzipping +* Implement CDN functionality +* Implement data: URI generation for images +* Support YUI CSS/JS Compressor +* Performance increases +* Handle WP Super Cache's cache files better +* Update translations + += 1.0 = +* Add workaround for whos.among.us +* Support preserving HTML Comments. +* Implement "delayed cache compression" +* French translation +* Update Spanish translation + += 0.9 = +* Add workaround for networkedblogs. +* Add workarounds for histats and statscounter +* Add workaround for smowtion and infolinks. +* Add workaround for Featured Content Gallery +* Simplified Chinese translation +* Update Spanish Translation +* Modify the cache system so it uses wp-content/cache/ +* Add a clear cache button + += 0.8 = +* Add workaround for Vipers Video Quicktags +* Support <link> tags without media. +* Take even more precautions so we don't break urls in CSS +* Support adding try-catch wrappings to JavaScript code +* Add workaround for Wordpress.com Stats +* Fix a bug in where the tags wouldn't move +* Update translation template +* Update Spanish translation + += 0.7 = +* Add fix for DISQUS Comment System. + += 0.6 = +* Add workaround for mybloglog, blogcatalog, tweetmeme and Google CSE + += 0.5 = +* Support localization +* Fix the move and don't move system (again) +* Improve url detection in CSS +* Support looking for scripts and styles on just the header +* Fix an issue with data: uris getting modified +* Spanish translation + += 0.4 = +* Write plugin description in English +* Set default config to everything off +* Add link from plugins page to options page +* Fix problems with scripts that shouldn't be moved and were moved all the same + += 0.3 = +* Disable CSS media on @imports - caused an infinite loop + += 0.2 = +* Support CSS media +* Fix an issue in the IE Hacks preservation mechanism +* Fix an issue with some urls getting broken in CSS + += 0.1 = +* First released version. diff --git a/tests/test-ao.php b/tests/test-ao.php new file mode 100644 index 00000000..40fc234a --- /dev/null +++ b/tests/test-ao.php @@ -0,0 +1,1663 @@ +<?php + +class AOTest extends WP_UnitTestcase +{ + /** + * @var autoptimizeMain + */ + protected $ao; + + /** + * Normalizes EOLs into "\n" otherwise some tests fail due to simple newline + * differences in the markup (depending on how/where it was entered/generated). + * This can occasionally get even more complicated by git changing newlines + * on checkout (if so configured). + * + * @param $str + * + * @return mixed + */ + private function normalize_newlines($str) + { + return str_replace("\r\n", "\n", $str); + } + + protected function getAoStylesDefaultOptions() + { + $conf = autoptimizeConfig::instance(); + + return [ + 'aggregate' => $conf->get('autoptimize_css_aggregate'), + 'justhead' => $conf->get('autoptimize_css_justhead'), + 'datauris' => $conf->get('autoptimize_css_datauris'), + 'defer' => $conf->get('autoptimize_css_defer'), + 'defer_inline' => $conf->get('autoptimize_css_defer_inline'), + 'inline' => $conf->get('autoptimize_css_inline'), + 'css_exclude' => $conf->get('autoptimize_css_exclude'), + 'cdn_url' => $conf->get('autoptimize_cdn_url'), + 'include_inline' => $conf->get('autoptimize_css_include_inline'), + 'nogooglefont' => $conf->get('autoptimize_css_nogooglefont') + ]; + } + + protected function getAoScriptsDefaultOptions() + { + $conf = autoptimizeConfig::instance(); + + return [ + 'aggregate' => $conf->get( 'autoptimize_js_aggregate' ), + 'justhead' => $conf->get( 'autoptimize_js_justhead' ), + 'forcehead' => $conf->get( 'autoptimize_js_forcehead' ), + 'trycatch' => $conf->get( 'autoptimize_js_trycatch' ), + 'js_exclude' => $conf->get( 'autoptimize_js_exclude' ), + 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ), + 'include_inline' => $conf->get( 'autoptimize_js_include_inline' ), + ]; + } + + public function setUp() + { + $this->ao = new autoptimizeMain( AUTOPTIMIZE_PLUGIN_VERSION, AUTOPTIMIZE_PLUGIN_FILE ); + + parent::setUp(); + } + + // Runs after each test method + public function tearDown() + { + // Making sure certain filters are removed after each test to ensure isolation + $filter_tags = array( + 'autoptimize_filter_noptimize', + 'autoptimize_filter_base_cdnurl', + 'autoptimize_filter_css_is_datauri_candidate', + 'autoptimize_filter_css_datauri_image', + 'autoptimize_filter_css_inlinesize', + 'autoptimize_filter_css_fonts_cdn' + ); + foreach ( $filter_tags as $filter_tag ) { + remove_all_filters( $filter_tag ); + } + + parent::tearDown(); + } + + const TEST_MARKUP = <<<MARKUP +<!DOCTYPE html> +<!--[if lt IE 7]> <html class="no-svg no-js lt-ie9 lt-ie8 lt-ie7" xmlns:fb="https://www.facebook.com/2008/fbml" xmlns:og="http://ogp.me/ns#" lang="hr"> <![endif]--> +<!--[if IE 7]> <html class="no-svg no-js lt-ie9 lt-ie8" xmlns:fb="https://www.facebook.com/2008/fbml" xmlns:og="http://ogp.me/ns#" lang="hr"> <![endif]--> +<!--[if IE 8]> <html class="no-svg no-js lt-ie9" xmlns:fb="https://www.facebook.com/2008/fbml" xmlns:og="http://ogp.me/ns#" lang="hr"> <![endif]--> +<!--[if gt IE 8]><!--> <html class="no-svg no-js" xmlns:fb="https://www.facebook.com/2008/fbml" xmlns:og="http://ogp.me/ns#" lang="hr"> <!--<![endif]--> +<head> +<meta charset="utf-8"> +<title>Mliječna juha od brokule ♨ Kuhaj.hr + + + + + + + + +
    + + + + + + + + + + + + + + +MARKUP; + + const TEST_MARKUP_OUTPUT = << + + + + + + +Mliječna juha od brokule ♨ Kuhaj.hr + + + + + + + + +
    + + + + + + + + + + + + + + +MARKUP; + + // When `is_multisite()` returns true, default path to files is different + const TEST_MARKUP_OUTPUT_MS = << + + + + + + +Mliječna juha od brokule ♨ Kuhaj.hr + + + + + + + + +
    + + + + + + + + + + + + + + +MARKUP; + + const TEST_MARKUP_OUTPUT_INLINE_DEFER = << + + + + + + +Mliječna juha od brokule ♨ Kuhaj.hr + + + + + + + + +
    + + + + + + + + + + + + + + +MARKUP; + + const TEST_MARKUP_OUTPUT_INLINE_DEFER_MS = << + + + + + + +Mliječna juha od brokule ♨ Kuhaj.hr + + + + + + + + +
    + + + + + + + + + + + + + + +MARKUP; + + /** + * @dataProvider provider_test_rewrite_markup_with_cdn + */ + function test_rewrite_markup_with_cdn($input, $expected) + { + $actual = $this->ao->end_buffering( $input ); + + // $this->markTestIncomplete('Full-blown rewrite test currently doesn\'t work on Windows (or with any custom WP-tests setup/location really).'); + $this->assertEquals($expected, $actual); + } + + public function provider_test_rewrite_markup_with_cdn() + { + return array( + + array( + // input + self::TEST_MARKUP, + // expected output + // TODO/FIXME: this seemed like the fastest way to get MS crude test to pass + ( is_multisite() ? self::TEST_MARKUP_OUTPUT_MS : self::TEST_MARKUP_OUTPUT ) + ), + + ); + } + + public function test_rewrite_css_assets() + { + $css_in = <<setOption('cdn_url', 'http://cdn.example.org'); + + $css_actual = $instance->rewrite_assets($css_in); + + $this->assertEquals($css_expected, $css_actual); + } + + public function test_default_cssmin_minifier() + { + $css = <<run_minifier_on($css); + + $this->assertEquals($expected, $minified); + } + + /** + * @dataProvider provider_test_should_aggregate_script_types + * @covers autoptimizeScripts::should_aggregate + */ + public function test_should_aggregate_script_types($input, $expected) + { + $instance = new autoptimizeScripts(''); + $actual = $instance->should_aggregate($input); + + $this->assertEquals($expected, $actual); + } + + public function provider_test_should_aggregate_script_types() + { + return array( + // no type attribute at all + array( + // input + '', + // expected output + true + ), + // case-insensitive + array( + '', + true + ), + // allowed/aggregated now (wasn't previously) + array( + '', + true + ), + // quotes shouldn't matter, nor should case-sensitivity + array( + '', + true + ), + // liberal to whitespace around attribute names/values + array( + '', + true + ), + // something custom, should be ignored/skipped + array( + '', + false + ), + // type attribute checking should be constrained to actual script tag's type attribute + // only, regardless of any `type=` string present in the actual inline script contents + array( + // since there's no type attribute, it should be aggregate by default + '', + true + ), + // application/ld+json should not be aggregated by default regardless of spacing around attr/values + array( + '