diff --git a/src/Integrations/Divi/Design/ThemeOptionsRestorePlanner.php b/src/Integrations/Divi/Design/ThemeOptionsRestorePlanner.php new file mode 100644 index 0000000..e0365ff --- /dev/null +++ b/src/Integrations/Divi/Design/ThemeOptionsRestorePlanner.php @@ -0,0 +1,71 @@ +options); + } + + public function rights(): ?WP_Error + { + return DesignRights::themeOptions($this->writer, $this->options); + } + + /** Like lit/divi-theme-options-update: reads et_divi (ThemeOptionRead), writes it (ThemeOptionWrite). */ + public function features(): array + { + return [DiviFeature::ThemeOptionRead, DiviFeature::ThemeOptionWrite]; + } + + public function plan(array $current, array $snapshot, string $snapshotId): DesignPlan|WP_Error + { + // A stored false is "not set" (W3), as in lit/divi-theme-options-update: no noise rows for it. + $after = ThemeOptionBook::fromDivi($snapshot)->withoutFalse(); + $diff = ThemeOptionDiff::between(ThemeOptionBook::fromDivi($current)->withoutFalse(), $after); + $reset = []; + foreach ($diff as $row) { + if ($row['action'] === 'reset') { + $reset[] = '„' . $row['label'] . '“'; + } + } + $warnings = $diff === [] ? [] : [ThemeOptionsArea::SITE_WIDE]; + if ($reset !== []) { + $warnings[] = sprintf(self::RESET, implode(', ', $reset)); + } + + return new DesignPlan( + $after->data(), + $diff, + sprintf('Snapshot %s wiederherstellen – %s', $snapshotId, ThemeOptionDiff::summary($diff)), + null, + $warnings, + true + ); + } +} diff --git a/tests/Unit/Integrations/Divi/Design/ThemeOptionsRestorePlannerTest.php b/tests/Unit/Integrations/Divi/Design/ThemeOptionsRestorePlannerTest.php new file mode 100644 index 0000000..e6c0cd7 --- /dev/null +++ b/tests/Unit/Integrations/Divi/Design/ThemeOptionsRestorePlannerTest.php @@ -0,0 +1,102 @@ + The options now: larger text, a gutter set after the snapshot. */ + public static function current(): array + { + return ['body_font_size' => 18, 'gutter_width' => 2]; + } + + /** @return array The snapshot: body font and text size 16, no gutter. */ + public static function snapshot(): array + { + return ['body_font' => 'Open Sans', 'body_font_size' => 16]; + } + + private static function planner(bool $customize = true): ThemeOptionsRestorePlanner + { + $stored = []; + $writes = []; + $writer = Mockery::mock(DesignSystemWriter::class); + $writer->allows('canUseVisualBuilder')->andReturn(true); + + return new ThemeOptionsRestorePlanner( + $writer, + ThemeOptionsAreaTest::adapter($stored, $writes, null, $customize) + ); + } + + public function testAreaFeaturesAndDesignArea(): void + { + $planner = self::planner(); + + self::assertSame('theme_options', $planner->area()); + self::assertSame([DiviFeature::ThemeOptionRead, DiviFeature::ThemeOptionWrite], $planner->features()); + self::assertInstanceOf(ThemeOptionsArea::class, $planner->designArea()); + } + + /** Ruling W11: the rights of lit/divi-theme-options-update. */ + public function testNeedsTheThemeOptionRights(): void + { + Functions\when('current_user_can')->justReturn(true); + self::assertNull(self::planner()->rights()); + self::assertToolError(self::planner(false)->rights(), 'forbidden'); + + Functions\when('current_user_can')->justReturn(false); + self::assertToolError(self::planner()->rights(), 'forbidden'); + } + + /** Ruling W6: the snapshot replaces the whole whitelist – keys it lacks return to Divi's default. */ + public function testTheSnapshotReplacesEveryOptionAndNamesTheResetOnes(): void + { + $plan = self::planner()->plan(self::current(), self::snapshot(), self::SNAPSHOT_ID); + + self::assertInstanceOf(DesignPlan::class, $plan); + self::assertTrue($plan->mayRemove); + self::assertNull($plan->usages); + self::assertSame(ThemeOptionBook::fromDivi(self::snapshot())->data(), $plan->raw); + self::assertSame( + [['body_font', 'added'], ['body_font_size', 'changed'], ['gutter_width', 'reset']], + array_map(static fn (array $row): array => [$row['key'], $row['action']], $plan->diff) + ); + self::assertSame( + 'Snapshot ' . self::SNAPSHOT_ID . ' wiederherstellen – ' . ThemeOptionDiff::summary($plan->diff), + $plan->summary + ); + self::assertSame([ + ThemeOptionsArea::SITE_WIDE, + sprintf( + 'Theme-Optionen, die im Snapshot nicht gesetzt sind, kehren zum Divi-Standard zurück („%s“).', + ThemeOptionSpec::definition('gutter_width')['label'] + ), + ], $plan->warnings); + } + + public function testTheSameOptionsAreNoChange(): void + { + $plan = self::planner()->plan(self::snapshot(), self::snapshot(), self::SNAPSHOT_ID); + + self::assertInstanceOf(DesignPlan::class, $plan); + self::assertTrue($plan->isEmpty()); + self::assertSame([], $plan->warnings); + } +}