refactor: Divi design adapter, logo lookup moves out of media

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 01:31:23 +02:00
co-authored by Claude Opus 5.5
parent 7fc41bc2ac
commit 1a215fd7bf
5 changed files with 97 additions and 12 deletions
@@ -6,24 +6,45 @@ namespace LIT\MCP\Integrations\Divi\Design;
use ET\Builder\Packages\GlobalData\GlobalData; use ET\Builder\Packages\GlobalData\GlobalData;
use ET\Builder\Packages\GlobalData\GlobalPreset; use ET\Builder\Packages\GlobalData\GlobalPreset;
use LIT\MCP\Integrations\Divi\Release\Call;
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
use LIT\MCP\Integrations\Divi\Release\Manifest;
defined('ABSPATH') || exit; defined('ABSPATH') || exit;
/** /**
* Reads the design system from Divi's own APIs once per request (spec §2 principle 2 – no * Adapter "Designsystem" (spec §3): reads the design system from Divi's own APIs
* copy is ever stored). Not final so abilities can be unit-tested with a mock. * once per request (spec §2 principle 2 – no copy is ever stored), and the logo of
* Divi's theme options. Not final so abilities can be unit-tested with a mock.
*/ */
class DesignSystemReader class DesignSystemReader implements DiviAdapter
{ {
/** Divi's theme options (one array option); customizer colors, fonts and
* the logo live there. */
private const OPTIONS = 'et_divi';
private ?DesignSystem $designSystem = null; private ?DesignSystem $designSystem = null;
public static function manifest(): Manifest
{
return new Manifest(
Manifest::DESIGN,
calls: [
new Call(GlobalData::class . '::get_global_colors', 0),
new Call(GlobalData::class . '::get_global_variables', 0),
new Call(GlobalPreset::class . '::get_data', 0),
],
keys: [self::OPTIONS]
);
}
public function read(): DesignSystem public function read(): DesignSystem
{ {
if ($this->designSystem === null) { if ($this->designSystem === null) {
$colors = GlobalData::get_global_colors(); $colors = GlobalData::get_global_colors();
$variables = GlobalData::get_global_variables(); $variables = GlobalData::get_global_variables();
$presets = GlobalPreset::get_data(); $presets = GlobalPreset::get_data();
$options = get_option('et_divi', []); $options = get_option(self::OPTIONS, []);
$this->designSystem = DesignSystem::fromDivi( $this->designSystem = DesignSystem::fromDivi(
is_array($colors) ? $colors : [], is_array($colors) ? $colors : [],
is_array($variables) ? $variables : [], is_array($variables) ? $variables : [],
@@ -34,4 +55,15 @@ class DesignSystemReader
return $this->designSystem; return $this->designSystem;
} }
/** The logo URL of Divi's theme options (Divi → Theme-Optionen → Logo),
* '' when none is set. */
public static function diviLogo(): string
{
$options = get_option(self::OPTIONS);
return is_array($options) && is_string($options['divi_logo'] ?? null)
? $options['divi_logo']
: '';
}
} }
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release; namespace LIT\MCP\Integrations\Divi\Release;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore; use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
use LIT\MCP\Integrations\Divi\Library\DiviRoles; use LIT\MCP\Integrations\Divi\Library\DiviRoles;
use LIT\MCP\Integrations\Divi\Pages\DiviPages; use LIT\MCP\Integrations\Divi\Pages\DiviPages;
@@ -13,7 +14,8 @@ use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
defined('ABSPATH') || exit; defined('ABSPATH') || exit;
/** The Divi boundary (spec §3): the only classes that touch Divi, in the order reports list them. */ /** The Divi boundary (spec §3): the only classes that touch Divi, in the order
* reports list them. */
final class DiviAdapters final class DiviAdapters
{ {
/** @var list<class-string<DiviAdapter>> */ /** @var list<class-string<DiviAdapter>> */
@@ -22,6 +24,7 @@ final class DiviAdapters
DiviLibraryStore::class, DiviLibraryStore::class,
DiviRoles::class, DiviRoles::class,
DiviThemeBuilder::class, DiviThemeBuilder::class,
DesignSystemReader::class,
DiviModules::class, DiviModules::class,
DynamicContentMap::class, DynamicContentMap::class,
]; ];
+8 -3
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Media; namespace LIT\MCP\Integrations\Media;
use Closure; use Closure;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\WordPress\PostStatuses; use LIT\MCP\Integrations\WordPress\PostStatuses;
use LIT\MCP\Log; use LIT\MCP\Log;
@@ -256,10 +257,14 @@ class MediaUsageFinder
); );
} }
$divi = get_option('et_divi'); $diviLogo = DesignSystemReader::diviLogo();
$diviLogo = is_array($divi) && is_string($divi['divi_logo'] ?? null) ? $divi['divi_logo'] : '';
if ($diviLogo !== '' && ContentMediaReferences::matches($fileRegex, $diviLogo) !== false) { if ($diviLogo !== '' && ContentMediaReferences::matches($fileRegex, $diviLogo) !== false) {
$found['site:divi_logo'] = self::siteUsage('Divi-Logo', 'option', 'divi_logo', self::POSSIBLE); $found['site:divi_logo'] = self::siteUsage(
'Divi-Logo',
'option',
'divi_logo',
self::POSSIBLE
);
} }
$this->addWidgetUsages($found, $attachmentId, $fileRegex); $this->addWidgetUsages($found, $attachmentId, $fileRegex);
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Release\Call;
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
use LIT\MCP\Integrations\Divi\Release\Manifest;
use LIT\MCP\Tests\Unit\TestCase;
final class DesignSystemReaderTest extends TestCase
{
public function testIsTheRegisteredDesignAdapter(): void
{
$manifest = DesignSystemReader::manifest();
self::assertContains(DesignSystemReader::class, DiviAdapters::CLASSES);
self::assertSame(Manifest::DESIGN, $manifest->area);
self::assertSame(
['GlobalData::get_global_colors()', 'GlobalData::get_global_variables()',
'GlobalPreset::get_data()'],
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
);
self::assertSame(['et_divi'], $manifest->keys);
}
public function testDiviLogoReadsTheThemeOptions(): void
{
Functions\when('get_option')->alias(
static fn (string $name): mixed => $name === 'et_divi' ?
['divi_logo' => 'https://k.de/logo.svg'] : false
);
self::assertSame('https://k.de/logo.svg', DesignSystemReader::diviLogo());
}
public function testDiviLogoIsEmptyWithoutOptions(): void
{
Functions\when('get_option')->justReturn(false);
self::assertSame('', DesignSystemReader::diviLogo());
}
}
@@ -20,13 +20,12 @@ use ReflectionClass;
final class DiviBoundaryTest extends TestCase final class DiviBoundaryTest extends TestCase
{ {
/** /**
* Files that still touch Divi outside an adapter (paths below src/). Tasks 3–7 shrink this list to * Files that still touch Divi outside an adapter (paths below src/). Tasks 3–7
* DiviCompatibility.php; Task 8 removes it together with testLegacyFilesStillTouchDivi. * shrink this list to DiviCompatibility.php; Task 8 removes it together with
* testLegacyFilesStillTouchDivi.
*/ */
private const LEGACY = [ private const LEGACY = [
'Integrations/Divi/Design/DesignSystemReader.php',
'Integrations/Divi/DiviCompatibility.php', 'Integrations/Divi/DiviCompatibility.php',
'Integrations/Media/MediaUsageFinder.php',
]; ];
public function testOnlyAdaptersTouchDivi(): void public function testOnlyAdaptersTouchDivi(): void