feat(cache): WP Super Cache über den Kompatibilitätskern anbinden
SuperCacheFeature, SuperCacheCompatibility (LockOnFailure, getestet 3.1.5, Version aus dem Plugin-Header) und SuperCacheIntegration ohne Tools. Der SuperCacheListener sammelt Änderungen über lit_mcp_changed und leert einmal am Request-Ende: einen Beitrag per wp_cache_post_edit, mehrere Beiträge oder seitenweite Änderungen per wp_cache_clear_cache (Multisite: eigener Blog). Komplett-Purges höchstens alle 30 s, mit genau einem Nachlauf per WP-Cron. Freigabe-Einträge leeren nichts. Uninstaller räumt Optionen, Transient und Cron-Hook ab; Reiter und lit-site-status zeigen das Profil automatisch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c383b99e7d
commit
05bf890481
@@ -98,3 +98,8 @@ nichts geändert. Vorgehen:
|
|||||||
nicht über andere Werkzeuge umgehen.
|
nicht über andere Werkzeuge umgehen.
|
||||||
5. Dasselbe gilt für Pods: Ist eine Pods-Fähigkeit (`pods-…`) nicht nutzbar, antwortet das betroffene Werkzeug mit
|
5. Dasselbe gilt für Pods: Ist eine Pods-Fähigkeit (`pods-…`) nicht nutzbar, antwortet das betroffene Werkzeug mit
|
||||||
`compat_incompatible`; der Stand steht im selben Reiter „Compatibility“.
|
`compat_incompatible`; der Stand steht im selben Reiter „Compatibility“.
|
||||||
|
|
||||||
|
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. Ist der Cache
|
||||||
|
dafür gesperrt, bleibt er unverändert; der Zustand steht im Reiter „Compatibility“ und in `lit-site-status`
|
||||||
|
(`wpsc`).
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ final class CompatibilityTab implements AdminTab
|
|||||||
/** admin-post actions; the nonce action of a request is the action plus "_<profile ID>". */
|
/** admin-post actions; the nonce action of a request is the action plus "_<profile ID>". */
|
||||||
public const RELEASE_ACTION = 'lit_mcp_compat_release';
|
public const RELEASE_ACTION = 'lit_mcp_compat_release';
|
||||||
public const WITHDRAW_ACTION = 'lit_mcp_compat_withdraw';
|
public const WITHDRAW_ACTION = 'lit_mcp_compat_withdraw';
|
||||||
|
private const ABILITY_PREFIX = 'lit/';
|
||||||
|
private const ABILITY_SUFFIX = '-release';
|
||||||
|
|
||||||
/** @var Closure(Profile): CheckReportStore */
|
/** @var Closure(Profile): CheckReportStore */
|
||||||
private Closure $reports;
|
private Closure $reports;
|
||||||
@@ -71,6 +73,20 @@ final class CompatibilityTab implements AdminTab
|
|||||||
$this->releases = $releases ?? ReleaseStore::forProfile(...);
|
$this->releases = $releases ?? ReleaseStore::forProfile(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ability name of the release and withdraw entries in the change log: lit/<profile>-release. */
|
||||||
|
public static function releaseAbility(string $profileId): string
|
||||||
|
{
|
||||||
|
return self::ABILITY_PREFIX . $profileId . self::ABILITY_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the ability names such an entry (any profile). */
|
||||||
|
public static function isReleaseAbility(string $ability): bool
|
||||||
|
{
|
||||||
|
return strlen($ability) > strlen(self::ABILITY_PREFIX . self::ABILITY_SUFFIX)
|
||||||
|
&& str_starts_with($ability, self::ABILITY_PREFIX)
|
||||||
|
&& str_ends_with($ability, self::ABILITY_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
public function slug(): string
|
public function slug(): string
|
||||||
{
|
{
|
||||||
return self::SLUG;
|
return self::SLUG;
|
||||||
@@ -137,7 +153,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
|
|
||||||
($this->releases)($profile)->add($version, Plugin::VERSION, get_current_user_id(), $report->id);
|
($this->releases)($profile)->add($version, Plugin::VERSION, get_current_user_id(), $report->id);
|
||||||
$this->log?->record(
|
$this->log?->record(
|
||||||
'lit/' . $profile->id . '-release',
|
self::releaseAbility($profile->id),
|
||||||
$profile->id,
|
$profile->id,
|
||||||
$version,
|
$version,
|
||||||
sprintf(
|
sprintf(
|
||||||
@@ -167,7 +183,7 @@ final class CompatibilityTab implements AdminTab
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->log?->record(
|
$this->log?->record(
|
||||||
'lit/' . $profile->id . '-release',
|
self::releaseAbility($profile->id),
|
||||||
$profile->id,
|
$profile->id,
|
||||||
$version,
|
$version,
|
||||||
sprintf('Freigabe von %s %s zurückgezogen.', $profile->label, $version)
|
sprintf('Freigabe von %s %s zurückgezogen.', $profile->label, $version)
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use LIT\MCP\Compatibility\Call;
|
||||||
|
use LIT\MCP\Compatibility\ConstantRequirement;
|
||||||
|
use LIT\MCP\Compatibility\GlobalRequirement;
|
||||||
|
use LIT\MCP\Compatibility\Manifest;
|
||||||
|
use LIT\MCP\Compatibility\Profile;
|
||||||
|
use LIT\MCP\Compatibility\Tier;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WP Super Cache as a compatibility profile: the two purge functions SuperCacheListener calls, checked against
|
||||||
|
* 3.1.5 (wp-cache-phase2.php). wp_cache_post_edit is no documented API: if a release drops it, only the purge is
|
||||||
|
* locked (visible in the tab and in lit-site-status). Both functions read the global $cache_path, which wp-cache.php
|
||||||
|
* sets on load from wp-cache-config.php or its sample; wp_cache_post_edit also reads $blog_cache_dir
|
||||||
|
* (wp-cache-base.php).
|
||||||
|
*/
|
||||||
|
final class SuperCacheCompatibility
|
||||||
|
{
|
||||||
|
public const LABEL = 'WP Super Cache';
|
||||||
|
|
||||||
|
public static function profile(): Profile
|
||||||
|
{
|
||||||
|
$all = SuperCacheFeature::cases();
|
||||||
|
|
||||||
|
return new Profile(
|
||||||
|
id: 'wpsc',
|
||||||
|
label: self::LABEL,
|
||||||
|
tier: Tier::LockOnFailure,
|
||||||
|
capabilities: $all,
|
||||||
|
manifests: [new Manifest(
|
||||||
|
'WP Super Cache',
|
||||||
|
calls: [
|
||||||
|
new Call('wp_cache_post_edit', 1, [SuperCacheFeature::PurgePost]),
|
||||||
|
new Call('wp_cache_clear_cache', 1, [SuperCacheFeature::PurgeAll]),
|
||||||
|
],
|
||||||
|
constants: [new ConstantRequirement('WPCACHEHOME', $all)],
|
||||||
|
globals: [
|
||||||
|
new GlobalRequirement('cache_path', $all),
|
||||||
|
new GlobalRequirement('blog_cache_dir', [SuperCacheFeature::PurgePost]),
|
||||||
|
]
|
||||||
|
)],
|
||||||
|
active: static fn (): bool => 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']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use LIT\MCP\Compatibility\Capability;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
case PurgePost = 'wpsc-purge-post';
|
||||||
|
case PurgeAll = 'wpsc-purge-all';
|
||||||
|
|
||||||
|
public function id(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PurgePost => 'Cache eines Beitrags leeren',
|
||||||
|
self::PurgeAll => 'Gesamten Cache leeren',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isWrite(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use LIT\MCP\Compatibility\ProvidesCompatibility;
|
||||||
|
use LIT\MCP\Integrations\Integration;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WP Super Cache has no tools: the integration only brings the compatibility profile (SuperCacheListener does the
|
||||||
|
* work, wired by Plugin). Without abilities it is no tool group.
|
||||||
|
*/
|
||||||
|
final class SuperCacheIntegration implements Integration, ProvidesCompatibility
|
||||||
|
{
|
||||||
|
public function id(): string
|
||||||
|
{
|
||||||
|
return 'wpsc';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return SuperCacheCompatibility::LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAvailable(): bool
|
||||||
|
{
|
||||||
|
return defined('WPCACHEHOME');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function abilities(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compatibilities(): array
|
||||||
|
{
|
||||||
|
return [SuperCacheCompatibility::profile()];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use LIT\MCP\Admin\CompatibilityTab;
|
||||||
|
use LIT\MCP\ChangeLog\ChangeLog;
|
||||||
|
use LIT\MCP\Compatibility\Compatibility;
|
||||||
|
use LIT\MCP\Log;
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* 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. Single-post purges are never throttled. The release/withdraw entries of the check workflow change
|
||||||
|
* nothing on the site and purge nothing.
|
||||||
|
*/
|
||||||
|
final class SuperCacheListener
|
||||||
|
{
|
||||||
|
public const TRAILING_HOOK = 'lit_mcp_wpsc_trailing_purge';
|
||||||
|
/** Transient with the time of the last whole-cache purge (also in Uninstaller). */
|
||||||
|
public const PURGED_AT = 'lit_mcp_wpsc_purged_at';
|
||||||
|
/** Seconds between two whole-cache purges. */
|
||||||
|
private const THROTTLE = 30;
|
||||||
|
|
||||||
|
private Closure $usable;
|
||||||
|
private Closure $cacheEnabled;
|
||||||
|
private Closure $purgePost;
|
||||||
|
private Closure $purgeAll;
|
||||||
|
private Closure $postIdFor;
|
||||||
|
/** @var array<int, true> posts changed in this request */
|
||||||
|
private array $posts = [];
|
||||||
|
private bool $everything = false;
|
||||||
|
private bool $queued = false;
|
||||||
|
private bool $warned = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults are the real WPSC 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(string, string): ?int)|null $postIdFor Post ID if objectType/objectId name an existing
|
||||||
|
* post of a public type but attachment.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
Closure $usable,
|
||||||
|
?Closure $cacheEnabled = null,
|
||||||
|
?Closure $purgePost = null,
|
||||||
|
?Closure $purgeAll = null,
|
||||||
|
?Closure $postIdFor = null
|
||||||
|
) {
|
||||||
|
$this->usable = $usable;
|
||||||
|
$this->cacheEnabled = $cacheEnabled ?? static fn (): bool => ! empty($GLOBALS['cache_enabled']);
|
||||||
|
$this->purgePost = $purgePost ?? static function (int $id): void {
|
||||||
|
wp_cache_post_edit($id);
|
||||||
|
};
|
||||||
|
$this->purgeAll = $purgeAll ?? static function (int $blog): void {
|
||||||
|
wp_cache_clear_cache($blog);
|
||||||
|
};
|
||||||
|
$this->postIdFor = $postIdFor ?? self::publicPostId(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(Compatibility $compatibility): self
|
||||||
|
{
|
||||||
|
return new self($compatibility->usable(...));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
add_action(ChangeLog::CHANGED_ACTION, [$this, 'onChange'], 10, 4);
|
||||||
|
add_action(self::TRAILING_HOOK, [$this, 'trailingPurge']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** lit_mcp_changed: only collects, see flush(). Never throws. */
|
||||||
|
public function onChange(string $ability, string $objectType, string $objectId, ?int $logId = null): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (CompatibilityTab::isReleaseAbility($ability) || ! ($this->cacheEnabled)()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$postId = ($this->postIdFor)($objectType, $objectId);
|
||||||
|
if ($postId === null) {
|
||||||
|
$this->everything = true;
|
||||||
|
} else {
|
||||||
|
$this->posts[$postId] = true;
|
||||||
|
}
|
||||||
|
if (! $this->queued) {
|
||||||
|
$this->queued = true;
|
||||||
|
add_action('shutdown', [$this, 'flush']);
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::failure('WP Super Cache purge', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** shutdown: the one purge of the request. Never throws: a failed purge must not break the response. */
|
||||||
|
public function flush(): void
|
||||||
|
{
|
||||||
|
$posts = array_keys($this->posts);
|
||||||
|
$everything = $this->everything;
|
||||||
|
$this->posts = [];
|
||||||
|
$this->everything = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($everything || count($posts) > 1) {
|
||||||
|
$this->purgeEverything();
|
||||||
|
} elseif ($posts !== []) {
|
||||||
|
$this->purge(SuperCacheFeature::PurgePost, fn () => ($this->purgePost)($posts[0]));
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::failure('WP Super Cache purge', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cron TRAILING_HOOK: the whole-cache purge a throttled one postponed. Same checks as the first try. */
|
||||||
|
public function trailingPurge(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (($this->cacheEnabled)()) {
|
||||||
|
$this->purgeEverything();
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::failure('WP Super Cache trailing purge', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
private function purgeEverything(): void
|
||||||
|
{
|
||||||
|
$this->purge(SuperCacheFeature::PurgeAll, function (): void {
|
||||||
|
$last = get_transient(self::PURGED_AT);
|
||||||
|
// Transients in the options table come back as strings.
|
||||||
|
if (is_numeric($last) && time() - (int) $last < self::THROTTLE) {
|
||||||
|
if (wp_next_scheduled(self::TRAILING_HOOK) === false) {
|
||||||
|
wp_schedule_single_event((int) $last + self::THROTTLE, self::TRAILING_HOOK);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
set_transient(self::PURGED_AT, time(), self::THROTTLE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param Closure(): void $purge */
|
||||||
|
private function purge(SuperCacheFeature $feature, Closure $purge): void
|
||||||
|
{
|
||||||
|
if (! ($this->usable)($feature)) {
|
||||||
|
if (! $this->warned) {
|
||||||
|
$this->warned = true;
|
||||||
|
Log::line(sprintf('WP Super Cache not purged: %s not usable (see tab Compatibility)', $feature->id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$purge();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function publicPostId(string $objectType, string $objectId): ?int
|
||||||
|
{
|
||||||
|
if ($objectType === 'attachment' || ! ctype_digit($objectId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$type = get_post_type_object($objectType);
|
||||||
|
if ($type === null || empty($type->public)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_post((int) $objectId) instanceof \WP_Post ? (int) $objectId : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-1
@@ -37,6 +37,8 @@ use LIT\MCP\Integrations\Pods\PodsCompatibility;
|
|||||||
use LIT\MCP\Integrations\Pods\PodsFeature;
|
use LIT\MCP\Integrations\Pods\PodsFeature;
|
||||||
use LIT\MCP\Integrations\Pods\PodsFieldSource;
|
use LIT\MCP\Integrations\Pods\PodsFieldSource;
|
||||||
use LIT\MCP\Integrations\Pods\PodsIntegration;
|
use LIT\MCP\Integrations\Pods\PodsIntegration;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheIntegration;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheListener;
|
||||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||||
use LIT\MCP\Integrations\WordPress\Schema\FieldSource;
|
use LIT\MCP\Integrations\WordPress\Schema\FieldSource;
|
||||||
use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource;
|
use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource;
|
||||||
@@ -96,6 +98,8 @@ final class Plugin
|
|||||||
// Integration-owned hooks (Divi: upgrader hook, check route, CLI command, tab "Divi") – before the settings
|
// Integration-owned hooks (Divi: upgrader hook, check route, CLI command, tab "Divi") – before the settings
|
||||||
// page reads its tabs.
|
// page reads its tabs.
|
||||||
$registry->boot();
|
$registry->boot();
|
||||||
|
// After every write of the plugin: empty the WP Super Cache where WordPress itself would not.
|
||||||
|
SuperCacheListener::create($registry->compatibility())->register();
|
||||||
// The admin check of every compatibility profile (POST lit-mcp/v1/<id>-check), REST like the MCP calls.
|
// The admin check of every compatibility profile (POST lit-mcp/v1/<id>-check), REST like the MCP calls.
|
||||||
(new CheckRoute($registry->compatibility()))->register();
|
(new CheckRoute($registry->compatibility()))->register();
|
||||||
|
|
||||||
@@ -209,7 +213,8 @@ final class Plugin
|
|||||||
$divi,
|
$divi,
|
||||||
$divi->library(),
|
$divi->library(),
|
||||||
$divi->themeBuilder(),
|
$divi->themeBuilder(),
|
||||||
new PodsIntegration()
|
new PodsIntegration(),
|
||||||
|
new SuperCacheIntegration()
|
||||||
);
|
);
|
||||||
|
|
||||||
return $registry;
|
return $registry;
|
||||||
|
|||||||
+10
-5
@@ -19,10 +19,12 @@ defined('ABSPATH') || exit;
|
|||||||
* Limits::OPTION, ChangeLog\ChangeLogInstaller::VERSION_OPTION, Integrations\Divi\Release\
|
* Limits::OPTION, ChangeLog\ChangeLogInstaller::VERSION_OPTION, Integrations\Divi\Release\
|
||||||
* lit_mcp_<profile>_releases (Compatibility\ReleaseStore), lit_mcp_<profile>_check_report
|
* lit_mcp_<profile>_releases (Compatibility\ReleaseStore), lit_mcp_<profile>_check_report
|
||||||
* (Compatibility\CheckReportStore) and lit_mcp_<profile>_check_lock (Admin\CheckRoute) for the
|
* (Compatibility\CheckReportStore) and lit_mcp_<profile>_check_lock (Admin\CheckRoute) for the
|
||||||
* profiles divi and pods (an explicit list: a LIKE pattern could hit foreign options),
|
* profiles divi, pods and wpsc (an explicit list: a LIKE pattern could hit foreign options),
|
||||||
* Integrations\Divi\Design\DesignLock::option(), Admin\ConnectionStatus::ENDPOINT_TRANSIENT,
|
* Integrations\Divi\Design\DesignLock::option(), Admin\ConnectionStatus::ENDPOINT_TRANSIENT,
|
||||||
* Integrations\Divi\Release\ReleaseResolver::CACHE,
|
* Integrations\Divi\Release\ReleaseResolver::CACHE,
|
||||||
* ChangeLog\ChangeLogCleanup::HOOK, ChangeLog\ChangeLog::TABLE, Admin\LogTab::PER_PAGE_OPTION and
|
* Integrations\SuperCache\SuperCacheListener::PURGED_AT (transient),
|
||||||
|
* ChangeLog\ChangeLogCleanup::HOOK, Integrations\SuperCache\SuperCacheListener::TRAILING_HOOK (cron),
|
||||||
|
* ChangeLog\ChangeLog::TABLE, Admin\LogTab::PER_PAGE_OPTION and
|
||||||
* Updater\RecheckOnInstall::OPTION on purpose;
|
* Updater\RecheckOnInstall::OPTION on purpose;
|
||||||
* tests/Unit/UninstallerTest.php and tests/Unit/UninstallTest.php keep them in sync with
|
* tests/Unit/UninstallerTest.php and tests/Unit/UninstallTest.php keep them in sync with
|
||||||
* those constants.
|
* those constants.
|
||||||
@@ -38,6 +40,7 @@ final class Uninstaller
|
|||||||
'lit_mcp_allowed_roles', 'lit_mcp_exposure', 'lit_mcp_limits', 'lit_mcp_db_version',
|
'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_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_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_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets',
|
'lit_mcp_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets',
|
||||||
'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css',
|
'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',
|
'lit_mcp_lock_divi_options', 'lit_mcp_lock_variables', 'lit_mcp_lock_presets', 'lit_mcp_lock_custom_css',
|
||||||
@@ -45,8 +48,8 @@ final class Uninstaller
|
|||||||
'lit_mcp_lock_colors', 'lit_mcp_lock_theme_options',
|
'lit_mcp_lock_colors', 'lit_mcp_lock_theme_options',
|
||||||
'lit_mcp_installed_version',
|
'lit_mcp_installed_version',
|
||||||
];
|
];
|
||||||
private const TRANSIENTS = ['lit_mcp_endpoint_check', 'lit_mcp_divi_state'];
|
private const TRANSIENTS = ['lit_mcp_endpoint_check', 'lit_mcp_divi_state', 'lit_mcp_wpsc_purged_at'];
|
||||||
private const CRON_HOOK = 'lit_mcp_log_cleanup';
|
private const CRON_HOOKS = ['lit_mcp_log_cleanup', 'lit_mcp_wpsc_trailing_purge'];
|
||||||
private const TABLE = 'lit_mcp_log';
|
private const TABLE = 'lit_mcp_log';
|
||||||
|
|
||||||
/** Screen option "Einträge pro Seite" of the Protokoll tab (Admin\LogTab::PER_PAGE_OPTION): user meta. */
|
/** Screen option "Einträge pro Seite" of the Protokoll tab (Admin\LogTab::PER_PAGE_OPTION): user meta. */
|
||||||
@@ -80,7 +83,9 @@ final class Uninstaller
|
|||||||
foreach (self::TRANSIENTS as $transient) {
|
foreach (self::TRANSIENTS as $transient) {
|
||||||
delete_transient($transient);
|
delete_transient($transient);
|
||||||
}
|
}
|
||||||
wp_clear_scheduled_hook(self::CRON_HOOK);
|
foreach (self::CRON_HOOKS as $hook) {
|
||||||
|
wp_clear_scheduled_hook($hook);
|
||||||
|
}
|
||||||
|
|
||||||
$table = $wpdb->prefix . self::TABLE;
|
$table = $wpdb->prefix . self::TABLE;
|
||||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- own table name, not user input.
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- own table name, not user input.
|
||||||
|
|||||||
@@ -165,6 +165,16 @@ final class CompatibilityTabTest extends TestCase
|
|||||||
return (string) ob_get_clean();
|
return (string) ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** One source for the ability name of the release and withdraw entries (SuperCacheListener reads it too). */
|
||||||
|
public function testTheReleaseAbilityNameHasOneSource(): void
|
||||||
|
{
|
||||||
|
self::assertSame('lit/divi-release', CompatibilityTab::releaseAbility('divi'));
|
||||||
|
self::assertTrue(CompatibilityTab::isReleaseAbility(CompatibilityTab::releaseAbility('wpsc')));
|
||||||
|
foreach (['lit/content-update', 'lit/-release', 'lit/release', 'lit/divi-releases'] as $other) {
|
||||||
|
self::assertFalse(CompatibilityTab::isReleaseAbility($other), $other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testItIsTheTabCompatibility(): void
|
public function testItIsTheTabCompatibility(): void
|
||||||
{
|
{
|
||||||
$tab = $this->tab(self::profile());
|
$tab = $this->tab(self::profile());
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Compatibility\Call;
|
||||||
|
use LIT\MCP\Compatibility\Compatibility;
|
||||||
|
use LIT\MCP\Compatibility\Tier;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviProfile;
|
||||||
|
use LIT\MCP\Integrations\Pods\PodsCompatibility;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheFeature;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
|
||||||
|
final class SuperCacheCompatibilityTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testDescribesSuperCacheAsALockOnFailurePlugin(): void
|
||||||
|
{
|
||||||
|
$profile = SuperCacheCompatibility::profile();
|
||||||
|
|
||||||
|
self::assertSame('wpsc', $profile->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 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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use LIT\MCP\Compatibility\Capability;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheFeature;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
|
||||||
|
final class SuperCacheFeatureTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEveryFeatureIsAWritingCapabilityWithAPrefixedId(): void
|
||||||
|
{
|
||||||
|
foreach (SuperCacheFeature::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())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||||
|
|
||||||
|
use LIT\MCP\Compatibility\ProvidesCompatibility;
|
||||||
|
use LIT\MCP\Integrations\Integration;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheCompatibility;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheIntegration;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
|
||||||
|
final class SuperCacheIntegrationTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testIsAnIntegrationWithoutAbilitiesThatProvidesItsProfile(): void
|
||||||
|
{
|
||||||
|
$integration = new SuperCacheIntegration();
|
||||||
|
|
||||||
|
self::assertInstanceOf(Integration::class, $integration);
|
||||||
|
self::assertInstanceOf(ProvidesCompatibility::class, $integration);
|
||||||
|
self::assertSame('wpsc', $integration->id());
|
||||||
|
self::assertSame('WP Super Cache', $integration->label());
|
||||||
|
self::assertSame([], $integration->abilities());
|
||||||
|
self::assertEquals([SuperCacheCompatibility::profile()], $integration->compatibilities());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsAvailableOnlyWithSuperCache(): void
|
||||||
|
{
|
||||||
|
self::assertSame(defined('WPCACHEHOME'), (new SuperCacheIntegration())->isAvailable());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\SuperCache;
|
||||||
|
|
||||||
|
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\Tests\Unit\TestCase;
|
||||||
|
use WP_Post;
|
||||||
|
|
||||||
|
final class SuperCacheListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @var list<string> */
|
||||||
|
private array $purges = [];
|
||||||
|
private string $errorLog = '';
|
||||||
|
private string|false $previousErrorLog = false;
|
||||||
|
/** @var list<array{int, string}> wp_schedule_single_event calls: [timestamp, hook] */
|
||||||
|
private array $scheduled = [];
|
||||||
|
/** @var list<array{string, int, int}> set_transient calls */
|
||||||
|
private array $stamped = [];
|
||||||
|
private int|string|false $lastPurge = false;
|
||||||
|
private int|false $planned = false;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->purges = [];
|
||||||
|
$this->scheduled = [];
|
||||||
|
$this->stamped = [];
|
||||||
|
$this->lastPurge = false;
|
||||||
|
$this->planned = false;
|
||||||
|
Functions\when('is_multisite')->justReturn(false);
|
||||||
|
Functions\when('get_transient')->alias(fn (): int|string|false => $this->lastPurge);
|
||||||
|
Functions\when('set_transient')->alias(function (string $key, int $value, int $ttl): bool {
|
||||||
|
$this->stamped[] = [$key, $value, $ttl];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
Functions\when('wp_next_scheduled')->alias(fn (): int|false => $this->planned);
|
||||||
|
Functions\when('wp_schedule_single_event')->alias(function (int $at, string $hook): bool {
|
||||||
|
$this->scheduled[] = [$at, $hook];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
$this->errorLog = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
|
||||||
|
$this->previousErrorLog = ini_set('error_log', $this->errorLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
ini_set('error_log', (string) $this->previousErrorLog);
|
||||||
|
unlink($this->errorLog);
|
||||||
|
unset($GLOBALS['cache_enabled']);
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param (Closure(string, string): ?int)|null $postIdFor */
|
||||||
|
private function listener(
|
||||||
|
bool $cacheOn = true,
|
||||||
|
bool $usablePost = true,
|
||||||
|
bool $usableAll = true,
|
||||||
|
?Closure $postIdFor = null
|
||||||
|
): SuperCacheListener {
|
||||||
|
return new SuperCacheListener(
|
||||||
|
static fn (SuperCacheFeature $feature): bool => $feature === SuperCacheFeature::PurgePost
|
||||||
|
? $usablePost
|
||||||
|
: $usableAll,
|
||||||
|
static fn (): bool => $cacheOn,
|
||||||
|
function (int $id): void {
|
||||||
|
$this->purges[] = 'post:' . $id;
|
||||||
|
},
|
||||||
|
function (int $blog): void {
|
||||||
|
$this->purges[] = 'all:' . $blog;
|
||||||
|
},
|
||||||
|
$postIdFor ?? static fn (string $type, string $id): ?int => in_array($type, ['page', 'post'], true)
|
||||||
|
? (int) $id
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logged(): string
|
||||||
|
{
|
||||||
|
return (string) file_get_contents($this->errorLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Collect, do not purge: the purge runs once at the end of the request. */
|
||||||
|
public function testAPublicPostPurgesOnlyThatPostAtTheEndOfTheRequest(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['post:12'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheSamePostTwiceIsOnePostPurge(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 6);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['post:12'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** More than one post: one whole-cache purge instead of N single ones (loops over many pages). */
|
||||||
|
public function testMoreThanOnePostIsOneWholeCachePurge(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->onChange('lit/content-update', 'page', '13', 6);
|
||||||
|
$listener->onChange('lit/content-update', 'post', '14', 7);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['all:0'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testThePurgeIsDoneOncePerFlush(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->flush();
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['post:12'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheShutdownHookIsRegisteredOnceForManyChanges(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->onChange('lit/content-update', 'page', '13', 6);
|
||||||
|
|
||||||
|
self::assertSame(10, has_action('shutdown', [$listener, 'flush']));
|
||||||
|
$listener->flush();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '14', 7);
|
||||||
|
$listener->flush();
|
||||||
|
self::assertSame(['all:0', 'post:14'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider siteWideChanges */
|
||||||
|
public function testEverythingElsePurgesTheWholeCache(string $ability, string $type, string $id): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange($ability, $type, $id, null);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['all:0'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, array{string, string, string}> */
|
||||||
|
public static function siteWideChanges(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'option' => ['lit/wp-settings-update', 'option', 'blogname'],
|
||||||
|
'menu' => ['lit/menu-set-items', 'nav_menu', '4'],
|
||||||
|
'menu location' => ['lit/menu-assign-location', 'menu_location', 'primary'],
|
||||||
|
'attachment' => ['lit/media-update', 'attachment', '31'],
|
||||||
|
'library module' => ['lit/library-save', 'et_pb_layout', '77'],
|
||||||
|
'divi design' => ['lit/divi-design-update', 'divi_design', 'colors'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAPostThatIsGoneIsAWholeCachePurge(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener(postIdFor: static fn (): ?int => null);
|
||||||
|
$listener->onChange('lit/post-trash', 'post', '99', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['all:0'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One whole-cache change among single posts: still one purge, no single ones on top. */
|
||||||
|
public function testAWholeCacheChangeSwallowsThePostPurges(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 1);
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'a', 2);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['all:0'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Release and withdraw entries of the check workflow change nothing on the site (shared ability name). */
|
||||||
|
public function testReleaseEntriesOfTheCheckWorkflowPurgeNothing(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange(CompatibilityTab::releaseAbility('divi'), 'divi', '5.0.0', 1);
|
||||||
|
$listener->onChange(CompatibilityTab::releaseAbility('pods'), 'pods', '3.3.9.2', 2);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNothingHappensWhileTheCacheIsOff(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener(cacheOn: false);
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
self::assertSame('', $this->logged());
|
||||||
|
self::assertFalse(has_action('shutdown', [$listener, 'flush']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testALockedCapabilityPurgesNothingAndLogsOncePerRequest(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener(usablePost: false);
|
||||||
|
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->flush();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '13', 6);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
self::assertSame(1, substr_count($this->logged(), 'wpsc-purge-post'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The capabilities lock separately: a gone purge-post does not stop the whole-cache purge. */
|
||||||
|
public function testEachPurgeNeedsItsOwnCapability(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener(usablePost: false);
|
||||||
|
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['all:0'], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testALockedWholeCachePurgeSchedulesNothing(): void
|
||||||
|
{
|
||||||
|
$this->lastPurge = time() - 5;
|
||||||
|
$listener = $this->listener(usableAll: false);
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
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();
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertCount(1, $this->stamped);
|
||||||
|
self::assertSame('lit_mcp_wpsc_purged_at', $this->stamped[0][0]);
|
||||||
|
self::assertEqualsWithDelta(time(), $this->stamped[0][1], 2);
|
||||||
|
self::assertSame(30, $this->stamped[0][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Single-post purges are never throttled and leave no stamp. */
|
||||||
|
public function testPostPurgesAreNotThrottled(): void
|
||||||
|
{
|
||||||
|
$this->lastPurge = time() - 1;
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame(['post:12'], $this->purges);
|
||||||
|
self::assertSame([], $this->stamped);
|
||||||
|
self::assertSame([], $this->scheduled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inside the lock time: no purge now, exactly one trailing purge at the end of the lock time. */
|
||||||
|
public function testAWholeCachePurgeInsideTheLockTimeSchedulesOneTrailingPurge(): void
|
||||||
|
{
|
||||||
|
$this->lastPurge = time() - 10;
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
self::assertSame([], $this->stamped);
|
||||||
|
self::assertSame([[$this->lastPurge + 30, 'lit_mcp_wpsc_trailing_purge']], $this->scheduled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Options-table transients come back as strings. */
|
||||||
|
public function testAStringStampFromTheDatabaseThrottlesToo(): void
|
||||||
|
{
|
||||||
|
$this->lastPurge = (string) (time() - 10);
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
self::assertCount(1, $this->scheduled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoSecondTrailingPurgeIsScheduledWhileOneIsPlanned(): void
|
||||||
|
{
|
||||||
|
$this->lastPurge = time() - 10;
|
||||||
|
$this->planned = time() + 20;
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([], $this->scheduled);
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheTrailingPurgeEmptiesTheWholeCache(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->trailingPurge();
|
||||||
|
|
||||||
|
self::assertSame(['all:0'], $this->purges);
|
||||||
|
self::assertCount(1, $this->stamped);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheTrailingPurgeKeepsTheChecks(): void
|
||||||
|
{
|
||||||
|
$this->listener(cacheOn: false)->trailingPurge();
|
||||||
|
$this->listener(usableAll: false)->trailingPurge();
|
||||||
|
|
||||||
|
self::assertSame([], $this->purges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAThrowingTrailingPurgeIsLogged(): void
|
||||||
|
{
|
||||||
|
$listener = new SuperCacheListener(
|
||||||
|
static fn (): bool => true,
|
||||||
|
static fn (): bool => true,
|
||||||
|
null,
|
||||||
|
static function (): void {
|
||||||
|
throw new \RuntimeException('trailing broke');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$listener->trailingPurge();
|
||||||
|
|
||||||
|
self::assertStringContainsString('trailing broke', $this->logged());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAThrowingPurgeIsLoggedAndNotRethrown(): void
|
||||||
|
{
|
||||||
|
$listener = new SuperCacheListener(
|
||||||
|
static fn (): bool => true,
|
||||||
|
static fn (): bool => true,
|
||||||
|
static function (): void {
|
||||||
|
throw new \RuntimeException('disk full');
|
||||||
|
},
|
||||||
|
static fn (int $blog): null => null,
|
||||||
|
static fn (): int => 12
|
||||||
|
);
|
||||||
|
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertStringContainsString('disk full', $this->logged());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRegisterHooksTheChangeAnnouncementAndTheTrailingPurge(): void
|
||||||
|
{
|
||||||
|
$listener = $this->listener();
|
||||||
|
$listener->register();
|
||||||
|
|
||||||
|
self::assertSame(10, has_action('lit_mcp_changed', [$listener, 'onChange']));
|
||||||
|
self::assertSame(10, has_action('lit_mcp_wpsc_trailing_purge', [$listener, 'trailingPurge']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Plugin wires create(): without WP Super 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);
|
||||||
|
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
self::assertSame('', $this->logged());
|
||||||
|
|
||||||
|
$GLOBALS['cache_enabled'] = true;
|
||||||
|
$listener->onChange('lit/wp-settings-update', 'option', 'blogname', 1);
|
||||||
|
$listener->flush();
|
||||||
|
self::assertStringContainsString('wpsc-purge-all not usable', $this->logged());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheDefaultCacheSwitchIsTheWpscGlobal(): 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);
|
||||||
|
|
||||||
|
$GLOBALS['cache_enabled'] = false;
|
||||||
|
$listener->onChange('lit/content-update', 'page', '12', 5);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame('', $this->logged(), 'unset or false cache_enabled: silent, nothing purged');
|
||||||
|
self::assertFalse(has_action('shutdown', [$listener, 'flush']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider unpurgeablePosts */
|
||||||
|
public function testTheDefaultPostLookupAcceptsOnlyPublicPostTypesThatExist(
|
||||||
|
string $type,
|
||||||
|
string $id,
|
||||||
|
bool $public,
|
||||||
|
bool $exists
|
||||||
|
): void {
|
||||||
|
$GLOBALS['cache_enabled'] = true;
|
||||||
|
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 {
|
||||||
|
$purged[] = 'all';
|
||||||
|
});
|
||||||
|
Functions\when('wp_cache_post_edit')->alias(static function () use (&$purged): void {
|
||||||
|
$purged[] = 'post';
|
||||||
|
});
|
||||||
|
|
||||||
|
$listener = new SuperCacheListener(static fn (): bool => true);
|
||||||
|
$listener->onChange('lit/x', $type, $id, 1);
|
||||||
|
$listener->flush();
|
||||||
|
|
||||||
|
self::assertSame([$public && $exists && $type !== 'attachment' && ctype_digit($id) ? 'post' : 'all'], $purged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, array{string, string, bool, bool}> */
|
||||||
|
public static function unpurgeablePosts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'public and present' => ['page', '12', true, true],
|
||||||
|
'not public' => ['et_pb_layout', '12', false, true],
|
||||||
|
'attachment' => ['attachment', '12', true, true],
|
||||||
|
'gone' => ['page', '12', true, false],
|
||||||
|
'id not numeric' => ['page', 'abc', true, true],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ final class PluginTest extends TestCase
|
|||||||
public function testIntegrationsInRegistryOrder(): void
|
public function testIntegrationsInRegistryOrder(): void
|
||||||
{
|
{
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
['core', 'wordpress', 'media', 'divi', 'divi-library', 'divi-theme-builder', 'pods'],
|
['core', 'wordpress', 'media', 'divi', 'divi-library', 'divi-theme-builder', 'pods', 'wpsc'],
|
||||||
self::registry()->ids()
|
self::registry()->ids()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -95,8 +95,17 @@ final class PluginTest extends TestCase
|
|||||||
self::assertArrayNotHasKey('pods', $registry->grouped());
|
self::assertArrayNotHasKey('pods', $registry->grouped());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** lit-site-status keeps its order: Divi, Pods, The Events Calendar. */
|
/** WP Super Cache has no tools either: registered for its profile only. */
|
||||||
public function testSiteStatusListsDiviPodsAndTheEventsCalendarInThatOrder(): void
|
public function testSuperCacheIsRegisteredWithoutBecomingAToolGroup(): void
|
||||||
|
{
|
||||||
|
$registry = self::registry();
|
||||||
|
|
||||||
|
self::assertContains('wpsc', $registry->ids());
|
||||||
|
self::assertArrayNotHasKey('wpsc', $registry->grouped());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** lit-site-status keeps its order: Divi, Pods, WP Super Cache, The Events Calendar. */
|
||||||
|
public function testSiteStatusListsDiviPodsSuperCacheAndTheEventsCalendarInThatOrder(): void
|
||||||
{
|
{
|
||||||
Functions\when('get_bloginfo')->justReturn('x');
|
Functions\when('get_bloginfo')->justReturn('x');
|
||||||
Functions\when('home_url')->justReturn('https://example.test');
|
Functions\when('home_url')->justReturn('https://example.test');
|
||||||
@@ -113,17 +122,23 @@ final class PluginTest extends TestCase
|
|||||||
|
|
||||||
$components = $status->execute([])['components'];
|
$components = $status->execute([])['components'];
|
||||||
|
|
||||||
self::assertSame(['divi', 'pods', 'the-events-calendar'], array_column($components, 'id'));
|
self::assertSame(['divi', 'pods', 'wpsc', 'the-events-calendar'], array_column($components, 'id'));
|
||||||
$pods = array_column($components, null, 'id')['pods'];
|
$pods = array_column($components, null, 'id')['pods'];
|
||||||
self::assertFalse($pods['active'], 'no Pods in unit tests');
|
self::assertFalse($pods['active'], 'no Pods in unit tests');
|
||||||
self::assertSame([], $pods['problems']);
|
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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTheCompatibilityCoversAllProfilesWithoutIdCollision(): void
|
public function testTheCompatibilityCoversAllProfilesWithoutIdCollision(): void
|
||||||
{
|
{
|
||||||
$compat = self::registry()->compatibility();
|
$compat = self::registry()->compatibility();
|
||||||
|
|
||||||
self::assertSame(['divi', 'pods'], array_map(static fn (Profile $p): string => $p->id, $compat->profiles()));
|
$ids = array_map(static fn (Profile $p): string => $p->id, $compat->profiles());
|
||||||
|
self::assertSame(['divi', 'pods', 'wpsc'], $ids);
|
||||||
|
self::assertSame($ids, array_values(array_unique($ids)));
|
||||||
|
self::assertSame('wpsc', $compat->owner($compat->capability('wpsc-purge-all'))?->id);
|
||||||
self::assertNotNull($compat->capability('page-write'));
|
self::assertNotNull($compat->capability('page-write'));
|
||||||
self::assertNotNull($compat->capability('pods-field-read'));
|
self::assertNotNull($compat->capability('pods-field-read'));
|
||||||
self::assertSame('divi', $compat->owner($compat->capability('page-write'))?->id);
|
self::assertSame('divi', $compat->owner($compat->capability('page-write'))?->id);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use LIT\MCP\Admin\ConnectionStatus;
|
|||||||
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
||||||
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheListener;
|
||||||
use LIT\MCP\Limits;
|
use LIT\MCP\Limits;
|
||||||
use LIT\MCP\Updater\RecheckOnInstall;
|
use LIT\MCP\Updater\RecheckOnInstall;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
@@ -41,7 +42,11 @@ final class UninstallTest extends TestCase
|
|||||||
$transients[] = $transient;
|
$transients[] = $transient;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
Functions\expect('wp_clear_scheduled_hook')->twice()->with(ChangeLogCleanup::HOOK);
|
$hooks = [];
|
||||||
|
Functions\when('wp_clear_scheduled_hook')->alias(static function (string $hook) use (&$hooks): int {
|
||||||
|
$hooks[] = $hook;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
Functions\expect('delete_metadata')->once()->with('user', 0, 'lit_mcp_log_per_page', '', true);
|
Functions\expect('delete_metadata')->once()->with('user', 0, 'lit_mcp_log_per_page', '', true);
|
||||||
$deleted = [];
|
$deleted = [];
|
||||||
Functions\when('delete_option')->alias(static function (string $option) use (&$deleted): bool {
|
Functions\when('delete_option')->alias(static function (string $option) use (&$deleted): bool {
|
||||||
@@ -55,6 +60,7 @@ final class UninstallTest extends TestCase
|
|||||||
Access::OPTION, ExposurePolicy::OPTION, Limits::OPTION, ChangeLogInstaller::VERSION_OPTION,
|
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_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_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_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets',
|
'lit_mcp_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets',
|
||||||
'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css',
|
'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css',
|
||||||
DesignLock::option('colors'), DesignLock::option('variables'), DesignLock::option('presets'), DesignLock::option('custom_css'),
|
DesignLock::option('colors'), DesignLock::option('variables'), DesignLock::option('presets'), DesignLock::option('custom_css'),
|
||||||
@@ -67,10 +73,17 @@ final class UninstallTest extends TestCase
|
|||||||
self::assertNotContains('wpmedia_mcp_oauth_rewrite_version', $deleted);
|
self::assertNotContains('wpmedia_mcp_oauth_rewrite_version', $deleted);
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
[
|
[
|
||||||
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE,
|
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT,
|
||||||
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE,
|
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT,
|
||||||
],
|
],
|
||||||
$transients
|
$transients
|
||||||
);
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[
|
||||||
|
ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK,
|
||||||
|
ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK,
|
||||||
|
],
|
||||||
|
$hooks
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use LIT\MCP\Admin\LogTab;
|
|||||||
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
use LIT\MCP\ChangeLog\ChangeLogCleanup;
|
||||||
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
use LIT\MCP\ChangeLog\ChangeLogInstaller;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
|
||||||
|
use LIT\MCP\Integrations\SuperCache\SuperCacheListener;
|
||||||
use LIT\MCP\Limits;
|
use LIT\MCP\Limits;
|
||||||
use LIT\MCP\Uninstaller;
|
use LIT\MCP\Uninstaller;
|
||||||
use LIT\MCP\Updater\RecheckOnInstall;
|
use LIT\MCP\Updater\RecheckOnInstall;
|
||||||
@@ -44,7 +45,11 @@ final class UninstallerTest extends TestCase
|
|||||||
$transients[] = $transient;
|
$transients[] = $transient;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
Functions\expect('wp_clear_scheduled_hook')->once()->with(ChangeLogCleanup::HOOK);
|
$hooks = [];
|
||||||
|
Functions\when('wp_clear_scheduled_hook')->alias(static function (string $hook) use (&$hooks): int {
|
||||||
|
$hooks[] = $hook;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
// Review m-8: the Protokoll screen option is user meta (network-wide): removed once.
|
// Review m-8: the Protokoll screen option is user meta (network-wide): removed once.
|
||||||
Functions\expect('delete_metadata')->once()->with('user', 0, LogTab::PER_PAGE_OPTION, '', true);
|
Functions\expect('delete_metadata')->once()->with('user', 0, LogTab::PER_PAGE_OPTION, '', true);
|
||||||
$deleted = [];
|
$deleted = [];
|
||||||
@@ -55,12 +60,17 @@ final class UninstallerTest extends TestCase
|
|||||||
|
|
||||||
Uninstaller::run();
|
Uninstaller::run();
|
||||||
|
|
||||||
self::assertSame([ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE], $transients);
|
self::assertSame(
|
||||||
|
[ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT],
|
||||||
|
$transients
|
||||||
|
);
|
||||||
|
self::assertSame([ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK], $hooks);
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
[
|
[
|
||||||
Access::OPTION, ExposurePolicy::OPTION, Limits::OPTION, ChangeLogInstaller::VERSION_OPTION,
|
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_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_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_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets',
|
'lit_mcp_snapshots_colors', 'lit_mcp_snapshots_variables', 'lit_mcp_snapshots_presets',
|
||||||
'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css',
|
'lit_mcp_snapshots_theme_options', 'lit_mcp_snapshots_custom_css',
|
||||||
DesignLock::option('colors'), DesignLock::option('variables'), DesignLock::option('presets'), DesignLock::option('custom_css'),
|
DesignLock::option('colors'), DesignLock::option('variables'), DesignLock::option('presets'), DesignLock::option('custom_css'),
|
||||||
@@ -86,7 +96,11 @@ final class UninstallerTest extends TestCase
|
|||||||
$transients[] = $transient;
|
$transients[] = $transient;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
Functions\expect('wp_clear_scheduled_hook')->twice()->with(ChangeLogCleanup::HOOK);
|
$hooks = [];
|
||||||
|
Functions\when('wp_clear_scheduled_hook')->alias(static function (string $hook) use (&$hooks): int {
|
||||||
|
$hooks[] = $hook;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
Functions\expect('delete_metadata')->once()->with('user', 0, LogTab::PER_PAGE_OPTION, '', true);
|
Functions\expect('delete_metadata')->once()->with('user', 0, LogTab::PER_PAGE_OPTION, '', true);
|
||||||
Functions\when('delete_option')->justReturn(true);
|
Functions\when('delete_option')->justReturn(true);
|
||||||
|
|
||||||
@@ -94,10 +108,17 @@ final class UninstallerTest extends TestCase
|
|||||||
|
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
[
|
[
|
||||||
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE,
|
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT,
|
||||||
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE,
|
ConnectionStatus::ENDPOINT_TRANSIENT, ReleaseResolver::CACHE, SuperCacheListener::PURGED_AT,
|
||||||
],
|
],
|
||||||
$transients
|
$transients
|
||||||
);
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[
|
||||||
|
ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK,
|
||||||
|
ChangeLogCleanup::HOOK, SuperCacheListener::TRAILING_HOOK,
|
||||||
|
],
|
||||||
|
$hooks
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user