From bee9da3a8676cc910f1ead9aa4f13a1b5429a8e7 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Fri, 6 Nov 2026 19:57:55 +0100 Subject: [PATCH] feat(cache): WP Fastest Cache statt WP Super Cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Cache-Integration war für WP Super Cache gebaut, eingesetzt wird aber WP Fastest Cache. Profil (wpfc), Listener, Drosselung, lit/cache-clear und Leeren-Probe laufen jetzt über die öffentlichen WPFC-Funktionen wpfc_clear_post_cache_by_id/wpfc_clear_all_cache; die Probe leert nur die Wegwerf-Seite (singleDeleteCache ohne Elternseiten) und prüft, dass WPFC den Hook noch verarbeitet. Smoke fastest-cache-smoke.sh ersetzt den WPSC-Smoke. Co-Authored-By: Claude Opus 5.5 --- docs/guides/overview.md | 9 +- src/Compatibility/ProbeStep.php | 2 +- src/Compatibility/ThrowawayPost.php | 2 +- .../CacheClearAbility.php | 51 +- .../FastestCacheCheckSteps.php} | 12 +- .../FastestCacheCompatibility.php | 72 +++ .../FastestCacheFeature.php} | 10 +- .../FastestCacheIntegration.php} | 31 +- .../FastestCacheListener.php} | 49 +- .../FastestCache/FastestCacheProbe.php | 194 +++++++ .../FastestCachePurger.php} | 44 +- .../SuperCache/SuperCacheCompatibility.php | 63 --- .../SuperCache/SuperCacheProbe.php | 230 -------- src/Plugin.php | 18 +- src/Uninstaller.php | 12 +- ...-cache-smoke.sh => fastest-cache-smoke.sh} | 217 +++----- tests/Integration/lib.sh | 4 +- tests/Unit/Admin/CompatibilityTabTest.php | 4 +- .../Unit/Compatibility/ThrowawayPostTest.php | 2 +- .../CacheClearAbilityTest.php | 47 +- .../FastestCacheCheckStepsTest.php} | 24 +- .../FastestCacheCompatibilityTest.php | 136 +++++ .../FastestCacheFeatureTest.php} | 12 +- .../FastestCacheIntegrationTest.php | 86 +++ .../FastestCacheListenerTest.php} | 92 ++- .../FastestCache/FastestCacheProbeTest.php | 421 ++++++++++++++ .../FastestCachePurgerTest.php} | 22 +- .../SuperCacheCompatibilityTest.php | 121 ---- .../SuperCache/SuperCacheIntegrationTest.php | 75 --- .../SuperCache/SuperCacheProbeTest.php | 525 ------------------ tests/Unit/PluginTest.php | 57 +- tests/Unit/UninstallTest.php | 12 +- tests/Unit/UninstallerTest.php | 16 +- 33 files changed, 1231 insertions(+), 1441 deletions(-) rename src/Integrations/{SuperCache => FastestCache}/CacheClearAbility.php (75%) rename src/Integrations/{SuperCache/SuperCacheCheckSteps.php => FastestCache/FastestCacheCheckSteps.php} (66%) create mode 100644 src/Integrations/FastestCache/FastestCacheCompatibility.php rename src/Integrations/{SuperCache/SuperCacheFeature.php => FastestCache/FastestCacheFeature.php} (70%) rename src/Integrations/{SuperCache/SuperCacheIntegration.php => FastestCache/FastestCacheIntegration.php} (53%) rename src/Integrations/{SuperCache/SuperCacheListener.php => FastestCache/FastestCacheListener.php} (66%) create mode 100644 src/Integrations/FastestCache/FastestCacheProbe.php rename src/Integrations/{SuperCache/SuperCachePurger.php => FastestCache/FastestCachePurger.php} (64%) delete mode 100644 src/Integrations/SuperCache/SuperCacheCompatibility.php delete mode 100644 src/Integrations/SuperCache/SuperCacheProbe.php rename tests/Integration/{super-cache-smoke.sh => fastest-cache-smoke.sh} (52%) rename tests/Unit/Integrations/{SuperCache => FastestCache}/CacheClearAbilityTest.php (86%) rename tests/Unit/Integrations/{SuperCache/SuperCacheCheckStepsTest.php => FastestCache/FastestCacheCheckStepsTest.php} (56%) create mode 100644 tests/Unit/Integrations/FastestCache/FastestCacheCompatibilityTest.php rename tests/Unit/Integrations/{SuperCache/SuperCacheFeatureTest.php => FastestCache/FastestCacheFeatureTest.php} (55%) create mode 100644 tests/Unit/Integrations/FastestCache/FastestCacheIntegrationTest.php rename tests/Unit/Integrations/{SuperCache/SuperCacheListenerTest.php => FastestCache/FastestCacheListenerTest.php} (83%) create mode 100644 tests/Unit/Integrations/FastestCache/FastestCacheProbeTest.php rename tests/Unit/Integrations/{SuperCache/SuperCachePurgerTest.php => FastestCache/FastestCachePurgerTest.php} (71%) delete mode 100644 tests/Unit/Integrations/SuperCache/SuperCacheCompatibilityTest.php delete mode 100644 tests/Unit/Integrations/SuperCache/SuperCacheIntegrationTest.php delete mode 100644 tests/Unit/Integrations/SuperCache/SuperCacheProbeTest.php diff --git a/docs/guides/overview.md b/docs/guides/overview.md index 858a38b..6694055 100644 --- a/docs/guides/overview.md +++ b/docs/guides/overview.md @@ -100,11 +100,10 @@ nichts geändert. Vorgehen: `compat_incompatible`; der Stand steht im selben Reiter „Kompatibilität“. Seitenweite Änderungen (Optionen, Menüs und Menü-Positionen, Medien, Divi-Design, Theme Builder, globale -Bibliotheksmodule) leeren den Cache von WP Super Cache, Änderungen an einem Beitrag nur dessen Seite. Dabei leert der -Purge eines einzelnen Beitrags die Cache-Dateien unter dem Host, über den der MCP-Aufruf eingeht; bei verschiedenen -Hosts (z. B. mit und ohne `www`) bleiben die Dateien des anderen Hosts womöglich ungeleert (der Komplett-Purge ist davon -nicht betroffen). Ist der Cache dafür gesperrt, bleibt er unverändert; der Zustand steht im Reiter „Kompatibilität“ -und in `lit-site-status` (`wpsc`). Der Nachlauf-Purge, der auf ein gedrosseltes Leeren des ganzen Caches folgt, braucht +Bibliotheksmodule) leeren den Cache von WP Fastest Cache, Änderungen an einem Beitrag dessen Seite samt Startseite, +Feeds, Sitemap, Nachbarbeiträgen und Archiven seiner Begriffe (so leert WP Fastest Cache einen Beitrag). Ist der Cache +dafür gesperrt, bleibt er unverändert; der Zustand steht im Reiter „Kompatibilität“ +und in `lit-site-status` (`wpfc`). Der Nachlauf-Purge, der auf ein gedrosseltes Leeren des ganzen Caches folgt, braucht WP-Cron (oder einen System-Cron, der `wp-cron.php` aufruft). Auf Divi-Seiten leert jede Prüfung im Reiter den ganzen Cache einmal: Divi selbst leert ihn beim Anlegen und Löschen der Testseite. Wurde etwas außerhalb von LIT MCP geändert, leert `lit/cache-clear` den Cache auf Wunsch (`scope` `all` oder `post` mit `post_id`; erst Probelauf, dann diff --git a/src/Compatibility/ProbeStep.php b/src/Compatibility/ProbeStep.php index 106a5f8..471b75a 100644 --- a/src/Compatibility/ProbeStep.php +++ b/src/Compatibility/ProbeStep.php @@ -8,7 +8,7 @@ use Closure; defined('ABSPATH') || exit; -/** The one check step a profile's behaviour probe makes (Profile::$extraSteps), shared by Pods and WP Super Cache. */ +/** The one check step a profile's behaviour probe makes (Profile::$extraSteps), shared by Pods and WP Fastest Cache. */ final class ProbeStep { /** diff --git a/src/Compatibility/ThrowawayPost.php b/src/Compatibility/ThrowawayPost.php index a372bb2..58921bc 100644 --- a/src/Compatibility/ThrowawayPost.php +++ b/src/Compatibility/ThrowawayPost.php @@ -22,7 +22,7 @@ defined('ABSPATH') || exit; */ final class ThrowawayPost { - /** Meta key and title prefix the Pods and WP Super Cache probes give their entries (the Divi write probe has its own). */ + /** Meta key and title prefix the Pods and WP Fastest Cache probes give their entries (the Divi write probe has its own). */ public const MARKER = '_lit_mcp_check_entry'; public const TITLE = '[LIT MCP Prüfung]'; diff --git a/src/Integrations/SuperCache/CacheClearAbility.php b/src/Integrations/FastestCache/CacheClearAbility.php similarity index 75% rename from src/Integrations/SuperCache/CacheClearAbility.php rename to src/Integrations/FastestCache/CacheClearAbility.php index 6ad5bed..3ea6d53 100644 --- a/src/Integrations/SuperCache/CacheClearAbility.php +++ b/src/Integrations/FastestCache/CacheClearAbility.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Integrations\SuperCache; +namespace LIT\MCP\Integrations\FastestCache; use LIT\MCP\Abilities\Ability; use LIT\MCP\Abilities\Annotations; @@ -17,18 +17,19 @@ use WP_Post; defined('ABSPATH') || exit; /** - * lit/cache-clear: empties the WP Super Cache on request – for changes made outside this plugin (the plugin's own - * writes empty it anyway, SuperCacheListener). Dry run without confirm; with confirm: true the whole cache goes - * through the same throttle and trailing purge as the listener (SuperCachePurger), a post through wp_cache_post_edit, - * which skips every status but publish and private. The change log entry is ignored by the listener (NAME). + * lit/cache-clear: empties the WP Fastest Cache on request – for changes made outside this plugin (the plugin's own + * writes empty it anyway, FastestCacheListener). Dry run without confirm; with confirm: true the whole cache goes + * through the same throttle and trailing purge as the listener (FastestCachePurger), a post through + * wpfc_clear_post_cache_by_id (any status: WPFC also finds the address of a trashed post). The change log entry is + * ignored by the listener (NAME). */ final class CacheClearAbility implements Ability, DeclaresRequirements { public const NAME = 'lit/cache-clear'; - private const OUTCOMES = ['purged', 'throttled', 'nothing', 'cache_off']; + private const OUTCOMES = ['purged', 'throttled', 'cache_off']; - public function __construct(private SuperCachePurger $purger, private ChangeLog $log) + public function __construct(private FastestCachePurger $purger, private ChangeLog $log) { } @@ -44,7 +45,7 @@ final class CacheClearAbility implements Ability, DeclaresRequirements public function description(): string { - return 'Leert den Seiten-Cache von WP Super Cache: scope "all" den ganzen Cache, scope "post" nur die Seite ' + return 'Leert den Seiten-Cache von WP Fastest Cache: scope "all" den ganzen Cache, scope "post" nur die Seite ' . 'eines Beitrags (post_id). Änderungen durch LIT MCP leeren ihn ohnehin automatisch – das Tool ist für ' . 'Fälle, in denen etwas außerhalb von LIT MCP geändert wurde. Ohne confirm nur Probelauf (nichts wird ' . 'geleert); mit confirm: true nach Rückfrage beim Nutzer ausführen. Das Leeren des ganzen Caches ist auf ' @@ -89,8 +90,8 @@ final class CacheClearAbility implements Ability, DeclaresRequirements public function requirements(array $input): array { return match ($input['scope'] ?? null) { - 'all' => [SuperCacheFeature::PurgeAll->id()], - 'post' => [SuperCacheFeature::PurgePost->id()], + 'all' => [FastestCacheFeature::PurgeAll->id()], + 'post' => [FastestCacheFeature::PurgePost->id()], default => [], }; } @@ -123,34 +124,26 @@ final class CacheClearAbility implements Ability, DeclaresRequirements return $this->result( $scope, 'cache_off', - 'Der Seiten-Cache von WP Super Cache ist ausgeschaltet; nichts zu leeren.' + 'Der Seiten-Cache von WP Fastest Cache ist ausgeschaltet; nichts zu leeren.' ); } if ($post !== null) { - if (! $this->clearsStatus($post)) { - return $this->result($scope, 'nothing', sprintf( - 'Nichts zu leeren: "%s" ist ein Entwurf bzw. nicht veröffentlicht (Status %s); WP Super Cache ' - . 'cacht ihn nicht.', - $post->post_title, - $post->post_status - )); - } - $outcome = $this->purger->post((int) $post->ID) ? SuperCachePurger::PURGED : SuperCachePurger::LOCKED; + $outcome = $this->purger->post((int) $post->ID) ? FastestCachePurger::PURGED : FastestCachePurger::LOCKED; } else { $outcome = $this->purger->everything(); } - if ($outcome === SuperCachePurger::LOCKED) { + if ($outcome === FastestCachePurger::LOCKED) { return Errors::make( 'compat_incompatible', - 'WP Super Cache ist gesperrt (siehe Reiter Kompatibilität). Es wurde nichts geleert.', - 'Den Nutzer informieren, dass die Kompatibilität von WP Super Cache geprüft werden muss.', + 'WP Fastest Cache ist gesperrt (siehe Reiter Kompatibilität). Es wurde nichts geleert.', + 'Den Nutzer informieren, dass die Kompatibilität von WP Fastest Cache geprüft werden muss.', 423 ); } $summary = match ($outcome) { - SuperCachePurger::THROTTLED => 'Der ganze Seiten-Cache wurde vor weniger als 30 Sekunden geleert; das ' + FastestCachePurger::THROTTLED => 'Der ganze Seiten-Cache wurde vor weniger als 30 Sekunden geleert; das ' . 'Leeren folgt als Nachlauf per WP-Cron.', default => ucfirst($what) . ' wurde geleert.', }; @@ -166,8 +159,6 @@ final class CacheClearAbility implements Ability, DeclaresRequirements $note = ' Der Cache ist ausgeschaltet; es würde nichts geleert.'; } elseif ($throttled) { $note = ' Das Leeren wäre gerade gedrosselt und folgt als Nachlauf.'; - } elseif ($post !== null && ! $this->clearsStatus($post)) { - $note = ' Der Beitrag ist nicht veröffentlicht; es gäbe nichts zu leeren.'; } return Errors::confirmRequired( @@ -188,7 +179,7 @@ final class CacheClearAbility implements Ability, DeclaresRequirements $logId = FollowUp::run( fn () => $this->log->record( self::NAME, - 'wpsc', + 'wpfc', $post === null ? 'all' : (string) $post->ID, $summary ), @@ -202,12 +193,6 @@ final class CacheClearAbility implements Ability, DeclaresRequirements return $result->toArray(); } - /** WPSC skips everything but publish and private unless WPSCFORCEUPDATE is defined (wp_cache_post_edit). */ - private function clearsStatus(WP_Post $post): bool - { - return defined('WPSCFORCEUPDATE') || in_array($post->post_status, ['publish', 'private'], true); - } - /** * The checked target: the WP_Post of scope "post", true for "all" – or the error. Input is not schema-validated * yet when permission() runs. diff --git a/src/Integrations/SuperCache/SuperCacheCheckSteps.php b/src/Integrations/FastestCache/FastestCacheCheckSteps.php similarity index 66% rename from src/Integrations/SuperCache/SuperCacheCheckSteps.php rename to src/Integrations/FastestCache/FastestCacheCheckSteps.php index 75185a3..248d04d 100644 --- a/src/Integrations/SuperCache/SuperCacheCheckSteps.php +++ b/src/Integrations/FastestCache/FastestCacheCheckSteps.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Integrations\SuperCache; +namespace LIT\MCP\Integrations\FastestCache; use Closure; use LIT\MCP\Compatibility\CheckStep; @@ -10,8 +10,8 @@ use LIT\MCP\Compatibility\ProbeStep; defined('ABSPATH') || exit; -/** The check step only WP Super Cache has (the purge probe, SuperCacheProbe) and its labels. */ -final class SuperCacheCheckSteps +/** The check step only WP Fastest Cache has (the purge probe, FastestCacheProbe) and its labels. */ +final class FastestCacheCheckSteps { public const PROBE = 'probe'; @@ -19,10 +19,10 @@ final class SuperCacheCheckSteps private const LABEL_CACHE_OFF = self::LABEL . ' (Cache ist ausgeschaltet, nicht geprüft)'; /** - * The Profile::$extraSteps of WP Super Cache. A switched-off cache is a green step that says so: a skipped step + * The Profile::$extraSteps of WP Fastest Cache. A switched-off cache is a green step that says so: a skipped step * would lock the purge on a site that caches nothing. * - * @param Closure(): (list|null) $probe SuperCacheProbe::run(). + * @param Closure(): (list|null) $probe FastestCacheProbe::run(). * @return Closure(bool): list */ public static function extra(Closure $probe): Closure @@ -35,7 +35,7 @@ final class SuperCacheCheckSteps // Reports saved before labels were stored have none. return $step->label ?? ($step->key === self::PROBE ? self::LABEL : null) - ?? CheckStep::genericLabel($step->key, SuperCacheCompatibility::LABEL) + ?? CheckStep::genericLabel($step->key, FastestCacheCompatibility::LABEL) ?? $step->key; } } diff --git a/src/Integrations/FastestCache/FastestCacheCompatibility.php b/src/Integrations/FastestCache/FastestCacheCompatibility.php new file mode 100644 index 0000000..08647a9 --- /dev/null +++ b/src/Integrations/FastestCache/FastestCacheCompatibility.php @@ -0,0 +1,72 @@ + defined('WPFC_MAIN_PATH'), + version: static function (): ?string { + if (! defined('WPFC_MAIN_PATH')) { + return null; + } + $header = get_file_data(WPFC_MAIN_PATH . 'wpFastestCache.php', ['Version' => 'Version']); + $version = $header['Version'] ?? ''; + + return $version === '' ? null : $version; + }, + testedVersions: ['1.5.5'], + extraSteps: FastestCacheCheckSteps::extra(static fn (): ?array => (new FastestCacheProbe())->run()), + stepLabel: FastestCacheCheckSteps::label(...) + ); + } + + /** WPFC caches only while its switch is on (wpFastestCacheStatus in the decoded option, an object or []). */ + public static function cacheEnabled(): bool + { + $options = $GLOBALS['wp_fastest_cache_options'] ?? null; + + return is_object($options) && ! empty($options->wpFastestCacheStatus); + } +} diff --git a/src/Integrations/SuperCache/SuperCacheFeature.php b/src/Integrations/FastestCache/FastestCacheFeature.php similarity index 70% rename from src/Integrations/SuperCache/SuperCacheFeature.php rename to src/Integrations/FastestCache/FastestCacheFeature.php index 3c99130..73d59df 100644 --- a/src/Integrations/SuperCache/SuperCacheFeature.php +++ b/src/Integrations/FastestCache/FastestCacheFeature.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Integrations\SuperCache; +namespace LIT\MCP\Integrations\FastestCache; use LIT\MCP\Compatibility\Capability; @@ -11,11 +11,11 @@ defined('ABSPATH') || exit; // The installed PHPCompatibility sniffs predate enums (PHP 8.1) and read their methods as plain functions. // phpcs:disable PHPCompatibility.Variables.ForbiddenThisUseContexts.OutsideObjectContext -/** What the plugin does to WP Super Cache: empty its cache for one post or entirely. */ -enum SuperCacheFeature: string implements Capability +/** What the plugin does to WP Fastest Cache: empty its cache for one post or entirely. */ +enum FastestCacheFeature: string implements Capability { - case PurgePost = 'wpsc-purge-post'; - case PurgeAll = 'wpsc-purge-all'; + case PurgePost = 'wpfc-purge-post'; + case PurgeAll = 'wpfc-purge-all'; public function id(): string { diff --git a/src/Integrations/SuperCache/SuperCacheIntegration.php b/src/Integrations/FastestCache/FastestCacheIntegration.php similarity index 53% rename from src/Integrations/SuperCache/SuperCacheIntegration.php rename to src/Integrations/FastestCache/FastestCacheIntegration.php index d197915..6b7569d 100644 --- a/src/Integrations/SuperCache/SuperCacheIntegration.php +++ b/src/Integrations/FastestCache/FastestCacheIntegration.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Integrations\SuperCache; +namespace LIT\MCP\Integrations\FastestCache; use LIT\MCP\Admin\CompatibilityTab; use LIT\MCP\ChangeLog\ChangeLog; @@ -13,22 +13,22 @@ use LIT\MCP\Integrations\Integration; defined('ABSPATH') || exit; /** - * WP Super Cache: the compatibility profile (SuperCacheListener purges after writes, wired by Plugin), the cleanup of - * probe pages a dead check left behind and the tool lit/cache-clear – a tool group only while WP Super Cache is - * loaded. + * WP Fastest Cache: the compatibility profile (FastestCacheListener purges after writes, wired by Plugin), the + * cleanup of probe pages a dead check left behind and the tool lit/cache-clear – a tool group only while WP Fastest + * Cache is loaded. */ -final class SuperCacheIntegration implements Integration, ProvidesCompatibility, Bootable +final class FastestCacheIntegration implements Integration, ProvidesCompatibility, Bootable { /** @var callable(): bool */ private $available; - /** @param callable(): bool|null $available Whether WP Super Cache is loaded; null = WPCACHEHOME is defined. */ + /** @param callable(): bool|null $available Whether WP Fastest Cache is loaded; null = WPFC_MAIN_PATH is defined. */ public function __construct( ?callable $available, - private SuperCachePurger $purger, + private FastestCachePurger $purger, private ChangeLog $log ) { - $this->available = $available ?? static fn (): bool => defined('WPCACHEHOME'); + $this->available = $available ?? static fn (): bool => defined('WPFC_MAIN_PATH'); } public function boot(): void @@ -37,24 +37,25 @@ final class SuperCacheIntegration implements Integration, ProvidesCompatibility, } /** - * lit_mcp_compat_profile_load('wpsc'), before any output: deletes probe pages a dead check left behind - * (SuperCacheProbe::removeOrphans()). A GET without nonce on purpose: only the plugin's own leftovers, idempotent. + * lit_mcp_compat_profile_load('wpfc'), before any output: deletes probe pages a dead check left behind + * (FastestCacheProbe::removeOrphans()). A GET without nonce on purpose: only the plugin's own leftovers, + * idempotent. */ public function removeOrphans(string $profileId): void { - if ($profileId === 'wpsc' && $this->isAvailable() && current_user_can('manage_options')) { - SuperCacheProbe::removeOrphans(); + if ($profileId === 'wpfc' && $this->isAvailable() && current_user_can('manage_options')) { + FastestCacheProbe::removeOrphans(); } } public function id(): string { - return 'wpsc'; + return 'wpfc'; } public function label(): string { - return SuperCacheCompatibility::LABEL; + return FastestCacheCompatibility::LABEL; } public function isAvailable(): bool @@ -69,6 +70,6 @@ final class SuperCacheIntegration implements Integration, ProvidesCompatibility, public function compatibilities(): array { - return [SuperCacheCompatibility::profile()]; + return [FastestCacheCompatibility::profile()]; } } diff --git a/src/Integrations/SuperCache/SuperCacheListener.php b/src/Integrations/FastestCache/FastestCacheListener.php similarity index 66% rename from src/Integrations/SuperCache/SuperCacheListener.php rename to src/Integrations/FastestCache/FastestCacheListener.php index d30f844..d4d780c 100644 --- a/src/Integrations/SuperCache/SuperCacheListener.php +++ b/src/Integrations/FastestCache/FastestCacheListener.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Integrations\SuperCache; +namespace LIT\MCP\Integrations\FastestCache; use Closure; use LIT\MCP\Admin\CompatibilityTab; @@ -14,25 +14,26 @@ use Throwable; defined('ABSPATH') || exit; /** - * Empties the WP Super Cache after a write of this plugin (hook lit_mcp_changed). WPSC purges on its own only what - * goes through wp_update_post: field, term and featured-image updates, options, menu locations, Divi design, - * theme builder and global library modules it never sees. + * Empties the WP Fastest Cache after a write of this plugin (hook lit_mcp_changed). WPFC purges on its own only on + * post status transitions (and only with its "new post"/"update post" settings on) and term edits: field, term and + * featured-image updates, options, menu locations, Divi design, theme builder and global library modules it never + * sees. * * Purge with trailing run: the listener only collects, one purge runs at the end of the request (shutdown). A post - * of a public post type (not attachment) that still exists: that post (wp_cache_post_edit, which skips drafts and - * honours the user's "clear all on edit" setting). More than one post, or anything else: the whole cache of this - * site, once. The whole-cache purge runs at most every THROTTLE seconds (a client looping over media updates must - * not keep the cache empty): a purge inside the lock time schedules exactly one trailing purge (TRAILING_HOOK) at - * its end (SuperCachePurger, shared with lit/cache-clear). Single-post purges are never throttled. The release/withdraw - * entries of the check workflow change nothing on the site and purge nothing; the entries of lit/cache-clear purged - * themselves. + * of a public post type (not attachment) that still exists: that post (wpfc_clear_post_cache_by_id, which also + * clears the home page, feeds, sitemap, neighbours and term archives of the post). More than one post, or anything + * else: the whole cache of this site, once. The whole-cache purge runs at most every THROTTLE seconds (a client + * looping over media updates must not keep the cache empty): a purge inside the lock time schedules exactly one + * trailing purge (TRAILING_HOOK) at its end (FastestCachePurger, shared with lit/cache-clear). Single-post purges are + * never throttled. The release/withdraw entries of the check workflow change nothing on the site and purge nothing; + * the entries of lit/cache-clear purged themselves. */ -final class SuperCacheListener +final class FastestCacheListener { - public const TRAILING_HOOK = SuperCachePurger::TRAILING_HOOK; - public const PURGED_AT = SuperCachePurger::PURGED_AT; + public const TRAILING_HOOK = FastestCachePurger::TRAILING_HOOK; + public const PURGED_AT = FastestCachePurger::PURGED_AT; - private SuperCachePurger $purger; + private FastestCachePurger $purger; private Closure $postIdFor; /** @var array posts changed in this request */ private array $posts = []; @@ -40,12 +41,12 @@ final class SuperCacheListener private bool $queued = false; /** - * Defaults are the real WPSC and WordPress functions; closures only for tests. + * Defaults are the real WPFC and WordPress functions; closures only for tests. * - * @param Closure(SuperCacheFeature): bool $usable Compatibility::usable(). - * @param (Closure(): bool)|null $cacheEnabled WPSC caching switched on ($cache_enabled). - * @param (Closure(int): void)|null $purgePost wp_cache_post_edit($postId). - * @param (Closure(int): void)|null $purgeAll wp_cache_clear_cache($blogId), 0 = all sites. + * @param Closure(FastestCacheFeature): bool $usable Compatibility::usable(). + * @param (Closure(): bool)|null $cacheEnabled WPFC caching switched on. + * @param (Closure(int): void)|null $purgePost wpfc_clear_post_cache_by_id($postId). + * @param (Closure(): void)|null $purgeAll wpfc_clear_all_cache(). * @param (Closure(string, string): ?int)|null $postIdFor Post ID if objectType/objectId name an existing * post of a public type but attachment. */ @@ -56,7 +57,7 @@ final class SuperCacheListener ?Closure $purgeAll = null, ?Closure $postIdFor = null ) { - $this->purger = new SuperCachePurger($usable, $cacheEnabled, $purgePost, $purgeAll); + $this->purger = new FastestCachePurger($usable, $cacheEnabled, $purgePost, $purgeAll); $this->postIdFor = $postIdFor ?? self::publicPostId(...); } @@ -91,7 +92,7 @@ final class SuperCacheListener add_action('shutdown', [$this, 'flush']); } } catch (Throwable $e) { - Log::failure('WP Super Cache purge', $e); + Log::failure('WP Fastest Cache purge', $e); } } @@ -110,7 +111,7 @@ final class SuperCacheListener $this->purger->post($posts[0]); } } catch (Throwable $e) { - Log::failure('WP Super Cache purge', $e); + Log::failure('WP Fastest Cache purge', $e); } } @@ -122,7 +123,7 @@ final class SuperCacheListener $this->purger->everything(); } } catch (Throwable $e) { - Log::failure('WP Super Cache trailing purge', $e); + Log::failure('WP Fastest Cache trailing purge', $e); } } diff --git a/src/Integrations/FastestCache/FastestCacheProbe.php b/src/Integrations/FastestCache/FastestCacheProbe.php new file mode 100644 index 0000000..8ea3371 --- /dev/null +++ b/src/Integrations/FastestCache/FastestCacheProbe.php @@ -0,0 +1,194 @@ +/index.html, WPFC's own getWpContentDir), WpFastestCache:: + * singleDeleteCache – the method behind wpfc_clear_post_cache_by_id, which FastestCacheListener calls – runs for it, + * and the file must be gone (WPFC moves the directory to cache/tmpWpfc, which it empties itself). The probe also + * checks that WPFC still hooks wpfc_clear_post_cache_by_id to that method: without the hook the public function does + * nothing, silently. The page and the cache directory are always removed (ThrowawayPost); entries a dead check left + * behind are deleted by removeOrphans(). The whole-cache purge (wpfc_clear_all_cache) is not probed: it would empty + * the live cache of the site on every check. Runs only from the check route (Compatibility::check). + * + * The purge is narrowed to the page ($to_clear_parents = false; otherwise WPFC also empties the home page, author + * page, sitemap, neighbours and term archives). NOT avoidable: WPFC still empties the two feed caches and purges the + * external caches configured in WPFC (Varnish, Cloudflare) on every call; creating and deleting the page fire the + * hooks of every other plugin (Divi 5 empties caches on insert and delete of a page). + * + * The page stays a draft; WPFC takes the address a draft would have once published. The draft gets a fixed slug + * straight in the database, without hooks: an empty slug would make its address the home page. + * + * shortcut: needs a pretty permalink structure (a "?page_id=" address has no cache directory, nothing to probe then). + */ +final class FastestCacheProbe +{ + private const SLUG_PREFIX = 'lit-mcp-check-'; + private const PURGE_HOOK = 'wpfc_clear_post_cache_by_id'; + + private const ABORTED = 'Fastest-Cache-Probe abgebrochen (Details im PHP-Error-Log).'; + private const NOT_HOOKED = 'WP Fastest Cache verarbeitet wpfc_clear_post_cache_by_id nicht mehr; das Leeren eines ' + . 'Beitrags bliebe wirkungslos.'; + private const NOT_PURGED = 'Die Testdatei im Cache blieb nach dem Leeren durch WP Fastest Cache bestehen.'; + private const NO_SLUG = 'Die Adresse der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'; + private const NOT_WRITTEN = 'Die Testdatei konnte nicht im Cache-Verzeichnis angelegt werden ' + . '(Details im PHP-Error-Log).'; + + private Closure $enabled; + private Closure $hooked; + private Closure $cacheDir; + private Closure $purge; + private Closure $setSlug; + + /** + * Defaults are the real WPFC and WordPress calls; closures only for tests. + * + * @param (Closure(): bool)|null $enabled WPFC caching switched on. + * @param (Closure(): bool)|null $hooked WPFC hooks wpfc_clear_post_cache_by_id to singleDeleteCache. + * @param (Closure(int): ?string)|null $cacheDir Cache directory (with trailing slash) of a page; null = none. + * @param (Closure(int): void)|null $purge singleDeleteCache for this page only. + * @param (Closure(int): bool)|null $setSlug Sets the probe slug without hooks; false = not changed. + */ + public function __construct( + ?Closure $enabled = null, + ?Closure $hooked = null, + ?Closure $cacheDir = null, + ?Closure $purge = null, + ?Closure $setSlug = null + ) { + $this->enabled = $enabled ?? FastestCacheCompatibility::cacheEnabled(...); + $this->hooked = $hooked ?? static fn (): bool + => has_action(self::PURGE_HOOK, [$GLOBALS['wp_fastest_cache'] ?? null, 'singleDeleteCache']) !== false; + $this->cacheDir = $cacheDir ?? self::cacheDirFor(...); + $this->purge = $purge ?? static function (int $id): void { + $GLOBALS['wp_fastest_cache']->singleDeleteCache(false, $id, false); + }; + $this->setSlug = $setSlug ?? self::writeSlug(...); + } + + /** @return list|null German problems, [] = green; null = nothing to probe (cache off, no usable path). */ + public function run(): ?array + { + if (! ($this->enabled)()) { + return null; + } + if (! ($this->hooked)()) { + return [self::NOT_HOOKED]; + } + + $unchecked = false; + $problems = self::throwaway()->run('page', function (int $id) use (&$unchecked): array { + if (! ($this->setSlug)($id)) { + Log::line('wpfc probe: the database did not set the slug of page ' . $id); + + return [self::NO_SLUG]; + } + $dir = ($this->cacheDir)($id); + if ($dir === null) { + $unchecked = true; + + return []; + } + + return $this->exercise($id, $dir); + }); + + return $unchecked && $problems === [] ? null : $problems; + } + + /** @return list IDs of probe pages a dead check left behind, deleted now. */ + public static function removeOrphans(): array + { + return self::throwaway()->removeOrphans('page'); + } + + private static function throwaway(): ThrowawayPost + { + return new ThrowawayPost( + ThrowawayPost::MARKER, + ThrowawayPost::TITLE, + self::ABORTED, + 'Die Probe-Seite konnte nicht angelegt werden (Details im PHP-Error-Log).', + 'Die Probe-Seite %d konnte nicht gelöscht werden; bitte endgültig löschen (Titel „' + . ThrowawayPost::TITLE . ' …“).', + 'wpfc probe' + ); + } + + /** + * WPFC's cache directory of the probe page, computed the way singleDeleteCache does (the path of the published + * address), or null when it is not exactly //: a plain "?page_id=" address, a path with "..". + */ + public static function cacheDirFor(int $postId): ?string + { + $post = get_post($postId); + if (! $post instanceof WP_Post) { + return null; + } + $published = clone $post; + $published->post_status = 'publish'; + $address = urldecode((string) get_permalink($published)); + if (preg_match('#^https?://[^/]+/(.+)$#', $address, $match) !== 1) { + return null; + } + $path = trim($match[1], '/'); + $slug = self::SLUG_PREFIX . $postId; + if (str_contains($path, '..') || ($path !== $slug && ! str_ends_with($path, '/' . $slug))) { + return null; + } + + return $GLOBALS['wp_fastest_cache']->getWpContentDir('/cache/all/') . $path . '/'; + } + + // The probe's own file in WPFC's own cache directory, written the way WPFC does it (plain PHP, no WP_Filesystem). + // phpcs:disable WordPress.WP.AlternativeFunctions + + /** @return list */ + private function exercise(int $id, string $dir): array + { + $file = $dir . 'index.html'; + try { + if (! wp_mkdir_p($dir) || file_put_contents($file, '') === false) { + Log::line('wpfc probe: could not write the test file ' . $file); + + return [self::NOT_WRITTEN]; + } + + ($this->purge)($id); + clearstatcache(true, $file); + if (! file_exists($file)) { + return []; + } + + Log::line('wpfc probe: singleDeleteCache left the test file ' . $file . ' in place.'); + + return [self::NOT_PURGED]; + } finally { + @unlink($file); + @rmdir($dir); + } + } + + // phpcs:enable WordPress.WP.AlternativeFunctions + + /** A fixed slug (an empty one would make the page's address the home page) – no hooks, no clean_post_cache. */ + private static function writeSlug(int $id): bool + { + global $wpdb; + + $rows = $wpdb->update($wpdb->posts, ['post_name' => self::SLUG_PREFIX . $id], ['ID' => $id]); + wp_cache_delete($id, 'posts'); + + return $rows !== false && (int) $rows > 0; + } +} diff --git a/src/Integrations/SuperCache/SuperCachePurger.php b/src/Integrations/FastestCache/FastestCachePurger.php similarity index 64% rename from src/Integrations/SuperCache/SuperCachePurger.php rename to src/Integrations/FastestCache/FastestCachePurger.php index 62818ea..0a1c5fd 100644 --- a/src/Integrations/SuperCache/SuperCachePurger.php +++ b/src/Integrations/FastestCache/FastestCachePurger.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace LIT\MCP\Integrations\SuperCache; +namespace LIT\MCP\Integrations\FastestCache; use Closure; use LIT\MCP\Log; @@ -10,15 +10,15 @@ use LIT\MCP\Log; defined('ABSPATH') || exit; /** - * The two purges of WP Super Cache, shared by SuperCacheListener (after every write) and lit/cache-clear: the + * The two purges of WP Fastest Cache, shared by FastestCacheListener (after every write) and lit/cache-clear: the * capability check, the 30 s throttle of the whole-cache purge with its one trailing purge (TRAILING_HOOK) and the * stamp (PURGED_AT). The state lives in the transient and the cron event, so any number of instances share it. */ -final class SuperCachePurger +final class FastestCachePurger { - public const TRAILING_HOOK = 'lit_mcp_wpsc_trailing_purge'; + public const TRAILING_HOOK = 'lit_mcp_wpfc_trailing_purge'; /** Transient with the time of the last whole-cache purge (also in Uninstaller). */ - public const PURGED_AT = 'lit_mcp_wpsc_purged_at'; + public const PURGED_AT = 'lit_mcp_wpfc_purged_at'; /** Seconds between two whole-cache purges. */ private const THROTTLE = 30; @@ -33,12 +33,12 @@ final class SuperCachePurger private bool $warned = false; /** - * Defaults are the real WPSC functions; closures only for tests. + * Defaults are the real WPFC functions; closures only for tests. * - * @param Closure(SuperCacheFeature): bool $usable Compatibility::usable(). - * @param (Closure(): bool)|null $cacheEnabled WPSC caching switched on ($cache_enabled). - * @param (Closure(int): void)|null $purgePost wp_cache_post_edit($postId). - * @param (Closure(int): void)|null $purgeAll wp_cache_clear_cache($blogId), 0 = all sites. + * @param Closure(FastestCacheFeature): bool $usable Compatibility::usable(). + * @param (Closure(): bool)|null $cacheEnabled WPFC caching switched on. + * @param (Closure(int): void)|null $purgePost wpfc_clear_post_cache_by_id($postId). + * @param (Closure(): void)|null $purgeAll wpfc_clear_all_cache(). */ public function __construct( Closure $usable, @@ -47,12 +47,12 @@ final class SuperCachePurger ?Closure $purgeAll = null ) { $this->usable = $usable; - $this->cacheEnabled = $cacheEnabled ?? static fn (): bool => ! empty($GLOBALS['cache_enabled']); + $this->cacheEnabled = $cacheEnabled ?? FastestCacheCompatibility::cacheEnabled(...); $this->purgePost = $purgePost ?? static function (int $id): void { - wp_cache_post_edit($id); + wpfc_clear_post_cache_by_id($id); }; - $this->purgeAll = $purgeAll ?? static function (int $blog): void { - wp_cache_clear_cache($blog); + $this->purgeAll = $purgeAll ?? static function (): void { + wpfc_clear_all_cache(); }; } @@ -80,10 +80,10 @@ final class SuperCachePurger return is_numeric($last) ? (int) $last : null; } - /** wp_cache_post_edit for one post; false = locked. Never throttled. */ + /** wpfc_clear_post_cache_by_id for one post; false = locked. Never throttled. */ public function post(int $postId): bool { - if (! $this->allowed(SuperCacheFeature::PurgePost)) { + if (! $this->allowed(FastestCacheFeature::PurgePost)) { return false; } ($this->purgePost)($postId); @@ -93,13 +93,13 @@ final class SuperCachePurger /** * Whole cache, at most every THROTTLE seconds. Inside the lock time one trailing purge is scheduled (cron events - * are per site: the callback runs on this blog, no blog switch, wp_cache_clear_cache takes the blog ID). + * are per site: the callback runs on this blog). * * @return self::PURGED|self::THROTTLED|self::LOCKED */ public function everything(): string { - if (! $this->allowed(SuperCacheFeature::PurgeAll)) { + if (! $this->allowed(FastestCacheFeature::PurgeAll)) { return self::LOCKED; } @@ -112,22 +112,20 @@ final class SuperCachePurger return self::THROTTLED; } - // Known and unavoidable on a multisite: WPSC also empties the shared blogs/ meta directory of all sites - // in its blog branch (wp-cache-phase2.php, wp_cache_clear_cache). - ($this->purgeAll)(is_multisite() ? get_current_blog_id() : 0); + ($this->purgeAll)(); set_transient(self::PURGED_AT, time(), self::THROTTLE); return self::PURGED; } - private function allowed(SuperCacheFeature $feature): bool + private function allowed(FastestCacheFeature $feature): bool { if (($this->usable)($feature)) { return true; } if (! $this->warned) { $this->warned = true; - Log::line(sprintf('WP Super Cache not purged: %s not usable (see tab Compatibility)', $feature->id())); + Log::line(sprintf('WP Fastest Cache not purged: %s not usable (see tab Compatibility)', $feature->id())); } return false; diff --git a/src/Integrations/SuperCache/SuperCacheCompatibility.php b/src/Integrations/SuperCache/SuperCacheCompatibility.php deleted file mode 100644 index 9e93609..0000000 --- a/src/Integrations/SuperCache/SuperCacheCompatibility.php +++ /dev/null @@ -1,63 +0,0 @@ - defined('WPCACHEHOME'), - version: static function (): ?string { - if (! defined('WPCACHEHOME')) { - return null; - } - // WPSC_VERSION_ID is a build counter, not the plugin version: read the plugin header. - $version = get_file_data(WPCACHEHOME . 'wp-cache.php', ['Version' => 'Version'])['Version'] ?? ''; - - return $version === '' ? null : $version; - }, - testedVersions: ['3.1.5'], - extraSteps: SuperCacheCheckSteps::extra(static fn (): ?array => (new SuperCacheProbe())->run()), - stepLabel: SuperCacheCheckSteps::label(...) - ); - } -} diff --git a/src/Integrations/SuperCache/SuperCacheProbe.php b/src/Integrations/SuperCache/SuperCacheProbe.php deleted file mode 100644 index 12cad67..0000000 --- a/src/Integrations/SuperCache/SuperCacheProbe.php +++ /dev/null @@ -1,230 +0,0 @@ -//; any other host (www or not) makes the probe - * "not checked" instead of looking in the wrong place. - * - * shortcut: needs a pretty permalink structure with WP in the home address' directory (WPSC does not purge a "?p=" - * address either, nothing to probe then). - */ -final class SuperCacheProbe -{ - private const SLUG_PREFIX = 'lit-mcp-check-'; - private const CLEAR_ON_EDIT = 'wp_cache_clear_on_post_edit'; - private const RELATED_PAGES_FILTER = 'wpsc_delete_related_pages_on_edit'; - /** Files WPSC writes per page and protocol, and what a purge renames them to. */ - private const CACHE_FILES = ['index.html', 'index-https.html']; - private const REBUILD_SUFFIX = '.needs-rebuild'; - - private const ABORTED = 'Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'; - private const NOT_PURGED = 'Die Testdatei im Cache blieb nach dem Leeren durch WP Super Cache bestehen.'; - private const NOT_PRIVATE = 'Der Status der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'; - private const NOT_WRITTEN = 'Die Testdatei konnte nicht im Cache-Verzeichnis angelegt werden ' - . '(Details im PHP-Error-Log).'; - - private Closure $enabled; - private Closure $cacheDir; - private Closure $purge; - private Closure $setStatus; - - /** - * Defaults are the real WPSC and WordPress calls; closures only for tests. - * - * @param (Closure(): bool)|null $enabled WPSC caching switched on ($cache_enabled). - * @param (Closure(int): ?string)|null $cacheDir Cache directory (with trailing slash) of a page; null = none. - * @param (Closure(int): void)|null $purge wp_cache_post_edit($postId). - * @param (Closure(int, string): bool)|null $setStatus Sets status (and the slug) of the page without hooks; - * false = the database did not change it. - */ - public function __construct( - ?Closure $enabled = null, - ?Closure $cacheDir = null, - ?Closure $purge = null, - ?Closure $setStatus = null - ) { - $this->enabled = $enabled ?? static fn (): bool => ! empty($GLOBALS['cache_enabled']); - $this->cacheDir = $cacheDir ?? self::cacheDirFor(...); - $this->purge = $purge ?? self::purgeThisPageOnly(...); - $this->setStatus = $setStatus ?? self::writeStatus(...); - } - - /** @return list|null German problems, [] = green; null = nothing to probe (cache off, no usable path). */ - public function run(): ?array - { - if (! ($this->enabled)()) { - return null; - } - - $unchecked = false; - $problems = self::throwaway()->run('page', function (int $id) use (&$unchecked): array { - try { - if (! ($this->setStatus)($id, 'private')) { - Log::line('wpsc probe: the database did not set page ' . $id . ' to private.'); - - return [self::NOT_PRIVATE]; - } - $dir = ($this->cacheDir)($id); - if ($dir === null) { - $unchecked = true; - - return []; - } - - return $this->exercise($id, $dir); - } finally { - $this->resetToDraft($id); - } - }); - - return $unchecked && $problems === [] ? null : $problems; - } - - /** @return list IDs of probe pages a dead check left behind (draft or private), deleted now. */ - public static function removeOrphans(): array - { - return self::throwaway()->removeOrphans('page'); - } - - private static function throwaway(): ThrowawayPost - { - return new ThrowawayPost( - ThrowawayPost::MARKER, - ThrowawayPost::TITLE, - self::ABORTED, - 'Die Probe-Seite konnte nicht angelegt werden (Details im PHP-Error-Log).', - 'Die Probe-Seite %d konnte nicht gelöscht werden; bitte endgültig löschen (Titel „' - . ThrowawayPost::TITLE . ' …“).', - 'wpsc probe' - ); - } - - /** Must not hide the result of the probe or end the check: the page is deleted right after anyway. */ - private function resetToDraft(int $id): void - { - try { - if (! ($this->setStatus)($id, 'draft')) { - Log::line('wpsc probe: the database did not set page ' . $id . ' back to draft.'); - } - } catch (Throwable $e) { - Log::failure('wpsc probe', $e); - } - } - - /** - * WPSC's own cache directory of the probe page, or null when the probe must not use it: anything but exactly - * // – another host than the home URL's (WPSC takes it from the request), a - * plain "?p=" address or an empty slug (both resolve to the home page's directory), a path with "..". - */ - public static function cacheDirFor(int $postId): ?string - { - $home = (string) get_supercache_dir(); - $dir = (string) get_current_url_supercache_dir($postId); - - return $home !== '' && $dir === $home . self::SLUG_PREFIX . $postId . '/' ? $dir : null; - } - - /** wp_cache_post_edit for the probe page only: see the class comment. Global and filter are put back exactly. */ - private static function purgeThisPageOnly(int $id): void - { - $hadGlobal = array_key_exists(self::CLEAR_ON_EDIT, $GLOBALS); - $before = $GLOBALS[self::CLEAR_ON_EDIT] ?? null; - // A closure of its own: remove_filter must not take away a "__return_false" another plugin added. - $off = static fn (): bool => false; - try { - $GLOBALS[self::CLEAR_ON_EDIT] = 0; - add_filter(self::RELATED_PAGES_FILTER, $off, PHP_INT_MAX); - wp_cache_post_edit($id); - } finally { - remove_filter(self::RELATED_PAGES_FILTER, $off, PHP_INT_MAX); - if ($hadGlobal) { - $GLOBALS[self::CLEAR_ON_EDIT] = $before; - } else { - unset($GLOBALS[self::CLEAR_ON_EDIT]); - } - } - } - - // The probe's own file in WPSC's own cache directory, written the way WPSC does it (plain PHP, no WP_Filesystem). - // phpcs:disable WordPress.WP.AlternativeFunctions - - /** @return list */ - private function exercise(int $id, string $dir): array - { - $file = $dir . 'index.html'; - try { - if (! wp_mkdir_p($dir) || file_put_contents($file, '') === false) { - Log::line('wpsc probe: could not write the test file ' . $file); - - return [self::NOT_WRITTEN]; - } - - ($this->purge)($id); - clearstatcache(true, $file); - if (! file_exists($file)) { - return []; - } - - Log::line('wpsc probe: wp_cache_post_edit left the test file ' . $file . ' in place.'); - - return [self::NOT_PURGED]; - } finally { - foreach (self::CACHE_FILES as $name) { - @unlink($dir . $name); - @unlink($dir . $name . self::REBUILD_SUFFIX); - } - @rmdir($dir); - } - } - - // phpcs:enable WordPress.WP.AlternativeFunctions - - /** Status and a fixed slug (an empty one would make the page's address the home page) – no hooks, no clean_post_cache. */ - private static function writeStatus(int $id, string $status): bool - { - global $wpdb; - - $columns = ['post_status' => $status, 'post_name' => self::SLUG_PREFIX . $id]; - $rows = $wpdb->update($wpdb->posts, $columns, ['ID' => $id]); - wp_cache_delete($id, 'posts'); - - return $rows !== false && (int) $rows > 0; - } -} diff --git a/src/Plugin.php b/src/Plugin.php index 9803307..5d384f3 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -37,10 +37,10 @@ use LIT\MCP\Integrations\Pods\PodsCompatibility; use LIT\MCP\Integrations\Pods\PodsFeature; use LIT\MCP\Integrations\Pods\PodsFieldSource; use LIT\MCP\Integrations\Pods\PodsIntegration; -use LIT\MCP\Integrations\SuperCache\SuperCacheFeature; -use LIT\MCP\Integrations\SuperCache\SuperCacheIntegration; -use LIT\MCP\Integrations\SuperCache\SuperCachePurger; -use LIT\MCP\Integrations\SuperCache\SuperCacheListener; +use LIT\MCP\Integrations\FastestCache\FastestCacheFeature; +use LIT\MCP\Integrations\FastestCache\FastestCacheIntegration; +use LIT\MCP\Integrations\FastestCache\FastestCachePurger; +use LIT\MCP\Integrations\FastestCache\FastestCacheListener; use LIT\MCP\Integrations\WordPress\Schema\ContentSchema; use LIT\MCP\Integrations\WordPress\Schema\FieldSource; use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource; @@ -100,8 +100,8 @@ final class Plugin // Integration-owned hooks (Divi: upgrader hook, check route, CLI command, tab "Divi") – before the settings // page reads its tabs. $registry->boot(); - // After every write of the plugin: empty the WP Super Cache where WordPress itself would not. - SuperCacheListener::create($registry->compatibility())->register(); + // After every write of the plugin: empty the WP Fastest Cache where WordPress itself would not. + FastestCacheListener::create($registry->compatibility())->register(); // The admin check of every compatibility profile (POST lit-mcp/v1/-check), REST like the MCP calls. (new CheckRoute($registry->compatibility()))->register(); @@ -216,11 +216,11 @@ final class Plugin $divi->library(), $divi->themeBuilder(), new PodsIntegration(), - new SuperCacheIntegration( + new FastestCacheIntegration( null, // Asked at call time: the compatibility exists only once the registry does. - new SuperCachePurger( - static function (SuperCacheFeature $feature) use (&$registry): bool { + new FastestCachePurger( + static function (FastestCacheFeature $feature) use (&$registry): bool { return $registry instanceof IntegrationRegistry && $registry->compatibility()->usable($feature); } ), diff --git a/src/Uninstaller.php b/src/Uninstaller.php index ebb25e0..28f637a 100644 --- a/src/Uninstaller.php +++ b/src/Uninstaller.php @@ -19,11 +19,11 @@ defined('ABSPATH') || exit; * Limits::OPTION, ChangeLog\ChangeLogInstaller::VERSION_OPTION, Integrations\Divi\Release\ * lit_mcp__releases (Compatibility\ReleaseStore), lit_mcp__check_report * (Compatibility\CheckReportStore) and lit_mcp__check_lock (Admin\CheckRoute) for the - * profiles divi, pods and wpsc (an explicit list: a LIKE pattern could hit foreign options), + * profiles divi, pods and wpfc (an explicit list: a LIKE pattern could hit foreign options), * Integrations\Divi\Design\DesignLock::option(), Admin\ConnectionStatus::ENDPOINT_TRANSIENT, * Integrations\Divi\Release\ReleaseResolver::CACHE, - * Integrations\SuperCache\SuperCacheListener::PURGED_AT (transient), - * ChangeLog\ChangeLogCleanup::HOOK, Integrations\SuperCache\SuperCacheListener::TRAILING_HOOK (cron), + * Integrations\FastestCache\FastestCacheListener::PURGED_AT (transient), + * ChangeLog\ChangeLogCleanup::HOOK, Integrations\FastestCache\FastestCacheListener::TRAILING_HOOK (cron), * ChangeLog\ChangeLog::TABLE, Admin\LogTab::PER_PAGE_OPTION and * Updater\RecheckOnInstall::OPTION on purpose; * tests/Unit/UninstallerTest.php and tests/Unit/UninstallTest.php keep them in sync with @@ -40,7 +40,7 @@ final class Uninstaller 'lit_mcp_allowed_roles', 'lit_mcp_exposure', 'lit_mcp_limits', 'lit_mcp_db_version', 'lit_mcp_divi_releases', 'lit_mcp_divi_check_report', 'lit_mcp_divi_check_lock', 'lit_mcp_pods_releases', 'lit_mcp_pods_check_report', 'lit_mcp_pods_check_lock', - 'lit_mcp_wpsc_releases', 'lit_mcp_wpsc_check_report', 'lit_mcp_wpsc_check_lock', + 'lit_mcp_wpfc_releases', 'lit_mcp_wpfc_check_report', 'lit_mcp_wpfc_check_lock', 'lit_mcp_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets', 'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css', 'lit_mcp_lock_divi_options', 'lit_mcp_lock_variables', 'lit_mcp_lock_presets', 'lit_mcp_lock_custom_css', @@ -48,8 +48,8 @@ final class Uninstaller 'lit_mcp_lock_colors', 'lit_mcp_lock_theme_options', 'lit_mcp_installed_version', ]; - private const TRANSIENTS = ['lit_mcp_endpoint_check', 'lit_mcp_divi_state', 'lit_mcp_wpsc_purged_at']; - private const CRON_HOOKS = ['lit_mcp_log_cleanup', 'lit_mcp_wpsc_trailing_purge']; + private const TRANSIENTS = ['lit_mcp_endpoint_check', 'lit_mcp_divi_state', 'lit_mcp_wpfc_purged_at']; + private const CRON_HOOKS = ['lit_mcp_log_cleanup', 'lit_mcp_wpfc_trailing_purge']; private const TABLE = 'lit_mcp_log'; /** Screen option "Einträge pro Seite" of the Protokoll tab (Admin\LogTab::PER_PAGE_OPTION): user meta. */ diff --git a/tests/Integration/super-cache-smoke.sh b/tests/Integration/fastest-cache-smoke.sh similarity index 52% rename from tests/Integration/super-cache-smoke.sh rename to tests/Integration/fastest-cache-smoke.sh index 1f75c67..11cc0eb 100644 --- a/tests/Integration/super-cache-smoke.sh +++ b/tests/Integration/fastest-cache-smoke.sh @@ -1,56 +1,38 @@ #!/usr/bin/env bash -# End-to-end checks of the WP Super Cache profile of the compatibility layer (plan 2026-11-06-kompatibilitaetsschicht, -# 2.5 and 2.6): status, check route /wpsc-check with the purge probe (cache off and on), the purge after a write of -# this plugin without wp_update_post (terms), the whole-cache purge after a setting, its throttle and the trailing -# purge by cron. Runs INSIDE the DDEV web container: -# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/super-cache-smoke.sh +# End-to-end checks of the WP Fastest Cache profile of the compatibility layer: status, check route /wpfc-check with +# the purge probe (cache off and on), the purge after a write of this plugin without wp_update_post (terms), the +# whole-cache purge after a setting, its throttle, the trailing purge by cron and lit/cache-clear. Runs INSIDE the +# DDEV web container: +# cd /home/janniclas/Projekte/WordPress && timeout 600 ddev exec bash wp-content/plugins/WP-LIT-MCP/tests/Integration/fastest-cache-smoke.sh # -# SIDE EFFECTS, all undone by the EXIT trap and proven at the end (verify_restored): activating WP Super Cache edits -# wp-config.php (WP_CACHE, WPCACHEHOME), creates wp-content/advanced-cache.php and wp-content/wp-cache-config.php, -# fills wp-content/cache/ and schedules wp_cache_* cron events and options. The script saves wp-config.php (and the -# files named in SAVED_FILES, if they exist) first, deactivates the plugin and writes the saved state back, byte for byte (sha256). -# It uses the plain PHP cache mode ($wp_cache_mod_rewrite = 0): no .htaccess rewrite rules are ever written; the run -# refuses to start if the cache directory is not empty, WP Super Cache is already active or WP_CACHE is already set. +# SIDE EFFECTS, all undone by the EXIT trap and proven at the end (verify_restored): WP Fastest Cache is activated if +# it was not, its option WpFastestCache is switched (only wpFastestCacheStatus, nothing that writes .htaccess) and set +# back byte for byte, a test post and two categories are created and deleted. Cache files of the site are emptied by +# the whole-cache purges – that is what the run checks; WPFC rebuilds them on the next requests. set -euo pipefail cd /var/www/html # shellcheck source=lib.sh source wp-content/plugins/WP-LIT-MCP/tests/Integration/lib.sh ROOT=/var/www/html -CACHE_DIR=$ROOT/wp-content/cache -SUPERCACHE_DIR=$CACHE_DIR/supercache -WP_CONFIG=$ROOT/wp-config.php +CACHE_ALL=$ROOT/wp-content/cache/all HTACCESS=$ROOT/.htaccess -SAVED_FILES=(wp-content/advanced-cache.php wp-content/wp-cache-config.php .htaccess) -CRON_PATTERN='wp_cache|wpsc|lit_mcp_wpsc' -OPTION_PATTERN='wpsc|wp_super_cache|wp_cache|super_cache|lit_mcp_wpsc' -TRAILING_HOOK=lit_mcp_wpsc_trailing_purge -PURGED_AT=lit_mcp_wpsc_purged_at +CRON_PATTERN='lit_mcp_wpfc' +OPTION_PATTERN='lit_mcp_wpfc' +TRAILING_HOOK=lit_mcp_wpfc_trailing_purge +PURGED_AT=lit_mcp_wpfc_purged_at BASE=http://localhost +CACHE_ON='{"wpFastestCacheStatus":"on"}' ADMIN_LOGIN=$(wp user list --role=administrator --field=user_login | head -1) PREFIX=$(wp db prefix) -# --- Guards: nothing is changed before all of them pass ------------------------------------------------------------ -wp plugin is-active wp-super-cache 2>/dev/null && fail "WP Super Cache is already active; this smoke expects it inactive" -grep -Eq "^\s*(define\s*\(\s*['\"]WP_CACHE['\"]|const\s+WP_CACHE\s*=)" "$WP_CONFIG" && fail "wp-config.php already sets WP_CACHE" -[ -d "$CACHE_DIR" ] && [ -n "$(find "$CACHE_DIR" -mindepth 1 -print -quit)" ] && fail "$CACHE_DIR is not empty; refusing to touch a cache" - -# --- Snapshot of everything the run can change --------------------------------------------------------------------- -BACKUP=$(mktemp -d) +# --- Guards and snapshot of everything the run can change ---------------------------------------------------------- +wp plugin is-installed wp-fastest-cache || fail "WP Fastest Cache is not installed" sums() { sha256sum "$@" 2>/dev/null | awk '{print $1}'; } -CONFIG_SUM_BEFORE=$(sums "$WP_CONFIG") -CONFIG_STAT_BEFORE=$(stat -c '%a %U:%G' "$WP_CONFIG") -# cp -a, not -p: wp-config.php carries an ACL entry; WP Super Cache replaces the file (new inode) and loses it, and -# only cp -a writes it back with the content. -cp -a "$WP_CONFIG" "$BACKUP/wp-config.php" -[ "$(sums "$BACKUP/wp-config.php")" = "$CONFIG_SUM_BEFORE" ] || fail "could not save wp-config.php" -for f in "${SAVED_FILES[@]}"; do - if [ -e "$ROOT/$f" ]; then cp -a "$ROOT/$f" "$BACKUP/$(basename "$f")"; echo "$(sums "$ROOT/$f")" > "$BACKUP/$(basename "$f").sum"; fi -done +WPFC_WAS_ACTIVE=no; wp plugin is-active wp-fastest-cache 2>/dev/null && WPFC_WAS_ACTIVE=yes +WPFC_OPTION_BEFORE=$(wp option get WpFastestCache 2>/dev/null || echo __absent__) HTACCESS_BEFORE=absent; [ -e "$HTACCESS" ] && HTACCESS_BEFORE=$(sums "$HTACCESS") -CACHE_DIR_EXISTED=no; CACHE_DIR_MODE=775 -if [ -d "$CACHE_DIR" ]; then CACHE_DIR_EXISTED=yes; CACHE_DIR_MODE=$(stat -c '%a' "$CACHE_DIR"); fi list_hooks() { wp cron event list --fields=hook --format=csv | tail -n +2 | grep -E "$CRON_PATTERN" | sort -u || true; } list_options() { wp db query "SELECT option_name FROM ${PREFIX}options WHERE option_name REGEXP '$OPTION_PATTERN' ORDER BY option_name" --skip-column-names; } HOOKS_BEFORE=$(list_hooks) @@ -60,41 +42,20 @@ ORIGINAL_DESCRIPTION=$(wp option get blogdescription) CREATED_POSTS=() CREATED_TERMS=() -# --- Restore --------------------------------------------------------------------------------------------------------- -# A saved file is written back atomically: a copy next to the target (same file system), then mv – a run killed in -# the middle never leaves a half-written wp-config.php. -restore_file() { # $1 = path relative to ROOT - local name tmp; name=$(basename "$1") - if [ -e "$BACKUP/$name" ]; then - tmp=$(mktemp "$ROOT/$(dirname "$1")/.$name.restore.XXXXXX") \ - && cp -a "$BACKUP/$name" "$tmp" && mv -f "$tmp" "$ROOT/$1" || { rm -f "$tmp"; return 1; } - else - rm -f "$ROOT/$1" - fi -} - restore() { set +e local id term hook option for id in "${CREATED_POSTS[@]}"; do wp post delete "$id" --force >/dev/null 2>&1; done for term in "${CREATED_TERMS[@]}"; do wp term delete category "$term" >/dev/null 2>&1; done wp option update blogdescription "$ORIGINAL_DESCRIPTION" >/dev/null 2>&1 - wp option delete lit_mcp_wpsc_check_report lit_mcp_wpsc_releases lit_mcp_wpsc_check_lock >/dev/null 2>&1 wp transient delete "$PURGED_AT" >/dev/null 2>&1 cleanup_app_passwords_for "$ADMIN_LOGIN" - # Deactivation lets WP Super Cache undo its own edits (WP_CACHE, WPCACHEHOME, advanced-cache.php, cache files) ... - wp plugin is-active wp-super-cache >/dev/null 2>&1 && wp plugin deactivate wp-super-cache >/dev/null 2>&1 - # ... and what it leaves, or does differently, is set back from the saved state. - restore_file wp-config.php - for id in "${SAVED_FILES[@]}"; do restore_file "$id"; done - [ -d "$CACHE_DIR" ] && find "$CACHE_DIR" -mindepth 1 -delete - if [ "$CACHE_DIR_EXISTED" = no ]; then - rmdir "$CACHE_DIR" 2>/dev/null + if [ "$WPFC_OPTION_BEFORE" = __absent__ ]; then + wp option delete WpFastestCache >/dev/null 2>&1 else - # Deactivation removes the whole cache directory; mkdir -m keeps the inherited ACL of its parent. - [ -d "$CACHE_DIR" ] || mkdir -m "$CACHE_DIR_MODE" "$CACHE_DIR" + wp option update WpFastestCache "$WPFC_OPTION_BEFORE" >/dev/null 2>&1 fi - # The profile's cron event and every option or event the plugin added. + [ "$WPFC_WAS_ACTIVE" = no ] && wp plugin deactivate wp-fastest-cache >/dev/null 2>&1 for hook in $(list_hooks); do grep -qx "$hook" <<<"$HOOKS_BEFORE" || wp cron event delete "$hook" >/dev/null 2>&1 done @@ -106,30 +67,11 @@ restore() { # Prints the proof; returns non-zero on any difference (used in the trap, so it must not exit). verify_restored() { - local ok=0 name after - after=$(sums "$WP_CONFIG") - if [ "$after" = "$CONFIG_SUM_BEFORE" ]; then pass "restored: wp-config.php sha256 $CONFIG_SUM_BEFORE (before) == $after (after)" - else echo "FAIL: wp-config.php sha256 $CONFIG_SUM_BEFORE (before) != $after (after)"; ok=1; fi - [ "$(stat -c '%a %U:%G' "$WP_CONFIG")" = "$CONFIG_STAT_BEFORE" ] \ - || { echo "FAIL: wp-config.php mode/owner changed: $CONFIG_STAT_BEFORE -> $(stat -c '%a %U:%G' "$WP_CONFIG")"; ok=1; } - for f in "${SAVED_FILES[@]}"; do - name=$(basename "$f") - if [ -e "$BACKUP/$name" ]; then - [ "$(sums "$ROOT/$f")" = "$(cat "$BACKUP/$name.sum")" ] || { echo "FAIL: $f differs from the saved one"; ok=1; } - else - [ ! -e "$ROOT/$f" ] || { echo "FAIL: $f was absent before and exists now"; ok=1; } - fi - done - [ "$ok" = 0 ] && pass "restored: advanced-cache.php and wp-cache-config.php as before (absent: $([ -e "$BACKUP/advanced-cache.php" ] && echo no || echo yes))" - local htaccess_after=absent; [ -e "$HTACCESS" ] && htaccess_after=$(sums "$HTACCESS") + local ok=0 htaccess_after=absent + [ "$(wp option get WpFastestCache 2>/dev/null || echo __absent__)" = "$WPFC_OPTION_BEFORE" ] \ + || { echo "FAIL: option WpFastestCache differs"; ok=1; } + [ -e "$HTACCESS" ] && htaccess_after=$(sums "$HTACCESS") [ "$htaccess_after" = "$HTACCESS_BEFORE" ] || { echo "FAIL: .htaccess changed ($HTACCESS_BEFORE -> $htaccess_after)"; ok=1; } - if [ "$CACHE_DIR_EXISTED" = yes ]; then - [ -d "$CACHE_DIR" ] && [ -z "$(find "$CACHE_DIR" -mindepth 1 -print -quit)" ] && [ "$(stat -c '%a' "$CACHE_DIR")" = "$CACHE_DIR_MODE" ] \ - || { echo "FAIL: cache directory not empty, gone or its mode changed"; ok=1; } - else - [ ! -e "$CACHE_DIR" ] || { echo "FAIL: cache directory was absent before and exists now"; ok=1; } - fi - wp plugin is-active wp-super-cache 2>/dev/null && { echo "FAIL: WP Super Cache is still active"; ok=1; } [ "$(wp option get active_plugins --format=json)" = "$ACTIVE_PLUGINS_BEFORE" ] || { echo "FAIL: active_plugins differs"; ok=1; } [ "$(list_hooks)" = "$HOOKS_BEFORE" ] || { echo "FAIL: cron events differ: $(list_hooks | tr '\n' ' ')"; ok=1; } [ "$(list_options)" = "$OPTIONS_BEFORE" ] || { echo "FAIL: options differ: $(list_options | tr '\n' ' ')"; ok=1; } @@ -137,8 +79,9 @@ verify_restored() { [ "$(wp option get blogdescription)" = "$ORIGINAL_DESCRIPTION" ] || { echo "FAIL: blogdescription not restored"; ok=1; } [ "$(wp post list --post_type=any --post_status=any --meta_key=_lit_mcp_check_entry --format=count)" = 0 ] \ || { echo "FAIL: a probe page is left"; ok=1; } - [ "$(wp post list --post_type=post --post_status=any --name=lit-wpsc-smoke --format=count)" = 0 ] \ + [ "$(wp post list --post_type=post --post_status=any --name=lit-wpfc-smoke --format=count)" = 0 ] \ || { echo "FAIL: the test post is left"; ok=1; } + [ "$ok" = 0 ] && pass "restored: WP Fastest Cache active=$WPFC_WAS_ACTIVE with its option as before, .htaccess untouched" return "$ok" } @@ -146,7 +89,6 @@ on_exit() { local rc=$1 restore verify_restored || rc=1 - rm -rf "$BACKUP" guard_exit "$rc" || exit $? exit "$rc" } @@ -154,11 +96,11 @@ trap 'on_exit $?' EXIT trap 'exit 130' INT TERM HUP # --- Helpers --------------------------------------------------------------------------------------------------------- -wpsc_component() { tool lit-site-status '{}' | jq -c '.components[] | select(.id == "wpsc")'; } +wpfc_component() { tool lit-site-status '{}' | jq -c '.components[] | select(.id == "wpfc")'; } stored_option() { wp option get "$1" --format=json 2>/dev/null || true; } event_count() { wp cron event list --hook="$TRAILING_HOOK" --format=count 2>/dev/null || echo 0; } -# The cache file of the test post as WP Super Cache keeps it (index.html; a ".needs-rebuild" copy is a purged one). -cache_file() { find "$SUPERCACHE_DIR" -type f -path '*lit-wpsc-smoke*' -name index.html 2>/dev/null | head -1; } +# The cache file of the test post where WP Fastest Cache keeps it (a purge moves the directory to cache/tmpWpfc). +cache_file() { local f="$CACHE_ALL${POST_PATH%/}/index.html"; [ -f "$f" ] && echo "$f" || true; } wait_for() { # $1 = description, rest = command; polls up to 15 s local what=$1 i; shift for i in $(seq 1 15); do "$@" && return 0; sleep 1; done @@ -166,97 +108,78 @@ wait_for() { # $1 = description, rest = command; polls up to 15 s } cache_file_exists() { [ -n "$(cache_file)" ]; } cache_file_gone() { [ -z "$(cache_file)" ]; } -front_page_by_wpsc() { [[ "$(curl -s -H "Host: $HOST" "$BASE/")" == *WP-Super-Cache* ]]; } -# One public request for the test post; the cache file appears at the end of the request. fetch_post() { local code code=$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $HOST" "$BASE$POST_PATH") [ "$code" = 200 ] || fail "GET $POST_PATH answered $code" } -# Requests the test post until WP Super Cache has written its file (the first request after a purge or a new post -# is not always the one that is stored). +# Requests the test post until WP Fastest Cache has written its file. fill_cache() { local i for i in $(seq 1 10); do fetch_post - sleep 1 cache_file_exists && return 0 + sleep 1 done - echo "--- response of $POST_PATH (selected headers) and the cache tree:" - curl -s -i -H "Host: $HOST" "$BASE$POST_PATH" | grep -iE '^(HTTP|cache-control|set-cookie)|WP Super Cache|Page not cached' | head -10 - find "$CACHE_DIR" | head -20 + echo "--- end of the response of $POST_PATH and the cache tree:" + curl -s -H "Host: $HOST" "$BASE$POST_PATH" | tail -c 300 + find "$CACHE_ALL" -maxdepth 4 | head -20 fail "timeout: cache file of the test post" } -config_value() { grep -E "^\s*\\\$$1\s*=" "$ROOT/wp-content/wp-cache-config.php" | head -1; } -# --- 0. Activate and look at what WP Super Cache did ------------------------------------------------------------------ -wp plugin activate wp-super-cache >/dev/null 2>&1 || fail "could not activate WP Super Cache" -grep -q "define( *'WP_CACHE', *true *)" "$WP_CONFIG" || fail "activation did not set WP_CACHE (the run cannot cache)" -[ -e "$ROOT/wp-content/advanced-cache.php" ] || fail "activation did not create advanced-cache.php" -# WP Super Cache keys its files by the host of the request: MCP calls must carry the host the page was cached under. -MCP_HOST=$HOST +# --- 0. Active, cache switched off ----------------------------------------------------------------------------------- +[ "$WPFC_WAS_ACTIVE" = yes ] || wp plugin activate wp-fastest-cache >/dev/null 2>&1 || fail "could not activate WP Fastest Cache" +wp option update WpFastestCache '{}' >/dev/null connect "$ADMIN_LOGIN" # 1. Status -wpsc_component | jq -e '.active == true and .version == "3.1.5" and .problems == []' >/dev/null \ - || fail "site status must show WP Super Cache 3.1.5 active without problems: $(wpsc_component)" -pass "lit-site-status: WP Super Cache 3.1.5 active, no problems" +wpfc_component | jq -e '.active == true and .version == "1.5.5" and .problems == []' >/dev/null \ + || fail "site status must show WP Fastest Cache 1.5.5 active without problems: $(wpfc_component)" +pass "lit-site-status: WP Fastest Cache 1.5.5 active, no problems" -# 2. Check route with the cache still switched off: the probe step is green and says "not checked" +# 2. Check route with the cache switched off: the probe step is green and says "not checked" APP_PASSWORD=$(wp user application-password create "$ADMIN_LOGIN" lit-mcp-integration-test --porcelain) run_check() { local headers - headers=$(curl -s -o /dev/null -D - -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' -u "$ADMIN_LOGIN:$APP_PASSWORD" "$BASE/wp-json/lit-mcp/v1/wpsc-check") + headers=$(curl -s -o /dev/null -D - -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' -u "$ADMIN_LOGIN:$APP_PASSWORD" "$BASE/wp-json/lit-mcp/v1/wpfc-check") echo "$headers" | grep -qi '^HTTP/[0-9.]* 303' || fail "check route must answer 303: $headers" echo "$headers" | grep -qi '^location:.*tab=compatibility.*lit_mcp_notice=compat_checked' || fail "redirect to the tab: $headers" } -[ "$(wp eval 'echo (int) ! empty($GLOBALS["cache_enabled"]);')" = 0 ] || fail "the cache must be off after activation" run_check -REPORT=$(stored_option lit_mcp_wpsc_check_report) +REPORT=$(stored_option lit_mcp_wpfc_check_report) echo "$REPORT" | jq -e '(.steps | length) == 2 and all(.steps[]; .problems == [] and .skipped == false) and ([.steps[] | select(.key == "probe")] | length == 1 and (.[0].label | contains("Cache ist ausgeschaltet, nicht geprüft")))' >/dev/null \ || fail "cache off: the report must be green and the probe must say it did not check: $REPORT" -pass "/wpsc-check with the cache off: green, probe step says 'Cache ist ausgeschaltet, nicht geprüft'" +pass "/wpfc-check with the cache off: green, probe step says 'Cache ist ausgeschaltet, nicht geprüft'" -# 3. Switch the cache on in the plain PHP mode (no mod_rewrite, no .htaccess) -wp eval 'wp_cache_enable();' >/dev/null 2>&1 || fail "wp_cache_enable failed" -# PHP-FPM's opcache re-reads a changed wp-cache-config.php at most every 2 s: wait until a public request is served -# by WP Super Cache (its HTML comment), the state the checks below need. -wait_for "WP Super Cache serves public requests" front_page_by_wpsc -config_value cache_enabled | grep -q 'true' || fail "wp-cache-config.php: cache_enabled is not true: $(config_value cache_enabled)" -config_value wp_cache_mod_rewrite | grep -Eq '= *0;' || fail "mod_rewrite mode is on: $(config_value wp_cache_mod_rewrite)" -config_value super_cache_enabled | grep -q 'true' || fail "super_cache_enabled is not true: $(config_value super_cache_enabled)" -[ "$(config_value wp_cache_clear_on_post_edit | grep -Ec '= *0;')" = 1 ] || fail "clear_on_post_edit must be 0: $(config_value wp_cache_clear_on_post_edit)" -[ ! -e "$HTACCESS" ] || [ "$(sums "$HTACCESS")" = "$HTACCESS_BEFORE" ] || fail ".htaccess changed" -pass "cache on in the plain PHP mode (mod_rewrite 0, clear_on_post_edit 0), .htaccess untouched" - -# 4. Check route with the cache on: the purge probe really runs +# 3. Cache on; the check route runs the real purge probe +wp option update WpFastestCache "$CACHE_ON" >/dev/null run_check -REPORT=$(stored_option lit_mcp_wpsc_check_report) +REPORT=$(stored_option lit_mcp_wpfc_check_report) echo "$REPORT" | jq -e '(.steps | length) == 2 and all(.steps[]; .problems == [] and .skipped == false) and ([.steps[] | select(.key == "probe")] | length == 1 and (.[0].label | contains("nicht geprüft") | not)) - and .version == "3.1.5"' >/dev/null || fail "cache on: the report must be green with a real probe step: $REPORT" -[ -z "$(stored_option lit_mcp_wpsc_check_lock)" ] || fail "the check lock must be released" + and .version == "1.5.5"' >/dev/null || fail "cache on: the report must be green with a real probe step: $REPORT" +[ -z "$(stored_option lit_mcp_wpfc_check_lock)" ] || fail "the check lock must be released" [ "$(wp post list --post_type=any --post_status=any --meta_key=_lit_mcp_check_entry --format=count)" = 0 ] || fail "the probe left a page behind" -[ "$(find "$SUPERCACHE_DIR" -path '*lit-mcp-check-*' -print -quit 2>/dev/null)" = "" ] || fail "the probe left a cache directory behind" -UNAUTH=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' "$BASE/wp-json/lit-mcp/v1/wpsc-check") +[ "$(find "$CACHE_ALL" -maxdepth 3 -path '*lit-mcp-check-*' -print -quit 2>/dev/null)" = "" ] || fail "the probe left a cache directory behind" +UNAUTH=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Host: $HOST" -H 'X-Forwarded-Proto: https' "$BASE/wp-json/lit-mcp/v1/wpfc-check") [ "$UNAUTH" = 401 ] || [ "$UNAUTH" = 403 ] || fail "anonymous check must be refused, got $UNAUTH" -pass "/wpsc-check with the cache on: green with the purge probe, no page or cache directory left, anonymous refused" +pass "/wpfc-check with the cache on: green with the purge probe, no page or cache directory left, anonymous refused" -# 5. A published test post, fetched until it is in the cache -CAT_A=$(wp term create category "LIT WPSC Smoke A" --porcelain); CREATED_TERMS+=("$CAT_A") -CAT_B=$(wp term create category "LIT WPSC Smoke B" --porcelain); CREATED_TERMS+=("$CAT_B") -POST_ID=$(wp post create --post_type=post --post_status=publish --post_title="LIT WPSC Smoke" --post_name=lit-wpsc-smoke \ - --post_content="Testbeitrag fuer den Super-Cache-Smoke." --porcelain) +# 4. A published test post, fetched until it is in the cache +CAT_A=$(wp term create category "LIT WPFC Smoke A" --porcelain); CREATED_TERMS+=("$CAT_A") +CAT_B=$(wp term create category "LIT WPFC Smoke B" --porcelain); CREATED_TERMS+=("$CAT_B") +POST_ID=$(wp post create --post_type=post --post_status=publish --post_title="LIT WPFC Smoke" --post_name=lit-wpfc-smoke \ + --post_content="Testbeitrag fuer den Fastest-Cache-Smoke." --porcelain) CREATED_POSTS+=("$POST_ID") POST_PATH=$(wp eval "echo wp_parse_url(get_permalink($POST_ID), PHP_URL_PATH);") fill_cache -pass "test post $POST_ID cached: $(cache_file | sed "s#$CACHE_DIR/##")" +pass "test post $POST_ID cached: $(cache_file | sed "s#$ROOT/##")" -# 6. Control: a term change outside this plugin does not touch the cache (WP Super Cache never sees it) ... +# 5. Control: a term change outside this plugin does not touch the cache (WP Fastest Cache never sees it) ... wp post term set "$POST_ID" category "$CAT_A" >/dev/null sleep 2 -cache_file_exists || fail "control: WP Super Cache itself purged on a plain term change; the next check proves nothing" +cache_file_exists || fail "control: WP Fastest Cache itself purged on a plain term change; the next check proves nothing" # ... but the same kind of change through lit-content-update (no wp_update_post) empties the cache of the post. REV=$(tool lit-content-get "{\"post_id\":$POST_ID}" | jq -r '.rev') UPDATED=$(tool lit-content-update "{\"post_id\":$POST_ID,\"rev\":\"$REV\",\"terms\":{\"category\":[$CAT_B]}}") @@ -265,10 +188,10 @@ wait_for "the cache file of the post is gone after lit-content-update" cache_fil [ "$(event_count)" = 0 ] || fail "a single-post purge must not schedule a trailing purge" pass "terms-only lit-content-update empties the cache of the post (a plain term change does not), no trailing event" -# 7. Whole-cache purge after a setting; the second one inside 30 s is throttled and leaves exactly one cron event +# 6. Whole-cache purge after a setting; the second one inside 30 s is throttled and leaves exactly one cron event fill_cache [ -z "$(wp transient get "$PURGED_AT" 2>/dev/null || true)" ] || fail "transient $PURGED_AT set before the first whole purge" -ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPSC Smoke 1"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 1" +ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPFC Smoke 1"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 1" wait_for "the cache is empty after a setting change" cache_file_gone wait_for "the purge time is stored" bash -c "[ -n \"\$(wp transient get $PURGED_AT 2>/dev/null)\" ]" LAST=$(wp transient get "$PURGED_AT") @@ -276,18 +199,18 @@ LAST=$(wp transient get "$PURGED_AT") pass "setting change empties the whole cache (purge time stored, no trailing event)" fill_cache -ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPSC Smoke 2"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 2" +ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPFC Smoke 2"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 2" wait_for "one trailing purge is scheduled" bash -c "[ \"\$(wp cron event list --hook=$TRAILING_HOOK --format=count)\" = 1 ]" sleep 2 cache_file_exists || fail "throttled: the second whole purge ran inside the lock time" -ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPSC Smoke 3"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 3" +ability lit/wp-settings-update '{"settings":{"blogdescription":"LIT WPFC Smoke 3"}}' | jq -e '.ok == true' >/dev/null || fail "settings update 3" sleep 3 [ "$(event_count)" = 1 ] || fail "exactly one trailing event must stay scheduled, found $(event_count)" cache_file_exists || fail "throttled: the third whole purge ran inside the lock time" [ "$(wp transient get "$PURGED_AT")" = "$LAST" ] || fail "throttled purges must not move the purge time" pass "second and third whole purge inside 30 s: throttled (file stays, purge time unchanged), exactly one $TRAILING_HOOK event" -# 8. Run the trailing purge once the lock is over: the cache is emptied and nothing is scheduled any more +# 7. Run the trailing purge once the lock is over: the cache is emptied and nothing is scheduled any more WAIT=$((LAST + 32 - $(date +%s))); [ "$WAIT" -gt 0 ] && sleep "$WAIT" wp cron event run "$TRAILING_HOOK" >/dev/null 2>&1 || fail "wp cron event run $TRAILING_HOOK" cache_file_gone || fail "the trailing purge did not empty the cache" @@ -295,7 +218,7 @@ cache_file_gone || fail "the trailing purge did not empty the cache" [ "$(wp transient get "$PURGED_AT")" -gt "$LAST" ] || fail "the trailing purge must store a new purge time" pass "trailing purge by cron: cache emptied, event gone, purge time renewed" -# 8b. lit/cache-clear: the dry run changes nothing, confirm empties the post's page / the whole cache +# 8. lit/cache-clear: the dry run changes nothing, confirm empties the post's page / the whole cache fill_cache for ARGS in "{\"scope\":\"post\",\"post_id\":$POST_ID}" '{"scope":"all"}'; do DRY=$(ability lit/cache-clear "$ARGS") @@ -339,6 +262,6 @@ pass "lit/cache-clear scope all inside 30 s: throttled, one trailing event, cron # 9. Everything back as before (the EXIT trap would do it too; here it is part of the verdict) restore verify_restored || fail "the state before the run was not restored" -pass "cleanup: plugin inactive, test post, categories, options, transient and cron events gone" +pass "cleanup: test post, categories, options, transient and cron events gone" finish diff --git a/tests/Integration/lib.sh b/tests/Integration/lib.sh index 6ae23dd..5f6ceab 100644 --- a/tests/Integration/lib.sh +++ b/tests/Integration/lib.sh @@ -6,7 +6,7 @@ # Requires jq and curl (both present in the DDEV web image). # Mutual exclusion of ALL smokes (every one sources this file inside the same web container): two at once would -# share one site state – WP Super Cache edits wp-config.php, every smoke empties the test application passwords of the +# share one site state – WP Fastest Cache edits wp-config.php, every smoke empties the test application passwords of the # admin (cleanup_app_passwords_for, harmless only because of this lock), Pods/Divi release options. The lock lives as # long as the script (fd 9 closes with it); a smoke that runs a second one itself must not source this file twice. exec 9>/tmp/lit-mcp-smoke.lock @@ -22,7 +22,7 @@ ENDPOINT=http://localhost/wp-json/mcp/mcp-oauth-server HOST=$(wp option get home | sed -E 's#^[a-zA-Z]+://##') # Optional: the Host header MCP requests carry (default: curl's own, "localhost"). A cache plugin that keys its files -# by the request's host (WP Super Cache) needs the site's real host here (super-cache-smoke.sh sets it). +# by the request's host (WP Fastest Cache) needs the site's real host here (none of the current smokes needs it). MCP_HOST=${MCP_HOST:-} TOKEN="" diff --git a/tests/Unit/Admin/CompatibilityTabTest.php b/tests/Unit/Admin/CompatibilityTabTest.php index 23f7945..cbf8ab1 100644 --- a/tests/Unit/Admin/CompatibilityTabTest.php +++ b/tests/Unit/Admin/CompatibilityTabTest.php @@ -173,11 +173,11 @@ final class CompatibilityTabTest extends TestCase return (string) ob_get_clean(); } - /** One source for the ability name of the release and withdraw entries (SuperCacheListener reads it too). */ + /** One source for the ability name of the release and withdraw entries (FastestCacheListener reads it too). */ public function testTheReleaseAbilityNameHasOneSource(): void { self::assertSame('lit/divi-release', CompatibilityTab::releaseAbility('divi')); - self::assertTrue(CompatibilityTab::isReleaseAbility(CompatibilityTab::releaseAbility('wpsc'))); + self::assertTrue(CompatibilityTab::isReleaseAbility(CompatibilityTab::releaseAbility('wpfc'))); foreach (['lit/content-update', 'lit/-release', 'lit/release', 'lit/divi-releases'] as $other) { self::assertFalse(CompatibilityTab::isReleaseAbility($other), $other); } diff --git a/tests/Unit/Compatibility/ThrowawayPostTest.php b/tests/Unit/Compatibility/ThrowawayPostTest.php index f654326..764d575 100644 --- a/tests/Unit/Compatibility/ThrowawayPostTest.php +++ b/tests/Unit/Compatibility/ThrowawayPostTest.php @@ -245,7 +245,7 @@ final class ThrowawayPostTest extends TestCase self::assertSame([], $this->throwaway()->removeOrphans('page')); } - /** A probe killed while its page was private (WP Super Cache) leaves a private page, not a draft. */ + /** Private entries count too (the old WP Super Cache probe passed through private). */ public function testShortStatusListCoversAProbeKilledWhilePrivate(): void { $query = null; diff --git a/tests/Unit/Integrations/SuperCache/CacheClearAbilityTest.php b/tests/Unit/Integrations/FastestCache/CacheClearAbilityTest.php similarity index 86% rename from tests/Unit/Integrations/SuperCache/CacheClearAbilityTest.php rename to tests/Unit/Integrations/FastestCache/CacheClearAbilityTest.php index 717a2c3..ffc2514 100644 --- a/tests/Unit/Integrations/SuperCache/CacheClearAbilityTest.php +++ b/tests/Unit/Integrations/FastestCache/CacheClearAbilityTest.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Integrations\SuperCache; +namespace LIT\MCP\Tests\Unit\Integrations\FastestCache; use Brain\Monkey\Functions; use LIT\MCP\Abilities\Ability; use LIT\MCP\Abilities\Annotations; use LIT\MCP\Abilities\DeclaresRequirements; -use LIT\MCP\Integrations\SuperCache\CacheClearAbility; -use LIT\MCP\Integrations\SuperCache\SuperCacheFeature; -use LIT\MCP\Integrations\SuperCache\SuperCachePurger; +use LIT\MCP\Integrations\FastestCache\CacheClearAbility; +use LIT\MCP\Integrations\FastestCache\FastestCacheFeature; +use LIT\MCP\Integrations\FastestCache\FastestCachePurger; use LIT\MCP\Tests\Support\FakeChangeLog; use LIT\MCP\Tests\Unit\TestCase; use WP_Error; @@ -51,20 +51,20 @@ final class CacheClearAbilityTest extends TestCase protected function tearDown(): void { - unset($GLOBALS['cache_enabled']); + unset($GLOBALS['wp_fastest_cache_options']); parent::tearDown(); } private function ability(?FakeChangeLog $log = null, bool $cacheOn = true, bool $usable = true): CacheClearAbility { - $purger = new SuperCachePurger( - static fn (SuperCacheFeature $feature): bool => $usable, + $purger = new FastestCachePurger( + static fn (FastestCacheFeature $feature): bool => $usable, static fn (): bool => $cacheOn, function (int $id): void { $this->purges[] = 'post:' . $id; }, - function (int $blog): void { - $this->purges[] = 'all:' . $blog; + function (): void { + $this->purges[] = 'all'; } ); @@ -80,7 +80,7 @@ final class CacheClearAbilityTest extends TestCase self::assertFalse($ability->isDirect()); self::assertSame(Annotations::write(true), $ability->annotations()); self::assertStringContainsString('confirm: true', $ability->description()); - self::assertStringContainsString('WP Super Cache', $ability->description()); + self::assertStringContainsString('WP Fastest Cache', $ability->description()); $schema = $ability->inputSchema(); self::assertSame(['scope'], $schema['required']); @@ -95,8 +95,8 @@ final class CacheClearAbilityTest extends TestCase $ability = $this->ability(); self::assertInstanceOf(DeclaresRequirements::class, $ability); - self::assertSame(['wpsc-purge-all'], $ability->requirements(['scope' => 'all'])); - self::assertSame(['wpsc-purge-post'], $ability->requirements(['scope' => 'post', 'post_id' => 7])); + self::assertSame(['wpfc-purge-all'], $ability->requirements(['scope' => 'all'])); + self::assertSame(['wpfc-purge-post'], $ability->requirements(['scope' => 'post', 'post_id' => 7])); self::assertSame([], $ability->requirements(['scope' => 'x'])); self::assertSame([], $ability->requirements([])); } @@ -165,7 +165,7 @@ final class CacheClearAbilityTest extends TestCase public function testTheDryRunClearsNothingAndReportsTheState(): void { - $GLOBALS['cache_enabled'] = true; + $GLOBALS['wp_fastest_cache_options'] = (object) ['wpFastestCacheStatus' => 'on']; $this->lastPurge = time() - 5; $payload = self::assertToolError($this->ability()->execute(['scope' => 'all']), 'confirm_required'); @@ -202,17 +202,16 @@ final class CacheClearAbilityTest extends TestCase self::assertTrue($result['ok']); self::assertSame('purged', $result['outcome']); self::assertSame(4, $result['log_id']); - $log->assertRecorded(['ability' => 'lit/cache-clear', 'object_type' => 'wpsc', 'object_id' => '7']); + $log->assertRecorded(['ability' => 'lit/cache-clear', 'object_type' => 'wpfc', 'object_id' => '7']); } - public function testADraftHasNothingToClear(): void + /** WPFC finds the address of any post (a trashed one too), so no status is skipped. */ + public function testADraftIsClearedToo(): void { - $GLOBALS['cache_enabled'] = true; - $result = $this->ability()->execute(['scope' => 'post', 'post_id' => 8, 'confirm' => true]); + $result = $this->ability(new FakeChangeLog(2))->execute(['scope' => 'post', 'post_id' => 8, 'confirm' => true]); - self::assertSame([], $this->purges); - self::assertSame('nothing', $result['outcome']); - self::assertStringContainsString('Entwurf', $result['summary']); + self::assertSame(['post:8'], $this->purges); + self::assertSame('purged', $result['outcome']); } public function testConfirmClearsTheWholeCacheAtOnce(): void @@ -221,9 +220,9 @@ final class CacheClearAbilityTest extends TestCase $result = $this->ability($log)->execute(['scope' => 'all', 'confirm' => true]); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); self::assertSame('purged', $result['outcome']); - $log->assertRecorded(['ability' => 'lit/cache-clear', 'object_type' => 'wpsc', 'object_id' => 'all']); + $log->assertRecorded(['ability' => 'lit/cache-clear', 'object_type' => 'wpfc', 'object_id' => 'all']); } public function testAWholePurgeInsideTheLockTimeIsThrottledWithATrailingPurge(): void @@ -233,7 +232,7 @@ final class CacheClearAbilityTest extends TestCase $result = $this->ability(new FakeChangeLog(1))->execute(['scope' => 'all', 'confirm' => true]); self::assertSame([], $this->purges); - self::assertSame([SuperCachePurger::TRAILING_HOOK], $this->scheduled); + self::assertSame([FastestCachePurger::TRAILING_HOOK], $this->scheduled); self::assertSame('throttled', $result['outcome']); self::assertStringContainsString('Nachlauf', $result['summary']); } @@ -271,7 +270,7 @@ final class CacheClearAbilityTest extends TestCase self::assertInstanceOf(WP_Error::class, $result); self::assertSame('lit_compat_incompatible', $result->get_error_code()); self::assertStringContainsString( - 'WP Super Cache ist gesperrt (siehe Reiter Kompatibilität). Es wurde nichts geleert.', + 'WP Fastest Cache ist gesperrt (siehe Reiter Kompatibilität). Es wurde nichts geleert.', $result->get_error_message() ); self::assertSame([], $this->purges); diff --git a/tests/Unit/Integrations/SuperCache/SuperCacheCheckStepsTest.php b/tests/Unit/Integrations/FastestCache/FastestCacheCheckStepsTest.php similarity index 56% rename from tests/Unit/Integrations/SuperCache/SuperCacheCheckStepsTest.php rename to tests/Unit/Integrations/FastestCache/FastestCacheCheckStepsTest.php index 723d58f..067dfd6 100644 --- a/tests/Unit/Integrations/SuperCache/SuperCacheCheckStepsTest.php +++ b/tests/Unit/Integrations/FastestCache/FastestCacheCheckStepsTest.php @@ -2,33 +2,33 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Integrations\SuperCache; +namespace LIT\MCP\Tests\Unit\Integrations\FastestCache; use LIT\MCP\Compatibility\CheckStep; -use LIT\MCP\Integrations\SuperCache\SuperCacheCheckSteps; -use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility; +use LIT\MCP\Integrations\FastestCache\FastestCacheCheckSteps; +use LIT\MCP\Integrations\FastestCache\FastestCacheCompatibility; use LIT\MCP\Tests\Unit\TestCase; -final class SuperCacheCheckStepsTest extends TestCase +final class FastestCacheCheckStepsTest extends TestCase { public function testTheProbeIsSkippedWithARedManifestAndNotRun(): void { $ran = false; - $steps = SuperCacheCheckSteps::extra(static function () use (&$ran): array { + $steps = FastestCacheCheckSteps::extra(static function () use (&$ran): array { $ran = true; return []; })(false); self::assertCount(1, $steps); - self::assertSame(SuperCacheCheckSteps::PROBE, $steps[0]->key); + self::assertSame(FastestCacheCheckSteps::PROBE, $steps[0]->key); self::assertTrue($steps[0]->skipped); self::assertFalse($ran); } public function testTheStepCarriesTheProbeProblems(): void { - $steps = SuperCacheCheckSteps::extra(static fn (): array => ['rot'])(true); + $steps = FastestCacheCheckSteps::extra(static fn (): array => ['rot'])(true); self::assertSame(['rot'], $steps[0]->problems); self::assertFalse($steps[0]->skipped); @@ -38,7 +38,7 @@ final class SuperCacheCheckStepsTest extends TestCase /** A switched-off cache is no red step: a skipped one would lock the purge of a site that simply caches nothing. */ public function testWithoutAnActiveCacheTheStepIsGreenAndSaysSo(): void { - $steps = SuperCacheCheckSteps::extra(static fn (): ?array => null)(true); + $steps = FastestCacheCheckSteps::extra(static fn (): ?array => null)(true); self::assertTrue($steps[0]->green()); self::assertStringContainsString('Cache ist ausgeschaltet, nicht geprüft', $steps[0]->label()); @@ -46,11 +46,11 @@ final class SuperCacheCheckStepsTest extends TestCase public function testTheLabelOfAnOldStepComesFromTheKey(): void { - self::assertSame('Leeren-Test an einer Wegwerf-Seite im Cache', SuperCacheCheckSteps::label(new CheckStep(SuperCacheCheckSteps::PROBE, []))); + self::assertSame('Leeren-Test an einer Wegwerf-Seite im Cache', FastestCacheCheckSteps::label(new CheckStep(FastestCacheCheckSteps::PROBE, []))); self::assertSame( - SuperCacheCompatibility::LABEL . '-Funktionen, Klassen und Registrierungen', - SuperCacheCheckSteps::label(new CheckStep(CheckStep::MANIFEST, [])) + FastestCacheCompatibility::LABEL . '-Funktionen, Klassen und Registrierungen', + FastestCacheCheckSteps::label(new CheckStep(CheckStep::MANIFEST, [])) ); - self::assertSame('other', SuperCacheCheckSteps::label(new CheckStep('other', []))); + self::assertSame('other', FastestCacheCheckSteps::label(new CheckStep('other', []))); } } diff --git a/tests/Unit/Integrations/FastestCache/FastestCacheCompatibilityTest.php b/tests/Unit/Integrations/FastestCache/FastestCacheCompatibilityTest.php new file mode 100644 index 0000000..55aa8fe --- /dev/null +++ b/tests/Unit/Integrations/FastestCache/FastestCacheCompatibilityTest.php @@ -0,0 +1,136 @@ +id); + self::assertSame('WP Fastest Cache', $profile->label); + self::assertSame(Tier::LockOnFailure, $profile->tier); + self::assertSame(['1.5.5'], $profile->testedVersions); + self::assertNull($profile->minVersion); + self::assertSame(FastestCacheFeature::cases(), $profile->capabilities); + } + + /** Argument counts are those the purger and the probe pass. */ + public function testTheManifestHoldsTheTwoPurgeFunctionsWithTheirArgumentCounts(): void + { + $manifest = FastestCacheCompatibility::profile()->manifests[0]; + $calls = []; + foreach ($manifest->calls as $call) { + $calls[$call->symbol] = [$call->args, $call->features()]; + } + + self::assertSame([ + 'wpfc_clear_post_cache_by_id' => [1, [FastestCacheFeature::PurgePost]], + 'wpfc_clear_all_cache' => [0, [FastestCacheFeature::PurgeAll]], + 'WpFastestCache::singleDeleteCache' => [3, [FastestCacheFeature::PurgePost]], + 'WpFastestCache::getWpContentDir' => [1, [FastestCacheFeature::PurgePost]], + ], $calls); + self::assertContainsOnlyInstancesOf(Call::class, $manifest->calls); + } + + /** The instance that hooks the purge actions and the decoded options with the cache switch. */ + public function testTheGlobalsTheFunctionsReadAreRequired(): void + { + $manifest = FastestCacheCompatibility::profile()->manifests[0]; + + self::assertSame(['WPFC_MAIN_PATH'], array_map(static fn ($c) => $c->name, $manifest->constants)); + $globals = []; + foreach ($manifest->globals as $global) { + $globals[$global->name] = $global->features(); + } + self::assertSame( + ['wp_fastest_cache' => FastestCacheFeature::cases(), 'wp_fastest_cache_options' => FastestCacheFeature::cases()], + $globals + ); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testActiveAndVersionFollowTheMainPathConstantAndThePluginHeader(): void + { + $profile = FastestCacheCompatibility::profile(); + self::assertFalse(($profile->active)()); + self::assertNull(($profile->version)()); + + define('WPFC_MAIN_PATH', '/plugins/wp-fastest-cache/'); + Functions\expect('get_file_data') + ->once() + ->with('/plugins/wp-fastest-cache/wpFastestCache.php', ['Version' => 'Version']) + ->andReturn(['Version' => '1.5.5']); + + self::assertTrue(($profile->active)()); + self::assertSame('1.5.5', ($profile->version)()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testAnUnreadableHeaderIsAnUnknownVersion(): void + { + define('WPFC_MAIN_PATH', '/plugins/wp-fastest-cache/'); + Functions\when('get_file_data')->justReturn(['Version' => '']); + + self::assertNull((FastestCacheCompatibility::profile()->version)()); + } + + public function testTheCheckHasTheProbeAsExtraStep(): void + { + $profile = FastestCacheCompatibility::profile(); + + self::assertNotNull($profile->extraSteps); + $steps = ($profile->extraSteps)(false); + self::assertSame([FastestCacheCheckSteps::PROBE], array_map(static fn ($step): string => $step->key, $steps)); + self::assertTrue($steps[0]->skipped); + self::assertNotNull($profile->stepLabel); + self::assertSame( + 'Leeren-Test an einer Wegwerf-Seite im Cache', + ($profile->stepLabel)(new CheckStep(FastestCacheCheckSteps::PROBE, [])) + ); + } + + public function testTheCacheIsOnlyEnabledWhileTheSwitchInTheDecodedOptionsIsOn(): void + { + unset($GLOBALS['wp_fastest_cache_options']); + self::assertFalse(FastestCacheCompatibility::cacheEnabled()); + $GLOBALS['wp_fastest_cache_options'] = []; + self::assertFalse(FastestCacheCompatibility::cacheEnabled(), 'WPFC stores [] when the option is missing'); + $GLOBALS['wp_fastest_cache_options'] = (object) ['wpFastestCacheMinifyHtml' => 'on']; + self::assertFalse(FastestCacheCompatibility::cacheEnabled()); + $GLOBALS['wp_fastest_cache_options'] = (object) ['wpFastestCacheStatus' => 'on']; + self::assertTrue(FastestCacheCompatibility::cacheEnabled()); + unset($GLOBALS['wp_fastest_cache_options']); + } + + public function testAllProfilesCoexistWithoutIdCollision(): void + { + $compatibility = new Compatibility( + [DiviProfile::profile(), PodsCompatibility::profile(), FastestCacheCompatibility::profile()] + ); + + self::assertSame(['divi', 'pods', 'wpfc'], array_map(static fn ($p) => $p->id, $compatibility->profiles())); + self::assertSame(FastestCacheFeature::PurgeAll, $compatibility->capability('wpfc-purge-all')); + } +} diff --git a/tests/Unit/Integrations/SuperCache/SuperCacheFeatureTest.php b/tests/Unit/Integrations/FastestCache/FastestCacheFeatureTest.php similarity index 55% rename from tests/Unit/Integrations/SuperCache/SuperCacheFeatureTest.php rename to tests/Unit/Integrations/FastestCache/FastestCacheFeatureTest.php index a090529..f301948 100644 --- a/tests/Unit/Integrations/SuperCache/SuperCacheFeatureTest.php +++ b/tests/Unit/Integrations/FastestCache/FastestCacheFeatureTest.php @@ -2,25 +2,25 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Integrations\SuperCache; +namespace LIT\MCP\Tests\Unit\Integrations\FastestCache; use LIT\MCP\Compatibility\Capability; -use LIT\MCP\Integrations\SuperCache\SuperCacheFeature; +use LIT\MCP\Integrations\FastestCache\FastestCacheFeature; use LIT\MCP\Tests\Unit\TestCase; -final class SuperCacheFeatureTest extends TestCase +final class FastestCacheFeatureTest extends TestCase { public function testEveryFeatureIsAWritingCapabilityWithAPrefixedId(): void { - foreach (SuperCacheFeature::cases() as $feature) { + foreach (FastestCacheFeature::cases() as $feature) { self::assertInstanceOf(Capability::class, $feature); self::assertSame($feature->value, $feature->id()); self::assertNotSame('', $feature->label()); self::assertTrue($feature->isWrite()); } self::assertSame( - ['wpsc-purge-post', 'wpsc-purge-all'], - array_map(static fn (SuperCacheFeature $f): string => $f->id(), SuperCacheFeature::cases()) + ['wpfc-purge-post', 'wpfc-purge-all'], + array_map(static fn (FastestCacheFeature $f): string => $f->id(), FastestCacheFeature::cases()) ); } } diff --git a/tests/Unit/Integrations/FastestCache/FastestCacheIntegrationTest.php b/tests/Unit/Integrations/FastestCache/FastestCacheIntegrationTest.php new file mode 100644 index 0000000..f7c61a3 --- /dev/null +++ b/tests/Unit/Integrations/FastestCache/FastestCacheIntegrationTest.php @@ -0,0 +1,86 @@ + true); + } + + public function testIsAnIntegrationThatProvidesItsProfileAndTheCacheClearTool(): void + { + $integration = new FastestCacheIntegration(null, self::purger(), FakeChangeLog::forbidden()); + + self::assertInstanceOf(Integration::class, $integration); + self::assertInstanceOf(ProvidesCompatibility::class, $integration); + self::assertSame('wpfc', $integration->id()); + self::assertSame('WP Fastest Cache', $integration->label()); + self::assertEquals(['lit/cache-clear'], array_map(static fn ($a): string => $a->name(), $integration->abilities())); + self::assertEquals([FastestCacheCompatibility::profile()], $integration->compatibilities()); + } + + /** + * The same constant the profile's active() reads: WP Fastest Cache defines it when its plugin file loads. + * + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testIsAvailableOnlyWithFastestCache(): void + { + $integration = new FastestCacheIntegration(null, self::purger(), FakeChangeLog::forbidden()); + self::assertFalse($integration->isAvailable()); + + define('WPFC_MAIN_PATH', '/plugins/wp-fastest-cache/'); + self::assertTrue($integration->isAvailable()); + self::assertTrue((FastestCacheCompatibility::profile()->active)()); + } + + public function testBootHooksTheOrphanCleanupIntoTheProfileLoad(): void + { + $integration = new FastestCacheIntegration(null, self::purger(), FakeChangeLog::forbidden()); + $integration->boot(); + + self::assertInstanceOf(Bootable::class, $integration); + self::assertNotFalse(has_action(CompatibilityTab::LOAD_ACTION, [$integration, 'removeOrphans'])); + } + + public function testNoCleanupForAnotherProfileWithoutWpscOrWithoutTheCapability(): void + { + Functions\expect('get_posts')->never(); + Functions\when('current_user_can')->justReturn(true); + (new FastestCacheIntegration(static fn (): bool => true, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('pods'); + (new FastestCacheIntegration(static fn (): bool => false, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('wpfc'); + + Functions\when('current_user_can')->justReturn(false); + (new FastestCacheIntegration(static fn (): bool => true, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('wpfc'); + $this->addToAssertionCount(1); + } + + public function testCleansUpPagesForTheAdministratorWithWpsc(): void + { + Functions\when('current_user_can')->justReturn(true); + Functions\expect('get_posts')->once()->andReturnUsing(static function (array $args): array { + self::assertSame('page', $args['post_type']); + self::assertSame(['draft', 'private'], $args['post_status']); + + return []; + }); + + (new FastestCacheIntegration(static fn (): bool => true, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('wpfc'); + } +} diff --git a/tests/Unit/Integrations/SuperCache/SuperCacheListenerTest.php b/tests/Unit/Integrations/FastestCache/FastestCacheListenerTest.php similarity index 83% rename from tests/Unit/Integrations/SuperCache/SuperCacheListenerTest.php rename to tests/Unit/Integrations/FastestCache/FastestCacheListenerTest.php index 25bbee9..fc1f9be 100644 --- a/tests/Unit/Integrations/SuperCache/SuperCacheListenerTest.php +++ b/tests/Unit/Integrations/FastestCache/FastestCacheListenerTest.php @@ -2,19 +2,19 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Integrations\SuperCache; +namespace LIT\MCP\Tests\Unit\Integrations\FastestCache; use Brain\Monkey\Functions; use Closure; use LIT\MCP\Admin\CompatibilityTab; use LIT\MCP\Compatibility\Compatibility; -use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility; -use LIT\MCP\Integrations\SuperCache\SuperCacheFeature; -use LIT\MCP\Integrations\SuperCache\SuperCacheListener; +use LIT\MCP\Integrations\FastestCache\FastestCacheCompatibility; +use LIT\MCP\Integrations\FastestCache\FastestCacheFeature; +use LIT\MCP\Integrations\FastestCache\FastestCacheListener; use LIT\MCP\Tests\Unit\TestCase; use WP_Post; -final class SuperCacheListenerTest extends TestCase +final class FastestCacheListenerTest extends TestCase { /** @var list */ private array $purges = []; @@ -56,7 +56,7 @@ final class SuperCacheListenerTest extends TestCase { ini_set('error_log', (string) $this->previousErrorLog); unlink($this->errorLog); - unset($GLOBALS['cache_enabled']); + unset($GLOBALS['wp_fastest_cache_options']); parent::tearDown(); } @@ -66,17 +66,17 @@ final class SuperCacheListenerTest extends TestCase bool $usablePost = true, bool $usableAll = true, ?Closure $postIdFor = null - ): SuperCacheListener { - return new SuperCacheListener( - static fn (SuperCacheFeature $feature): bool => $feature === SuperCacheFeature::PurgePost + ): FastestCacheListener { + return new FastestCacheListener( + static fn (FastestCacheFeature $feature): bool => $feature === FastestCacheFeature::PurgePost ? $usablePost : $usableAll, static fn (): bool => $cacheOn, function (int $id): void { $this->purges[] = 'post:' . $id; }, - function (int $blog): void { - $this->purges[] = 'all:' . $blog; + function (): void { + $this->purges[] = 'all'; }, $postIdFor ?? static fn (string $type, string $id): ?int => in_array($type, ['page', 'post'], true) ? (int) $id @@ -120,7 +120,7 @@ final class SuperCacheListenerTest extends TestCase $listener->onChange('lit/content-update', 'post', '14', 7); $listener->flush(); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); } public function testThePurgeIsDoneOncePerFlush(): void @@ -143,7 +143,7 @@ final class SuperCacheListenerTest extends TestCase $listener->flush(); $listener->onChange('lit/content-update', 'page', '14', 7); $listener->flush(); - self::assertSame(['all:0', 'post:14'], $this->purges); + self::assertSame(['all', 'post:14'], $this->purges); } /** @dataProvider siteWideChanges */ @@ -153,7 +153,7 @@ final class SuperCacheListenerTest extends TestCase $listener->onChange($ability, $type, $id, null); $listener->flush(); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); } /** @return array */ @@ -175,7 +175,7 @@ final class SuperCacheListenerTest extends TestCase $listener->onChange('lit/post-trash', 'post', '99', 1); $listener->flush(); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); } /** One whole-cache change among single posts: still one purge, no single ones on top. */ @@ -186,7 +186,7 @@ final class SuperCacheListenerTest extends TestCase $listener->onChange('lit/wp-settings-update', 'option', 'a', 2); $listener->flush(); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); } /** Release and withdraw entries of the check workflow change nothing on the site (shared ability name). */ @@ -204,8 +204,8 @@ final class SuperCacheListenerTest extends TestCase public function testTheCacheClearToolsOwnEntriesPurgeNothing(): void { $listener = $this->listener(); - $listener->onChange('lit/cache-clear', 'wpsc', 'all', 1); - $listener->onChange('lit/cache-clear', 'wpsc', '12', 2); + $listener->onChange('lit/cache-clear', 'wpfc', 'all', 1); + $listener->onChange('lit/cache-clear', 'wpfc', '12', 2); $listener->flush(); self::assertSame([], $this->purges); @@ -233,7 +233,7 @@ final class SuperCacheListenerTest extends TestCase $listener->flush(); self::assertSame([], $this->purges); - self::assertSame(1, substr_count($this->logged(), 'wpsc-purge-post')); + self::assertSame(1, substr_count($this->logged(), 'wpfc-purge-post')); } /** The capabilities lock separately: a gone purge-post does not stop the whole-cache purge. */ @@ -244,7 +244,7 @@ final class SuperCacheListenerTest extends TestCase $listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1); $listener->flush(); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); } public function testALockedWholeCachePurgeSchedulesNothing(): void @@ -258,18 +258,6 @@ final class SuperCacheListenerTest extends TestCase self::assertSame([], $this->scheduled); } - public function testOnAMultisiteTheCurrentBlogIsPurged(): void - { - Functions\when('is_multisite')->justReturn(true); - Functions\when('get_current_blog_id')->justReturn(3); - - $listener = $this->listener(); - $listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1); - $listener->flush(); - - self::assertSame(['all:3'], $this->purges); - } - public function testAWholeCachePurgeStampsTheTimeForThirtySeconds(): void { $listener = $this->listener(); @@ -277,7 +265,7 @@ final class SuperCacheListenerTest extends TestCase $listener->flush(); self::assertCount(1, $this->stamped); - self::assertSame('lit_mcp_wpsc_purged_at', $this->stamped[0][0]); + self::assertSame('lit_mcp_wpfc_purged_at', $this->stamped[0][0]); self::assertEqualsWithDelta(time(), $this->stamped[0][1], 2); self::assertSame(30, $this->stamped[0][2]); } @@ -305,7 +293,7 @@ final class SuperCacheListenerTest extends TestCase self::assertSame([], $this->purges); self::assertSame([], $this->stamped); - self::assertSame([[$this->lastPurge + 30, 'lit_mcp_wpsc_trailing_purge']], $this->scheduled); + self::assertSame([[$this->lastPurge + 30, 'lit_mcp_wpfc_trailing_purge']], $this->scheduled); } /** Options-table transients come back as strings. */ @@ -337,7 +325,7 @@ final class SuperCacheListenerTest extends TestCase $listener = $this->listener(); $listener->trailingPurge(); - self::assertSame(['all:0'], $this->purges); + self::assertSame(['all'], $this->purges); self::assertCount(1, $this->stamped); } @@ -351,7 +339,7 @@ final class SuperCacheListenerTest extends TestCase public function testAThrowingTrailingPurgeIsLogged(): void { - $listener = new SuperCacheListener( + $listener = new FastestCacheListener( static fn (): bool => true, static fn (): bool => true, null, @@ -367,13 +355,13 @@ final class SuperCacheListenerTest extends TestCase public function testAThrowingPurgeIsLoggedAndNotRethrown(): void { - $listener = new SuperCacheListener( + $listener = new FastestCacheListener( static fn (): bool => true, static fn (): bool => true, static function (): void { throw new \RuntimeException('disk full'); }, - static fn (int $blog): null => null, + static fn (): null => null, static fn (): int => 12 ); @@ -389,37 +377,37 @@ final class SuperCacheListenerTest extends TestCase $listener->register(); self::assertSame(10, has_action('lit_mcp_changed', [$listener, 'onChange'])); - self::assertSame(10, has_action('lit_mcp_wpsc_trailing_purge', [$listener, 'trailingPurge'])); + self::assertSame(10, has_action('lit_mcp_wpfc_trailing_purge', [$listener, 'trailingPurge'])); } - /** Plugin wires create(): without WP Super Cache nothing is usable, and with the cache off nothing is asked. */ + /** Plugin wires create(): without WP Fastest Cache nothing is usable, and with the cache off nothing is asked. */ public function testCreateAsksTheCompatibilityOnlyWhenTheCacheIsOn(): void { - $compatibility = new Compatibility([SuperCacheCompatibility::profile()]); - $listener = SuperCacheListener::create($compatibility); + $compatibility = new Compatibility([FastestCacheCompatibility::profile()]); + $listener = FastestCacheListener::create($compatibility); $listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1); $listener->flush(); self::assertSame('', $this->logged()); - $GLOBALS['cache_enabled'] = true; + $GLOBALS['wp_fastest_cache_options'] = (object) ['wpFastestCacheStatus' => 'on']; $listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1); $listener->flush(); - self::assertStringContainsString('wpsc-purge-all not usable', $this->logged()); + self::assertStringContainsString('wpfc-purge-all not usable', $this->logged()); } - public function testTheDefaultCacheSwitchIsTheWpscGlobal(): void + public function testTheDefaultCacheSwitchIsTheStatusOfWpfc(): void { $post = new WP_Post(); Functions\when('get_post')->justReturn($post); Functions\when('get_post_type_object')->justReturn((object) ['public' => true]); - $listener = new SuperCacheListener(static fn (): bool => true); + $listener = new FastestCacheListener(static fn (): bool => true); - $GLOBALS['cache_enabled'] = false; + $GLOBALS['wp_fastest_cache_options'] = (object) []; $listener->onChange('lit/content-update', 'page', '12', 5); $listener->flush(); - self::assertSame('', $this->logged(), 'unset or false cache_enabled: silent, nothing purged'); + self::assertSame('', $this->logged(), 'cache switched off: silent, nothing purged'); self::assertFalse(has_action('shutdown', [$listener, 'flush'])); } @@ -430,18 +418,18 @@ final class SuperCacheListenerTest extends TestCase bool $public, bool $exists ): void { - $GLOBALS['cache_enabled'] = true; + $GLOBALS['wp_fastest_cache_options'] = (object) ['wpFastestCacheStatus' => 'on']; Functions\when('get_post_type_object')->justReturn((object) ['public' => $public]); Functions\when('get_post')->justReturn($exists ? new WP_Post() : null); $purged = []; - Functions\when('wp_cache_clear_cache')->alias(static function () use (&$purged): void { + Functions\when('wpfc_clear_all_cache')->alias(static function () use (&$purged): void { $purged[] = 'all'; }); - Functions\when('wp_cache_post_edit')->alias(static function () use (&$purged): void { + Functions\when('wpfc_clear_post_cache_by_id')->alias(static function () use (&$purged): void { $purged[] = 'post'; }); - $listener = new SuperCacheListener(static fn (): bool => true); + $listener = new FastestCacheListener(static fn (): bool => true); $listener->onChange('lit/x', $type, $id, 1); $listener->flush(); diff --git a/tests/Unit/Integrations/FastestCache/FastestCacheProbeTest.php b/tests/Unit/Integrations/FastestCache/FastestCacheProbeTest.php new file mode 100644 index 0000000..4816558 --- /dev/null +++ b/tests/Unit/Integrations/FastestCache/FastestCacheProbeTest.php @@ -0,0 +1,421 @@ + */ + private array $deleted = []; + + /** @var array|null The wp_insert_post arguments. */ + private ?array $inserted = null; + + /** @var array Globals the test changes (only those that existed). */ + private array $savedGlobals = []; + + private const GLOBALS_CHANGED = ['wp_fastest_cache', 'wp_fastest_cache_options', 'wpdb']; + + protected function setUp(): void + { + parent::setUp(); + $this->cacheRoot = sys_get_temp_dir() . '/lit-mcp-wpfc-' . bin2hex(random_bytes(4)) . '/'; + mkdir($this->cacheRoot . 'cache/all/', 0777, true); + $this->deleted = []; + $this->inserted = null; + $this->savedGlobals = []; + foreach (self::GLOBALS_CHANGED as $name) { + if (array_key_exists($name, $GLOBALS)) { + $this->savedGlobals[$name] = $GLOBALS[$name]; + } + } + Functions\when('wp_insert_post')->alias(function (array $post): int { + $this->inserted = $post; + + return 77; + }); + Functions\when('wp_delete_post')->alias(function (int $id, bool $force): WP_Post { + self::assertTrue($force, 'never to the trash'); + $this->deleted[] = $id; + + return new WP_Post(['ID' => $id]); + }); + Functions\when('wp_mkdir_p')->alias(static fn (string $dir): bool => is_dir($dir) || @mkdir($dir, 0777, true)); + } + + protected function tearDown(): void + { + foreach (self::GLOBALS_CHANGED as $name) { + if (array_key_exists($name, $this->savedGlobals)) { + $GLOBALS[$name] = $this->savedGlobals[$name]; + } else { + unset($GLOBALS[$name]); + } + } + self::removeTree($this->cacheRoot); + parent::tearDown(); + } + + private static function removeTree(string $path): void + { + foreach (glob(rtrim($path, '/') . '/{,.}*', GLOB_BRACE) ?: [] as $entry) { + if (in_array(basename($entry), ['.', '..'], true)) { + continue; + } + is_dir($entry) ? self::removeTree($entry) : unlink($entry); + } + @rmdir($path); + } + + private function pageDir(): string + { + return $this->cacheRoot . 'cache/all/lit-mcp-check-77/'; + } + + /** + * @param (Closure(int): void)|null $purge + * @param (Closure(int): ?string)|null $cacheDir + */ + private function probe( + ?Closure $purge = null, + ?Closure $cacheDir = null, + bool $enabled = true, + bool $hooked = true, + bool $slugSet = true + ): FastestCacheProbe { + return new FastestCacheProbe( + static fn (): bool => $enabled, + static fn (): bool => $hooked, + $cacheDir ?? fn (int $id): string => $this->pageDir(), + $purge ?? fn (int $id) => unlink($this->pageDir() . 'index.html'), + static fn (int $id): bool => $slugSet + ); + } + + /** Captures what Log writes to the PHP error log while $run runs. */ + private function logged(Closure $run): string + { + $log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log'); + $previous = ini_set('error_log', $log); + try { + $run(); + + return (string) file_get_contents($log); + } finally { + ini_set('error_log', (string) $previous); + unlink($log); + } + } + + public function testGreenWhenThePurgeRemovesTheCacheFileAndNothingIsLeftBehind(): void + { + $probe = $this->probe(function (int $id): void { + self::assertFileExists($this->pageDir() . 'index.html', 'the file is there before the purge'); + unlink($this->pageDir() . 'index.html'); + }); + + self::assertSame([], $probe->run()); + self::assertDirectoryDoesNotExist($this->pageDir()); + self::assertDirectoryExists($this->cacheRoot . 'cache/all/'); + self::assertSame([77], $this->deleted); + } + + public function testCreatesAMarkedDraftPage(): void + { + $this->probe()->run(); + + self::assertSame('page', $this->inserted['post_type']); + self::assertSame('draft', $this->inserted['post_status']); + self::assertStringStartsWith(ThrowawayPost::TITLE, $this->inserted['post_title']); + self::assertSame([ThrowawayPost::MARKER => '1'], $this->inserted['meta_input']); + } + + /** WPFC moves the whole directory away (to cache/tmpWpfc). */ + public function testADirectoryThePurgeMovedAwayIsGreen(): void + { + $problems = $this->probe(function (int $id): void { + rename($this->pageDir(), $this->cacheRoot . 'moved'); + })->run(); + + self::assertSame([], $problems); + } + + public function testACacheFileThatSurvivesThePurgeIsAFixedTextAndIsRemovedAnyway(): void + { + $problems = []; + $logged = $this->logged(function () use (&$problems): void { + $problems = $this->probe(static fn (int $id) => null)->run(); + }); + + self::assertSame(['Die Testdatei im Cache blieb nach dem Leeren durch WP Fastest Cache bestehen.'], $problems); + self::assertStringContainsString('lit-mcp-check-77', $logged); + self::assertDirectoryDoesNotExist($this->pageDir()); + self::assertSame([77], $this->deleted); + } + + public function testAForeignFileInThePageDirectoryIsLeftAlone(): void + { + $problems = $this->probe(function (int $id): void { + unlink($this->pageDir() . 'index.html'); + touch($this->pageDir() . 'fremd.txt'); + })->run(); + + self::assertSame([], $problems); + self::assertFileExists($this->pageDir() . 'fremd.txt'); + } + + public function testAnExceptionOfThePurgeIsAFixedTextALogLineAndEverythingIsCleanedUp(): void + { + $problems = []; + $logged = $this->logged(function () use (&$problems): void { + $problems = $this->probe(static function (int $id): never { + throw new RuntimeException('WPFC kaputt'); + })->run(); + }); + + self::assertSame(['Fastest-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems); + self::assertStringContainsString('WPFC kaputt', $logged); + self::assertDirectoryDoesNotExist($this->pageDir()); + self::assertSame([77], $this->deleted); + } + + public function testNothingToProbeWhileTheCacheIsSwitchedOff(): void + { + self::assertNull($this->probe(enabled: false)->run()); + self::assertNull($this->inserted); + } + + /** Without the hook wpfc_clear_post_cache_by_id does nothing: the purge of a post would silently fail. */ + public function testAMissingPurgeHookIsAProblemWithoutAnyPage(): void + { + self::assertSame( + ['WP Fastest Cache verarbeitet wpfc_clear_post_cache_by_id nicht mehr; das Leeren eines Beitrags bliebe wirkungslos.'], + $this->probe(hooked: false)->run() + ); + self::assertNull($this->inserted); + } + + public function testNothingToProbeWithoutACachePathForThePage(): void + { + self::assertNull($this->probe(cacheDir: static fn (int $id): ?string => null)->run()); + self::assertSame([77], $this->deleted); + } + + public function testARefusedSlugIsItsOwnFixedProblemAndTheProbeNeverPurges(): void + { + $purged = false; + $problems = []; + $logged = $this->logged(function () use (&$problems, &$purged): void { + $problems = $this->probe(static function (int $id) use (&$purged): void { + $purged = true; + }, slugSet: false)->run(); + }); + + self::assertSame(['Die Adresse der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'], $problems); + self::assertStringContainsString('77', $logged); + self::assertFalse($purged); + self::assertSame([77], $this->deleted); + } + + public function testACacheDirectoryThatCannotBeCreatedIsAFixedText(): void + { + $blocker = $this->cacheRoot . 'blocker'; + file_put_contents($blocker, 'x'); + $problems = []; + $logged = $this->logged(function () use (&$problems, $blocker): void { + $problems = $this->probe(cacheDir: static fn (int $id): string => $blocker . '/seite/')->run(); + }); + + self::assertSame(['Die Testdatei konnte nicht im Cache-Verzeichnis angelegt werden (Details im PHP-Error-Log).'], $problems); + self::assertStringContainsString('seite', $logged); + self::assertSame([77], $this->deleted); + } + + // --- Defaults: the real WPFC and WordPress calls ---------------------------------------------------------------- + + /** Records calls; getWpContentDir like WPFC without WPML/multisite: content dir plus path. */ + private function fakeWpfc(): object + { + return new class ($this->cacheRoot) { + /** @var list */ + public array $calls = []; + + public function __construct(private string $root) + { + } + + public function singleDeleteCache(mixed $comment = false, mixed $post = false, mixed $parents = true): void + { + $this->calls[] = [$comment, $post, $parents]; + } + + public function getWpContentDir(string $path): string + { + return rtrim($this->root, '/') . $path; + } + }; + } + + public function testTheDefaultPurgeIsSingleDeleteCacheWithoutTheParentPages(): void + { + $GLOBALS['wp_fastest_cache'] = $wpfc = $this->fakeWpfc(); + $probe = new FastestCacheProbe( + static fn (): bool => true, + static fn (): bool => true, + fn (int $id): string => $this->pageDir(), + null, + static fn (int $id): bool => true + ); + + $this->logged(static function () use ($probe): void { + $probe->run(); + }); + + self::assertSame([[false, 77, false]], $wpfc->calls); + } + + public function testTheDefaultHookCheckAsksForTheMethodOfTheWpfcInstance(): void + { + $GLOBALS['wp_fastest_cache'] = $wpfc = $this->fakeWpfc(); + Functions\expect('has_action')->twice() + ->with('wpfc_clear_post_cache_by_id', [$wpfc, 'singleDeleteCache']) + ->andReturn(10, false); + $probe = new FastestCacheProbe( + static fn (): bool => true, + null, + static fn (int $id): ?string => null, + null, + static fn (int $id): bool => true + ); + + self::assertNull($probe->run(), 'hooked: on to the page, no path: not checked'); + self::assertCount(1, $probe->run() ?? [], 'not hooked: one problem'); + } + + public function testTheDefaultEnabledCheckReadsTheWpfcSwitch(): void + { + $GLOBALS['wp_fastest_cache_options'] = (object) []; + self::assertNull((new FastestCacheProbe())->run()); + + $GLOBALS['wp_fastest_cache_options'] = []; + self::assertNull((new FastestCacheProbe())->run()); + } + + public function testTheDefaultSlugIsWrittenStraightToTheDatabaseWithoutHooks(): void + { + $wpdb = Mockery::mock(); + $wpdb->posts = 'wp_posts'; + $wpdb->expects('update')->with('wp_posts', ['post_name' => 'lit-mcp-check-77'], ['ID' => 77])->andReturn(1); + $GLOBALS['wpdb'] = $wpdb; + Functions\expect('clean_post_cache')->never(); + $dropped = []; + Functions\when('wp_cache_delete')->alias(static function (int $id, string $group) use (&$dropped): bool { + $dropped[] = $group . ':' . $id; + + return true; + }); + + $probe = new FastestCacheProbe( + static fn (): bool => true, + static fn (): bool => true, + fn (int $id): string => $this->pageDir(), + fn (int $id) => unlink($this->pageDir() . 'index.html') + ); + + self::assertSame([], $probe->run()); + self::assertSame(['posts:77'], $dropped, 'the object cache must not keep the empty slug'); + } + + /** @return array */ + public static function refusedUpdates(): array + { + return ['database error' => [false], 'no row changed' => [0]]; + } + + /** @dataProvider refusedUpdates */ + public function testTheDefaultSlugWriterTreatsAFailedOrEmptyUpdateAsRefused(int|false $result): void + { + $wpdb = Mockery::mock(); + $wpdb->posts = 'wp_posts'; + $wpdb->shouldReceive('update')->andReturn($result); + $GLOBALS['wpdb'] = $wpdb; + Functions\when('wp_cache_delete')->justReturn(true); + + $problems = []; + $this->logged(function () use (&$problems): void { + $problems = (new FastestCacheProbe( + static fn (): bool => true, + static fn (): bool => true, + fn (int $id): string => $this->pageDir(), + static fn (int $id) => self::fail('no purge without the slug') + ))->run(); + }); + + self::assertSame(['Die Adresse der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'], $problems); + } + + private function permalink(string $address): void + { + $GLOBALS['wp_fastest_cache'] = $this->fakeWpfc(); + Functions\when('get_post')->justReturn(new WP_Post(['ID' => 77, 'post_status' => 'draft'])); + Functions\when('get_permalink')->alias(static function (WP_Post $post) use ($address): string { + self::assertSame('publish', $post->post_status, 'the address the draft would have once published'); + + return $address; + }); + } + + public function testTheDefaultCachePathIsTheProbePageDirectoryBelowCacheAll(): void + { + $this->permalink('https://example.test/lit-mcp-check-77/'); + + self::assertSame($this->pageDir(), FastestCacheProbe::cacheDirFor(77)); + } + + public function testTheDefaultCachePathKeepsTheSubdirectoryOfTheSite(): void + { + $this->permalink('https://example.test/wp/lit-mcp-check-77/'); + + self::assertSame($this->cacheRoot . 'cache/all/wp/lit-mcp-check-77/', FastestCacheProbe::cacheDirFor(77)); + } + + /** @return array */ + public static function foreignAddresses(): array + { + return [ + 'plain address' => ['https://example.test/?page_id=77'], + 'home page' => ['https://example.test/'], + 'other slug' => ['https://example.test/seite/'], + 'other probe' => ['https://example.test/lit-mcp-check-78/'], + 'slug as suffix only' => ['https://example.test/x-lit-mcp-check-77/'], + 'traversal' => ['https://example.test/../lit-mcp-check-77/'], + ]; + } + + /** @dataProvider foreignAddresses */ + public function testTheDefaultCachePathIsNullForAnythingButTheProbePage(string $address): void + { + $this->permalink($address); + + self::assertNull(FastestCacheProbe::cacheDirFor(77)); + } + + public function testTheDefaultCachePathIsNullForAMissingPost(): void + { + Functions\when('get_post')->justReturn(null); + + self::assertNull(FastestCacheProbe::cacheDirFor(77)); + } +} diff --git a/tests/Unit/Integrations/SuperCache/SuperCachePurgerTest.php b/tests/Unit/Integrations/FastestCache/FastestCachePurgerTest.php similarity index 71% rename from tests/Unit/Integrations/SuperCache/SuperCachePurgerTest.php rename to tests/Unit/Integrations/FastestCache/FastestCachePurgerTest.php index 3a57c56..0a7b063 100644 --- a/tests/Unit/Integrations/SuperCache/SuperCachePurgerTest.php +++ b/tests/Unit/Integrations/FastestCache/FastestCachePurgerTest.php @@ -2,27 +2,27 @@ declare(strict_types=1); -namespace LIT\MCP\Tests\Unit\Integrations\SuperCache; +namespace LIT\MCP\Tests\Unit\Integrations\FastestCache; use Brain\Monkey\Functions; -use LIT\MCP\Integrations\SuperCache\SuperCacheFeature; -use LIT\MCP\Integrations\SuperCache\SuperCachePurger; +use LIT\MCP\Integrations\FastestCache\FastestCacheFeature; +use LIT\MCP\Integrations\FastestCache\FastestCachePurger; use LIT\MCP\Tests\Unit\TestCase; -final class SuperCachePurgerTest extends TestCase +final class FastestCachePurgerTest extends TestCase { public function testALockedWholePurgeDoesNothingAndSaysSo(): void { $previous = ini_set('error_log', '/dev/null'); Functions\expect('get_transient')->never(); - $purger = new SuperCachePurger( - static fn (SuperCacheFeature $feature): bool => false, + $purger = new FastestCachePurger( + static fn (FastestCacheFeature $feature): bool => false, static fn (): bool => true, static fn (int $id) => self::fail('no post purge'), - static fn (int $blog) => self::fail('no whole purge') + static fn () => self::fail('no whole purge') ); - self::assertSame(SuperCachePurger::LOCKED, $purger->everything()); + self::assertSame(FastestCachePurger::LOCKED, $purger->everything()); self::assertFalse($purger->post(3)); ini_set('error_log', (string) $previous); } @@ -33,7 +33,7 @@ final class SuperCachePurgerTest extends TestCase Functions\when('get_transient')->alias(static function () use (&$stamp) { return $stamp; }); - $purger = new SuperCachePurger(static fn (): bool => true); + $purger = new FastestCachePurger(static fn (): bool => true); self::assertFalse($purger->throttled()); $stamp = (string) (time() - 5); @@ -56,9 +56,9 @@ final class SuperCachePurgerTest extends TestCase return true; }); - $purger = new SuperCachePurger(static fn (): bool => true); + $purger = new FastestCachePurger(static fn (): bool => true); - self::assertSame(SuperCachePurger::THROTTLED, $purger->everything()); + self::assertSame(FastestCachePurger::THROTTLED, $purger->everything()); self::assertSame(1, $reads); self::assertCount(1, $scheduledAt); diff --git a/tests/Unit/Integrations/SuperCache/SuperCacheCompatibilityTest.php b/tests/Unit/Integrations/SuperCache/SuperCacheCompatibilityTest.php deleted file mode 100644 index c369cc5..0000000 --- a/tests/Unit/Integrations/SuperCache/SuperCacheCompatibilityTest.php +++ /dev/null @@ -1,121 +0,0 @@ -id); - self::assertSame('WP Super Cache', $profile->label); - self::assertSame(Tier::LockOnFailure, $profile->tier); - self::assertSame(['3.1.5'], $profile->testedVersions); - self::assertNull($profile->minVersion); - self::assertSame(SuperCacheFeature::cases(), $profile->capabilities); - } - - /** Argument counts are those the listener passes; wp_cache_clear_cache($blog_id = 0) gets the blog. */ - public function testTheManifestHoldsTheTwoPurgeFunctionsWithTheirArgumentCounts(): void - { - $manifest = SuperCacheCompatibility::profile()->manifests[0]; - $calls = []; - foreach ($manifest->calls as $call) { - $calls[$call->symbol] = [$call->args, $call->features()]; - } - - self::assertSame([ - 'wp_cache_post_edit' => [1, [SuperCacheFeature::PurgePost]], - 'wp_cache_clear_cache' => [1, [SuperCacheFeature::PurgeAll]], - ], $calls); - self::assertContainsOnlyInstancesOf(Call::class, $manifest->calls); - } - - /** Both purge functions read $cache_path; wp_cache_post_edit also $blog_cache_dir. */ - public function testTheGlobalsTheFunctionsReadAreRequired(): void - { - $manifest = SuperCacheCompatibility::profile()->manifests[0]; - - self::assertSame(['WPCACHEHOME'], array_map(static fn ($c) => $c->name, $manifest->constants)); - $globals = []; - foreach ($manifest->globals as $global) { - $globals[$global->name] = $global->features(); - } - self::assertSame( - ['cache_path' => SuperCacheFeature::cases(), 'blog_cache_dir' => [SuperCacheFeature::PurgePost]], - $globals - ); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testActiveAndVersionFollowTheCacheHomeConstantAndThePluginHeader(): void - { - $profile = SuperCacheCompatibility::profile(); - self::assertFalse(($profile->active)()); - self::assertNull(($profile->version)()); - - define('WPCACHEHOME', '/plugins/wp-super-cache/'); - Functions\expect('get_file_data') - ->once() - ->with('/plugins/wp-super-cache/wp-cache.php', ['Version' => 'Version']) - ->andReturn(['Version' => '3.1.5']); - - self::assertTrue(($profile->active)()); - self::assertSame('3.1.5', ($profile->version)()); - } - - /** - * @runInSeparateProcess - * @preserveGlobalState disabled - */ - public function testAnUnreadableHeaderIsAnUnknownVersion(): void - { - define('WPCACHEHOME', '/plugins/wp-super-cache/'); - Functions\when('get_file_data')->justReturn(['Version' => '']); - - self::assertNull((SuperCacheCompatibility::profile()->version)()); - } - - public function testTheCheckHasTheProbeAsExtraStep(): void - { - $profile = SuperCacheCompatibility::profile(); - - self::assertNotNull($profile->extraSteps); - $steps = ($profile->extraSteps)(false); - self::assertSame([SuperCacheCheckSteps::PROBE], array_map(static fn ($step): string => $step->key, $steps)); - self::assertTrue($steps[0]->skipped); - self::assertNotNull($profile->stepLabel); - self::assertSame( - 'Leeren-Test an einer Wegwerf-Seite im Cache', - ($profile->stepLabel)(new CheckStep(SuperCacheCheckSteps::PROBE, [])) - ); - } - - public function testAllProfilesCoexistWithoutIdCollision(): void - { - $compatibility = new Compatibility( - [DiviProfile::profile(), PodsCompatibility::profile(), SuperCacheCompatibility::profile()] - ); - - self::assertSame(['divi', 'pods', 'wpsc'], array_map(static fn ($p) => $p->id, $compatibility->profiles())); - self::assertSame(SuperCacheFeature::PurgeAll, $compatibility->capability('wpsc-purge-all')); - } -} diff --git a/tests/Unit/Integrations/SuperCache/SuperCacheIntegrationTest.php b/tests/Unit/Integrations/SuperCache/SuperCacheIntegrationTest.php deleted file mode 100644 index de63220..0000000 --- a/tests/Unit/Integrations/SuperCache/SuperCacheIntegrationTest.php +++ /dev/null @@ -1,75 +0,0 @@ - true); - } - - public function testIsAnIntegrationThatProvidesItsProfileAndTheCacheClearTool(): void - { - $integration = new SuperCacheIntegration(null, self::purger(), FakeChangeLog::forbidden()); - - self::assertInstanceOf(Integration::class, $integration); - self::assertInstanceOf(ProvidesCompatibility::class, $integration); - self::assertSame('wpsc', $integration->id()); - self::assertSame('WP Super Cache', $integration->label()); - self::assertEquals(['lit/cache-clear'], array_map(static fn ($a): string => $a->name(), $integration->abilities())); - self::assertEquals([SuperCacheCompatibility::profile()], $integration->compatibilities()); - } - - public function testIsAvailableOnlyWithSuperCache(): void - { - self::assertSame(defined('WPCACHEHOME'), (new SuperCacheIntegration(null, self::purger(), FakeChangeLog::forbidden()))->isAvailable()); - } - - public function testBootHooksTheOrphanCleanupIntoTheProfileLoad(): void - { - $integration = new SuperCacheIntegration(null, self::purger(), FakeChangeLog::forbidden()); - $integration->boot(); - - self::assertInstanceOf(Bootable::class, $integration); - self::assertNotFalse(has_action(CompatibilityTab::LOAD_ACTION, [$integration, 'removeOrphans'])); - } - - public function testNoCleanupForAnotherProfileWithoutWpscOrWithoutTheCapability(): void - { - Functions\expect('get_posts')->never(); - Functions\when('current_user_can')->justReturn(true); - (new SuperCacheIntegration(static fn (): bool => true, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('pods'); - (new SuperCacheIntegration(static fn (): bool => false, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('wpsc'); - - Functions\when('current_user_can')->justReturn(false); - (new SuperCacheIntegration(static fn (): bool => true, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('wpsc'); - $this->addToAssertionCount(1); - } - - public function testCleansUpPagesForTheAdministratorWithWpsc(): void - { - Functions\when('current_user_can')->justReturn(true); - Functions\expect('get_posts')->once()->andReturnUsing(static function (array $args): array { - self::assertSame('page', $args['post_type']); - self::assertSame(['draft', 'private'], $args['post_status']); - - return []; - }); - - (new SuperCacheIntegration(static fn (): bool => true, self::purger(), FakeChangeLog::forbidden()))->removeOrphans('wpsc'); - } -} diff --git a/tests/Unit/Integrations/SuperCache/SuperCacheProbeTest.php b/tests/Unit/Integrations/SuperCache/SuperCacheProbeTest.php deleted file mode 100644 index 58edcd6..0000000 --- a/tests/Unit/Integrations/SuperCache/SuperCacheProbeTest.php +++ /dev/null @@ -1,525 +0,0 @@ - */ - private array $deleted = []; - - /** @var list ":" in call order. */ - private array $statuses = []; - - /** @var array|null The wp_insert_post arguments. */ - private ?array $inserted = null; - - /** @var array Globals the test changes (only those that existed). */ - private array $savedGlobals = []; - - private const GLOBALS_CHANGED = ['cache_enabled', 'cache_path', 'wpdb', 'wp_cache_clear_on_post_edit']; - - /** The status the fake status switch refuses to set (returns false); null = none. */ - private ?string $refusedStatus = null; - - /** The status the fake status switch throws for; null = none. */ - private ?string $throwingStatus = null; - - protected function setUp(): void - { - parent::setUp(); - $this->cacheRoot = sys_get_temp_dir() . '/lit-mcp-wpsc-' . bin2hex(random_bytes(4)) . '/'; - mkdir($this->cacheRoot . 'supercache/example.test/', 0777, true); - $this->deleted = []; - $this->statuses = []; - $this->inserted = null; - $this->refusedStatus = null; - $this->throwingStatus = null; - $this->savedGlobals = []; - foreach (self::GLOBALS_CHANGED as $name) { - if (array_key_exists($name, $GLOBALS)) { - $this->savedGlobals[$name] = $GLOBALS[$name]; - } - } - Functions\when('wp_insert_post')->alias(function (array $post): int { - $this->inserted = $post; - - return 77; - }); - Functions\when('wp_delete_post')->alias(function (int $id, bool $force): WP_Post { - self::assertTrue($force, 'never to the trash'); - $this->deleted[] = $id; - - return new WP_Post(['ID' => $id]); - }); - Functions\when('wp_mkdir_p')->alias(static fn (string $dir): bool => is_dir($dir) || @mkdir($dir, 0777, true)); - } - - protected function tearDown(): void - { - foreach (self::GLOBALS_CHANGED as $name) { - if (array_key_exists($name, $this->savedGlobals)) { - $GLOBALS[$name] = $this->savedGlobals[$name]; - } else { - unset($GLOBALS[$name]); - } - } - self::removeTree($this->cacheRoot); - parent::tearDown(); - } - - private static function removeTree(string $path): void - { - foreach (glob(rtrim($path, '/') . '/{,.}*', GLOB_BRACE) ?: [] as $entry) { - if (in_array(basename($entry), ['.', '..'], true)) { - continue; - } - is_dir($entry) ? self::removeTree($entry) : unlink($entry); - } - @rmdir($path); - } - - private function pageDir(): string - { - return $this->cacheRoot . 'supercache/example.test/lit-mcp-check-77/'; - } - - /** @param (Closure(int): void)|null $purge */ - private function probe(?Closure $purge = null, ?Closure $cacheDir = null, bool $enabled = true): SuperCacheProbe - { - return new SuperCacheProbe( - static fn (): bool => $enabled, - $cacheDir ?? fn (int $id): string => $this->pageDir(), - $purge ?? fn (int $id) => unlink($this->pageDir() . 'index.html'), - function (int $id, string $status): bool { - $this->statuses[] = $id . ':' . $status; - if ($status === $this->throwingStatus) { - throw new RuntimeException('Status kaputt'); - } - - return $status !== $this->refusedStatus; - } - ); - } - - /** Purge fake that does what WPSC does: the page's cache file disappears. */ - private function purgingProbe(): SuperCacheProbe - { - return $this->probe(function (int $id): void { - self::assertFileExists($this->pageDir() . 'index.html', 'the file is there before the purge'); - unlink($this->pageDir() . 'index.html'); - }); - } - - /** Captures what Log writes to the PHP error log while $run runs. */ - private function logged(Closure $run): string - { - $log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log'); - $previous = ini_set('error_log', $log); - try { - $run(); - - return (string) file_get_contents($log); - } finally { - ini_set('error_log', (string) $previous); - unlink($log); - } - } - - public function testGreenWhenThePurgeRemovesTheCacheFileAndNothingIsLeftBehind(): void - { - self::assertSame([], $this->purgingProbe()->run()); - - self::assertDirectoryDoesNotExist($this->pageDir()); - self::assertSame([77], $this->deleted); - } - - public function testCreatesAMarkedDraftPageAndOnlyPassesThroughPrivateForThePurge(): void - { - $this->purgingProbe()->run(); - - self::assertSame('page', $this->inserted['post_type']); - self::assertSame('draft', $this->inserted['post_status']); - self::assertStringStartsWith(ThrowawayPost::TITLE, $this->inserted['post_title']); - self::assertSame([ThrowawayPost::MARKER => '1'], $this->inserted['meta_input']); - self::assertSame(['77:private', '77:draft'], $this->statuses); - } - - public function testACacheFileThatSurvivesThePurgeIsAFixedTextAndIsRemovedAnyway(): void - { - $problems = []; - $logged = $this->logged(function () use (&$problems): void { - $problems = $this->probe(static fn (int $id) => null)->run(); - }); - - self::assertSame(['Die Testdatei im Cache blieb nach dem Leeren durch WP Super Cache bestehen.'], $problems); - self::assertStringContainsString('lit-mcp-check-77', $logged); - self::assertDirectoryDoesNotExist($this->pageDir()); - self::assertSame([77], $this->deleted); - } - - public function testARenamedRebuildFileCountsAsPurgedAndIsCleanedUp(): void - { - $problems = $this->probe(function (int $id): void { - rename($this->pageDir() . 'index.html', $this->pageDir() . 'index.html.needs-rebuild'); - })->run(); - - self::assertSame([], $problems); - self::assertDirectoryDoesNotExist($this->pageDir()); - } - - public function testADirectoryThePurgeAlreadyRemovedIsNoProblem(): void - { - $problems = $this->probe(function (int $id): void { - unlink($this->pageDir() . 'index.html'); - rmdir($this->pageDir()); - })->run(); - - self::assertSame([], $problems); - } - - public function testAnExceptionOfThePurgeIsAFixedTextALogLineAndEverythingIsCleanedUp(): void - { - $problems = []; - $logged = $this->logged(function () use (&$problems): void { - $problems = $this->probe(static function (int $id): never { - throw new RuntimeException('WPSC kaputt'); - })->run(); - }); - - self::assertSame(['Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems); - self::assertStringContainsString('WPSC kaputt', $logged); - self::assertDirectoryDoesNotExist($this->pageDir()); - self::assertSame([77], $this->deleted); - self::assertSame(['77:private', '77:draft'], $this->statuses); - } - - public function testNothingToProbeWhileTheCacheIsSwitchedOff(): void - { - self::assertNull($this->probe(null, null, false)->run()); - self::assertNull($this->inserted); - } - - public function testNothingToProbeWithoutACachePathForThePage(): void - { - self::assertNull($this->probe(null, static fn (int $id): ?string => null)->run()); - self::assertSame([77], $this->deleted); - } - - public function testACacheDirectoryThatCannotBeCreatedIsAFixedText(): void - { - $blocker = $this->cacheRoot . 'blocker'; - file_put_contents($blocker, 'x'); - $problems = []; - $logged = $this->logged(function () use (&$problems, $blocker): void { - $problems = $this->probe(null, static fn (int $id): string => $blocker . '/seite/')->run(); - }); - - self::assertSame(['Die Testdatei konnte nicht im Cache-Verzeichnis angelegt werden (Details im PHP-Error-Log).'], $problems); - self::assertStringContainsString('seite', $logged); - self::assertSame([77], $this->deleted); - } - - public function testTheDefaultPurgeIsWpCachePostEdit(): void - { - Functions\expect('wp_cache_post_edit')->once()->with(77)->andReturnUsing(function (int $id): int { - unlink($this->pageDir() . 'index.html'); - - return $id; - }); - - $probe = new SuperCacheProbe( - static fn (): bool => true, - fn (int $id): string => $this->pageDir(), - null, - static fn (): bool => true - ); - - self::assertSame([], $probe->run()); - } - - public function testTheDefaultStatusSwitchWritesStatusAndSlugStraightToTheDatabaseWithoutHooks(): void - { - $wpdb = Mockery::mock(); - $wpdb->posts = 'wp_posts'; - $wpdb->expects('update')->with('wp_posts', ['post_status' => 'private', 'post_name' => 'lit-mcp-check-77'], ['ID' => 77])->andReturn(1); - $wpdb->expects('update')->with('wp_posts', ['post_status' => 'draft', 'post_name' => 'lit-mcp-check-77'], ['ID' => 77])->andReturn(1); - $GLOBALS['wpdb'] = $wpdb; - Functions\expect('clean_post_cache')->never(); - $dropped = []; - Functions\when('wp_cache_delete')->alias(static function (int $id, string $group) use (&$dropped): bool { - $dropped[] = $group . ':' . $id; - - return true; - }); - - $probe = new SuperCacheProbe( - static fn (): bool => true, - fn (int $id): string => $this->pageDir(), - fn (int $id) => unlink($this->pageDir() . 'index.html') - ); - - self::assertSame([], $probe->run()); - self::assertSame(['posts:77', 'posts:77'], $dropped, 'the object cache must not keep the old status'); - } - - public function testTheDefaultEnabledCheckReadsTheWpscGlobal(): void - { - $GLOBALS['cache_enabled'] = false; - self::assertNull((new SuperCacheProbe())->run()); - - $GLOBALS['cache_enabled'] = null; - self::assertNull((new SuperCacheProbe())->run()); - } - - private function wpscDirs(string $home, string $current): void - { - Functions\when('get_supercache_dir')->justReturn($this->cacheRoot . 'supercache/' . $home . '/'); - Functions\when('get_current_url_supercache_dir')->justReturn($this->cacheRoot . 'supercache/' . $current . '/'); - } - - public function testTheDefaultCachePathIsTheProbePageDirectoryBelowTheHomeHostDirectory(): void - { - $this->wpscDirs('example.test', 'example.test/lit-mcp-check-77'); - - self::assertSame($this->pageDir(), SuperCacheProbe::cacheDirFor(77)); - } - - /** WPSC derives the host from the request's Host header; the probe must not follow it somewhere else. */ - public function testTheDefaultCachePathIsNullWhenWpscWouldUseAnotherHostThanTheHomeUrl(): void - { - $this->wpscDirs('example.test', 'www.example.test/lit-mcp-check-77'); - - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - } - - public function testTheDefaultCachePathIsNullForAnythingButTheProbePageOfThisSite(): void - { - // A plain "?page_id=" address or an empty slug: WPSC resolves it to the home page's directory. - $this->wpscDirs('example.test', 'example.test'); - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - - $this->wpscDirs('example.test', 'example.test/seite'); - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - - $this->wpscDirs('example.test', 'example.test/lit-mcp-check-77/../../x'); - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - - $this->wpscDirs('example.test', 'example.test/lit-mcp-check-78'); - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - - Functions\when('get_current_url_supercache_dir')->justReturn('/var/www/html/lit-mcp-check-77/'); - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - } - - public function testTheDefaultCachePathIsNullWhenWpscKnowsNoDirectory(): void - { - Functions\when('get_supercache_dir')->justReturn(false); - Functions\when('get_current_url_supercache_dir')->justReturn(false); - - self::assertNull(SuperCacheProbe::cacheDirFor(77)); - } - - public function testOnlyTheKnownCacheFilesOfTheProbePageAreRemoved(): void - { - $problems = $this->probe(function (int $id): void { - // What WPSC leaves after a rebuild-style purge, plus a file that is none of its names. - rename($this->pageDir() . 'index.html', $this->pageDir() . 'index.html.needs-rebuild'); - touch($this->pageDir() . 'index-https.html'); - touch($this->pageDir() . 'index-https.html.needs-rebuild'); - touch($this->pageDir() . 'fremd.txt'); - })->run(); - - self::assertSame([], $problems); - self::assertSame(['fremd.txt'], array_values(array_diff(scandir($this->pageDir()), ['.', '..']))); - } - - public function testTheCacheDirectoryOfThePageIsRemovedWhenEmpty(): void - { - $this->purgingProbe()->run(); - - self::assertDirectoryDoesNotExist($this->pageDir()); - self::assertDirectoryExists($this->cacheRoot . 'supercache/example.test/'); - } - - // --- P1: the purge must not empty the live cache ------------------------------------------------------------------ - - private function defaultPurgeProbe(): SuperCacheProbe - { - return new SuperCacheProbe( - static fn (): bool => true, - fn (int $id): string => $this->pageDir(), - null, - static fn (): bool => true - ); - } - - public function testTheDefaultPurgeRunsWithClearOnEditOffAndTheRelatedPagesFilterOffAndRestoresBoth(): void - { - $GLOBALS['wp_cache_clear_on_post_edit'] = 1; - $events = []; - $added = null; - Functions\expect('add_filter')->once()->with('wpsc_delete_related_pages_on_edit', Mockery::type(Closure::class), PHP_INT_MAX) - ->andReturnUsing(static function (string $hook, Closure $callback) use (&$events, &$added): bool { - $events[] = 'add_filter returning ' . var_export($callback(true), true); - $added = $callback; - - return true; - }); - Functions\expect('remove_filter')->once()->with('wpsc_delete_related_pages_on_edit', Mockery::type(Closure::class), PHP_INT_MAX) - ->andReturnUsing(static function (string $hook, Closure $callback) use (&$events, &$added): bool { - $events[] = $callback === $added ? 'remove_filter of the same callback' : 'remove_filter of ANOTHER callback'; - - return true; - }); - Functions\expect('wp_cache_post_edit')->once()->with(77)->andReturnUsing(function (int $id) use (&$events): int { - $events[] = 'purge with clear_on_post_edit=' . var_export($GLOBALS['wp_cache_clear_on_post_edit'], true); - unlink($this->pageDir() . 'index.html'); - - return $id; - }); - - self::assertSame([], $this->defaultPurgeProbe()->run()); - - self::assertSame( - ['add_filter returning false', 'purge with clear_on_post_edit=0', 'remove_filter of the same callback'], - $events - ); - self::assertSame(1, $GLOBALS['wp_cache_clear_on_post_edit']); - } - - public function testTheDefaultPurgeLeavesAnUnsetClearOnEditGlobalUnset(): void - { - unset($GLOBALS['wp_cache_clear_on_post_edit']); - Functions\when('add_filter')->justReturn(true); - Functions\when('remove_filter')->justReturn(true); - Functions\expect('wp_cache_post_edit')->once()->andReturnUsing(function (int $id): int { - self::assertSame(0, $GLOBALS['wp_cache_clear_on_post_edit']); - unlink($this->pageDir() . 'index.html'); - - return $id; - }); - - self::assertSame([], $this->defaultPurgeProbe()->run()); - self::assertArrayNotHasKey('wp_cache_clear_on_post_edit', $GLOBALS); - } - - public function testTheDefaultPurgeRestoresGlobalAndFilterWhenWpscThrows(): void - { - $GLOBALS['wp_cache_clear_on_post_edit'] = '1'; - Functions\expect('add_filter')->once()->andReturn(true); - Functions\expect('remove_filter')->once()->with('wpsc_delete_related_pages_on_edit', Mockery::type(Closure::class), PHP_INT_MAX)->andReturn(true); - Functions\expect('wp_cache_post_edit')->once()->andThrow(new RuntimeException('WPSC kaputt')); - - $problems = []; - $this->logged(function () use (&$problems): void { - $problems = $this->defaultPurgeProbe()->run(); - }); - - self::assertSame(['Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems); - self::assertSame('1', $GLOBALS['wp_cache_clear_on_post_edit']); - } - - // --- P2: a status the database refused is no purge result --------------------------------------------------------- - - public function testARefusedPrivateStatusIsItsOwnFixedProblemAndTheProbeNeverPurges(): void - { - $this->refusedStatus = 'private'; - $purged = false; - $problems = []; - $logged = $this->logged(function () use (&$problems, &$purged): void { - $problems = $this->probe(static function (int $id) use (&$purged): void { - $purged = true; - })->run(); - }); - - self::assertSame(['Der Status der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'], $problems); - self::assertStringContainsString('77', $logged); - self::assertStringContainsString('private', $logged); - self::assertFalse($purged); - self::assertSame([77], $this->deleted); - self::assertSame(['77:private', '77:draft'], $this->statuses); - } - - /** @return array */ - public static function refusedUpdates(): array - { - return ['database error' => [false], 'no row changed' => [0]]; - } - - /** @dataProvider refusedUpdates */ - public function testTheDefaultStatusSwitchTreatsAFailedOrEmptyUpdateAsRefused(int|false $result): void - { - $wpdb = Mockery::mock(); - $wpdb->posts = 'wp_posts'; - $wpdb->shouldReceive('update')->andReturn($result); - $GLOBALS['wpdb'] = $wpdb; - Functions\when('wp_cache_delete')->justReturn(true); - Functions\expect('wp_cache_post_edit')->never(); - - $problems = []; - $this->logged(function () use (&$problems): void { - $problems = (new SuperCacheProbe( - static fn (): bool => true, - fn (int $id): string => $this->pageDir(), - fn (int $id) => null - ))->run(); - }); - - self::assertSame(['Der Status der Testseite konnte nicht gesetzt werden (Details im PHP-Error-Log).'], $problems); - self::assertSame([77], $this->deleted); - } - - public function testAFailingResetToDraftNeitherHidesTheResultNorEndsTheCheck(): void - { - $this->throwingStatus = 'draft'; - $problems = []; - $logged = $this->logged(function () use (&$problems): void { - $problems = $this->purgingProbe()->run(); - }); - - self::assertSame([], $problems); - self::assertStringContainsString('Status kaputt', $logged); - self::assertSame([77], $this->deleted); - self::assertDirectoryDoesNotExist($this->pageDir()); - } - - public function testAFailingResetToDraftDoesNotReplaceAnExceptionOfThePurge(): void - { - $this->throwingStatus = 'draft'; - $problems = []; - $logged = $this->logged(function () use (&$problems): void { - $problems = $this->probe(static function (int $id): never { - throw new RuntimeException('WPSC kaputt'); - })->run(); - }); - - self::assertSame(['Super-Cache-Probe abgebrochen (Details im PHP-Error-Log).'], $problems); - self::assertStringContainsString('WPSC kaputt', $logged); - self::assertSame([77], $this->deleted); - } - - public function testARefusedResetToDraftIsLoggedOnly(): void - { - $this->refusedStatus = 'draft'; - $problems = []; - $logged = $this->logged(function () use (&$problems): void { - $problems = $this->purgingProbe()->run(); - }); - - self::assertSame([], $problems); - self::assertStringContainsString('draft', $logged); - } -} diff --git a/tests/Unit/PluginTest.php b/tests/Unit/PluginTest.php index 460f6ce..40ae4cf 100644 --- a/tests/Unit/PluginTest.php +++ b/tests/Unit/PluginTest.php @@ -50,18 +50,18 @@ final class PluginTest extends TestCase public function testIntegrationsInRegistryOrder(): void { self::assertSame( - ['core', 'wordpress', 'media', 'divi', 'divi-library', 'divi-theme-builder', 'pods', 'wpsc'], + ['core', 'wordpress', 'media', 'divi', 'divi-library', 'divi-theme-builder', 'pods', 'wpfc'], self::registry()->ids() ); } - /** Divi, Pods and WP Super Cache boot (hooks, orphan cleanup); a second boot registers nothing twice. */ + /** Divi, Pods and WP Fastest Cache boot (hooks, orphan cleanup); a second boot registers nothing twice. */ public function testTheBootableIntegrationsBootExactlyOnce(): void { Actions\expectAdded('upgrader_process_complete')->once(); Actions\expectAdded('lit_mcp_compat_changed')->once(); Filters\expectAdded(SettingsPage::TABS_FILTER)->never(); - Actions\expectAdded(CompatibilityTab::LOAD_ACTION)->times(3); // Divi, Pods and WP Super Cache + Actions\expectAdded(CompatibilityTab::LOAD_ACTION)->times(3); // Divi, Pods and WP Fastest Cache Actions\expectAdded(CompatibilityTab::EXTRA_ACTION)->once(); Filters\expectAdded(CompatibilityTab::COLUMNS_FILTER)->once(); $registry = self::registry(); @@ -97,17 +97,17 @@ final class PluginTest extends TestCase self::assertArrayNotHasKey('pods', $registry->grouped()); } - /** WP Super Cache is registered with its profile; its tool group appears only while the plugin is loaded. */ - public function testSuperCacheIsRegisteredAndNoToolGroupWithoutTheCachePlugin(): void + /** WP Fastest Cache is registered with its profile; its tool group appears only while the plugin is loaded. */ + public function testFastestCacheIsRegisteredAndNoToolGroupWithoutTheCachePlugin(): void { $registry = self::registry(); - self::assertContains('wpsc', $registry->ids()); - self::assertArrayNotHasKey('wpsc', $registry->grouped()); + self::assertContains('wpfc', $registry->ids()); + self::assertArrayNotHasKey('wpfc', $registry->grouped()); } /** - * The purger of the real wiring asks the registry's Compatibility at call time: with WP Super Cache loaded and + * The purger of the real wiring asks the registry's Compatibility at call time: with WP Fastest Cache loaded and * its manifest met, the whole-cache purge runs (a registry captured by value would still be null: always locked). * * @runInSeparateProcess @@ -115,33 +115,34 @@ final class PluginTest extends TestCase */ public function testTheWiredPurgerAsksTheCompatibilityOfTheRegistry(): void { - define('WPCACHEHOME', '/plugins/wp-super-cache/'); - eval('function wp_cache_post_edit($id) { $GLOBALS["wpsc_calls"][] = "post:" . $id; }' - . 'function wp_cache_clear_cache($blog = 0) { $GLOBALS["wpsc_calls"][] = "all:" . $blog; }'); - $GLOBALS['wpsc_calls'] = []; - $GLOBALS['cache_path'] = '/cache/'; - $GLOBALS['blog_cache_dir'] = '/cache/blogs/'; - $GLOBALS['cache_enabled'] = true; - Functions\when('get_file_data')->justReturn(['Version' => '3.1.5']); + define('WPFC_MAIN_PATH', '/plugins/wp-fastest-cache/'); + eval('function wpfc_clear_post_cache_by_id($id = false) { $GLOBALS["wpfc_calls"][] = "post:" . $id; }' + . 'function wpfc_clear_all_cache($minified = false) { $GLOBALS["wpfc_calls"][] = "all"; }' + . 'class WpFastestCache { public function singleDeleteCache($c = false, $p = false, $parents = true) {}' + . ' public function getWpContentDir($path = false) { return ""; } }'); + $GLOBALS['wpfc_calls'] = []; + $GLOBALS['wp_fastest_cache'] = new \WpFastestCache(); + $GLOBALS['wp_fastest_cache_options'] = (object) ['wpFastestCacheStatus' => 'on']; + Functions\when('get_file_data')->justReturn(['Version' => '1.5.5']); Functions\when('get_option')->justReturn(false); Functions\when('get_transient')->justReturn(false); Functions\when('set_transient')->justReturn(true); Functions\when('is_multisite')->justReturn(false); Functions\when('current_user_can')->justReturn(true); $integrations = (new ReflectionProperty(IntegrationRegistry::class, 'integrations'))->getValue(self::registry(log: new FakeChangeLog(1))); - $wpsc = array_values(array_filter( + $wpfc = array_values(array_filter( $integrations, - static fn ($integration): bool => $integration->id() === 'wpsc' + static fn ($integration): bool => $integration->id() === 'wpfc' ))[0]; - $result = $wpsc->abilities()[0]->execute(['scope' => 'all', 'confirm' => true]); + $result = $wpfc->abilities()[0]->execute(['scope' => 'all', 'confirm' => true]); self::assertSame('purged', $result['outcome']); - self::assertSame(['all:0'], $GLOBALS['wpsc_calls']); + self::assertSame(['all'], $GLOBALS['wpfc_calls']); } - /** lit-site-status keeps its order: Divi, Pods, WP Super Cache, The Events Calendar. */ - public function testSiteStatusListsDiviPodsSuperCacheAndTheEventsCalendarInThatOrder(): void + /** lit-site-status keeps its order: Divi, Pods, WP Fastest Cache, The Events Calendar. */ + public function testSiteStatusListsDiviPodsFastestCacheAndTheEventsCalendarInThatOrder(): void { Functions\when('get_bloginfo')->justReturn('x'); Functions\when('home_url')->justReturn('https://example.test'); @@ -158,13 +159,13 @@ final class PluginTest extends TestCase $components = $status->execute([])['components']; - self::assertSame(['divi', 'pods', 'wpsc', 'the-events-calendar'], array_column($components, 'id')); + self::assertSame(['divi', 'pods', 'wpfc', 'the-events-calendar'], array_column($components, 'id')); $pods = array_column($components, null, 'id')['pods']; self::assertFalse($pods['active'], 'no Pods in unit tests'); self::assertSame([], $pods['problems']); - $wpsc = array_column($components, null, 'id')['wpsc']; - self::assertFalse($wpsc['active'], 'no WP Super Cache in unit tests'); - self::assertSame([], $wpsc['problems']); + $wpfc = array_column($components, null, 'id')['wpfc']; + self::assertFalse($wpfc['active'], 'no WP Fastest Cache in unit tests'); + self::assertSame([], $wpfc['problems']); } public function testTheCompatibilityCoversAllProfilesWithoutIdCollision(): void @@ -172,9 +173,9 @@ final class PluginTest extends TestCase $compat = self::registry()->compatibility(); $ids = array_map(static fn (Profile $p): string => $p->id, $compat->profiles()); - self::assertSame(['divi', 'pods', 'wpsc'], $ids); + self::assertSame(['divi', 'pods', 'wpfc'], $ids); self::assertSame($ids, array_values(array_unique($ids))); - self::assertSame('wpsc', $compat->owner($compat->capability('wpsc-purge-all'))?->id); + self::assertSame('wpfc', $compat->owner($compat->capability('wpfc-purge-all'))?->id); self::assertNotNull($compat->capability('page-write')); self::assertNotNull($compat->capability('pods-field-read')); self::assertSame('divi', $compat->owner($compat->capability('page-write'))?->id); diff --git a/tests/Unit/UninstallTest.php b/tests/Unit/UninstallTest.php index 21bd0b2..62d421a 100644 --- a/tests/Unit/UninstallTest.php +++ b/tests/Unit/UninstallTest.php @@ -12,7 +12,7 @@ use LIT\MCP\Admin\ConnectionStatus; use LIT\MCP\ChangeLog\ChangeLogCleanup; use LIT\MCP\ChangeLog\ChangeLogInstaller; use LIT\MCP\Integrations\Divi\Release\ReleaseResolver; -use LIT\MCP\Integrations\SuperCache\SuperCacheListener; +use LIT\MCP\Integrations\FastestCache\FastestCacheListener; use LIT\MCP\Limits; use LIT\MCP\Updater\RecheckOnInstall; use Mockery; @@ -60,7 +60,7 @@ final class UninstallTest extends TestCase Access::OPTION, ExposurePolicy::OPTION, Limits::OPTION, ChangeLogInstaller::VERSION_OPTION, 'lit_mcp_divi_releases', 'lit_mcp_divi_check_report', 'lit_mcp_divi_check_lock', 'lit_mcp_pods_releases', 'lit_mcp_pods_check_report', 'lit_mcp_pods_check_lock', - 'lit_mcp_wpsc_releases', 'lit_mcp_wpsc_check_report', 'lit_mcp_wpsc_check_lock', + 'lit_mcp_wpfc_releases', 'lit_mcp_wpfc_check_report', 'lit_mcp_wpfc_check_lock', 'lit_mcp_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets', 'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css', DesignLock::option('colors'), DesignLock::option('variables'), DesignLock::option('presets'), DesignLock::option('custom_css'), @@ -73,15 +73,15 @@ final class UninstallTest extends TestCase self::assertNotContains('wpmedia_mcp_oauth_rewrite_version', $deleted); self::assertSame( [ - ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT, - ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT, + ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, FastestCacheListener::PURGED_AT, + ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, FastestCacheListener::PURGED_AT, ], $transients ); self::assertSame( [ - ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK, - ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK, + ChangeLogCleanup::HOOK, FastestCacheListener::TRAILING_HOOK, + ChangeLogCleanup::HOOK, FastestCacheListener::TRAILING_HOOK, ], $hooks ); diff --git a/tests/Unit/UninstallerTest.php b/tests/Unit/UninstallerTest.php index bdf2d06..b6a88ae 100644 --- a/tests/Unit/UninstallerTest.php +++ b/tests/Unit/UninstallerTest.php @@ -13,7 +13,7 @@ use LIT\MCP\Admin\LogTab; use LIT\MCP\ChangeLog\ChangeLogCleanup; use LIT\MCP\ChangeLog\ChangeLogInstaller; use LIT\MCP\Integrations\Divi\Release\ReleaseResolver; -use LIT\MCP\Integrations\SuperCache\SuperCacheListener; +use LIT\MCP\Integrations\FastestCache\FastestCacheListener; use LIT\MCP\Limits; use LIT\MCP\Uninstaller; use LIT\MCP\Updater\RecheckOnInstall; @@ -61,16 +61,16 @@ final class UninstallerTest extends TestCase Uninstaller::run(); self::assertSame( - [ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT], + [ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, FastestCacheListener::PURGED_AT], $transients ); - self::assertSame([ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK], $hooks); + self::assertSame([ChangeLogCleanup::HOOK, FastestCacheListener::TRAILING_HOOK], $hooks); self::assertSame( [ Access::OPTION, ExposurePolicy::OPTION, Limits::OPTION, ChangeLogInstaller::VERSION_OPTION, 'lit_mcp_divi_releases', 'lit_mcp_divi_check_report', 'lit_mcp_divi_check_lock', 'lit_mcp_pods_releases', 'lit_mcp_pods_check_report', 'lit_mcp_pods_check_lock', - 'lit_mcp_wpsc_releases', 'lit_mcp_wpsc_check_report', 'lit_mcp_wpsc_check_lock', + 'lit_mcp_wpfc_releases', 'lit_mcp_wpfc_check_report', 'lit_mcp_wpfc_check_lock', 'lit_mcp_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets', 'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css', DesignLock::option('colors'), DesignLock::option('variables'), DesignLock::option('presets'), DesignLock::option('custom_css'), @@ -108,15 +108,15 @@ final class UninstallerTest extends TestCase self::assertSame( [ - ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT, - ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT, + ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, FastestCacheListener::PURGED_AT, + ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, FastestCacheListener::PURGED_AT, ], $transients ); self::assertSame( [ - ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK, - ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK, + ChangeLogCleanup::HOOK, FastestCacheListener::TRAILING_HOOK, + ChangeLogCleanup::HOOK, FastestCacheListener::TRAILING_HOOK, ], $hooks );