From 99c7aab23890da6133738d6217052116e50a39a7 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Tue, 20 Oct 2026 00:26:40 +0200 Subject: [PATCH] fix(audit L5): unter der Sperre Divis Laufzeit-Caches verwerfen (Options-Cache-Gruppe, GlobalData-/GlobalPreset-Static, et_theme_options) vor raw() und design_rev Co-Authored-By: Claude Sonnet 5.5 --- src/Integrations/Divi/Design/ColorArea.php | 5 ++++ src/Integrations/Divi/Design/DesignArea.php | 6 ++++ src/Integrations/Divi/Design/DesignChange.php | 3 ++ src/Integrations/Divi/Design/DesignLock.php | 11 +++---- .../Divi/Design/DesignSystemWriter.php | 13 +++++++++ .../Divi/Design/GlobalColorsAdapter.php | 27 ++++++++++++++++- .../Divi/Design/GlobalVariablesAdapter.php | 24 +++++++++++++++ src/Integrations/Divi/Design/PresetArea.php | 5 ++++ .../Divi/Design/ThemeOptionsArea.php | 5 ++++ src/Integrations/Divi/Design/VariableArea.php | 5 ++++ .../Divi/Design/ColorAreaTest.php | 1 + .../Divi/Design/ColorRestorePlannerTest.php | 1 + .../Divi/Design/DesignChangeTest.php | 29 +++++++++++++++++++ .../Divi/Design/DesignLockTest.php | 21 +++++++++----- .../Divi/Design/DesignRightsTest.php | 6 ++++ .../Divi/Design/DiviColorsSetAbilityTest.php | 2 ++ .../Design/DiviDesignDetachAbilityTest.php | 6 ++++ .../Design/DiviDesignRestoreAbilityTest.php | 7 +++++ .../Divi/Design/DiviPresetGetAbilityTest.php | 1 + .../Design/DiviPresetsAssignAbilityTest.php | 1 + .../Design/DiviPresetsDeleteAbilityTest.php | 3 ++ .../Divi/Design/DiviPresetsSetAbilityTest.php | 2 ++ .../Design/DiviVariablesSetAbilityTest.php | 2 ++ .../Divi/Design/PresetAreaTest.php | 2 ++ .../Divi/Design/VariableAreaTest.php | 1 + .../Design/VariableRestorePlannerTest.php | 2 ++ .../Divi/Pages/DiviCompositionTest.php | 2 ++ tests/Unit/TestCase.php | 2 ++ 28 files changed, 182 insertions(+), 13 deletions(-) diff --git a/src/Integrations/Divi/Design/ColorArea.php b/src/Integrations/Divi/Design/ColorArea.php index d5e9e04..d7ec0ec 100644 --- a/src/Integrations/Divi/Design/ColorArea.php +++ b/src/Integrations/Divi/Design/ColorArea.php @@ -36,6 +36,11 @@ final class ColorArea implements DesignArea return DiviFeature::ColorWrite; } + public function refresh(): void + { + $this->colors->refresh(); + } + public function raw(): array { return ColorBook::fromDivi($this->colors->read())->data(); diff --git a/src/Integrations/Divi/Design/DesignArea.php b/src/Integrations/Divi/Design/DesignArea.php index d926e0e..8495ebc 100644 --- a/src/Integrations/Divi/Design/DesignArea.php +++ b/src/Integrations/Divi/Design/DesignArea.php @@ -25,6 +25,12 @@ interface DesignArea /** The Divi feature a write of this area needs (DesignChange asks ReleaseGate::assertFeature() for it). */ public function feature(): DiviFeature; + /** + * Drops what this request cached of the inventory (Divi's static and option caches), called right after the + * lock is taken: another writer may have finished since the request began (audit L5). + */ + public function refresh(): void; + /** @return array */ public function raw(): array; diff --git a/src/Integrations/Divi/Design/DesignChange.php b/src/Integrations/Divi/Design/DesignChange.php index 047700a..a68a20a 100644 --- a/src/Integrations/Divi/Design/DesignChange.php +++ b/src/Integrations/Divi/Design/DesignChange.php @@ -93,6 +93,9 @@ final class DesignChange ); } try { + // Audit L5: drop Divi's request caches, so the inventory and design_rev below are the stored state. + $area->refresh(); + return $this->attempt($ability, $area, $rev, $plan); } finally { $this->lock->release($area->name()); diff --git a/src/Integrations/Divi/Design/DesignLock.php b/src/Integrations/Divi/Design/DesignLock.php index 21aa837..fdf5ceb 100644 --- a/src/Integrations/Divi/Design/DesignLock.php +++ b/src/Integrations/Divi/Design/DesignLock.php @@ -15,8 +15,8 @@ defined('ABSPATH') || exit; * DiviCheckRoute (add_option() reads first and writes after, so two requests could both win; audit L6). A lock * older than STALE_AFTER seconds (a request that died) is taken over with a conditional UPDATE on the value just * read, so only one taker wins; release() deletes the row only while it still holds this instance's token, so a - * holder that lost a stale lock cannot free the new holder's. A taken lock drops the cache of the autoloaded - * options, so the inventory read under the lock (and design_rev) is the stored one. + * holder that lost a stale lock cannot free the new holder's. A taken lock drops the options cache, so the + * inventory read under the lock (and design_rev) is the stored one. */ final class DesignLock { @@ -66,9 +66,10 @@ final class DesignLock } if ($taken) { $this->tokens[$option] = $token; - // Audit L5: what the holder reads and rewrites must be the stored state, not the request-start cache - // (autoloaded options like Divi's live in "alloptions"); another writer may have finished since. - wp_cache_delete('alloptions', 'options'); + // Audit L5: what the holder reads and rewrites must be the stored state, not the request's option cache + // ("alloptions" for autoloaded options, one entry each for the others); another writer may have + // finished since. A cache without group flushing loses everything – rare, and only under the lock. + wp_cache_supports('flush_group') ? wp_cache_flush_group('options') : wp_cache_flush(); } return $taken; diff --git a/src/Integrations/Divi/Design/DesignSystemWriter.php b/src/Integrations/Divi/Design/DesignSystemWriter.php index a3f8081..925a8e7 100644 --- a/src/Integrations/Divi/Design/DesignSystemWriter.php +++ b/src/Integrations/Divi/Design/DesignSystemWriter.php @@ -81,6 +81,19 @@ class DesignSystemWriter implements DiviAdapter ); } + /** + * Audit L5: GlobalPreset::get_data() keeps a static copy that can be older than another writer's finished save + * (the option cache is dropped by DesignLock). Called under the lock, before reading. + */ + public function refresh(): void + { + try { + (new \ReflectionProperty(GlobalPreset::class, '_data'))->setValue(null, null); + } catch (\ReflectionException) { + // Divi without that property: its reads are not cached this way. + } + } + /** @return array The stored inventory (GlobalPreset::get_data(), formats §3). */ public function presets(): array { diff --git a/src/Integrations/Divi/Design/GlobalColorsAdapter.php b/src/Integrations/Divi/Design/GlobalColorsAdapter.php index 7531f69..9912c6a 100644 --- a/src/Integrations/Divi/Design/GlobalColorsAdapter.php +++ b/src/Integrations/Divi/Design/GlobalColorsAdapter.php @@ -84,7 +84,32 @@ class GlobalColorsAdapter implements DiviAdapter } // Audit L5: set_global_colors() ends in et_update_option(), which writes Divi's request global back whole. - $GLOBALS['et_theme_options'] = get_option('et_divi'); + $this->refresh(); GlobalData::set_global_colors($clean, ! $replace); } + + /** + * Audit L5: colors are read through et_get_option(), i.e. Divi's request global and GlobalData's static cache – + * both can be older than another writer's finished save. Called under the lock, before reading. + */ + public function refresh(): void + { + $GLOBALS['et_theme_options'] = get_option(self::OPTIONS); + self::forgetCached('colors'); + } + + /** Resets one entry of GlobalData's static runtime cache (private, so by reflection; gone = nothing to reset). */ + private static function forgetCached(string $key): void + { + try { + $cache = new \ReflectionProperty(GlobalData::class, '_cached_data'); + $data = $cache->getValue(); + if (is_array($data) && array_key_exists($key, $data)) { + $data[$key] = null; + $cache->setValue(null, $data); + } + } catch (\ReflectionException) { + // Divi without that property: its reads are not cached this way. + } + } } diff --git a/src/Integrations/Divi/Design/GlobalVariablesAdapter.php b/src/Integrations/Divi/Design/GlobalVariablesAdapter.php index 4b05233..df71d19 100644 --- a/src/Integrations/Divi/Design/GlobalVariablesAdapter.php +++ b/src/Integrations/Divi/Design/GlobalVariablesAdapter.php @@ -86,4 +86,28 @@ class GlobalVariablesAdapter implements DiviAdapter { return function_exists('et_pb_is_allowed') && (bool) et_pb_is_allowed('variables_manager'); } + + /** + * Audit L5: GlobalData keeps a static copy of the variables that can be older than another writer's finished + * save (the option cache is dropped by DesignLock). Called under the lock, before reading. + */ + public function refresh(): void + { + self::forgetCached('variables'); + } + + /** Resets one entry of GlobalData's static runtime cache (private, so by reflection; gone = nothing to reset). */ + private static function forgetCached(string $key): void + { + try { + $cache = new \ReflectionProperty(GlobalData::class, '_cached_data'); + $data = $cache->getValue(); + if (is_array($data) && array_key_exists($key, $data)) { + $data[$key] = null; + $cache->setValue(null, $data); + } + } catch (\ReflectionException) { + // Divi without that property: its reads are not cached this way. + } + } } diff --git a/src/Integrations/Divi/Design/PresetArea.php b/src/Integrations/Divi/Design/PresetArea.php index 9ddeed6..d31cf52 100644 --- a/src/Integrations/Divi/Design/PresetArea.php +++ b/src/Integrations/Divi/Design/PresetArea.php @@ -34,6 +34,11 @@ final class PresetArea implements DesignArea return DiviFeature::PresetWrite; } + public function refresh(): void + { + $this->writer->refresh(); + } + public function raw(): array { return PresetBook::fromDivi($this->writer->presets())->data(); diff --git a/src/Integrations/Divi/Design/ThemeOptionsArea.php b/src/Integrations/Divi/Design/ThemeOptionsArea.php index 91ffb41..b731e14 100644 --- a/src/Integrations/Divi/Design/ThemeOptionsArea.php +++ b/src/Integrations/Divi/Design/ThemeOptionsArea.php @@ -40,6 +40,11 @@ final class ThemeOptionsArea implements DesignArea return DiviFeature::ThemeOptionWrite; } + public function refresh(): void + { + // read() asks get_option(), whose cache the lock drops; write() reloads Divi's request global itself. + } + public function raw(): array { return ThemeOptionBook::fromDivi($this->options->read())->data(); diff --git a/src/Integrations/Divi/Design/VariableArea.php b/src/Integrations/Divi/Design/VariableArea.php index dc58dcc..1be5e2b 100644 --- a/src/Integrations/Divi/Design/VariableArea.php +++ b/src/Integrations/Divi/Design/VariableArea.php @@ -40,6 +40,11 @@ final class VariableArea implements DesignArea return DiviFeature::VariableWrite; } + public function refresh(): void + { + $this->variables->refresh(); + } + public function raw(): array { return VariableBook::fromDivi($this->variables->read())->data(); diff --git a/tests/Unit/Integrations/Divi/Design/ColorAreaTest.php b/tests/Unit/Integrations/Divi/Design/ColorAreaTest.php index 7c2c1b1..b27c9ee 100644 --- a/tests/Unit/Integrations/Divi/Design/ColorAreaTest.php +++ b/tests/Unit/Integrations/Divi/Design/ColorAreaTest.php @@ -43,6 +43,7 @@ final class ColorAreaTest extends TestCase public static function adapter(array &$stored, array &$saves): GlobalColorsAdapter { $colors = Mockery::mock(GlobalColorsAdapter::class); + $colors->allows('refresh'); $colors->allows('read')->andReturnUsing(static function () use (&$stored): array { return $stored; }); diff --git a/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php b/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php index 1dfd089..76459d4 100644 --- a/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php +++ b/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php @@ -47,6 +47,7 @@ final class ColorRestorePlannerTest extends TestCase private static function planner(bool $visualBuilder = true, ?GlobalUsages $usages = null): ColorRestorePlanner { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn($visualBuilder); return new ColorRestorePlanner( diff --git a/tests/Unit/Integrations/Divi/Design/DesignChangeTest.php b/tests/Unit/Integrations/Divi/Design/DesignChangeTest.php index 40a9ea1..abc0a73 100644 --- a/tests/Unit/Integrations/Divi/Design/DesignChangeTest.php +++ b/tests/Unit/Integrations/Divi/Design/DesignChangeTest.php @@ -56,6 +56,9 @@ final class DesignChangeTest extends TestCase public array $restores = []; + /** @var list refresh()/raw() in the order they happened */ + public array $calls = []; + public function __construct( private array &$stored, private int $failWrites, @@ -79,8 +82,15 @@ final class DesignChangeTest extends TestCase return DiviFeature::PresetWrite; } + public function refresh(): void + { + $this->calls[] = 'refresh'; + } + public function raw(): array { + $this->calls[] = 'raw'; + return $this->stored; } @@ -205,6 +215,25 @@ final class DesignChangeTest extends TestCase self::assertSame([], $area->writes); } + /** Audit L5 (re-review): under the lock Divi's request caches are dropped before the inventory is read. */ + public function testTheAreaDropsItsCachesUnderTheLockBeforeReading(): void + { + $stored = ['v' => 1]; + $area = self::area($stored); + $snapshots = Mockery::mock(SnapshotStore::class); + $snapshots->allows('save')->andReturn('presets-1'); + $snapshots->allows('attachLog'); + + (new DesignChange($snapshots, new FakeChangeLog(1), new DesignLock()))->run( + 'lit/divi-presets-set', + $area, + ['confirm' => true, 'design_rev' => $area->rev($stored)], + self::plan(['v' => 2]) + ); + + self::assertSame(['refresh', 'raw'], array_slice($area->calls, 0, 2)); + } + /** Final review I-5: a confirm while another change of the area runs writes nothing. */ public function testAHeldLockRefusesTheConfirm(): void { diff --git a/tests/Unit/Integrations/Divi/Design/DesignLockTest.php b/tests/Unit/Integrations/Divi/Design/DesignLockTest.php index e448db1..0a8f297 100644 --- a/tests/Unit/Integrations/Divi/Design/DesignLockTest.php +++ b/tests/Unit/Integrations/Divi/Design/DesignLockTest.php @@ -102,26 +102,33 @@ final class DesignLockTest extends TestCase public function testTheLockHolderReadsEtDiviFreshNotFromTheRequestStartCache(): void { $db = $this->table(); - $stored = ['et_divi' => ['color' => 'alt']]; + $stored = ['et_divi' => ['color' => 'alt'], 'et_divi_global_variables' => ['v' => 'alt']]; $cache = []; + // Like WordPress: every option is cached under its own key (not autoloaded ones) or in "alloptions". Functions\when('get_option')->alias(static function (string $name) use (&$stored, &$cache) { - if (! isset($cache['options:' . $name]) && ! isset($cache['options:alloptions'][$name])) { - $cache['options:alloptions'][$name] = $stored[$name] ?? false; - } + $key = 'options:' . $name; + $cache[$key] ??= $stored[$name] ?? false; - return $cache['options:' . $name] ?? $cache['options:alloptions'][$name]; + return $cache[$key]; }); - Functions\when('wp_cache_delete')->alias(static function (string $key, string $group = '') use (&$cache): bool { - unset($cache[$group . ':' . $key]); + Functions\when('wp_cache_flush_group')->alias(static function (string $group) use (&$cache): bool { + foreach (array_keys($cache) as $key) { + if (str_starts_with($key, $group . ':')) { + unset($cache[$key]); + } + } return true; }); self::assertSame(['color' => 'alt'], get_option('et_divi'), 'request B cached the old array'); + self::assertSame(['v' => 'alt'], get_option('et_divi_global_variables')); $stored['et_divi'] = ['color' => 'neu']; // request A wrote and released meanwhile + $stored['et_divi_global_variables'] = ['v' => 'neu']; self::assertTrue((new DesignLock())->acquire('colors')); self::assertSame(['color' => 'neu'], get_option('et_divi')); + self::assertSame(['v' => 'neu'], get_option('et_divi_global_variables'), 'also options that are not autoloaded'); self::assertNotSame([], $db->rows); } } diff --git a/tests/Unit/Integrations/Divi/Design/DesignRightsTest.php b/tests/Unit/Integrations/Divi/Design/DesignRightsTest.php index 3417641..363403f 100644 --- a/tests/Unit/Integrations/Divi/Design/DesignRightsTest.php +++ b/tests/Unit/Integrations/Divi/Design/DesignRightsTest.php @@ -18,6 +18,7 @@ final class DesignRightsTest extends TestCase public function testPresetsNeedEditThemeOptionsAndTheVisualBuilder(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true, false); Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options'); @@ -33,6 +34,7 @@ final class DesignRightsTest extends TestCase public function testColorsNeedEditThemeOptionsAndTheVisualBuilder(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true, false); Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options'); @@ -49,8 +51,10 @@ final class DesignRightsTest extends TestCase public function testVariablesAlsoNeedTheVariablesManager(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true); $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('canManage')->andReturn(true, false); Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options'); @@ -63,8 +67,10 @@ final class DesignRightsTest extends TestCase public function testVariablesAskTheThemeRightAndTheVisualBuilderFirst(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(false); $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('canManage')->andReturn(true); Functions\when('current_user_can')->justReturn(true); diff --git a/tests/Unit/Integrations/Divi/Design/DiviColorsSetAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviColorsSetAbilityTest.php index 0e8e57c..3963f1e 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviColorsSetAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviColorsSetAbilityTest.php @@ -53,6 +53,7 @@ final class DiviColorsSetAbilityTest extends TestCase bool $visualBuilder = true ): DiviColorsSetAbility { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn($visualBuilder); $writer->allows('presets')->andReturn($presets === [] ? PresetBookTest::stored() : $presets); $counter = 0; @@ -297,6 +298,7 @@ final class DiviColorsSetAbilityTest extends TestCase { $calls = []; $colors = Mockery::mock(GlobalColorsAdapter::class); + $colors->allows('refresh'); $colors->allows('read')->andReturn(ColorAreaTest::stored()); $colors->allows('save')->andReturnUsing(static function (array $entries, bool $replace) use (&$calls): void { $calls[] = $replace; diff --git a/tests/Unit/Integrations/Divi/Design/DiviDesignDetachAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviDesignDetachAbilityTest.php index b378fc9..f162759 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviDesignDetachAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviDesignDetachAbilityTest.php @@ -80,6 +80,7 @@ final class DiviDesignDetachAbilityTest extends TestCase private static function readOnlyWriter(?array $stored = null): DesignSystemWriter&MockInterface { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn($stored ?? PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets')->never(); @@ -91,6 +92,7 @@ final class DiviDesignDetachAbilityTest extends TestCase private static function variableAdapter(bool $manage = true, string $radius = '8px'): GlobalVariablesAdapter&MockInterface { $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('read')->andReturn(self::variables($radius)); $variables->allows('canManage')->andReturn($manage); $variables->expects('save')->never(); @@ -246,6 +248,7 @@ final class DiviDesignDetachAbilityTest extends TestCase $calls = []; $stored = self::storedWithVariable(); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn($stored); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets') @@ -451,6 +454,7 @@ final class DiviDesignDetachAbilityTest extends TestCase { $calls = []; $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets') @@ -502,6 +506,7 @@ final class DiviDesignDetachAbilityTest extends TestCase public function testAPageErrorAfterThePresetStepSaysThePresetsAreChanged(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets') @@ -633,6 +638,7 @@ final class DiviDesignDetachAbilityTest extends TestCase private function abilityAfterThePresetStep(PageRewriter $pages): DiviDesignDetachAbility { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets') diff --git a/tests/Unit/Integrations/Divi/Design/DiviDesignRestoreAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviDesignRestoreAbilityTest.php index d962680..9da6768 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviDesignRestoreAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviDesignRestoreAbilityTest.php @@ -42,6 +42,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase $older = PresetBookTest::stored(); unset($older['module']['divi/button']['items']['bbbbbbbbbb']); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets')->with(Mockery::type('array'), true)->andReturn($older); @@ -73,6 +74,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase { Functions\when('current_user_can')->justReturn(true); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn($current); $writer->allows('canUseVisualBuilder')->andReturn(true); $snapshots = Mockery::mock(SnapshotStore::class); @@ -108,6 +110,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase { Functions\when('current_user_can')->justReturn(false); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->shouldNotReceive('savePresets'); $snapshots = Mockery::mock(SnapshotStore::class); @@ -144,6 +147,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase { Functions\when('current_user_can')->justReturn(true); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true); $snapshots = Mockery::mock(SnapshotStore::class); $snapshots->shouldNotReceive('get'); @@ -171,6 +175,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase ): DiviDesignRestoreAbility { Functions\when('current_user_can')->justReturn(true); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->shouldNotReceive('savePresets'); @@ -206,6 +211,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase private static function colorStore(array $stored): GlobalColorsAdapter&MockInterface { $colors = Mockery::mock(GlobalColorsAdapter::class); + $colors->allows('refresh'); $colors->allows('read')->andReturnUsing(static function () use (&$stored): array { return $stored; }); @@ -225,6 +231,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase private static function variableStore(array $stored, bool $manage = true): GlobalVariablesAdapter&MockInterface { $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('canManage')->andReturn($manage); $variables->allows('read')->andReturnUsing(static function () use (&$stored): array { return $stored; diff --git a/tests/Unit/Integrations/Divi/Design/DiviPresetGetAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviPresetGetAbilityTest.php index ca3b663..1de84f5 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviPresetGetAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviPresetGetAbilityTest.php @@ -19,6 +19,7 @@ final class DiviPresetGetAbilityTest extends TestCase private function ability(?GlobalUsages $usages = null): DiviPresetGetAbility { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $usages ??= Mockery::mock(GlobalUsages::class); diff --git a/tests/Unit/Integrations/Divi/Design/DiviPresetsAssignAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviPresetsAssignAbilityTest.php index e2862d1..6a55e57 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviPresetsAssignAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviPresetsAssignAbilityTest.php @@ -29,6 +29,7 @@ final class DiviPresetsAssignAbilityTest extends TestCase private function ability(PageRewriter $pages): DiviPresetsAssignAbility { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); diff --git a/tests/Unit/Integrations/Divi/Design/DiviPresetsDeleteAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviPresetsDeleteAbilityTest.php index 1b627cd..0b1458f 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviPresetsDeleteAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviPresetsDeleteAbilityTest.php @@ -37,6 +37,7 @@ final class DiviPresetsDeleteAbilityTest extends TestCase Functions\when('current_user_can')->justReturn(true); if ($writer === null) { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn($stored === [] ? PresetBookTest::stored() : $stored); $writer->allows('canUseVisualBuilder')->andReturn(true); } @@ -59,6 +60,7 @@ final class DiviPresetsDeleteAbilityTest extends TestCase public function testUnusedPresetIsDeletedAfterConfirm(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->expects('savePresets') @@ -153,6 +155,7 @@ final class DiviPresetsDeleteAbilityTest extends TestCase 0, 1, [], 0, false); Functions\when('current_user_can')->justReturn(true); $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturn(PresetBookTest::stored()); $writer->allows('canUseVisualBuilder')->andReturn(true); $usages = Mockery::mock(GlobalUsages::class); diff --git a/tests/Unit/Integrations/Divi/Design/DiviPresetsSetAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviPresetsSetAbilityTest.php index 805a968..3db6dd3 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviPresetsSetAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviPresetsSetAbilityTest.php @@ -52,6 +52,7 @@ final class DiviPresetsSetAbilityTest extends TestCase private function savingWriter(): DesignSystemWriter { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturnUsing(fn (): array => $this->stored); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->allows('savePresets')->andReturnUsing(function (array $rest, bool $mayRemove): array { @@ -291,6 +292,7 @@ final class DiviPresetsSetAbilityTest extends TestCase public function testNothingIsWrittenWhenOneItemHasErrors(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('presets')->andReturnUsing(fn (): array => $this->stored); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->shouldNotReceive('savePresets'); diff --git a/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php b/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php index 230655e..1bb8a9d 100644 --- a/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php +++ b/tests/Unit/Integrations/Divi/Design/DiviVariablesSetAbilityTest.php @@ -53,6 +53,7 @@ final class DiviVariablesSetAbilityTest extends TestCase ?FontCatalog $fonts = null ): DiviVariablesSetAbility { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true); $writer->allows('presets')->andReturn($presets === [] ? PresetBookTest::stored() : $presets); $counter = 0; @@ -313,6 +314,7 @@ final class DiviVariablesSetAbilityTest extends TestCase { $calls = []; $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('canManage')->andReturn(true); $variables->allows('read')->andReturn(VariableAreaTest::stored()); $variables->allows('save')->andReturnUsing(static function (array $data, array $expected) use (&$calls): void { diff --git a/tests/Unit/Integrations/Divi/Design/PresetAreaTest.php b/tests/Unit/Integrations/Divi/Design/PresetAreaTest.php index dcbc88d..2dd9ce8 100644 --- a/tests/Unit/Integrations/Divi/Design/PresetAreaTest.php +++ b/tests/Unit/Integrations/Divi/Design/PresetAreaTest.php @@ -16,6 +16,7 @@ final class PresetAreaTest extends TestCase public function testWritesTheRestFormatAndReadsBack(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $stored = PresetBookTest::stored(); $writer->allows('presets')->andReturn($stored); $writer->expects('savePresets') @@ -50,6 +51,7 @@ final class PresetAreaTest extends TestCase public function testRestoreReplacesTheWholeInventory(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $stored = PresetBookTest::stored(); $writer->expects('savePresets')->with(PresetBook::fromDivi($stored)->toRest(), true)->andReturn($stored); diff --git a/tests/Unit/Integrations/Divi/Design/VariableAreaTest.php b/tests/Unit/Integrations/Divi/Design/VariableAreaTest.php index ec423ce..8c7ed28 100644 --- a/tests/Unit/Integrations/Divi/Design/VariableAreaTest.php +++ b/tests/Unit/Integrations/Divi/Design/VariableAreaTest.php @@ -57,6 +57,7 @@ final class VariableAreaTest extends TestCase public static function adapter(array &$stored, array &$saves, bool $manage = true): GlobalVariablesAdapter { $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('canManage')->andReturn($manage); $variables->allows('read')->andReturnUsing(static function () use (&$stored): array { return $stored; diff --git a/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php b/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php index f7b9995..e20e75d 100644 --- a/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php +++ b/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php @@ -47,8 +47,10 @@ final class VariableRestorePlannerTest extends TestCase private static function planner(bool $manage = true, ?GlobalUsages $usages = null): VariableRestorePlanner { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->allows('canUseVisualBuilder')->andReturn(true); $variables = Mockery::mock(GlobalVariablesAdapter::class); + $variables->allows('refresh'); $variables->allows('canManage')->andReturn($manage); return new VariableRestorePlanner($writer, $variables, $usages ?? UsageFixture::none()); diff --git a/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php b/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php index 9ba86c8..cf6b219 100644 --- a/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php +++ b/tests/Unit/Integrations/Divi/Pages/DiviCompositionTest.php @@ -57,6 +57,7 @@ final class DiviCompositionTest extends TestCase private function presetSource(int $reads = 1): DesignSystemWriter { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->expects('presets')->times($reads)->andReturn([ 'module' => ['divi/button' => ['default' => 'p-1', 'items' => [ 'p-1' => ['id' => 'p-1', 'name' => 'Standard', 'moduleName' => 'divi/button'], @@ -211,6 +212,7 @@ final class DiviCompositionTest extends TestCase public function testThePresetBookIsNotReadWithoutAPresetInTheInput(): void { $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('refresh'); $writer->shouldNotReceive('presets'); $writer->shouldNotReceive('groupsOf'); diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index 5f9a98b..cfb8267 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -34,6 +34,8 @@ abstract class TestCase extends BaseTestCase 'wp_slash' => static fn ($value) => $value, 'wp_unslash' => static fn ($value) => $value, 'wp_cache_delete' => true, + 'wp_cache_supports' => true, + 'wp_cache_flush_group' => true, 'sanitize_text_field' => static fn ($value): string => trim(strip_tags((string) $value)), ]);