diff --git a/.gitignore b/.gitignore index cb005dc..13e5d10 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ wp-content/wp-cache-config.php Thumbs.db # Panel .user.ini +wp-content/cache/ +wp-content/wp-cache-config.php diff --git a/wp-content/autoptimize_404_handler.php b/wp-content/autoptimize_404_handler.php new file mode 100644 index 0000000..6507899 --- /dev/null +++ b/wp-content/autoptimize_404_handler.php @@ -0,0 +1,52 @@ + +
+ [\d]+)']); + return $endpoints; +}); + +// --- Disable emoji (reduces ~20KB page weight) --- +remove_action('wp_head', 'print_emoji_detection_script', 7); +remove_action('wp_print_styles', 'print_emoji_styles'); +remove_action('admin_print_scripts', 'print_emoji_detection_script'); +remove_action('admin_print_styles', 'print_emoji_styles'); +remove_filter('the_content_feed', 'wp_staticize_emoji'); +remove_filter('comment_text_rss', 'wp_staticize_emoji'); +remove_filter('wp_mail', 'wp_staticize_emoji_for_email'); + +// --- Limit post revisions --- +if (!defined('WP_POST_REVISIONS')) { + define('WP_POST_REVISIONS', 3); +} diff --git a/wp-content/plugins/autoptimize/LICENSE b/wp-content/plugins/autoptimize/LICENSE new file mode 100644 index 0000000..d6a9326 --- /dev/null +++ b/wp-content/plugins/autoptimize/LICENSE @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc.,' . esc_html__( 'Autoptimize requires PHP 5.6 (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( 'AO_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'; + } elseif ( 'PAnD' === $class_name ) { + $file = 'persist-admin-notices-dismissal'; + $path = dirname( __FILE__ ) . '/classes/external/php/persist-admin-notices-dismissal/'; + $filepath = $path . $file . '.php'; + } + + // If we didn't match one of our rules, bail! + if ( ! isset( $filepath ) || ! is_readable( $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'; +} + +// filter to disable AO both on front- and backend. +if ( apply_filters( 'autoptimize_filter_disable_plugin', false ) ) { + return; +} + +/** + * 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/wp-content/plugins/autoptimize/autoptimize_helper.php_example b/wp-content/plugins/autoptimize/autoptimize_helper.php_example new file mode 100644 index 0000000..6e38db6 --- /dev/null +++ b/wp-content/plugins/autoptimize/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("`).
+ * Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'.
+ *
+ * @return void
+ */
+ protected function inject_in_html( $payload, $where )
+ {
+ $warned = false;
+ $position = autoptimizeUtils::strpos( $this->content, $where[0] );
+ if ( false !== $position ) {
+ // 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 = autoptimizeUtils::substr_replace(
+ $this->content,
+ $content,
+ $position,
+ // Using plain strlen() should be safe here for now, since
+ // we're not searching for multibyte chars here still...
+ 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] ) ? base64_decode( $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;
+ }
+
+ /**
+ * 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
+ */
+ public static function replace_contents_with_marker_if_exists( $marker, $search, $re_replace_pattern, $content )
+ {
+ $found = false;
+
+ $is_regex = autoptimizeUtils::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
+ );
+
+ // Check for error (for example, an error can occur if $content is very large).
+ if ( null === $content ) {
+ $error_message = 'Autoptimize: preg_replace_callback() failed';
+ if ( function_exists( 'preg_last_error_msg' ) ) {
+ $error_message .= ': ' . preg_last_error_msg();
+ }
+ error_log( $error_message );
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Complements `autoptimizeBase::replace_contents_with_marker_if_exists()`.
+ *
+ * @param string $marker Marker.
+ * @param string $content Markup.
+ *
+ * @return string
+ */
+ public static 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 );
+ }
+
+ /**
+ * Checks if a single local css/js file can be minified and returns source if so.
+ *
+ * @param string $filepath Filepath.
+ *
+ * @return bool|string to be minified code or false.
+ */
+ protected function prepare_minify_single( $filepath )
+ {
+ // Decide what we're dealing with, return false if we don't know.
+ if ( autoptimizeUtils::str_ends_in( $filepath, '.js' ) ) {
+ $type = 'js';
+ } elseif ( autoptimizeUtils::str_ends_in( $filepath, '.css' ) ) {
+ $type = 'css';
+ } else {
+ return false;
+ }
+
+ // Bail if it looks like its already minifed (by having -min or .min in filename).
+ $minified_variants = array(
+ '-min.' . $type,
+ '.min.' . $type,
+ );
+ foreach ( $minified_variants as $ending ) {
+ if ( autoptimizeUtils::str_ends_in( $filepath, $ending ) && true === apply_filters( 'autoptimize_filter_base_prepare_exclude_minified', true ) ) {
+ return false;
+ }
+ }
+
+ // Get file contents, bail if empty.
+ $contents = file_get_contents( $filepath );
+
+ return $contents;
+ }
+
+ /**
+ * Given an autoptimizeCache instance returns the (maybe cdn-ed) url of
+ * the cached file.
+ *
+ * @param autoptimizeCache $cache autoptimizeCache instance.
+ *
+ * @return string
+ */
+ protected function build_minify_single_url( autoptimizeCache $cache )
+ {
+ $url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
+
+ // CDN-replace the resulting URL if needed...
+ $url = $this->url_replace_cdn( $url );
+
+ return $url;
+ }
+}
diff --git a/wp-content/plugins/autoptimize/classes/autoptimizeCLI.php b/wp-content/plugins/autoptimize/classes/autoptimizeCLI.php
new file mode 100644
index 0000000..6d0adad
--- /dev/null
+++ b/wp-content/plugins/autoptimize/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 )
+ {
+ // hide calc() if filter says yes (as YUI CSS compressor PHP port has problems retaining spaces around + and - operators).
+ // regex see tests at https://regex101.com/r/ofGQG9/1
+ if ( apply_filters( 'autoptimize_filter_css_hide_calc', true ) ) {
+ $css = autoptimizeBase::replace_contents_with_marker_if_exists( 'CALC', 'calc(', '#(calc|min|max|clamp)\([^;}]*\)#m', $css );
+ }
+
+ // minify.
+ $result = $this->minifier->run( $css );
+
+ // restore calc() if filter says yes.
+ if ( apply_filters( 'autoptimize_filter_css_hide_calc', true ) ) {
+ $result = autoptimizeBase::restore_marked_content( 'CALC', $result );
+ }
+
+ 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/wp-content/plugins/autoptimize/classes/autoptimizeCache.php b/wp-content/plugins/autoptimize/classes/autoptimizeCache.php
new file mode 100644
index 0000000..f47f1f6
--- /dev/null
+++ b/wp-content/plugins/autoptimize/classes/autoptimizeCache.php
@@ -0,0 +1,855 @@
+ 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' )
+ {
+ $_min_ext = '';
+ if ( apply_filters( 'autoptimize_filter_cache_url_add_min_ext', false ) ) {
+ $_min_ext = '.min';
+ }
+
+ $this->cachedir = AUTOPTIMIZE_CACHE_DIR;
+ $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
+ if ( ! $this->nogzip ) {
+ $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.php';
+ } else {
+ if ( in_array( $ext, array( 'js', 'css' ) ) ) {
+ $this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
+ } else {
+ $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $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|bool
+ */
+ public function cache( $data, $mime )
+ {
+ // readonly FS explicitly OK'ed by developer, so just pretend all is OK.
+ if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
+ return true;
+ }
+
+ // off by default; check if cachedirs exist every time before caching
+ //
+ // to be activated for users that experience these ugly errors;
+ // PHP Warning: file_put_contents failed to open stream: No such file or directory.
+ if ( apply_filters( 'autoptimize_filter_cache_checkdirs_on_write', false ) ) {
+ $this->check_and_create_dirs();
+ }
+
+ 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 );
+ file_put_contents( $this->cachedir . $this->filename . '.none', $data );
+ } else {
+ // Write code to cache without doing anything else.
+ file_put_contents( $this->cachedir . $this->filename, $data );
+
+ // save fallback .js or .css file if filter true (to be false by default) but not if snippet or single.
+ if ( self::do_fallback() && strpos( $this->filename, '_snippet_' ) === false && strpos( $this->filename, '_single_' ) === false ) {
+ $_extension = pathinfo( $this->filename, PATHINFO_EXTENSION );
+ $_fallback_file = AUTOPTIMIZE_CACHEFILE_PREFIX . 'fallback.' . $_extension;
+ if ( ( 'css' === $_extension || 'js' === $_extension ) && ! file_exists( $this->cachedir . $_extension . '/' . $_fallback_file ) ) {
+ file_put_contents( $this->cachedir . $_extension . '/' . $_fallback_file, $data );
+ }
+ }
+
+ 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 ) );
+ // If PHP Brotli extension is installed, create an additional cached Brotli file.
+ if ( function_exists( 'brotli_compress' ) ) {
+ file_put_contents( $this->cachedir . $this->filename . '.br', brotli_compress( $data, 11, BROTLI_GENERIC ) );
+ }
+ }
+ }
+
+ // Provide 3rd party action hook for every cache file that is created.
+ // This hook can for example be used to inject a copy of the created cache file to a other domain.
+ do_action( 'autoptimize_action_cache_file_created', $this->cachedir . $this->filename );
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Clears contents of AUTOPTIMIZE_CACHE_DIR.
+ *
+ * @return void
+ */
+ protected static function clear_cache_classic()
+ {
+ $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
+ }
+
+ /**
+ * Recursively deletes the specified pathname (file/directory) if possible.
+ * Returns true on success, false otherwise.
+ *
+ * @param string $pathname Pathname to remove.
+ *
+ * @return bool
+ */
+ protected static function rmdir( $pathname )
+ {
+ $files = self::get_dir_contents( $pathname );
+ foreach ( $files as $file ) {
+ $path = $pathname . '/' . $file;
+ if ( is_dir( $path ) ) {
+ self::rmdir( $path );
+ } else {
+ unlink( $path );
+ }
+ }
+
+ return rmdir( $pathname );
+ }
+
+ /**
+ * Clears contents of AUTOPTIMIZE_CACHE_DIR by renaming the current
+ * cache directory into a new one with a unique name and then
+ * re-creating the default (empty) cache directory.
+ *
+ * Important/ Fixme: this does not take multisite into account, so
+ * if advanced_cache_clear_enabled is true (it is not by default)
+ * then the content for all subsites is zapped!
+ *
+ * @return bool Returns true when everything is done successfully, false otherwise.
+ */
+ protected static function clear_cache_via_rename()
+ {
+ $ok = false;
+ $dir = self::get_pathname_base();
+ $new_name = self::get_unique_name();
+
+ // Makes sure the new pathname is on the same level...
+ $new_pathname = dirname( $dir ) . '/' . $new_name;
+ $renamed = @rename( $dir, $new_pathname ); // @codingStandardsIgnoreLine
+
+ // When renamed, re-create the default cache directory back so it's
+ // available again...
+ if ( $renamed ) {
+ $ok = self::cacheavail();
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Returns true when advanced cache clearing is enabled.
+ *
+ * @return bool
+ */
+ public static function advanced_cache_clear_enabled()
+ {
+ return apply_filters( 'autoptimize_filter_cache_clear_advanced', false );
+ }
+
+ /**
+ * Returns a (hopefully) unique new cache folder name for renaming purposes.
+ *
+ * @return string
+ */
+ protected static function get_unique_name()
+ {
+ $prefix = self::get_advanced_cache_clear_prefix();
+ $new_name = uniqid( $prefix, true );
+
+ return $new_name;
+ }
+
+ /**
+ * Get cache prefix name used in advanced cache clearing mode.
+ *
+ * @return string
+ */
+ protected static function get_advanced_cache_clear_prefix()
+ {
+ $pathname = self::get_pathname_base();
+ $basename = basename( $pathname );
+ $prefix = $basename . '-artifact-';
+
+ return $prefix;
+ }
+
+ /**
+ * Returns an array of file and directory names found within
+ * the given $pathname without '.' and '..' elements.
+ *
+ * @param string $pathname Pathname.
+ *
+ * @return array
+ */
+ protected static function get_dir_contents( $pathname )
+ {
+ return array_slice( scandir( $pathname ), 2 );
+ }
+
+ /**
+ * Wipes directories which were created as part of the fast cache clearing
+ * routine (which renames the current cache directory into a new one with
+ * a custom-prefixed unique name).
+ *
+ * @return bool
+ */
+ public static function delete_advanced_cache_clear_artifacts()
+ {
+ // Don't go through these motions (called from the cachechecker) if advanced cache clear isn't even active.
+ if ( ! self::advanced_cache_clear_enabled() ) {
+ return false;
+ }
+
+ $dir = self::get_pathname_base();
+ $prefix = self::get_advanced_cache_clear_prefix();
+ $parent = dirname( $dir );
+ $ok = false;
+
+ // Returns the list of files without '.' and '..' elements.
+ $files = self::get_dir_contents( $parent );
+ if ( is_array( $files ) && ! empty( $files ) ) {
+ foreach ( $files as $file ) {
+ $path = $parent . '/' . $file;
+ $prefixed = ( false !== strpos( $path, $prefix ) );
+ // Removing only our own (prefixed) directories...
+ if ( is_dir( $path ) && $prefixed ) {
+ $ok = self::rmdir( $path );
+ }
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Returns the cache directory pathname used.
+ * Done as a function so we canSlightly different
+ * if multisite is used and `autoptimize_separate_blog_caches` filter
+ * is used.
+ *
+ * @return string
+ */
+ public static function get_pathname()
+ {
+ $pathname = self::get_pathname_base();
+
+ if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
+ $blog_id = get_current_blog_id();
+ $pathname .= $blog_id . '/';
+ }
+
+ return $pathname;
+ }
+
+ /**
+ * Returns the base path of our cache directory.
+ *
+ * @return string
+ */
+ protected static function get_pathname_base()
+ {
+ $pathname = WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR;
+
+ return $pathname;
+ }
+
+ /**
+ * Deletes everything from the cache directories.
+ *
+ * @param bool $propagate Whether to trigger additional actions when cache is purged.
+ *
+ * @return bool
+ */
+ public static function clearall( $propagate = true )
+ {
+ if ( defined( 'ET_CORE_VERSION' ) && 'Divi' === get_template() ) {
+ // see https://blog.futtta.be/2018/11/17/warning-divi-purging-autoptimizes-cache/ .
+ $dbt = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );
+ $caller = isset( $dbt[1]['function'] ) ? $dbt[1]['function'] : null;
+ if ( 'et_core_clear_wp_cache' === $caller ) {
+ if ( apply_filters( 'autoptimize_filter_cache_divi_wrong_complain', true ) ) {
+ _doing_it_wrong( 'autoptimizeCache::clearall', 'Divi devs: please don\'t clear Autoptimize\'s cache, it is unneeded and can break sites. You can contact me at futtta@gmail.com to discuss.', 'Autoptimize 2.9.6' );
+ }
+ return false;
+ }
+ }
+
+ if ( ! self::cacheavail() || true === apply_filters( 'autoptimize_filter_cache_clearall_disabled', false ) ) {
+ return false;
+ }
+
+ // TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
+ if ( self::advanced_cache_clear_enabled() ) {
+ self::clear_cache_via_rename();
+ } else {
+ self::clear_cache_classic();
+ }
+
+ // Remove 404 handler if required.
+ if ( self::do_fallback() ) {
+ $_fallback_php = trailingslashit( WP_CONTENT_DIR ) . 'autoptimize_404_handler.php';
+ @unlink( $_fallback_php ); // @codingStandardsIgnoreLine
+ }
+
+ // Remove the transient so it gets regenerated...
+ delete_transient( 'autoptimize_stats' );
+
+ // Cache was just purged, clear page cache and allow others to hook into our purging...
+ if ( true === $propagate ) {
+ 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 ) && false == get_transient( 'autoptimize_cache_warmer_protector' ) ) {
+ set_transient( 'autoptimize_cache_warmer_protector', 'I shall not warm cache for another 10 minutes.', 60 * 10 );
+ $url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
+ $url = apply_filters( 'autoptimize_filter_cache_warmer_url', $url );
+ $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
+ unset( $cache );
+ }
+
+ return true;
+ }
+
+ /**
+ * Wrapper for clearall but with false param
+ * to ensure the event is not propagated to others
+ * through our own hooks (to avoid infinite loops).
+ *
+ * @return bool
+ */
+ public static function clearall_actionless()
+ {
+ return self::clearall( false );
+ }
+
+ /**
+ * 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 ( ! self::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()
+ {
+ // readonly FS explicitly OK'ed by dev, let's assume the cache dirs are there!
+ if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
+ return true;
+ }
+
+ if ( false === autoptimizeCache::check_and_create_dirs() ) {
+ 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 = '