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\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;
/**
* Reads the design system from Divi's own APIs once per request (spec §2 principle 2 – no
* copy is ever stored). Not final so abilities can be unit-tested with a mock.
* Adapter "Designsystem" (spec §3): reads the design system from Divi's own APIs
* 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;
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
{
if ($this->designSystem === null) {
$colors = GlobalData::get_global_colors();
$variables = GlobalData::get_global_variables();
$presets = GlobalPreset::get_data();
$options = get_option('et_divi', []);
$options = get_option(self::OPTIONS, []);
$this->designSystem = DesignSystem::fromDivi(
is_array($colors) ? $colors : [],
is_array($variables) ? $variables : [],
@@ -34,4 +55,15 @@ class DesignSystemReader
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;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
use LIT\MCP\Integrations\Divi\Library\DiviRoles;
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
@@ -13,7 +14,8 @@ use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
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
{
/** @var list<class-string<DiviAdapter>> */
@@ -22,6 +24,7 @@ final class DiviAdapters
DiviLibraryStore::class,
DiviRoles::class,
DiviThemeBuilder::class,
DesignSystemReader::class,
DiviModules::class,
DynamicContentMap::class,
];
+8 -3
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Media;
use Closure;
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
use LIT\MCP\Integrations\WordPress\PostStatuses;
use LIT\MCP\Log;
@@ -256,10 +257,14 @@ class MediaUsageFinder
);
}
$divi = get_option('et_divi');
$diviLogo = is_array($divi) && is_string($divi['divi_logo'] ?? null) ? $divi['divi_logo'] : '';
$diviLogo = DesignSystemReader::diviLogo();
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);
@@ -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
{
/**
* Files that still touch Divi outside an adapter (paths below src/). Tasks 3–7 shrink this list to
* DiviCompatibility.php; Task 8 removes it together with testLegacyFilesStillTouchDivi.
* Files that still touch Divi outside an adapter (paths below src/). Tasks 3–7
* shrink this list to DiviCompatibility.php; Task 8 removes it together with
* testLegacyFilesStillTouchDivi.
*/
private const LEGACY = [
'Integrations/Divi/Design/DesignSystemReader.php',
'Integrations/Divi/DiviCompatibility.php',
'Integrations/Media/MediaUsageFinder.php',
];
public function testOnlyAdaptersTouchDivi(): void