13 | 14 | • 15 | • 16 | • 17 |
18 | -------------------------------------------------------------------------------- /inc/cachify.settings.php: -------------------------------------------------------------------------------- 1 | __( 'Database', 'cachify' ), 16 | ); 17 | if ( Cachify_HDD::is_available() ) { 18 | $method_select[ Cachify::METHOD_HDD ] = __( 'Hard disk', 'cachify' ); 19 | } 20 | if ( Cachify_MEMCACHED::is_available() ) { 21 | $method_select[ Cachify::METHOD_MMC ] = __( 'Memcached', 'cachify' ); 22 | } 23 | if ( Cachify_REDIS::is_available() ) { 24 | $method_select[ Cachify::METHOD_REDIS ] = __( 'Redis', 'cachify' ); 25 | } 26 | 27 | /* 28 | * Minify cache dropdown 29 | */ 30 | $minify_select = array( 31 | Cachify::MINIFY_DISABLED => __( 'No minify', 'cachify' ), 32 | Cachify::MINIFY_HTML_ONLY => __( 'HTML', 'cachify' ), 33 | Cachify::MINIFY_HTML_JS => __( 'HTML + Inline JavaScript', 'cachify' ), 34 | ); 35 | 36 | ?> 37 | 38 | 180 | -------------------------------------------------------------------------------- /inc/class-cachify-backend.php: -------------------------------------------------------------------------------- 1 | false ) ); 27 | 28 | Cachify::flush_total_cache( $assoc_args['all-methods'] ); 29 | 30 | if ( $assoc_args['all-methods'] ) { 31 | WP_CLI::success( 'All Cachify caches flushed' ); 32 | } else { 33 | WP_CLI::success( 'Cachify cache flushed' ); 34 | } 35 | } 36 | 37 | /** 38 | * Get cache size 39 | * 40 | * @param array $args the CLI arguments as array. 41 | * @param array $assoc_args the CLI arguments as associative array. 42 | * 43 | * @since 2.3.0 44 | */ 45 | public static function get_cache_size( $args, $assoc_args ) { 46 | // Set default arguments. 47 | $assoc_args = wp_parse_args( $assoc_args, array( 'raw' => false ) ); 48 | 49 | $cache_size = Cachify::get_cache_size(); 50 | 51 | if ( $assoc_args['raw'] ) { 52 | $message = $cache_size; 53 | } else { 54 | $message = "The cache size is $cache_size bytes"; 55 | } 56 | 57 | WP_CLI::line( $message ); 58 | } 59 | 60 | /** 61 | * Register CLI Commands 62 | * 63 | * @since 2.3.0 64 | */ 65 | public static function add_commands() { 66 | // Add flush command. 67 | WP_CLI::add_command( 68 | 'cachify flush', 69 | array( 70 | 'Cachify_CLI', 71 | 'flush_cache', 72 | ), 73 | array( 74 | 'shortdesc' => 'Flush site cache', 75 | 'synopsis' => array( 76 | array( 77 | 'type' => 'flag', 78 | 'name' => 'all-methods', 79 | 'description' => 'Flush all caching methods', 80 | 'optional' => true, 81 | ), 82 | ), 83 | ) 84 | ); 85 | 86 | // Add cache-size command. 87 | WP_CLI::add_command( 88 | 'cachify cache-size', 89 | array( 90 | 'Cachify_CLI', 91 | 'get_cache_size', 92 | ), 93 | array( 94 | 'shortdesc' => 'Get the size of the cache in bytes', 95 | 'synopsis' => array( 96 | array( 97 | 'type' => 'flag', 98 | 'name' => 'raw', 99 | 'description' => 'Raw size output in bytes', 100 | 'optional' => true, 101 | ), 102 | ), 103 | ) 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /inc/class-cachify-db.php: -------------------------------------------------------------------------------- 1 | $data, 61 | 'meta' => array( 62 | 'queries' => self::_page_queries(), 63 | 'timer' => self::_page_timer(), 64 | 'memory' => self::_page_memory(), 65 | 'time' => current_time( 'timestamp' ), 66 | ), 67 | ), 68 | $lifetime 69 | ); 70 | } 71 | 72 | /** 73 | * Read item from cache 74 | * 75 | * @param string $hash Hash of the entry. 76 | * 77 | * @return mixed Content of the entry 78 | * 79 | * @since 2.0 80 | */ 81 | public static function get_item( $hash ) { 82 | return get_transient( $hash ); 83 | } 84 | 85 | /** 86 | * Delete item from cache 87 | * 88 | * @param string $hash Hash of the entry. 89 | * @param string $url URL of the entry [optional]. 90 | * 91 | * @since 2.0 92 | */ 93 | public static function delete_item( $hash, $url = '' ) { 94 | delete_transient( $hash ); 95 | } 96 | 97 | /** 98 | * Clear the cache 99 | * 100 | * @since 2.0 101 | */ 102 | public static function clear_cache() { 103 | /* Init */ 104 | global $wpdb; 105 | 106 | /* Clear */ 107 | $wpdb->query( 'DELETE FROM `' . $wpdb->options . "` WHERE `option_name` LIKE ('\_transient%.cachify')" ); 108 | } 109 | 110 | /** 111 | * Print the cache 112 | * 113 | * @param bool $sig_detail Show details in signature. 114 | * @param array $cache Array of cache values. 115 | * 116 | * @since 2.0 117 | * @since 2.3.0 added $sig_detail parameter 118 | */ 119 | public static function print_cache( $sig_detail, $cache ) { 120 | /* No array? */ 121 | if ( ! is_array( $cache ) ) { 122 | return; 123 | } 124 | 125 | /* Content */ 126 | echo $cache['data']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 127 | 128 | /* Signature - might contain runtime information, so it's generated at this point */ 129 | if ( isset( $cache['meta'] ) ) { 130 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 131 | echo self::_cache_signature( $sig_detail, $cache['meta'] ); 132 | } 133 | 134 | /* Quit */ 135 | exit; 136 | } 137 | 138 | /** 139 | * Get the cache size 140 | * 141 | * @return int Column size 142 | * 143 | * @since 2.0 144 | */ 145 | public static function get_stats() { 146 | /* Init */ 147 | global $wpdb; 148 | 149 | /* Read */ 150 | return intval( 151 | $wpdb->get_var( 152 | 'SELECT SUM( CHAR_LENGTH(option_value) ) FROM `' . $wpdb->options . "` WHERE `option_name` LIKE ('\_transient%.cachify')" 153 | ) 154 | ); 155 | } 156 | 157 | /** 158 | * Generate signature 159 | * 160 | * @param bool $detail Show details in signature. 161 | * @param array $meta Content of metadata. 162 | * 163 | * @return string Signature string 164 | * 165 | * @since 2.0 166 | * @since 2.3.0 added $detail parameter 167 | */ 168 | private static function _cache_signature( $detail, $meta ) { 169 | /* No array? */ 170 | if ( ! is_array( $meta ) ) { 171 | return ''; 172 | } 173 | 174 | if ( $detail ) { 175 | return sprintf( 176 | "\n\n", 177 | 'Cachify | https://cachify.pluginkollektiv.org', 178 | 'DB Cache', 179 | date_i18n( 180 | 'd.m.Y H:i:s', 181 | $meta['time'] 182 | ), 183 | sprintf( 184 | 'Without Cachify: %d DB queries, %s seconds, %s', 185 | $meta['queries'], 186 | $meta['timer'], 187 | $meta['memory'] 188 | ), 189 | sprintf( 190 | 'With Cachify: %d DB queries, %s seconds, %s', 191 | self::_page_queries(), 192 | self::_page_timer(), 193 | self::_page_memory() 194 | ) 195 | ); 196 | } else { 197 | return sprintf( 198 | "\n\n", 199 | 'Cachify | https://cachify.pluginkollektiv.org', 200 | __( 'Generated', 'cachify' ), 201 | date_i18n( 202 | 'd.m.Y H:i:s', 203 | $meta['time'] 204 | ) 205 | ); 206 | } 207 | } 208 | 209 | /** 210 | * Return query count 211 | * 212 | * @return int Number of queries 213 | * 214 | * @since 0.1 215 | */ 216 | private static function _page_queries() { 217 | return $GLOBALS['wpdb']->num_queries; 218 | } 219 | 220 | /** 221 | * Return execution time 222 | * 223 | * @return string Execution time in seconds 224 | * 225 | * @since 0.1 226 | */ 227 | private static function _page_timer() { 228 | return timer_stop( 0, 2 ); 229 | } 230 | 231 | /** 232 | * Return memory consumption 233 | * 234 | * @return string Formatted memory size 235 | * 236 | * @since 0.7 237 | */ 238 | private static function _page_memory() { 239 | return ( function_exists( 'memory_get_usage' ) ? size_format( memory_get_usage(), 2 ) : 0 ); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /inc/class-cachify-hdd.php: -------------------------------------------------------------------------------- 1 | ", 166 | 'Cachify | https://cachify.pluginkollektiv.org', 167 | ( $detail ? 'HDD Cache' : __( 'Generated', 'cachify' ) ), 168 | date_i18n( 169 | 'd.m.Y H:i:s', 170 | current_time( 'timestamp' ) 171 | ) 172 | ); 173 | } 174 | 175 | /** 176 | * Initialize caching process 177 | * 178 | * @param string $data Cache content. 179 | * 180 | * @since 2.0 181 | */ 182 | private static function _create_files( $data ) { 183 | $file_path = self::_file_path(); 184 | 185 | /* Create directory */ 186 | if ( ! wp_mkdir_p( $file_path ) ) { 187 | trigger_error( esc_html( __METHOD__ . ": Unable to create directory {$file_path}." ), E_USER_WARNING ); 188 | return; 189 | } 190 | /* Write to file */ 191 | self::_create_file( self::_file_html( $file_path ), $data ); 192 | 193 | /** 194 | * Filter that allows to enable/disable gzip file creation 195 | * 196 | * @param bool $create_gzip_files Whether to create gzip files. Default is `true` 197 | */ 198 | if ( self::is_gzip_enabled() ) { 199 | self::_create_file( self::_file_gzip( $file_path ), gzencode( $data, 9 ) ); 200 | } 201 | } 202 | 203 | /** 204 | * Create cache file 205 | * 206 | * @param string $file Path to cache file. 207 | * @param string $data Cache content. 208 | * 209 | * @since 2.0 210 | */ 211 | private static function _create_file( $file, $data ) { 212 | /* Writable? */ 213 | $handle = @fopen( $file, 'wb' ); 214 | if ( ! $handle ) { 215 | trigger_error( esc_html( __METHOD__ . ": Could not write file {$file}." ), E_USER_WARNING ); 216 | return; 217 | } 218 | 219 | /* Write */ 220 | fwrite( $handle, $data ); 221 | fclose( $handle ); 222 | clearstatcache(); 223 | 224 | /* Permissions */ 225 | $stat = @stat( dirname( $file ) ); 226 | $perms = $stat['mode'] & 0007777; 227 | $perms = $perms & 0000666; 228 | @chmod( $file, $perms ); 229 | clearstatcache(); 230 | } 231 | 232 | /** 233 | * Clear directory 234 | * 235 | * @param string $dir Directory path. 236 | * @param bool $recursive true for clearing subdirectories as well. 237 | * 238 | * @since 2.0 239 | */ 240 | private static function _clear_dir( $dir, $recursive = false ) { 241 | // Remove trailing slash. 242 | $dir = untrailingslashit( $dir ); 243 | 244 | // Is directory? 245 | if ( ! is_dir( $dir ) ) { 246 | return; 247 | } 248 | 249 | // List directory contents. 250 | $objects = array_diff( 251 | scandir( $dir ), 252 | array( '..', '.' ) 253 | ); 254 | 255 | // Loop over items. 256 | foreach ( $objects as $object ) { 257 | // Expand path. 258 | $object = $dir . DIRECTORY_SEPARATOR . $object; 259 | 260 | if ( is_dir( $object ) ) { 261 | if ( $recursive ) { 262 | // Recursively clear the directory. 263 | self::_clear_dir( $object, $recursive ); 264 | } elseif ( self::_user_can_delete( $object ) && 0 === count( glob( trailingslashit( $object ) . '*' ) ) ) { 265 | // Delete the directory, if empty. 266 | @rmdir( $object ); 267 | } 268 | } elseif ( self::_user_can_delete( $object ) ) { 269 | // Delete the file. 270 | unlink( $object ); 271 | } 272 | } 273 | 274 | // Remove directory, if empty. 275 | if ( self::_user_can_delete( $dir ) && 0 === count( glob( trailingslashit( $dir ) . '*' ) ) ) { 276 | @rmdir( $dir ); 277 | } 278 | 279 | // Clean up. 280 | clearstatcache(); 281 | } 282 | 283 | /** 284 | * Get directory size 285 | * 286 | * @param string $dir Directory path. 287 | * 288 | * @return int|false Directory size 289 | * 290 | * @since 2.0 291 | */ 292 | public static function _dir_size( $dir = '.' ) { 293 | /* Is directory? */ 294 | if ( ! is_dir( $dir ) ) { 295 | return false; 296 | } 297 | 298 | /* Read */ 299 | $objects = array_diff( 300 | scandir( $dir ), 301 | array( '..', '.' ) 302 | ); 303 | 304 | /* Empty? */ 305 | if ( empty( $objects ) ) { 306 | return false; 307 | } 308 | 309 | /* Init */ 310 | $size = 0; 311 | 312 | /* Loop over items */ 313 | foreach ( $objects as $object ) { 314 | /* Expand path */ 315 | $object = $dir . DIRECTORY_SEPARATOR . $object; 316 | 317 | /* Directory or file */ 318 | if ( is_dir( $object ) ) { 319 | $size += self::_dir_size( $object ); 320 | } else { 321 | $size += filesize( $object ); 322 | } 323 | } 324 | 325 | return $size; 326 | } 327 | 328 | /** 329 | * Path to cache file 330 | * 331 | * @param string $path Request URI or permalink [optional]. 332 | * 333 | * @return string Path to cache file 334 | * 335 | * @since 2.0 336 | */ 337 | private static function _file_path( $path = null ) { 338 | $prefix = is_ssl() ? 'https-' : ''; 339 | 340 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 341 | $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); 342 | 343 | $path = sprintf( 344 | '%s%s%s%s%s', 345 | CACHIFY_CACHE_DIR, 346 | DIRECTORY_SEPARATOR, 347 | $prefix, 348 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 349 | strtolower( wp_unslash( $_SERVER['HTTP_HOST'] ) ), 350 | $path_parts['path'] 351 | ); 352 | 353 | if ( validate_file( $path ) > 0 ) { 354 | wp_die( 'Invalid file path.' ); 355 | } 356 | 357 | return trailingslashit( $path ); 358 | } 359 | 360 | /** 361 | * Path to HTML file 362 | * 363 | * @param string $file_path File path [optional]. 364 | * 365 | * @return string Path to HTML file 366 | * 367 | * @since 2.0 368 | */ 369 | private static function _file_html( $file_path = '' ) { 370 | return ( empty( $file_path ) ? self::_file_path() : $file_path ) . 'index.html'; 371 | } 372 | 373 | /** 374 | * Path to GZIP file 375 | * 376 | * @param string $file_path File path [optional]. 377 | * 378 | * @return string Path to GZIP file 379 | * 380 | * @since 2.0 381 | */ 382 | private static function _file_gzip( $file_path = '' ) { 383 | return ( empty( $file_path ) ? self::_file_path() : $file_path ) . 'index.html.gz'; 384 | } 385 | 386 | /** 387 | * Does the user has the right to delete this file? 388 | * 389 | * @param string $file the file name. 390 | * 391 | * @return bool 392 | */ 393 | private static function _user_can_delete( $file ) { 394 | if ( ! is_file( $file ) && ! is_dir( $file ) ) { 395 | return false; 396 | } 397 | 398 | if ( 0 !== strpos( $file, CACHIFY_CACHE_DIR ) ) { 399 | return false; 400 | } 401 | 402 | // If its just a single blog, the user has the right to delete this file. 403 | // But also, if you are in the network admin, you should be able to delete all files. 404 | if ( ! is_multisite() || is_network_admin() ) { 405 | return true; 406 | } 407 | 408 | if ( is_dir( $file ) ) { 409 | $file = trailingslashit( $file ); 410 | } 411 | 412 | $ssl_prefix = is_ssl() ? 'https-' : ''; 413 | $current_blog = get_blog_details( get_current_blog_id() ); 414 | $blog_path = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . $current_blog->domain . $current_blog->path; 415 | 416 | if ( 0 !== strpos( $file, $blog_path ) ) { 417 | return false; 418 | } 419 | 420 | // We are on a subdirectory installation and the current blog is in a subdirectory. 421 | if ( '/' !== $current_blog->path ) { 422 | return true; 423 | } 424 | 425 | // If we are on the root blog in a subdirectory multisite, we check if the current dir is the root dir. 426 | $root_site_dir = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . DOMAIN_CURRENT_SITE . DIRECTORY_SEPARATOR; 427 | if ( $root_site_dir === $file ) { 428 | return false; 429 | } 430 | 431 | // If we are on the root blog in a subdirectory multisite, we check, if the current file is part of another blog. 432 | global $wpdb; 433 | $results = $wpdb->get_col( 434 | $wpdb->prepare( 435 | 'select path from ' . $wpdb->base_prefix . 'blogs where domain = %s && blog_id != %d', 436 | $current_blog->domain, 437 | $current_blog->blog_id 438 | ) 439 | ); 440 | foreach ( $results as $site ) { 441 | $forbidden_path = CACHIFY_CACHE_DIR . DIRECTORY_SEPARATOR . $ssl_prefix . $current_blog->domain . $site; 442 | if ( 0 === strpos( $file, $forbidden_path ) ) { 443 | return false; 444 | } 445 | } 446 | 447 | return true; 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /inc/class-cachify-memcached.php: -------------------------------------------------------------------------------- 1 | set( 74 | self::_file_path(), 75 | $data . self::_cache_signature( $sig_detail ), 76 | $lifetime 77 | ); 78 | } 79 | 80 | /** 81 | * Read item from cache 82 | * 83 | * @param string $hash Hash of the entry. 84 | * 85 | * @return mixed Content of the entry 86 | * 87 | * @since 2.0.7 88 | */ 89 | public static function get_item( $hash ) { 90 | /* Server connect */ 91 | if ( ! self::_connect_server() ) { 92 | return null; 93 | } 94 | 95 | /* Get item */ 96 | return self::$_memcached->get( 97 | self::_file_path() 98 | ); 99 | } 100 | 101 | /** 102 | * Delete item from cache 103 | * 104 | * @param string $hash Hash of the entry. 105 | * @param string $url URL of the entry [optional]. 106 | * 107 | * @since 2.0.7 108 | */ 109 | public static function delete_item( $hash, $url = '' ) { 110 | /* Server connect */ 111 | if ( ! self::_connect_server() ) { 112 | return; 113 | } 114 | 115 | /* Delete */ 116 | self::$_memcached->delete( 117 | self::_file_path( $url ) 118 | ); 119 | } 120 | 121 | /** 122 | * Clear the cache 123 | * 124 | * @since 2.0.7 125 | */ 126 | public static function clear_cache() { 127 | /* Server connect */ 128 | if ( ! self::_connect_server() ) { 129 | return; 130 | } 131 | 132 | if ( ! self::$_memcached instanceof Memcached ) { 133 | return; 134 | } 135 | 136 | /* Flush */ 137 | self::$_memcached->flush(); 138 | } 139 | 140 | /** 141 | * Print the cache 142 | * 143 | * @param bool $sig_detail Show details in signature. 144 | * @param array $cache Array of cache values. 145 | * 146 | * @since 2.0.7 147 | */ 148 | public static function print_cache( $sig_detail, $cache ) { 149 | // Not supported. 150 | } 151 | 152 | /** 153 | * Get the cache size 154 | * 155 | * @return mixed Cache size 156 | * 157 | * @since 2.0.7 158 | */ 159 | public static function get_stats() { 160 | /* Server connect */ 161 | if ( ! self::_connect_server() ) { 162 | return null; 163 | } 164 | 165 | /* Info */ 166 | $data = self::$_memcached->getStats(); 167 | 168 | /* No stats? */ 169 | if ( empty( $data ) ) { 170 | return null; 171 | } 172 | 173 | /* Get first key */ 174 | $data = $data[ key( $data ) ]; 175 | 176 | /* Empty */ 177 | if ( empty( $data['bytes'] ) ) { 178 | return null; 179 | } 180 | 181 | return $data['bytes']; 182 | } 183 | 184 | /** 185 | * Generate signature 186 | * 187 | * @param bool $detail Show details in signature. 188 | * 189 | * @return string Signature string 190 | * 191 | * @since 2.0.7 192 | * @since 2.3.0 added $detail parameter 193 | */ 194 | private static function _cache_signature( $detail ) { 195 | return sprintf( 196 | "\n\n", 197 | 'Cachify | https://cachify.pluginkollektiv.org', 198 | ( $detail ? 'Memcached' : __( 'Generated', 'cachify' ) ), 199 | date_i18n( 200 | 'd.m.Y H:i:s', 201 | current_time( 'timestamp' ) 202 | ) 203 | ); 204 | } 205 | 206 | /** 207 | * Path of cache file 208 | * 209 | * @param string $path Request URI or permalink [optional]. 210 | * 211 | * @return string Path to cache file 212 | * 213 | * @since 2.0.7 214 | */ 215 | private static function _file_path( $path = null ) { 216 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 217 | $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); 218 | 219 | return trailingslashit( 220 | sprintf( 221 | '%s%s', 222 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 223 | wp_unslash( $_SERVER['HTTP_HOST'] ), 224 | $path_parts['path'] 225 | ) 226 | ); 227 | } 228 | 229 | /** 230 | * Connect to Memcached server 231 | * 232 | * @hook array cachify_memcached_servers Array with memcached servers 233 | * 234 | * @return bool TRUE on success 235 | * 236 | * @since 2.0.7 237 | */ 238 | private static function _connect_server() { 239 | /* Not enabled? */ 240 | if ( ! self::is_available() ) { 241 | return false; 242 | } 243 | 244 | /* Already connected */ 245 | if ( is_object( self::$_memcached ) ) { 246 | return true; 247 | } 248 | 249 | /* Init */ 250 | self::$_memcached = new Memcached(); 251 | 252 | /* Set options */ 253 | if ( defined( 'HHVM_VERSION' ) ) { 254 | self::$_memcached->setOption( Memcached::OPT_COMPRESSION, false ); 255 | self::$_memcached->setOption( Memcached::OPT_BUFFER_WRITES, true ); 256 | self::$_memcached->setOption( Memcached::OPT_BINARY_PROTOCOL, true ); 257 | } else { 258 | self::$_memcached->setOptions( 259 | array( 260 | Memcached::OPT_COMPRESSION => false, 261 | Memcached::OPT_BUFFER_WRITES => true, 262 | Memcached::OPT_BINARY_PROTOCOL => true, 263 | ) 264 | ); 265 | } 266 | 267 | /* Connect */ 268 | self::$_memcached->addServers( 269 | (array) apply_filters( 270 | 'cachify_memcached_servers', 271 | array( 272 | array( 273 | '127.0.0.1', 274 | 11211, 275 | ), 276 | ) 277 | ) 278 | ); 279 | 280 | return true; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /inc/class-cachify-noop.php: -------------------------------------------------------------------------------- 1 | unavailable_method = $unavailable_method; 34 | } 35 | 36 | /** 37 | * Availability check 38 | * 39 | * @return bool TRUE when installed 40 | */ 41 | public static function is_available() { 42 | return true; 43 | } 44 | 45 | /** 46 | * Caching method as string 47 | * 48 | * @return string Caching method 49 | */ 50 | public static function stringify_method() { 51 | return 'NOOP'; 52 | } 53 | 54 | /** 55 | * Store item in cache 56 | * 57 | * @param string $hash Hash of the entry. 58 | * @param string $data Content of the entry. 59 | * @param int $lifetime Lifetime of the entry. 60 | * @param bool $sig_detail Show details in signature. 61 | */ 62 | public static function store_item( $hash, $data, $lifetime, $sig_detail ) { 63 | // NOOP. 64 | } 65 | 66 | /** 67 | * Read item from cache 68 | * 69 | * @param string $hash Hash of the entry. 70 | * 71 | * @return false No content 72 | */ 73 | public static function get_item( $hash ) { 74 | return false; 75 | } 76 | 77 | /** 78 | * Delete item from cache 79 | * 80 | * @param string $hash Hash of the entry. 81 | * @param string $url URL of the entry [optional]. 82 | */ 83 | public static function delete_item( $hash, $url = '' ) { 84 | // NOOP. 85 | } 86 | 87 | /** 88 | * Clear the cache 89 | */ 90 | public static function clear_cache() { 91 | // NOOP. 92 | } 93 | 94 | /** 95 | * Print the cache 96 | * 97 | * @param bool $sig_detail Show details in signature. 98 | * @param array $cache Array of cache values. 99 | */ 100 | public static function print_cache( $sig_detail, $cache ) { 101 | // NOOP. 102 | } 103 | 104 | /** 105 | * Get the cache size 106 | * 107 | * @return int Column size 108 | */ 109 | public static function get_stats() { 110 | return 0; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /inc/class-cachify-redis.php: -------------------------------------------------------------------------------- 1 | set( 65 | self::_file_path(), 66 | $data . self::_cache_signature( $sig_detail ), 67 | $lifetime 68 | ); 69 | } 70 | 71 | /** 72 | * Read item from cache 73 | * 74 | * @param string $hash Hash of the entry. 75 | * @return mixed Content of the entry 76 | */ 77 | public static function get_item( $hash ) { 78 | /* Server connect */ 79 | if ( ! self::_connect_server() ) { 80 | return null; 81 | } 82 | 83 | /* Get item */ 84 | return self::$_redis->get( 85 | self::_file_path() 86 | ); 87 | } 88 | 89 | /** 90 | * Delete item from cache 91 | * 92 | * @param string $hash Hash of the entry [ignored]. 93 | * @param string $url URL of the entry. 94 | */ 95 | public static function delete_item( $hash, $url ) { 96 | /* Server connect */ 97 | if ( ! self::_connect_server() ) { 98 | return; 99 | } 100 | 101 | /* Delete */ 102 | self::$_redis->del( 103 | self::_file_path( $url ) 104 | ); 105 | } 106 | 107 | /** 108 | * Clear the cache 109 | * 110 | * @return void 111 | */ 112 | public static function clear_cache() { 113 | /* Server connect */ 114 | if ( ! self::_connect_server() ) { 115 | return; 116 | } 117 | 118 | /* Flush */ 119 | @self::$_redis->flushAll(); 120 | } 121 | 122 | /** 123 | * Print the cache 124 | * 125 | * @param bool $sig_detail Show details in signature. 126 | * @param string $cache Cached content. 127 | */ 128 | public static function print_cache( $sig_detail, $cache ) { 129 | echo $cache; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 130 | exit; 131 | } 132 | 133 | /** 134 | * Get the cache size 135 | * 136 | * @return integer Directory size 137 | */ 138 | public static function get_stats() { 139 | /* Server connect */ 140 | if ( ! self::_connect_server() ) { 141 | return null; 142 | } 143 | 144 | /* Info */ 145 | $data = self::$_redis->info( 'MEMORY' ); 146 | 147 | /* No stats? */ 148 | if ( empty( $data ) ) { 149 | return null; 150 | } 151 | 152 | /* Empty */ 153 | if ( empty( $data['used_memory_dataset'] ) ) { 154 | return null; 155 | } 156 | 157 | return $data['used_memory_dataset']; 158 | } 159 | 160 | /** 161 | * Generate signature 162 | * 163 | * @param bool $detail Show details in signature. 164 | * @return string Signature string 165 | */ 166 | private static function _cache_signature( $detail ) { 167 | return sprintf( 168 | "\n\n", 169 | 'Cachify | https://cachify.pluginkollektiv.org', 170 | ( $detail ? 'Redis Cache' : __( 'Generated', 'cachify' ) ), 171 | date_i18n( 172 | 'd.m.Y H:i:s', 173 | current_time( 'timestamp' ) 174 | ) 175 | ); 176 | } 177 | 178 | /** 179 | * Path of cache file 180 | * 181 | * @param string $path Request URI or permalink [optional]. 182 | * @return string Path to cache file 183 | */ 184 | private static function _file_path( $path = null ) { 185 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 186 | $path_parts = wp_parse_url( $path ? $path : wp_unslash( $_SERVER['REQUEST_URI'] ) ); 187 | 188 | return trailingslashit( 189 | sprintf( 190 | '%s%s', 191 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated 192 | wp_unslash( $_SERVER['HTTP_HOST'] ), 193 | $path_parts['path'] 194 | ) 195 | ); 196 | } 197 | 198 | /** 199 | * Connect to Redis server 200 | * 201 | * @return boolean true/false TRUE on success 202 | */ 203 | private static function _connect_server() { 204 | /* Not enabled? */ 205 | if ( ! self::is_available() ) { 206 | return false; 207 | } 208 | 209 | /* Have object and it thinks it's connected to a server */ 210 | if ( is_object( self::$_redis ) && self::$_redis->isConnected() ) { 211 | return true; 212 | } 213 | 214 | /* Init */ 215 | self::$_redis = new Redis(); 216 | 217 | /** 218 | * Filter hook to adjust Redis connection parameters 219 | * 220 | * @param array $redis_server Redis connection parameters. 221 | * 222 | * @see Redis::connect() For supported parameters. 223 | * 224 | * @since 2.4.0 225 | */ 226 | $con = apply_filters( 'cachify_redis_servers', array( 'localhost' ) ); 227 | $con = self::sanitize_con_parameters( $con ); 228 | 229 | if ( false === $con ) { 230 | return false; 231 | } 232 | 233 | // Establish connection. 234 | try { 235 | self::$_redis->connect( ...$con ); 236 | 237 | if ( ! self::$_redis->isConnected() ) { 238 | return false; 239 | } 240 | } catch ( Exception $e ) { 241 | return false; 242 | } 243 | 244 | return true; 245 | } 246 | 247 | /** 248 | * Sanitize Redis connection parameters. 249 | * 250 | * @param mixed $con Connection parameters (from hook). 251 | * 252 | * @return array|false Array of connection arguments or FALSE, if invalid. 253 | */ 254 | private static function sanitize_con_parameters( $con ) { 255 | if ( is_string( $con ) ) { 256 | return array( $con ); 257 | } elseif ( is_array( $con ) && ! empty( $con ) ) { 258 | $con[0] = strval( $con[0] ); // Host or socket path. 259 | if ( count( $con ) > 1 ) { 260 | $con[1] = intval( $con[1] ); // Port number. 261 | } 262 | if ( count( $con ) > 2 ) { 263 | $con[2] = floatval( $con[2] ); // Socket timeout in seconds. 264 | } 265 | if ( count( $con ) > 3 && ! is_null( $con[3] ) ) { 266 | $con[3] = strval( $con[3] ); // Persistent connection ID. 267 | } 268 | if ( count( $con ) > 4 ) { 269 | $con[4] = intval( $con[4] ); // Retry interval in milliseconds. 270 | } 271 | if ( count( $con ) > 5 ) { 272 | $con[5] = floatval( $con[5] ); // Read timeout in seconds. 273 | } 274 | if ( count( $con ) > 6 && ! is_null( $con[6] ) && ! is_array( $con[6] ) ) { 275 | return false; // Context parameters, e.g. authentication (since PhpRedis 5.3). 276 | } 277 | if ( count( $con ) > 7 ) { 278 | $con = array_slice( $con, 0, 7 ); // Trim excessive parameters. 279 | } 280 | 281 | return $con; 282 | } else { 283 | return false; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /inc/class-cachify.php: -------------------------------------------------------------------------------- 1 | blog_id ); 231 | 232 | /* Install */ 233 | self::_install_backend(); 234 | 235 | /* Switch back */ 236 | restore_current_blog(); 237 | } 238 | 239 | /** 240 | * Actual installation of the options 241 | * 242 | * @since 1.0 243 | */ 244 | private static function _install_backend() { 245 | add_option( 246 | 'cachify', 247 | array() 248 | ); 249 | 250 | /* Flush */ 251 | self::flush_total_cache( true ); 252 | } 253 | 254 | /** 255 | * Uninstalling of the plugin per MU blog. 256 | * 257 | * @since 1.0 258 | */ 259 | public static function on_uninstall() { 260 | /* Global */ 261 | global $wpdb; 262 | 263 | /* Multisite & Network */ 264 | if ( is_multisite() && ! empty( $_GET['networkwide'] ) ) { 265 | /* Alter Blog */ 266 | $old = $wpdb->blogid; 267 | 268 | /* Blog IDs */ 269 | $ids = self::_get_blog_ids(); 270 | 271 | /* Loop */ 272 | foreach ( $ids as $id ) { 273 | switch_to_blog( $id ); 274 | self::_uninstall_backend(); 275 | } 276 | 277 | /* Switch back */ 278 | switch_to_blog( $old ); 279 | } else { 280 | self::_uninstall_backend(); 281 | } 282 | } 283 | 284 | /** 285 | * Uninstalling of the plugin for WPMS site. 286 | * 287 | * @param int|WP_Site $old_site Old site ID or object. 288 | * 289 | * @since 1.0 290 | * @since 2.4.0 supports WP_Site argument 291 | */ 292 | public static function uninstall_later( $old_site ) { 293 | /* No network plugin */ 294 | if ( ! is_plugin_active_for_network( CACHIFY_BASE ) ) { 295 | return; 296 | } 297 | 298 | /* Switch to blog */ 299 | switch_to_blog( is_int( $old_site ) ? $old_site : $old_site->blog_id ); 300 | 301 | /* Install */ 302 | self::_uninstall_backend(); 303 | 304 | /* Switch back */ 305 | restore_current_blog(); 306 | } 307 | 308 | /** 309 | * Actual uninstalling of the plugin 310 | * 311 | * @since 1.0 312 | */ 313 | private static function _uninstall_backend() { 314 | /* Option */ 315 | delete_option( 'cachify' ); 316 | 317 | /* Flush cache */ 318 | self::flush_total_cache( true ); 319 | } 320 | 321 | /** 322 | * Get IDs of installed blogs 323 | * 324 | * @return array Blog IDs 325 | * 326 | * @since 1.0 327 | */ 328 | private static function _get_blog_ids() { 329 | /* Global */ 330 | global $wpdb; 331 | 332 | return $wpdb->get_col( "SELECT blog_id FROM `$wpdb->blogs`" ); 333 | } 334 | 335 | /** 336 | * Register the styles 337 | * 338 | * @since 2.4.0 339 | */ 340 | public static function register_styles() { 341 | /* Register dashboard CSS */ 342 | wp_register_style( 343 | 'cachify-dashboard', 344 | plugins_url( 'css/dashboard.min.css', CACHIFY_FILE ), 345 | array(), 346 | filemtime( plugin_dir_path( CACHIFY_FILE ) . 'css/dashboard.min.css' ) 347 | ); 348 | 349 | /* Register admin bar flush CSS */ 350 | wp_register_style( 351 | 'cachify-admin-bar-flush', 352 | plugins_url( 'css/admin-bar-flush.min.css', CACHIFY_FILE ), 353 | array(), 354 | filemtime( plugin_dir_path( CACHIFY_FILE ) . 'css/admin-bar-flush.min.css' ) 355 | ); 356 | } 357 | 358 | /** 359 | * Register the scripts 360 | * 361 | * @since 2.4.0 362 | */ 363 | public static function register_scripts() { 364 | /* Register admin bar flush script */ 365 | wp_register_script( 366 | 'cachify-admin-bar-flush', 367 | plugins_url( 'js/admin-bar-flush.min.js', CACHIFY_FILE ), 368 | array(), 369 | filemtime( plugin_dir_path( CACHIFY_FILE ) . 'js/admin-bar-flush.min.js' ), 370 | true 371 | ); 372 | } 373 | 374 | /** 375 | * Register the language file 376 | * 377 | * @since 2.1.3 378 | */ 379 | public static function register_textdomain() { 380 | load_plugin_textdomain( 'cachify' ); 381 | } 382 | 383 | /** 384 | * Set default options 385 | * 386 | * @since 2.0 387 | */ 388 | private static function _set_default_vars() { 389 | /* Options */ 390 | self::$options = self::_get_options(); 391 | 392 | if ( self::METHOD_APC === self::$options['use_apc'] ) { 393 | /* APC */ 394 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 395 | self::$method = new Cachify_NOOP( 'APC' ); 396 | } elseif ( self::METHOD_HDD === self::$options['use_apc'] ) { 397 | /* HDD */ 398 | if ( Cachify_HDD::is_available() ) { 399 | self::$method = new Cachify_HDD(); 400 | } else { 401 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 402 | self::$method = new Cachify_NOOP( Cachify_HDD::stringify_method() ); 403 | } 404 | } elseif ( self::METHOD_MMC === self::$options['use_apc'] ) { 405 | /* Memcached */ 406 | if ( Cachify_MEMCACHED::is_available() ) { 407 | self::$method = new Cachify_MEMCACHED(); 408 | } else { 409 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 410 | self::$method = new Cachify_NOOP( Cachify_MEMCACHED::stringify_method() ); 411 | } 412 | } elseif ( self::METHOD_REDIS === self::$options['use_apc'] ) { 413 | /* Redis */ 414 | if ( Cachify_REDIS::is_available() ) { 415 | self::$method = new Cachify_REDIS(); 416 | } else { 417 | add_action( 'admin_notices', array( __CLASS__, 'admin_notice_unavailable' ) ); 418 | self::$method = new Cachify_NOOP( Cachify_REDIS::stringify_method() ); 419 | } 420 | } else { 421 | /* Database */ 422 | self::$method = new Cachify_DB(); 423 | } 424 | } 425 | 426 | /** 427 | * Show admin notice if caching backend is unavailable. 428 | * 429 | * @since 2.4.0 430 | */ 431 | public static function admin_notice_unavailable() { 432 | if ( current_user_can( 'manage_options' ) ) { 433 | $unavailable_method = '-'; 434 | if ( self::$method instanceof Cachify_NOOP ) { 435 | $unavailable_method = self::$method->unavailable_method; 436 | } 437 | 438 | printf( 439 | '%1$s
%2$s
%3$s
%s
<IfModule mod_cache.c> 74 | CacheDisable / 75 | </IfModule>76 |
AddDefaultCharset UTF-880 |
${host}
'
69 | );
70 | ?>
71 | ${host}
'
64 | );
65 | ?>
66 | memcached_pass 127.0.0.1:11211;
'
74 | );
75 | ?>
76 | Test Content.