diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30b9bc6e..f1fbc86c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,13 +12,15 @@ jobs: wordpress: '5.9' - php: '8.0' wordpress: '6.0' - - php: '8.1' + - php: '8.2' + wordpress: '6.4' + - php: '8.3' wordpress: 'latest' - - php: '8.1' + - php: '8.3' wordpress: 'nightly' steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup PHP @@ -43,16 +45,16 @@ jobs: quality: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.2' tools: composer - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '16' + node-version: '20' - name: Code style checks for PHP and CSS run: | composer install diff --git a/.github/workflows/wordpress-plugin-asset-update.yml b/.github/workflows/wordpress-plugin-asset-update.yml index b47e1ae9..a32fc4a8 100644 --- a/.github/workflows/wordpress-plugin-asset-update.yml +++ b/.github/workflows/wordpress-plugin-asset-update.yml @@ -8,11 +8,11 @@ jobs: name: Push to stable runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.2' tools: composer - name: Build run: composer install diff --git a/.github/workflows/wordpress-plugin-deploy.yml b/.github/workflows/wordpress-plugin-deploy.yml index e2406894..f74cfc47 100755 --- a/.github/workflows/wordpress-plugin-deploy.yml +++ b/.github/workflows/wordpress-plugin-deploy.yml @@ -9,11 +9,11 @@ jobs: name: New tag runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.2' tools: composer - name: Build run: composer install diff --git a/CHANGELOG.md b/CHANGELOG.md index ca39bf2e..b2b8ab16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. This projec Requires PHP 5.6 and WordPress 4.7 or above * Fix: invalidate cache when permalink changes (#285, #286, props raffaelj) +* Fix: remove empty directories when pruning the HDD cache (#289) * Enhance: adjust styling for setup instructions (#215, props timse201) * Enhance: update hooks for Multisite initialization in WordPress 5.1 and above (#246, props ouun) * Enhance: rework flush hooks and add some third-party triggers for Autoptimize and WooCommerce (#225, props timse201) @@ -64,8 +65,8 @@ Requires PHP 5.6 and WordPress 4.7 or above * Fix for the PHP notice "Call to undefined function is_plugin_active_for_network" on WordPress Multisite ## 2.2.0 -* Toolbar: Display of the "Flush the cachify cache" button on the frontend -* Toolbar: Controlling the display of the "Flush the cachify cache" button via hook +* Toolbar: Display of the "Flush the Cachify cache" button on the frontend +* Toolbar: Controlling the display of the "Flush the Cachify cache" button via hook ## 2.1.9 * Vervollständigung des Cachify-Pfades in `robots.txt`: `Disallow: /wp-content/cache/cachify/` diff --git a/README.md b/README.md index d8e9d2e2..209218a9 100755 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Cachify # -Smart, efficient cache solution for WordPress. Use DB, HDD, APC or Memcached for storing your blog pages. Make WordPress faster! +Smart, efficient cache solution for WordPress. Use DB, HDD, APC, Redis or Memcached for storing your blog pages. Make WordPress faster! ## Description ## -*Cachify* optimizes your page loads by caching posts, pages and custom post types as static content. You can choose between caching via database, on the web server’s hard drive (HDD), Memcached (only on Nginx) or — thanks to APC (Alternative PHP Cache) — directly in the web server’s system cache. Whenever a page or post is loaded, it can be pulled directly from the cache. The amount of database queries and PHP requests will dramatically decrease towards zero, depending on the caching method you chose. +*Cachify* optimizes your page loads by caching posts, pages and custom post types as static content. You can choose between caching via database, on the web server’s hard drive (HDD), Memcached (only on Nginx), Redis or — thanks to APC (Alternative PHP Cache) — directly in the web server’s system cache. Whenever a page or post is loaded, it can be pulled directly from the cache. The amount of database queries and PHP requests will dramatically decrease towards zero, depending on the caching method you chose. ### Features ### * Works with custom post types. -* Caching methods: DB, HDD, APC and Memcached. +* Caching methods: DB, HDD, APC, Redis and Memcached. * “Flush Cache” button in the WordPress toolbar. * Ready for WordPress Multisite. * Optional compression of HTML markup. @@ -39,10 +39,11 @@ Smart, efficient cache solution for WordPress. Use DB, HDD, APC or Memcached for * If you don’t know how to install a plugin for WordPress, [here’s how](https://wordpress.org/support/article/managing-plugins/#installing-plugins). ### Requirements ### -* PHP 5.2.4 or greater -* WordPress 4.4 or greater +* PHP 5.6 or greater +* WordPress 4.7 or greater * APC 3.1.4 or greater (optional) * Memcached in Nginx (optional) +* Redis (optional, via the phpredis module) ## Frequently Asked Questions ## diff --git a/cachify.php b/cachify.php index 3cba0d43..35d8f896 100644 --- a/cachify.php +++ b/cachify.php @@ -1,7 +1,7 @@ =5.2.0", - "composer/installers": "~1.0" + "php": ">=5.6", + "composer/installers": "^v1|^v2" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^v0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^v1.0", "matthiasmullie/minify": "^1.3", "phpunit/phpunit": "^5|^7|^9", - "squizlabs/php_codesniffer": "^3.7", + "squizlabs/php_codesniffer": "^3.9", "phpcompatibility/phpcompatibility-wp": "^2.1", - "wp-coding-standards/wpcs": "^2.3", - "yoast/phpunit-polyfills": "^1.0" + "wp-coding-standards/wpcs": "^3.1", + "yoast/phpunit-polyfills": "^2.0" }, "scripts": { "post-install-cmd": [ diff --git a/inc/cachify.settings.php b/inc/cachify.settings.php index 9f807e32..923284f9 100644 --- a/inc/cachify.settings.php +++ b/inc/cachify.settings.php @@ -41,7 +41,7 @@ printf( /* translators: Placeholder is the trash icon itself as dashicon */ esc_html__( 'Flush the cache by clicking the button below or the %1$s icon in the admin bar.', 'cachify' ), - '"' . esc_html__( 'Flush the cachify cache', 'cachify' ) . '"' + '"' . esc_html__( 'Flush the Cachify cache', 'cachify' ) . '"' ); ?>

diff --git a/inc/class-cachify-apc.php b/inc/class-cachify-apc.php index 0e8c17c9..7992e54c 100644 --- a/inc/class-cachify-apc.php +++ b/inc/class-cachify-apc.php @@ -105,7 +105,7 @@ public static function clear_cache() { * @since 2.0 */ public static function print_cache() { - return; + // Not supported. } /** diff --git a/inc/class-cachify-cli.php b/inc/class-cachify-cli.php index 4dc81d4c..f050dac3 100644 --- a/inc/class-cachify-cli.php +++ b/inc/class-cachify-cli.php @@ -74,10 +74,10 @@ public static function add_commands() { 'shortdesc' => 'Flush site cache', 'synopsis' => array( array( - 'type' => 'flag', - 'name' => 'all-methods', - 'description' => 'Flush all caching methods', - 'optional' => true, + 'type' => 'flag', + 'name' => 'all-methods', + 'description' => 'Flush all caching methods', + 'optional' => true, ), ), ) @@ -94,14 +94,13 @@ public static function add_commands() { 'shortdesc' => 'Get the size of the cache in bytes', 'synopsis' => array( array( - 'type' => 'flag', - 'name' => 'raw', - 'description' => 'Raw size output in bytes', - 'optional' => true, + 'type' => 'flag', + 'name' => 'raw', + 'description' => 'Raw size output in bytes', + 'optional' => true, ), ), ) ); - } } diff --git a/inc/class-cachify-db.php b/inc/class-cachify-db.php index 003c0394..ddbb9b6f 100644 --- a/inc/class-cachify-db.php +++ b/inc/class-cachify-db.php @@ -146,9 +146,10 @@ public static function get_stats() { global $wpdb; /* Read */ - - return $wpdb->get_var( - 'SELECT SUM( CHAR_LENGTH(option_value) ) FROM `' . $wpdb->options . "` WHERE `option_name` LIKE ('\_transient%.cachify')" + return intval( + $wpdb->get_var( + 'SELECT SUM( CHAR_LENGTH(option_value) ) FROM `' . $wpdb->options . "` WHERE `option_name` LIKE ('\_transient%.cachify')" + ) ); } @@ -166,7 +167,7 @@ public static function get_stats() { private static function _cache_signature( $detail, $meta ) { /* No array? */ if ( ! is_array( $meta ) ) { - return; + return ''; } if ( $detail ) { @@ -218,7 +219,7 @@ private static function _page_queries() { /** * Return execution time * - * @return int Execution time in seconds + * @return string Execution time in seconds * * @since 0.1 */ diff --git a/inc/class-cachify-hdd.php b/inc/class-cachify-hdd.php index 8d521a1e..1450bb49 100644 --- a/inc/class-cachify-hdd.php +++ b/inc/class-cachify-hdd.php @@ -122,7 +122,8 @@ public static function clear_cache() { */ public static function print_cache() { $filename = self::_file_html(); - $size = is_readable( $filename ) ? readfile( $filename ) : false; + $size = is_readable( $filename ) ? readfile( $filename ) : false; + if ( ! empty( $size ) ) { /* Ok, cache file has been sent to output. */ exit; @@ -174,7 +175,7 @@ private static function _create_files( $data ) { /* Create directory */ if ( ! wp_mkdir_p( $file_path ) ) { - trigger_error( esc_html( __METHOD__ . ": Unable to create directory {$file_path}.", E_USER_WARNING ) ); + trigger_error( esc_html( __METHOD__ . ": Unable to create directory {$file_path}." ), E_USER_WARNING ); return; } /* Write to file */ @@ -202,7 +203,7 @@ private static function _create_file( $file, $data ) { /* Writable? */ $handle = @fopen( $file, 'wb' ); if ( ! $handle ) { - trigger_error( esc_html( __METHOD__ . ": Could not write file {$file}.", E_USER_WARNING ) ); + trigger_error( esc_html( __METHOD__ . ": Could not write file {$file}." ), E_USER_WARNING ); return; } @@ -212,7 +213,7 @@ private static function _create_file( $file, $data ) { clearstatcache(); /* Permissions */ - $stat = @stat( dirname( $file ) ); + $stat = @stat( dirname( $file ) ); $perms = $stat['mode'] & 0007777; $perms = $perms & 0000666; @chmod( $file, $perms ); @@ -228,48 +229,45 @@ private static function _create_file( $file, $data ) { * @since 2.0 */ private static function _clear_dir( $dir, $recursive = false ) { - /* Remote training slash */ + // Remove trailing slash. $dir = untrailingslashit( $dir ); - /* Is directory? */ + // Is directory? if ( ! is_dir( $dir ) ) { return; } - /* Read */ + // List directory contents. $objects = array_diff( scandir( $dir ), array( '..', '.' ) ); - /* Empty? */ - if ( empty( $objects ) ) { - return; - } - - /* Loop over items */ + // Loop over items. foreach ( $objects as $object ) { - /* Expand path */ + // Expand path. $object = $dir . DIRECTORY_SEPARATOR . $object; - /* Directory or file */ - if ( is_dir( $object ) && $recursive ) { - self::_clear_dir( $object, $recursive ); - } else { - if ( self::_user_can_delete( $object ) ) { - unlink( $object ); + if ( is_dir( $object ) ) { + if ( $recursive ) { + // Recursively clear the directory. + self::_clear_dir( $object, $recursive ); + } elseif ( self::_user_can_delete( $object ) && 0 === count( glob( trailingslashit( $object ) . '*' ) ) ) { + // Delete the directory, if empty. + @rmdir( $object ); } + } elseif ( self::_user_can_delete( $object ) ) { + // Delete the file. + unlink( $object ); } } - /* Remove directory */ - if ( $recursive ) { - if ( self::_user_can_delete( $dir ) && 0 === count( glob( trailingslashit( $dir ) . '*' ) ) ) { - @rmdir( $dir ); - } + // Remove directory, if empty. + if ( self::_user_can_delete( $dir ) && 0 === count( glob( trailingslashit( $dir ) . '*' ) ) ) { + @rmdir( $dir ); } - /* Clean up */ + // Clean up. clearstatcache(); } @@ -278,14 +276,14 @@ private static function _clear_dir( $dir, $recursive = false ) { * * @param string $dir Directory path. * - * @return mixed Directory size + * @return int|false Directory size * * @since 2.0 */ public static function _dir_size( $dir = '.' ) { /* Is directory? */ if ( ! is_dir( $dir ) ) { - return; + return false; } /* Read */ @@ -296,7 +294,7 @@ public static function _dir_size( $dir = '.' ) { /* Empty? */ if ( empty( $objects ) ) { - return; + return false; } /* Init */ @@ -402,9 +400,9 @@ private static function _user_can_delete( $file ) { $file = trailingslashit( $file ); } - $ssl_prefix = is_ssl() ? 'https-' : ''; + $ssl_prefix = is_ssl() ? 'https-' : ''; $current_blog = get_blog_details( get_current_blog_id() ); - $blog_path = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . $current_blog->domain . $current_blog->path; + $blog_path = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . $current_blog->domain . $current_blog->path; if ( 0 !== strpos( $file, $blog_path ) ) { return false; diff --git a/inc/class-cachify-memcached.php b/inc/class-cachify-memcached.php index 688fbe5f..88d536a2 100644 --- a/inc/class-cachify-memcached.php +++ b/inc/class-cachify-memcached.php @@ -31,8 +31,8 @@ final class Cachify_MEMCACHED { */ public static function is_available() { return class_exists( 'Memcached' ) - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - && isset( $_SERVER['SERVER_SOFTWARE'] ) && strpos( strtolower( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ), 'nginx' ) !== false; + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + && isset( $_SERVER['SERVER_SOFTWARE'] ) && strpos( strtolower( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ), 'nginx' ) !== false; } /** @@ -89,7 +89,7 @@ public static function store_item( $hash, $data, $lifetime, $sig_detail ) { public static function get_item( $hash ) { /* Server connect */ if ( ! self::_connect_server() ) { - return; + return null; } /* Get item */ @@ -143,7 +143,7 @@ public static function clear_cache() { * @since 2.0.7 */ public static function print_cache() { - return; + // Not supported. } /** @@ -254,8 +254,8 @@ private static function _connect_server() { } else { self::$_memcached->setOptions( array( - Memcached::OPT_COMPRESSION => false, - Memcached::OPT_BUFFER_WRITES => true, + Memcached::OPT_COMPRESSION => false, + Memcached::OPT_BUFFER_WRITES => true, Memcached::OPT_BINARY_PROTOCOL => true, ) ); diff --git a/inc/class-cachify-redis.php b/inc/class-cachify-redis.php new file mode 100644 index 00000000..d32c1a5b --- /dev/null +++ b/inc/class-cachify-redis.php @@ -0,0 +1,235 @@ +set( + self::_file_path(), + $data . self::_cache_signature( $sig_detail ), + $lifetime + ); + } + + /** + * Read item from cache + * + * @param string $hash Hash of the entry. + * @return mixed Content of the entry + */ + public static function get_item( $hash ) { + /* Server connect */ + if ( ! self::_connect_server() ) { + return null; + } + + /* Get item */ + return self::$_redis->get( + self::_file_path() + ); + } + + /** + * Delete item from cache + * + * @param string $hash Hash of the entry [ignored]. + * @param string $url URL of the entry. + */ + public static function delete_item( $hash, $url ) { + /* Server connect */ + if ( ! self::_connect_server() ) { + return; + } + + /* Delete */ + self::$_redis->del( + self::_file_path( $url ) + ); + } + + /** + * Clear the cache + * + * @return void + */ + public static function clear_cache() { + /* Server connect */ + if ( ! self::_connect_server() ) { + return; + } + + /* Flush */ + @self::$_redis->flushAll(); + } + + /** + * Print the cache + * + * @param bool $sig_detail Show details in signature. + * @param string $cache Cached content. + */ + public static function print_cache( $sig_detail, $cache ) { + echo $cache; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + exit; + } + + /** + * Get the cache size + * + * @return integer Directory size + */ + public static function get_stats() { + /* Server connect */ + if ( ! self::_connect_server() ) { + return null; + } + + /* Info */ + $data = self::$_redis->info(); + + /* No stats? */ + if ( empty( $data ) ) { + return null; + } + + /* Empty */ + if ( empty( $data['used_memory'] ) ) { + return null; + } + + return $data['used_memory']; + } + + /** + * Generate signature + * + * @param bool $detail Show details in signature. + * @return string Signature string + */ + private static function _cache_signature( $detail ) { + return sprintf( + "\n\n", + 'Cachify | https://cachify.pluginkollektiv.org', + ( $detail ? 'Redis Cache' : __( 'Generated', 'cachify' ) ), + date_i18n( + 'd.m.Y H:i:s', + current_time( 'timestamp' ) + ) + ); + } + + /** + * Path of cache file + * + * @param string $path Request URI or permalink [optional]. + * @return string Path to cache file + */ + private static function _file_path( $path = null ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); + + return trailingslashit( + sprintf( + '%s%s', + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + wp_unslash( $_SERVER['HTTP_HOST'] ), + $path_parts['path'] + ) + ); + } + + /** + * Connect to Redis server + * + * @return boolean true/false TRUE on success + */ + private static function _connect_server() { + /* Not enabled? */ + if ( ! self::is_available() ) { + return false; + } + + /* Have object and it thinks it's connected to a server */ + if ( is_object( self::$_redis ) && self::$_redis->isConnected() ) { + return true; + } + + /* Init */ + self::$_redis = new Redis(); + + /* Set options & connect */ + try { + self::$_redis->connect( + (string) apply_filters( + 'cachify_redis_servers', + 'redis://localhost:6379' + ) + ); + } catch ( Exception $e ) { + return false; + } + + if ( ! self::$_redis->isConnected() ) { + return false; + } + + return true; + } +} diff --git a/inc/class-cachify.php b/inc/class-cachify.php index b0d1f74b..a893f5e8 100644 --- a/inc/class-cachify.php +++ b/inc/class-cachify.php @@ -47,10 +47,11 @@ final class Cachify { * * @since 2.0.9 */ - const METHOD_DB = 0; + const METHOD_DB = 0; const METHOD_APC = 1; const METHOD_HDD = 2; const METHOD_MMC = 3; + const METHOD_REDIS = 4; /** * Minify settings @@ -59,16 +60,16 @@ final class Cachify { * * @since 2.0.9 */ - const MINIFY_DISABLED = 0; + const MINIFY_DISABLED = 0; const MINIFY_HTML_ONLY = 1; - const MINIFY_HTML_JS = 2; + const MINIFY_HTML_JS = 2; /** * REST endpoints * * @var string */ - const REST_NAMESPACE = 'cachify/v1'; + const REST_NAMESPACE = 'cachify/v1'; const REST_ROUTE_FLUSH = 'flush'; /** @@ -96,6 +97,9 @@ public function __construct() { add_action( 'post_updated', array( __CLASS__, 'save_update_trash_post' ), 10, 3 ); add_action( 'pre_post_update', array( __CLASS__, 'post_update' ), 10, 2 ); add_action( 'cachify_remove_post_cache', array( __CLASS__, 'remove_page_cache_by_post_id' ) ); + add_action( 'comment_post', array( __CLASS__, 'new_comment' ), 99, 2 ); + add_action( 'edit_comment', array( __CLASS__, 'comment_edit' ), 10, 2 ); + add_action( 'transition_comment_status', array( __CLASS__, 'comment_status' ), 10, 3 ); /* Flush Hooks - third party */ add_action( 'woocommerce_product_set_stock', array( __CLASS__, 'flush_woocommerce' ) ); @@ -120,9 +124,6 @@ public function __construct() { add_action( 'init', array( __CLASS__, 'process_flush_request' ) ); - /* Flush (post) cache if comment is made from frontend or backend */ - add_action( 'pre_comment_approved', array( __CLASS__, 'pre_comment' ), 99, 2 ); - /* Add Cron for clearing the HDD Cache */ if ( self::METHOD_HDD === self::$options['use_apc'] ) { add_filter( 'cron_schedules', array( __CLASS__, 'add_cron_cache_expiration' ) ); @@ -156,10 +157,6 @@ public function __construct() { add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_dashboard_styles' ) ); - add_action( 'transition_comment_status', array( __CLASS__, 'touch_comment' ), 10, 3 ); - - add_action( 'edit_comment', array( __CLASS__, 'edit_comment' ) ); - add_filter( 'dashboard_glance_items', array( __CLASS__, 'add_dashboard_count' ) ); add_filter( 'plugin_row_meta', array( __CLASS__, 'row_meta' ), 10, 2 ); @@ -404,6 +401,10 @@ private static function _set_default_vars() { } elseif ( self::METHOD_MMC === self::$options['use_apc'] && Cachify_MEMCACHED::is_available() ) { self::$method = new Cachify_MEMCACHED(); + /* REDIS */ + } elseif ( self::METHOD_REDIS === self::$options['use_apc'] && Cachify_REDIS::is_available() ) { + self::$method = new Cachify_REDIS(); + /* DB */ } else { self::$method = new Cachify_DB(); @@ -421,15 +422,15 @@ private static function _get_options() { return wp_parse_args( get_option( 'cachify' ), array( - 'only_guests' => 1, - 'compress_html' => self::MINIFY_DISABLED, - 'cache_expires' => 12, - 'without_ids' => '', - 'without_agents' => '', - 'use_apc' => self::METHOD_DB, - 'reset_on_post' => 1, - 'reset_on_comment' => 0, - 'sig_detail' => 0, + 'only_guests' => 1, + 'compress_html' => self::MINIFY_DISABLED, + 'cache_expires' => 12, + 'without_ids' => '', + 'without_agents' => '', + 'use_apc' => self::METHOD_DB, + 'reset_on_post' => 1, + 'reset_on_comment' => 0, + 'sig_detail' => 0, ) ); } @@ -568,7 +569,7 @@ public static function add_dashboard_count( $items = array() ) { /* Right now item */ $items[] = sprintf( - ' + ' %s', @@ -578,8 +579,12 @@ public static function add_dashboard_count( $items = array() ) { ), admin_url( 'options-general.php' ) ), - esc_attr( strtolower( $method ) ), - esc_html__( 'Caching method', 'cachify' ), + sprintf( + /* translators: 1: "Caching method label"; 2: Actual method. */ + esc_html__( '%1$s: %2$s', 'cachify' ), + esc_html__( 'Caching method', 'cachify' ), + esc_attr( strtolower( $method ) ) + ), esc_attr( $method ), plugins_url( 'images/symbols.svg', CACHIFY_FILE ), esc_attr( strtolower( $method ) ), @@ -662,7 +667,7 @@ public static function add_flush_icon( $wp_admin_bar ) { ) . '', 'meta' => array( - 'title' => esc_html__( 'Flush the cachify cache', 'cachify' ), + 'title' => esc_html__( 'Flush the Cachify cache', 'cachify' ), ), ) ); @@ -707,10 +712,10 @@ public static function add_flush_icon_script( $wp_admin_bar ) { 'cachify-admin-bar-flush', 'cachify_admin_bar_flush_ajax_object', array( - 'url' => esc_url_raw( rest_url( self::REST_NAMESPACE . '/' . self::REST_ROUTE_FLUSH ) ), - 'nonce' => wp_create_nonce( 'wp_rest' ), - 'flushing' => __( 'Flushing cache', 'cachify' ), - 'flushed' => __( 'Cache flushed successfully', 'cachify' ), + 'url' => esc_url_raw( rest_url( self::REST_NAMESPACE . '/' . self::REST_ROUTE_FLUSH ) ), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'flushing' => __( 'Flushing cache', 'cachify' ), + 'flushed' => __( 'Cache flushed successfully', 'cachify' ), 'dashicon_success' => self::get_dashicon_success_class(), ) ); @@ -727,8 +732,8 @@ public static function add_flush_rest_endpoint() { self::REST_NAMESPACE, self::REST_ROUTE_FLUSH, array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( __CLASS__, 'flush_cache', ), @@ -780,7 +785,7 @@ public static function process_flush_request( $data ) { /* Load on demand */ if ( ! function_exists( 'is_plugin_active_for_network' ) ) { - require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + require_once ABSPATH . 'wp-admin/includes/plugin.php'; } /* Flush cache */ @@ -865,7 +870,7 @@ public static function flush_cache() { public static function flush_notice() { /* No admin */ if ( ! is_admin_bar_showing() || ! apply_filters( 'cachify_user_can_flush_cache', current_user_can( 'manage_options' ) ) ) { - return false; + return; } printf( @@ -881,14 +886,33 @@ public static function flush_notice() { * * @since 0.1.0 * @since 2.1.2 + * + * @deprecated 2.4.0 Use comment_edit($id, $comment) instead. */ public static function edit_comment( $id ) { - if ( self::$options['reset_on_comment'] ) { - self::flush_total_cache(); - } else { - self::remove_page_cache_by_post_id( - get_comment( $id )->comment_post_ID - ); + self::comment_edit( $id, array( 'comment_approved' => 1 ) ); + } + + /** + * Remove page from cache or flush on comment edit. + * + * @param integer $id Comment ID. + * @param array $comment Comment data. + * + * @since 2.4.0 Replacement for edit_comment($id) with additional comment parameter. + */ + public static function comment_edit( $id, $comment ) { + $approved = (int) $comment['comment_approved']; + + /* Approved comment? */ + if ( 1 === $approved ) { + if ( self::$options['reset_on_comment'] ) { + self::flush_total_cache(); + } else { + self::remove_page_cache_by_post_id( + get_comment( $id )->comment_post_ID + ); + } } } @@ -902,18 +926,33 @@ public static function edit_comment( $id ) { * * @since 0.1 * @since 2.1.2 + * @since 2.4.0 Replacement for edit_comment($id) with additional comment parameter. */ public static function pre_comment( $approved, $comment ) { + self::new_comment( $comment['comment_ID'], $approved ); + + return $approved; + } + + /** + * Remove page from cache or flush on new comment + * + * @param integer|string $id Comment ID. + * @param integer|string $approved Comment status. + * + * @since 0.1.0 + * @since 2.1.2 + * @since 2.4.0 Renamed with ID parameter instead of comment array. + */ + public static function new_comment( $id, $approved ) { /* Approved comment? */ if ( 1 === $approved ) { if ( self::$options['reset_on_comment'] ) { self::flush_total_cache(); } else { - self::remove_page_cache_by_post_id( $comment['comment_post_ID'] ); + self::remove_page_cache_by_post_id( get_comment( $id )->comment_post_ID ); } } - - return $approved; } /** @@ -925,9 +964,26 @@ public static function pre_comment( $approved, $comment ) { * * @since 0.1 * @since 2.1.2 + * + * @deprecated 2.4.0 Use comment_status($new_status, $old_status, $comment) instead. */ public static function touch_comment( $new_status, $old_status, $comment ) { - if ( $new_status !== $old_status ) { + self::comment_status( $new_status, $old_status, $comment ); + } + + /** + * Remove page from cache or flush on comment edit. + * + * @param string $new_status New status. + * @param string $old_status Old status. + * @param WP_Comment $comment The comment. + * + * @since 0.1 + * @since 2.1.2 + * @since 2.4.0 Renamed from touch_comment(). + */ + public static function comment_status( $new_status, $old_status, $comment ) { + if ( 'approved' === $old_status || 'approved' === $new_status ) { if ( self::$options['reset_on_comment'] ) { self::flush_total_cache(); } else { @@ -1048,7 +1104,7 @@ public static function post_update( $id, $data ) { public static function flush_cache_for_posts( $post ) { if ( is_int( $post ) ) { $post_id = $post; - $data = get_post( $post_id ); + $data = get_post( $post_id ); if ( ! is_object( $data ) ) { return; @@ -1164,7 +1220,8 @@ private static function _cache_hash( $url = '' ) { } $url_parts = wp_parse_url( $url ); - $hash_key = $prefix . $url_parts['host'] . $url_parts['path']; + $hash_key = $prefix . $url_parts['host'] . $url_parts['path']; + return md5( $hash_key ) . '.cachify'; } @@ -1242,17 +1299,17 @@ private static function _is_logged_in() { public static function register_flush_cache_hooks() { /* Define all default flush cache hooks */ $flush_cache_hooks = array( - 'cachify_flush_cache' => 10, - '_core_updated_successfully' => 10, - 'switch_theme' => 10, - 'before_delete_post' => 10, - 'wp_trash_post' => 10, - 'create_term' => 10, - 'delete_term' => 10, - 'edit_terms' => 10, - 'user_register' => 10, - 'edit_user_profile_update' => 10, - 'delete_user' => 10, + 'cachify_flush_cache' => 10, + '_core_updated_successfully' => 10, + 'switch_theme' => 10, + 'before_delete_post' => 10, + 'wp_trash_post' => 10, + 'create_term' => 10, + 'delete_term' => 10, + 'edit_terms' => 10, + 'user_register' => 10, + 'edit_user_profile_update' => 10, + 'delete_user' => 10, /* third party */ 'autoptimize_action_cachepurged' => 10, ); @@ -1263,7 +1320,6 @@ public static function register_flush_cache_hooks() { foreach ( $flush_cache_hooks as $hook => $priority ) { add_action( $hook, array( 'Cachify', 'flush_total_cache' ), $priority, 0 ); } - } /** @@ -1441,6 +1497,9 @@ public static function flush_total_cache( $clear_all_methods = false ) { /* HDD */ Cachify_HDD::clear_cache(); + /* REDIS */ + Cachify_REDIS::clear_cache(); + /* MEMCACHED */ Cachify_MEMCACHED::clear_cache(); } else { @@ -1580,7 +1639,6 @@ public static function add_admin_resources( $hook ) { default: break; } - } /** @@ -1639,6 +1697,7 @@ private static function _method_select() { self::METHOD_APC => esc_html__( 'APC', 'cachify' ), self::METHOD_HDD => esc_html__( 'Hard disk', 'cachify' ), self::METHOD_MMC => esc_html__( 'Memcached', 'cachify' ), + self::METHOD_REDIS => esc_html__( 'Redis', 'cachify' ), ); /* APC */ @@ -1656,6 +1715,11 @@ private static function _method_select() { unset( $methods[2] ); } + /* Redis */ + if ( ! Cachify_REDIS::is_available() ) { + unset( $methods[4] ); + } + return $methods; } @@ -1710,7 +1774,7 @@ public static function validate_options( $data ) { self::flush_total_cache( true ); /* Notification */ - if ( self::$options['use_apc'] !== $data['use_apc'] && $data['use_apc'] >= self::METHOD_APC ) { + if ( self::$options['use_apc'] !== $data['use_apc'] && $data['use_apc'] >= self::METHOD_APC && self::METHOD_REDIS != $data['use_apc'] ) { add_settings_error( 'cachify_method_tip', 'cachify_method_tip', @@ -1739,9 +1803,9 @@ public static function validate_options( $data ) { * @since 1.0 */ public static function options_page() { - $options = self::_get_options(); + $options = self::_get_options(); $cachify_tabs = self::_get_tabs( $options ); - $current_tab = isset( $_GET['cachify_tab'] ) && isset( $cachify_tabs[ $_GET['cachify_tab'] ] ) + $current_tab = isset( $_GET['cachify_tab'] ) && isset( $cachify_tabs[ $_GET['cachify_tab'] ] ) ? sanitize_text_field( wp_unslash( $_GET['cachify_tab'] ) ) : 'settings'; ?> @@ -1760,7 +1824,7 @@ public static function options_page() { esc_url( add_query_arg( array( - 'page' => 'cachify', + 'page' => 'cachify', 'cachify_tab' => $tab_key, ), admin_url( 'options-general.php' ) diff --git a/inc/setup/cachify.hdd.htaccess.php b/inc/setup/cachify.hdd.htaccess.php index db78363a..5b1efb14 100644 --- a/inc/setup/cachify.hdd.htaccess.php +++ b/inc/setup/cachify.hdd.htaccess.php @@ -44,7 +44,7 @@ } $beginning = str_replace( '{{GZIP}}', $gzip, $beginning ); -$middle = '/cache/cachify/%{ENV:CACHIFY_HOST}%{REQUEST_URI}index\.html -f +$middle = '/cache/cachify/%{ENV:CACHIFY_HOST}%{REQUEST_URI}index\.html%{ENV:CACHIFY_SUFFIX} -f RewriteRule ^(.*) '; $ending = '/cache/cachify/%{ENV:CACHIFY_HOST}%{REQUEST_URI}index\.html%{ENV:CACHIFY_SUFFIX} [L,NS] @@ -74,7 +74,7 @@
  • - +

  • diff --git a/package.json b/package.json index 2aa9ff0f..75f868ce 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "private": true, "devDependencies": { - "stylelint": "^14.10.0", - "eslint": "^8.22.0", - "@wordpress/eslint-plugin": "^12.9.0", - "@wordpress/stylelint-config": "^20.0.2" + "stylelint": "^14.16.1", + "eslint": "^8.57.0", + "@wordpress/eslint-plugin": "^17.11.0", + "@wordpress/stylelint-config": "^21.37.0" } } diff --git a/phpcs.xml b/phpcs.xml index 750725cb..356fca15 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -21,7 +21,11 @@ - + + + + + diff --git a/readme.txt b/readme.txt index c027032b..e8d47d79 100644 --- a/readme.txt +++ b/readme.txt @@ -9,14 +9,14 @@ * License: GPLv2 or later * License URI: http://www.gnu.org/licenses/gpl-2.0.html -Smart, efficient cache solution for WordPress. Use DB, HDD, APC or Memcached for storing your blog pages. Make WordPress faster! +Smart, efficient cache solution for WordPress. Use DB, HDD, APC, Redis or Memcached for storing your blog pages. Make WordPress faster! ## Description ## -*Cachify* optimizes your page loads by caching posts, pages and custom post types as static content. You can choose between caching via database, on the web server’s hard drive (HDD), Memcached (only on Nginx) or — thanks to APC (Alternative PHP Cache) — directly in the web server’s system cache. Whenever a page or post is loaded, it can be pulled directly from the cache. The amount of database queries and PHP requests will dramatically decrease towards zero, depending on the caching method you chose. +*Cachify* optimizes your page loads by caching posts, pages and custom post types as static content. You can choose between caching via database, on the web server’s hard drive (HDD), Memcached (only on Nginx), Redis or — thanks to APC (Alternative PHP Cache) — directly in the web server’s system cache. Whenever a page or post is loaded, it can be pulled directly from the cache. The amount of database queries and PHP requests will dramatically decrease towards zero, depending on the caching method you chose. ### Features ### * Works with custom post types. -* Caching methods: DB, HDD, APC and Memcached. +* Caching methods: DB, HDD, APC, Redis and Memcached. * “Flush Cache” button in the WordPress toolbar. * Ready for WordPress Multisite. * Optional compression of HTML markup. @@ -46,10 +46,11 @@ Smart, efficient cache solution for WordPress. Use DB, HDD, APC or Memcached for * If you don’t know how to install a plugin for WordPress, [here’s how](https://wordpress.org/support/article/managing-plugins/#installing-plugins). ### Requirements ### -* PHP 5.2.4 or greater -* WordPress 3.8 or greater +* PHP 5.6 or greater +* WordPress 4.7 or greater * APC 3.1.4 or greater (optional) * Memcached in Nginx (optional) +* Redis (optional, via the phpredis module) ## Frequently Asked Questions ## @@ -154,8 +155,8 @@ A complete documentation is available in the [online handbook](https://cachify.p * Fix for the PHP notice "Call to undefined function is_plugin_active_for_network" on WordPress Multisite ### 2.2.0 ### -* Toolbar: Display of the "Flush the cachify cache" button on the frontend -* Toolbar: Controlling the display of the "Flush the cachify cache" button via hook +* Toolbar: Display of the "Flush the Cachify cache" button on the frontend +* Toolbar: Controlling the display of the "Flush the Cachify cache" button via hook For the complete changelog, check out our [GitHub repository](https://github.com/pluginkollektiv/cachify). diff --git a/tests/test-cachify-hdd.php b/tests/test-cachify-hdd.php index 80bf3656..b5d0593b 100644 --- a/tests/test-cachify-hdd.php +++ b/tests/test-cachify-hdd.php @@ -73,6 +73,17 @@ public function test_caching() { ); self::assertStringEndsWith( ' -->', $cached ); + // A subpage + self::go_to( '/testme/sub' ); + Cachify_HDD::store_item( + '965b4abf2414e45036ab90c9d3f8dbc7', // Ignored. + 'Test Me

    This is a subpage.

    ', + 3600, // Ignored. + false + ); + self::assertTrue( Cachify_HDD::get_item() ); + self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub/index.html' ) ); + // Another item. self::go_to( '/test2/' ); Cachify_HDD::store_item( @@ -96,10 +107,17 @@ public function test_caching() { Cachify_HDD::delete_item( '965b4abf2414e45036ab90c9d3f8dbc7', 'http://example.org/testme/' ); self::assertFalse( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/index.html' ), 'first item was not deleted' ); self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2/index.html' ), 'second item should still be present' ); + self::assertTrue( is_file( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub/index.html' ), 'subpage should now have been deleted' ); + + // Delete the subpage. + Cachify_HDD::delete_item( '965b4abf2414e45036ab90c9d3f8dbc7', 'http://example.org/testme/sub' ); + self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub/index.html' ), 'subpage item was not deleted' ); + self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme/sub' ), 'empty directory was not deleted' ); // Clear the cache. Cachify_HDD::clear_cache(); - self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2' ) ); + self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/testme' ), 'empty directory was not deleted' ); + self::assertFalse( is_dir( CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . 'example.org/test2' ), 'second test page was not deleted' ); self::assertFalse( Cachify_HDD::get_item() ); } }