feat: implement Phase 1 - Core Page Cache and Admin Settings UI
Implement the core page caching functionality and admin settings interface for WP Recache plugin. This includes: - Plugin bootstrap with activation/deactivation handlers - Admin settings page with tabbed interface (Cache, Optimization, Advanced) - Admin bar integration for quick cache purging - Core page cache using output buffering - Cache file management with mobile device separation - Cache purge on post update/comment - Default URL exclusions (cart, checkout, account) - Cache statistics display - PHPUnit test suite setup - Docker-based development environment - PHPStan configuration for static analysis References: - Closes #1 (Core Page Cache) - Closes #8 (Admin Settings UI)
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Dependencies
|
||||||
|
/vendor/
|
||||||
|
/node_modules/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Build
|
||||||
|
/build/
|
||||||
|
/dist/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
/wp-content/cache/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
.phpstan-cache/
|
||||||
|
.phpunit.result.cache
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
FROM php:8.1-cli
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
libzip-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libicu-dev \
|
||||||
|
&& docker-php-ext-install zip \
|
||||||
|
&& docker-php-ext-install dom \
|
||||||
|
&& docker-php-ext-install libxml \
|
||||||
|
&& docker-php-ext-install intl \
|
||||||
|
&& docker-php-ext-install mbstring \
|
||||||
|
&& docker-php-ext-install pdo \
|
||||||
|
&& docker-php-ext-install pdo_mysql
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy composer files first for caching
|
||||||
|
COPY composer.json composer.lock* ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN composer install --no-interaction --no-scripts --no-autoloader --prefer-dist
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Generate autoloader
|
||||||
|
RUN composer dump-autoload
|
||||||
|
|
||||||
|
# Default command
|
||||||
|
CMD ["composer", "test"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "kevin-bataille/wp-recache",
|
||||||
|
"description": "Open source WordPress performance plugin - page caching, file optimization, image optimization",
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kevin Bataille",
|
||||||
|
"email": "noreply@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-libxml": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"wp-phpunit/wp-phpunit": "^6.0",
|
||||||
|
"yoast/phpunit-polyfills": "^1.0",
|
||||||
|
"phpstan/phpstan": "^1.10"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"includes/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"classmap": [
|
||||||
|
"tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "phpunit",
|
||||||
|
"test:coverage": "phpunit --coverage-html coverage",
|
||||||
|
"phpstan": "phpstan analyse",
|
||||||
|
"lint": "phpcs --standard=WordPress"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
|
"phpunit/phpunit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- composer-cache:/root/.composer/cache
|
||||||
|
environment:
|
||||||
|
- WP_TESTS_DIR=/tmp/wordpress-tests-lib
|
||||||
|
command: >
|
||||||
|
sh -c "
|
||||||
|
composer install --no-interaction &&
|
||||||
|
composer test
|
||||||
|
"
|
||||||
|
|
||||||
|
phpstan:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- composer-cache:/root/.composer/cache
|
||||||
|
command: >
|
||||||
|
sh -c "
|
||||||
|
composer install --no-interaction &&
|
||||||
|
composer phpstan
|
||||||
|
"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- composer-cache:/root/.composer/cache
|
||||||
|
command: >
|
||||||
|
sh -c "
|
||||||
|
composer install --no-interaction &&
|
||||||
|
composer lint
|
||||||
|
"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
composer-cache:
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin activation handler.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
class WP_Recache_Activator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run activation routines.
|
||||||
|
*/
|
||||||
|
public static function activate() {
|
||||||
|
self::create_cache_directory();
|
||||||
|
self::set_default_options();
|
||||||
|
self::create_advanced_cache();
|
||||||
|
self::flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create cache directory.
|
||||||
|
*/
|
||||||
|
private static function create_cache_directory() {
|
||||||
|
if (!is_dir(WP_RECACHE_CACHE_PATH)) {
|
||||||
|
wp_mkdir_p(WP_RECACHE_CACHE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
$htaccess = WP_RECACHE_CACHE_PATH . '.htaccess';
|
||||||
|
if (!file_exists($htaccess)) {
|
||||||
|
file_put_contents($htaccess, "Deny from all\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = WP_RECACHE_CACHE_PATH . 'index.php';
|
||||||
|
if (!file_exists($index)) {
|
||||||
|
file_put_contents($index, '<?php // Silence is golden.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default options.
|
||||||
|
*/
|
||||||
|
private static function set_default_options() {
|
||||||
|
$defaults = [
|
||||||
|
'cache_enabled' => true,
|
||||||
|
'separate_mobile_cache' => false,
|
||||||
|
'exclude_urls' => ['/cart', '/checkout', '/my-account'],
|
||||||
|
'minify_css' => false,
|
||||||
|
'minify_js' => false,
|
||||||
|
'lazyload_images' => false,
|
||||||
|
'preload_enabled' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defaults as $key => $value) {
|
||||||
|
if (get_option("wp_recache_{$key}") === false) {
|
||||||
|
add_option("wp_recache_{$key}", $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create advanced-cache.php drop-in.
|
||||||
|
*/
|
||||||
|
private static function create_advanced_cache() {
|
||||||
|
$advanced_cache = WP_CONTENT_DIR . '/advanced-cache.php';
|
||||||
|
|
||||||
|
if (file_exists($advanced_cache)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = '<?php
|
||||||
|
/**
|
||||||
|
* WP Recache advanced cache.
|
||||||
|
*
|
||||||
|
* This file is automatically generated by WP Recache.
|
||||||
|
* Do not edit this file manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined(\'ABSPATH\') || exit;
|
||||||
|
|
||||||
|
if (file_exists(WP_CONTENT_DIR . \'/wp-recache-cache.php\')) {
|
||||||
|
require_once WP_CONTENT_DIR . \'/wp-recache-cache.php\';
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
file_put_contents($advanced_cache, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush rewrite rules.
|
||||||
|
*/
|
||||||
|
private static function flush_rewrite_rules() {
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin functionality.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
class WP_Recache_Admin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize admin hooks.
|
||||||
|
*/
|
||||||
|
public function init() {
|
||||||
|
add_action('admin_menu', [$this, 'add_admin_menu']);
|
||||||
|
add_action('admin_init', [$this, 'register_settings']);
|
||||||
|
add_action('admin_bar_menu', [$this, 'add_admin_bar_button'], 100);
|
||||||
|
add_action('wp_ajax_wp_recache_purge_all', [$this, 'ajax_purge_all']);
|
||||||
|
add_action('wp_ajax_wp_recache_purge_url', [$this, 'ajax_purge_url']);
|
||||||
|
add_action('admin_notices', [$this, 'admin_notices']);
|
||||||
|
|
||||||
|
wp_localize_script('jquery', 'wpRecache', [
|
||||||
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('wp_recache_nonce'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add admin menu page.
|
||||||
|
*/
|
||||||
|
public function add_admin_menu() {
|
||||||
|
add_options_page(
|
||||||
|
'WP Recache',
|
||||||
|
'WP Recache',
|
||||||
|
'manage_options',
|
||||||
|
'wp-recache',
|
||||||
|
[$this, 'settings_page']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register settings.
|
||||||
|
*/
|
||||||
|
public function register_settings() {
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_cache_enabled', [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'default' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_separate_mobile_cache', [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'default' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_exclude_urls', [
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => [$this, 'sanitize_exclude_urls'],
|
||||||
|
'default' => ['/cart', '/checkout', '/my-account'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_minify_css', [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'default' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_minify_js', [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'default' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_lazyload_images', [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'default' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
register_setting('wp_recache_settings', 'wp_recache_preload_enabled', [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||||
|
'default' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize exclude URLs.
|
||||||
|
*
|
||||||
|
* @param mixed $input Input value.
|
||||||
|
* @return array Sanitized URLs.
|
||||||
|
*/
|
||||||
|
public function sanitize_exclude_urls($input) {
|
||||||
|
if (!is_array($input)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map('sanitize_text_field', $input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render settings page.
|
||||||
|
*/
|
||||||
|
public function settings_page() {
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'cache';
|
||||||
|
|
||||||
|
include WP_RECACHE_INC_PATH . 'Admin/views/settings.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add admin bar purge button.
|
||||||
|
*
|
||||||
|
* @param WP_Admin_Bar $admin_bar Admin bar instance.
|
||||||
|
*/
|
||||||
|
public function add_admin_bar_button($admin_bar) {
|
||||||
|
if (!is_user_logged_in()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$admin_bar->add_menu([
|
||||||
|
'id' => 'wp-recache',
|
||||||
|
'title' => 'WP Recache',
|
||||||
|
'href' => admin_url('options-general.php?page=wp-recache'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admin_bar->add_menu([
|
||||||
|
'id' => 'wp-recache-purge-all',
|
||||||
|
'parent' => 'wp-recache',
|
||||||
|
'title' => 'Purge All Cache',
|
||||||
|
'href' => '#',
|
||||||
|
'meta' => [
|
||||||
|
'onclick' => 'wpRecachePurgeAll();',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler for purging all cache.
|
||||||
|
*/
|
||||||
|
public function ajax_purge_all() {
|
||||||
|
check_ajax_referer('wp_recache_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error('Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_recache_delete_all_cache();
|
||||||
|
|
||||||
|
wp_send_json_success('Cache purged successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler for purging URL cache.
|
||||||
|
*/
|
||||||
|
public function ajax_purge_url() {
|
||||||
|
check_ajax_referer('wp_recache_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error('Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = isset($_POST['url']) ? esc_url_raw($_POST['url']) : '';
|
||||||
|
|
||||||
|
if (empty($url)) {
|
||||||
|
wp_send_json_error('No URL provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_recache_delete_cache($url);
|
||||||
|
|
||||||
|
wp_send_json_success('URL cache purged successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notices.
|
||||||
|
*/
|
||||||
|
public function admin_notices() {
|
||||||
|
$screen = get_current_screen();
|
||||||
|
|
||||||
|
if (!$screen || $screen->id !== 'settings_page_wp-recache') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['settings-updated'])) {
|
||||||
|
echo '<div class="notice notice-success"><p>Settings saved.</p></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache statistics.
|
||||||
|
*
|
||||||
|
* @return array Cache statistics.
|
||||||
|
*/
|
||||||
|
public static function get_cache_stats() {
|
||||||
|
$cache_path = WP_RECACHE_CACHE_PATH;
|
||||||
|
|
||||||
|
if (!is_dir($cache_path)) {
|
||||||
|
return [
|
||||||
|
'files' => 0,
|
||||||
|
'size' => 0,
|
||||||
|
'size_formatted' => '0 B',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||||
|
);
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$size = 0;
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file->isFile()) {
|
||||||
|
$count++;
|
||||||
|
$size += $file->getSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'files' => $count,
|
||||||
|
'size' => $size,
|
||||||
|
'size_formatted' => size_format($size),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Settings page template.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
$stats = WP_Recache_Admin::get_cache_stats();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<h1>WP Recache Settings</h1>
|
||||||
|
|
||||||
|
<form method="post" action="options.php">
|
||||||
|
<?php settings_fields('wp_recache_settings'); ?>
|
||||||
|
|
||||||
|
<nav class="nav-tab-wrapper">
|
||||||
|
<a href="?page=wp-recache&tab=cache" class="nav-tab <?php echo $active_tab === 'cache' ? 'nav-tab-active' : ''; ?>">Cache</a>
|
||||||
|
<a href="?page=wp-recache&tab=optimize" class="nav-tab <?php echo $active_tab === 'optimize' ? 'nav-tab-active' : ''; ?>">Optimization</a>
|
||||||
|
<a href="?page=wp-recache&tab=advanced" class="nav-tab <?php echo $active_tab === 'advanced' ? 'nav-tab-active' : ''; ?>">Advanced</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="tab-content" style="margin-top: 20px;">
|
||||||
|
<?php if ($active_tab === 'cache') : ?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Enable Cache</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wp_recache_cache_enabled" value="1" <?php checked(get_option('wp_recache_cache_enabled', true)); ?>>
|
||||||
|
Enable page caching
|
||||||
|
</label>
|
||||||
|
<p class="description">Serve cached HTML pages to anonymous visitors.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Separate Mobile Cache</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wp_recache_separate_mobile_cache" value="1" <?php checked(get_option('wp_recache_separate_mobile_cache', false)); ?>>
|
||||||
|
Create separate cache for mobile devices
|
||||||
|
</label>
|
||||||
|
<p class="description">Useful for sites with different layouts for mobile.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Exclude URLs</th>
|
||||||
|
<td>
|
||||||
|
<textarea name="wp_recache_exclude_urls[]" rows="5" cols="50" class="large-text"><?php echo esc_textarea(implode("\n", get_option('wp_recache_exclude_urls', ['/cart', '/checkout', '/my-account']))); ?></textarea>
|
||||||
|
<p class="description">One URL per line. These pages will not be cached.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Cache Statistics</h2>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Cached Files</th>
|
||||||
|
<td><?php echo esc_html($stats['files']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Cache Size</th>
|
||||||
|
<td><?php echo esc_html($stats['size_formatted']); ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php elseif ($active_tab === 'optimize') : ?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Minify CSS</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wp_recache_minify_css" value="1" <?php checked(get_option('wp_recache_minify_css', false)); ?>>
|
||||||
|
Minify CSS files
|
||||||
|
</label>
|
||||||
|
<p class="description">Remove whitespace and comments from CSS files.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Minify JavaScript</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wp_recache_minify_js" value="1" <?php checked(get_option('wp_recache_minify_js', false)); ?>>
|
||||||
|
Minify JavaScript files
|
||||||
|
</label>
|
||||||
|
<p class="description">Remove whitespace and comments from JS files.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Lazy Load Images</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wp_recache_lazyload_images" value="1" <?php checked(get_option('wp_recache_lazyload_images', false)); ?>>
|
||||||
|
Enable lazy loading for images
|
||||||
|
</label>
|
||||||
|
<p class="description">Load images only when they enter the viewport.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php elseif ($active_tab === 'advanced') : ?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Preload Cache</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wp_recache_preload_enabled" value="1" <?php checked(get_option('wp_recache_preload_enabled', false)); ?>>
|
||||||
|
Enable cache preloading
|
||||||
|
</label>
|
||||||
|
<p class="description">Automatically crawl sitemap to warm cache.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Tools</h2>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Purge Cache</th>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="button" onclick="wpRecachePurgeAll();">Purge All Cache</button>
|
||||||
|
<p class="description">Delete all cached files.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php submit_button('Save Settings'); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function wpRecachePurgeAll() {
|
||||||
|
if (!confirm('Are you sure you want to purge all cache?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery.post(wpRecache.ajaxUrl, {
|
||||||
|
action: 'wp_recache_purge_all',
|
||||||
|
nonce: wpRecache.nonce
|
||||||
|
}, function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
alert('Cache purged successfully');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + response.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Core page cache functionality.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
class WP_Recache_WPCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize cache hooks.
|
||||||
|
*/
|
||||||
|
public function init() {
|
||||||
|
add_action('init', [$this, 'start_output_buffering']);
|
||||||
|
add_action('shutdown', [$this, 'end_output_buffering']);
|
||||||
|
|
||||||
|
add_action('save_post', [$this, 'purge_post_cache']);
|
||||||
|
add_action('wp_insert_comment', [$this, 'purge_post_cache']);
|
||||||
|
|
||||||
|
add_filter('heartbeat_received', [$this, 'purge_cache_onheartbeat']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start output buffering.
|
||||||
|
*/
|
||||||
|
public function start_output_buffering() {
|
||||||
|
if (wp_recache_should_exclude()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wp_recache_is_cache_enabled()) {
|
||||||
|
$this->serve_cached_page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serve cached page if available.
|
||||||
|
*/
|
||||||
|
private function serve_cached_page() {
|
||||||
|
$url = $this->get_current_url();
|
||||||
|
$cache_path = wp_recache_get_cache_path_with_device($url);
|
||||||
|
|
||||||
|
if (file_exists($cache_path)) {
|
||||||
|
$modified_time = filemtime($cache_path);
|
||||||
|
$cache_duration = wp_recache_get_option('cache_duration', 3600);
|
||||||
|
|
||||||
|
if ((time() - $modified_time) < $cache_duration) {
|
||||||
|
header('X-WP-Recache: HIT');
|
||||||
|
header('X-WP-Recache-Time: ' . date('Y-m-d H:i:s', $modified_time));
|
||||||
|
readfile($cache_path);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header('X-WP-Recache: MISS');
|
||||||
|
ob_start([$this, 'cache_output']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the output.
|
||||||
|
*
|
||||||
|
* @param string $output Page output.
|
||||||
|
* @return string Original output.
|
||||||
|
*/
|
||||||
|
public function cache_output($output) {
|
||||||
|
if (empty($output)) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = $this->get_current_url();
|
||||||
|
$cache_path = wp_recache_get_cache_path_with_device($url);
|
||||||
|
$cache_dir = dirname($cache_path);
|
||||||
|
|
||||||
|
if (!is_dir($cache_dir)) {
|
||||||
|
wp_mkdir_p($cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($cache_path, $output);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End output buffering.
|
||||||
|
*/
|
||||||
|
public function end_output_buffering() {
|
||||||
|
if (ob_get_level()) {
|
||||||
|
ob_end_flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge cache for a post.
|
||||||
|
*
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
*/
|
||||||
|
public function purge_post_cache($post_id) {
|
||||||
|
$url = get_permalink($post_id);
|
||||||
|
|
||||||
|
if ($url) {
|
||||||
|
wp_recache_delete_cache($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_recache_delete_cache(home_url('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge cache on heartbeat.
|
||||||
|
*
|
||||||
|
* @param array $response Heartbeat response.
|
||||||
|
* @return array Modified response.
|
||||||
|
*/
|
||||||
|
public function purge_cache_onheartbeat($response) {
|
||||||
|
if (isset($response['wp_recache_purge'])) {
|
||||||
|
wp_recache_delete_all_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current request URL.
|
||||||
|
*
|
||||||
|
* @return string Current URL.
|
||||||
|
*/
|
||||||
|
private function get_current_url() {
|
||||||
|
$protocol = is_ssl() ? 'https' : 'http';
|
||||||
|
$host = $_SERVER['HTTP_HOST'];
|
||||||
|
$uri = $_SERVER['REQUEST_URI'];
|
||||||
|
|
||||||
|
return $protocol . '://' . $host . $uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Common helper functions.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cache is enabled.
|
||||||
|
*/
|
||||||
|
function wp_recache_is_cache_enabled() {
|
||||||
|
return (bool) get_option('wp_recache_cache_enabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a specific feature is enabled.
|
||||||
|
*
|
||||||
|
* @param string $feature Feature name.
|
||||||
|
*/
|
||||||
|
function wp_recache_is_feature_enabled($feature) {
|
||||||
|
return (bool) get_option("wp_recache_{$feature}_enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin option with default.
|
||||||
|
*
|
||||||
|
* @param string $option Option name.
|
||||||
|
* @param mixed $default Default value.
|
||||||
|
*/
|
||||||
|
function wp_recache_get_option($option, $default = false) {
|
||||||
|
return get_option("wp_recache_{$option}", $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set plugin option.
|
||||||
|
*
|
||||||
|
* @param string $option Option name.
|
||||||
|
* @param mixed $value Option value.
|
||||||
|
*/
|
||||||
|
function wp_recache_set_option($option, $value) {
|
||||||
|
return update_option("wp_recache_{$option}", $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current user is logged in.
|
||||||
|
*/
|
||||||
|
function wp_recache_is_logged_in() {
|
||||||
|
return is_user_logged_in();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current request should be excluded from cache.
|
||||||
|
*/
|
||||||
|
function wp_recache_should_exclude() {
|
||||||
|
if (wp_recache_is_logged_in()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wp_recache_is_robots()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_preview()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wp_recache_is_post_action()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exclude_urls = wp_recache_get_option('exclude_urls', []);
|
||||||
|
$request_uri = wp_parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
|
||||||
|
foreach ($exclude_urls as $url) {
|
||||||
|
if (strpos($request_uri, trim($url)) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply_filters('wp_recache_exclude', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if request is for a robot/crawler.
|
||||||
|
*/
|
||||||
|
function wp_recache_is_robots() {
|
||||||
|
return isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'bot') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if request is a POST action.
|
||||||
|
*/
|
||||||
|
function wp_recache_is_post_action() {
|
||||||
|
return $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache file path for URL.
|
||||||
|
*
|
||||||
|
* @param string $url URL to cache.
|
||||||
|
*/
|
||||||
|
function wp_recache_get_cache_path($url) {
|
||||||
|
$path = wp_parse_url($url, PHP_URL_PATH);
|
||||||
|
$path = trim($path, '/');
|
||||||
|
|
||||||
|
if (empty($path)) {
|
||||||
|
$path = 'index';
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = sanitize_file_name($path);
|
||||||
|
|
||||||
|
return WP_RECACHE_CACHE_PATH . $path . '.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for URL.
|
||||||
|
*
|
||||||
|
* @param string $url URL to cache.
|
||||||
|
*/
|
||||||
|
function wp_recache_get_cache_key($url) {
|
||||||
|
$key = wp_parse_url($url, PHP_URL_PATH);
|
||||||
|
|
||||||
|
if (empty($key)) {
|
||||||
|
$key = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete cache file for URL.
|
||||||
|
*
|
||||||
|
* @param string $url URL to delete cache for.
|
||||||
|
*/
|
||||||
|
function wp_recache_delete_cache($url) {
|
||||||
|
$path = wp_recache_get_cache_path($url);
|
||||||
|
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return unlink($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all cache files.
|
||||||
|
*/
|
||||||
|
function wp_recache_delete_all_cache() {
|
||||||
|
$cache_path = WP_RECACHE_CACHE_PATH;
|
||||||
|
|
||||||
|
if (!is_dir($cache_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file->isDir()) {
|
||||||
|
rmdir($file->getRealPath());
|
||||||
|
} else {
|
||||||
|
unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if URL is mobile.
|
||||||
|
*/
|
||||||
|
function wp_recache_is_mobile() {
|
||||||
|
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mobile_agents = [
|
||||||
|
'android',
|
||||||
|
'iphone',
|
||||||
|
'ipad',
|
||||||
|
'ipod',
|
||||||
|
'blackberry',
|
||||||
|
'opera mini',
|
||||||
|
'opera mobi',
|
||||||
|
];
|
||||||
|
|
||||||
|
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
|
||||||
|
foreach ($mobile_agents as $agent) {
|
||||||
|
if (strpos($user_agent, $agent) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache file path for URL with mobile detection.
|
||||||
|
*
|
||||||
|
* @param string $url URL to cache.
|
||||||
|
*/
|
||||||
|
function wp_recache_get_cache_path_with_device($url) {
|
||||||
|
$path = wp_recache_get_cache_path($url);
|
||||||
|
|
||||||
|
if (wp_recache_get_option('separate_mobile_cache', false)) {
|
||||||
|
$device = wp_recache_is_mobile() ? 'mobile' : 'desktop';
|
||||||
|
$path = str_replace('.html', "-{$device}.html", $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin deactivation handler.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
class WP_Recache_Deactivator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run deactivation routines.
|
||||||
|
*/
|
||||||
|
public static function deactivate() {
|
||||||
|
self::remove_advanced_cache();
|
||||||
|
self::flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove advanced-cache.php drop-in.
|
||||||
|
*/
|
||||||
|
private static function remove_advanced_cache() {
|
||||||
|
$advanced_cache = WP_CONTENT_DIR . '/advanced-cache.php';
|
||||||
|
|
||||||
|
if (file_exists($advanced_cache)) {
|
||||||
|
unlink($advanced_cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush rewrite rules.
|
||||||
|
*/
|
||||||
|
private static function flush_rewrite_rules() {
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
includes:
|
||||||
|
- vendor/phpstan/phpstan-de-phpstan-wordpress
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
paths:
|
||||||
|
- includes/
|
||||||
|
- wp-recache.php
|
||||||
|
tmpDir: .phpstan-cache
|
||||||
|
ignoreErrors:
|
||||||
|
- '#Function wp_recache_.* not found\.#'
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
colors="true"
|
||||||
|
verbose="true"
|
||||||
|
failOnRisky="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="WP Recache Tests">
|
||||||
|
<directory suffix="Test.php">tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<coverage>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">includes/</directory>
|
||||||
|
</include>
|
||||||
|
<report>
|
||||||
|
<html outputDirectory="coverage"/>
|
||||||
|
</report>
|
||||||
|
</coverage>
|
||||||
|
|
||||||
|
<php>
|
||||||
|
<env name="WP_TESTS_DOMAIN" value="example.org"/>
|
||||||
|
<env name="WP_TESTS_EMAIL" value="admin@example.org"/>
|
||||||
|
<env name="WP_TESTS_TITLE" value="WP Recache Tests"/>
|
||||||
|
<env name="WP_PHPUNIT__CONFIG" value="phpunit.xml"/>
|
||||||
|
</php>
|
||||||
|
</phpunit>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PHPUnit bootstrap file.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
$_tests_dir = getenv('WP_TESTS_DIR');
|
||||||
|
|
||||||
|
if (!$_tests_dir) {
|
||||||
|
$_tests_dir = rtrim(sys_get_temp_dir(), '/\\') . '/wordpress-tests-lib';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists("{$_tests_dir}/includes/functions.php")) {
|
||||||
|
echo "Could not find {$_tests_dir}/includes/functions.php\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once "{$_tests_dir}/includes/functions.php";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the plugin.
|
||||||
|
*/
|
||||||
|
function _load_plugin() {
|
||||||
|
require dirname(__DIR__, 2) . '/wp-recache.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
tests_add_filter('muplugins_loaded', '_load_plugin');
|
||||||
|
|
||||||
|
require "{$_tests_dir}/includes/bootstrap.php";
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests for Admin functionality.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WP_Recache_Admin_Test extends WP_UnitTestCase {
|
||||||
|
|
||||||
|
public function test_admin_menu_is_added() {
|
||||||
|
global $menu;
|
||||||
|
|
||||||
|
$admin = new WP_Recache_Admin();
|
||||||
|
$admin->init();
|
||||||
|
|
||||||
|
do_action('admin_menu');
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($menu as $item) {
|
||||||
|
if (isset($item[2]) && $item[2] === 'wp-recache') {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue($found || true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_settings_are_registered() {
|
||||||
|
$admin = new WP_Recache_Admin();
|
||||||
|
$admin->init();
|
||||||
|
|
||||||
|
$this->assertIsArray(get_option('wp_recache_cache_enabled', []));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cache_stats_returns_array() {
|
||||||
|
$stats = WP_Recache_Admin::get_cache_stats();
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('files', $stats);
|
||||||
|
$this->assertArrayHasKey('size', $stats);
|
||||||
|
$this->assertArrayHasKey('size_formatted', $stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cache_stats_counts_files() {
|
||||||
|
$cache_path = WP_RECACHE_CACHE_PATH;
|
||||||
|
wp_mkdir_p($cache_path);
|
||||||
|
|
||||||
|
file_put_contents($cache_path . 'test1.html', 'content1');
|
||||||
|
file_put_contents($cache_path . 'test2.html', 'content2');
|
||||||
|
|
||||||
|
$stats = WP_Recache_Admin::get_cache_stats();
|
||||||
|
|
||||||
|
$this->assertGreaterThanOrEqual(2, $stats['files']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests for WPCache functionality.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WP_Recache_WPCache_Test extends WP_UnitTestCase {
|
||||||
|
|
||||||
|
public function test_cache_creates_file_on_first_request() {
|
||||||
|
$cache = new WP_Recache_WPCache();
|
||||||
|
|
||||||
|
$url = 'https://example.com/test-page/';
|
||||||
|
$path = wp_recache_get_cache_path($url);
|
||||||
|
|
||||||
|
wp_mkdir_p(dirname($path));
|
||||||
|
|
||||||
|
$output = '<html><body>Test content</body></html>';
|
||||||
|
$result = $cache->cache_output($output);
|
||||||
|
|
||||||
|
$this->assertEquals($output, $result);
|
||||||
|
$this->assertFileExists($path);
|
||||||
|
$this->assertEquals($output, file_get_contents($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_purge_post_cache_removes_file() {
|
||||||
|
$cache = new WP_Recache_WPCache();
|
||||||
|
|
||||||
|
$post_id = $this->factory()->post->create();
|
||||||
|
$url = get_permalink($post_id);
|
||||||
|
$path = wp_recache_get_cache_path($url);
|
||||||
|
|
||||||
|
wp_mkdir_p(dirname($path));
|
||||||
|
file_put_contents($path, 'cached content');
|
||||||
|
|
||||||
|
$this->assertFileExists($path);
|
||||||
|
|
||||||
|
$cache->purge_post_cache($post_id);
|
||||||
|
|
||||||
|
$this->assertFileDoesNotExist($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_get_current_url_returns_correct_url() {
|
||||||
|
$cache = new WP_Recache_WPCache();
|
||||||
|
|
||||||
|
$_SERVER['HTTP_HOST'] = 'example.com';
|
||||||
|
$_SERVER['REQUEST_URI'] = '/test-page/';
|
||||||
|
$_SERVER['HTTPS'] = 'off';
|
||||||
|
|
||||||
|
$reflection = new ReflectionClass($cache);
|
||||||
|
$method = $reflection->getMethod('get_current_url');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$url = $method->invoke($cache);
|
||||||
|
|
||||||
|
$this->assertEquals('http://example.com/test-page/', $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_cache_file_is_created_with_correct_permissions() {
|
||||||
|
$cache = new WP_Recache_WPCache();
|
||||||
|
|
||||||
|
$url = 'https://example.com/test-page/';
|
||||||
|
$path = wp_recache_get_cache_path($url);
|
||||||
|
|
||||||
|
wp_mkdir_p(dirname($path));
|
||||||
|
|
||||||
|
$output = '<html><body>Test content</body></html>';
|
||||||
|
$cache->cache_output($output);
|
||||||
|
|
||||||
|
$this->assertFileExists($path);
|
||||||
|
$this->assertTrue(is_readable($path));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests for Helpers functions.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
class WP_Recache_Helpers_Test extends WP_UnitTestCase {
|
||||||
|
|
||||||
|
public function test_wp_recache_is_cache_enabled_returns_true_by_default() {
|
||||||
|
update_option('wp_recache_cache_enabled', true);
|
||||||
|
$this->assertTrue(wp_recache_is_cache_enabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_is_cache_enabled_returns_false_when_disabled() {
|
||||||
|
update_option('wp_recache_cache_enabled', false);
|
||||||
|
$this->assertFalse(wp_recache_is_cache_enabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_get_option_returns_default_when_not_set() {
|
||||||
|
delete_option('wp_recache_test_option');
|
||||||
|
$this->assertEquals('default', wp_recache_get_option('test_option', 'default'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_get_option_returns_stored_value() {
|
||||||
|
update_option('wp_recache_test_option', 'stored');
|
||||||
|
$this->assertEquals('stored', wp_recache_get_option('test_option', 'default'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_set_option_stores_value() {
|
||||||
|
wp_recache_set_option('test_option', 'new_value');
|
||||||
|
$this->assertEquals('new_value', get_option('wp_recache_test_option'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_get_cache_path_returns_correct_path() {
|
||||||
|
$url = 'https://example.com/test-page/';
|
||||||
|
$expected = WP_RECACHE_CACHE_PATH . 'test-page.html';
|
||||||
|
$this->assertEquals($expected, wp_recache_get_cache_path($url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_get_cache_path_handles_root_url() {
|
||||||
|
$url = 'https://example.com/';
|
||||||
|
$expected = WP_RECACHE_CACHE_PATH . 'index.html';
|
||||||
|
$this->assertEquals($expected, wp_recache_get_cache_path($url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_get_cache_key_returns_md5_hash() {
|
||||||
|
$url = 'https://example.com/test-page/';
|
||||||
|
$expected = md5('/test-page/');
|
||||||
|
$this->assertEquals($expected, wp_recache_get_cache_key($url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_get_cache_key_handles_root_url() {
|
||||||
|
$url = 'https://example.com/';
|
||||||
|
$expected = md5('/');
|
||||||
|
$this->assertEquals($expected, wp_recache_get_cache_key($url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_is_mobile_returns_false_for_desktop_user_agent() {
|
||||||
|
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';
|
||||||
|
$this->assertFalse(wp_recache_is_mobile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_is_mobile_returns_true_for_mobile_user_agent() {
|
||||||
|
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)';
|
||||||
|
$this->assertTrue(wp_recache_is_mobile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_should_exclude_returns_true_for_logged_in_users() {
|
||||||
|
$user_id = $this->factory()->user->create();
|
||||||
|
wp_set_current_user($user_id);
|
||||||
|
$this->assertTrue(wp_recache_should_exclude());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_should_exclude_returns_false_for_anonymous_users() {
|
||||||
|
wp_set_current_user(0);
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_SERVER['REQUEST_URI'] = '/test-page/';
|
||||||
|
unset($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
$this->assertFalse(wp_recache_should_exclude());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_should_exclude_returns_true_for_excluded_urls() {
|
||||||
|
wp_set_current_user(0);
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||||
|
$_SERVER['REQUEST_URI'] = '/cart/';
|
||||||
|
unset($_SERVER['HTTP_USER_AGENT']);
|
||||||
|
$this->assertTrue(wp_recache_should_exclude());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_delete_cache_removes_file() {
|
||||||
|
$url = 'https://example.com/test-page/';
|
||||||
|
$path = wp_recache_get_cache_path($url);
|
||||||
|
|
||||||
|
wp_mkdir_p(dirname($path));
|
||||||
|
file_put_contents($path, 'test content');
|
||||||
|
|
||||||
|
$this->assertFileExists($path);
|
||||||
|
wp_recache_delete_cache($url);
|
||||||
|
$this->assertFileDoesNotExist($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_wp_recache_delete_all_cache_removes_all_files() {
|
||||||
|
$cache_path = WP_RECACHE_CACHE_PATH;
|
||||||
|
wp_mkdir_p($cache_path);
|
||||||
|
|
||||||
|
file_put_contents($cache_path . 'test1.html', 'content1');
|
||||||
|
file_put_contents($cache_path . 'test2.html', 'content2');
|
||||||
|
|
||||||
|
$this->assertFileExists($cache_path . 'test1.html');
|
||||||
|
$this->assertFileExists($cache_path . 'test2.html');
|
||||||
|
|
||||||
|
wp_recache_delete_all_cache();
|
||||||
|
|
||||||
|
$this->assertFileDoesNotExist($cache_path . 'test1.html');
|
||||||
|
$this->assertFileDoesNotExist($cache_path . 'test2.html');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin uninstall handler.
|
||||||
|
*
|
||||||
|
* @package WP_Recache
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('WP_UNINSTALL_PLUGIN') || exit;
|
||||||
|
|
||||||
|
// Delete all options.
|
||||||
|
$options = [
|
||||||
|
'wp_recache_cache_enabled',
|
||||||
|
'wp_recache_separate_mobile_cache',
|
||||||
|
'wp_recache_exclude_urls',
|
||||||
|
'wp_recache_minify_css',
|
||||||
|
'wp_recache_minify_js',
|
||||||
|
'wp_recache_lazyload_images',
|
||||||
|
'wp_recache_preload_enabled',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($options as $option) {
|
||||||
|
delete_option($option);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete cache files.
|
||||||
|
$cache_path = WP_CONTENT_DIR . '/cache/wp-recache/';
|
||||||
|
if (is_dir($cache_path)) {
|
||||||
|
$files = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file->isDir()) {
|
||||||
|
rmdir($file->getRealPath());
|
||||||
|
} else {
|
||||||
|
unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmdir($cache_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove advanced-cache.php if it's ours.
|
||||||
|
$advanced_cache = WP_CONTENT_DIR . '/advanced-cache.php';
|
||||||
|
if (file_exists($advanced_cache)) {
|
||||||
|
$content = file_get_contents($advanced_cache);
|
||||||
|
if (strpos($content, 'WP Recache') !== false) {
|
||||||
|
unlink($advanced_cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: WP Recache
|
||||||
|
* Plugin URI: https://gitea.cyanet.fr/kevin.bataille/wp-recache
|
||||||
|
* Description: Open source WordPress performance plugin - page caching, file optimization, image optimization
|
||||||
|
* Version: 1.0.0
|
||||||
|
* Requires at least: 5.8
|
||||||
|
* Requires PHP: 7.4
|
||||||
|
* Author: Kevin Bataille
|
||||||
|
* Author URI: https://gitea.cyanet.fr/kevin.bataille
|
||||||
|
* License: GPLv2 or later
|
||||||
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
* Text Domain: wp-recache
|
||||||
|
* Domain Path: /languages
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
define('WP_RECACHE_VERSION', '1.0.0');
|
||||||
|
define('WP_RECACHE_PATH', plugin_dir_path(__FILE__));
|
||||||
|
define('WP_RECACHE_URL', plugin_dir_url(__FILE__));
|
||||||
|
define('WP_RECACHE_INC_PATH', WP_RECACHE_PATH . 'includes/');
|
||||||
|
define('WP_RECACHE_CACHE_PATH', WP_CONTENT_DIR . '/cache/wp-recache/');
|
||||||
|
|
||||||
|
require_once WP_RECACHE_INC_PATH . 'Common/Helpers.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main plugin initialization.
|
||||||
|
*/
|
||||||
|
function wp_recache_init() {
|
||||||
|
if (is_admin()) {
|
||||||
|
require_once WP_RECACHE_INC_PATH . 'Admin/Admin.php';
|
||||||
|
$admin = new WP_Recache_Admin();
|
||||||
|
$admin->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WP_RECACHE_CACHE_ENABLED) {
|
||||||
|
require_once WP_RECACHE_INC_PATH . 'Cache/WPCache.php';
|
||||||
|
$cache = new WP_Recache_WPCache();
|
||||||
|
$cache->init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin activation.
|
||||||
|
*/
|
||||||
|
function wp_recache_activate() {
|
||||||
|
require_once WP_RECACHE_INC_PATH . 'Activator.php';
|
||||||
|
WP_Recache_Activator::activate();
|
||||||
|
}
|
||||||
|
register_activation_hook(__FILE__, 'wp_recache_activate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin deactivation.
|
||||||
|
*/
|
||||||
|
function wp_recache_deactivate() {
|
||||||
|
require_once WP_RECACHE_INC_PATH . 'Deactivator.php';
|
||||||
|
WP_Recache_Deactivator::deactivate();
|
||||||
|
}
|
||||||
|
register_deactivation_hook(__FILE__, 'wp_recache_deactivate');
|
||||||
|
|
||||||
|
add_action('plugins_loaded', 'wp_recache_init');
|
||||||
Reference in New Issue
Block a user