feat: Divi module catalog, attribute paths, short form and lit/divi-catalog

Adds per-path attribute kind (text/richtext/code/url/image/color/enum/
onoff/number/list/icon), derived from Divi's field component metadata
where an attribute path names one unambiguously (module.paths() itself
is unchanged; kinds() is a new, separate lookup exposed as path_kinds
on lit/divi-catalog's module detail). This is not in the task-4 brief;
it is added because 2b's sanitization routing (ruling R3) needs a
metadata-derived field kind per attribute path. Verified live against
the full 100-module catalog (564/4358 paths resolve a kind; the rest
default to "unknown" and fall back to 2b's key-name routing).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 11:48:27 +02:00
co-authored by Claude Opus 5.5
parent 08d70abfc7
commit 2f4dcf9ce4
14 changed files with 5227 additions and 2 deletions
@@ -0,0 +1,56 @@
<?php
/**
* Writes tests/Fixtures/divi/metadata-sample.json: Divi's own module metadata (name, title,
* category, children, attribute settings) and default attributes for the modules the unit
* tests use, so ModuleCatalog is tested against real Divi data without WordPress.
* Usage (DDEV, WordPress root): wp eval-file wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-metadata-fixture.php
* No `declare(strict_types=1);` – wp eval-file runs the file through eval().
*/
defined('WP_CLI') && WP_CLI || exit;
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
if (! class_exists(ModuleRegistration::class)) {
WP_CLI::error('Divi 5 is not active.');
}
$wanted = [
'section', 'row', 'column', 'text', 'heading', 'button', 'image', 'blurb', 'code', 'accordion',
'accordion-item', 'fullwidth-header', 'group',
];
$all = ModuleRegistration::get_all_core_modules_metadata();
$metadata = [];
$defaults = [];
foreach ($wanted as $key) {
$module = $all[$key] ?? null;
if (! is_array($module)) {
WP_CLI::error('Missing module ' . $key);
}
$attributes = [];
foreach ((array) ($module['attributes'] ?? []) as $attrName => $definition) {
$attributes[$attrName] = ['settings' => $definition['settings'] ?? []];
}
$metadata[] = array_filter([
'name' => $module['name'],
'title' => $module['title'] ?? '',
'category' => $module['category'] ?? '',
'childrenName' => $module['childrenName'] ?? null,
'childModuleName' => $module['childModuleName'] ?? null,
'attributes' => $attributes,
], static fn ($value): bool => $value !== null);
$defaults[$module['name']] = ModuleRegistration::get_default_attrs($module['name'], 'default');
}
$file = dirname(__DIR__) . '/Fixtures/divi/metadata-sample.json';
$json = wp_json_encode(
['divi' => ET_BUILDER_PRODUCT_VERSION, 'metadata' => $metadata, 'defaults' => $defaults],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
if (! is_string($json) || file_put_contents($file, $json . "\n") === false) {
WP_CLI::error('Cannot write ' . $file);
}
WP_CLI::success(sprintf('metadata-sample.json: %d modules, %d bytes', count($metadata), strlen($json)));