feat: Divi-Fähigkeiten (DiviFeature, Requirement, ConstantRequirement) an Call, Mirror und Manifest; alle acht Adapter getaggt; Tests für Vollständigkeit und die gepinnte Zuordnung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
e6e0f914ba
commit
392fe7c569
@@ -8,6 +8,7 @@ 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\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
@@ -34,7 +35,8 @@ class DesignSystemReader implements DiviAdapter
|
|||||||
new Call(GlobalData::class . '::get_global_variables', 0),
|
new Call(GlobalData::class . '::get_global_variables', 0),
|
||||||
new Call(GlobalPreset::class . '::get_data', 0),
|
new Call(GlobalPreset::class . '::get_data', 0),
|
||||||
],
|
],
|
||||||
keys: [self::OPTIONS]
|
keys: [self::OPTIONS],
|
||||||
|
features: [DiviFeature::DesignRead, DiviFeature::PageRead]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use ET\Builder\Packages\GlobalData\GlobalPreset;
|
|||||||
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
|
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
@@ -44,7 +45,8 @@ class DesignSystemWriter implements DiviAdapter
|
|||||||
return new Manifest(
|
return new Manifest(
|
||||||
Manifest::DESIGN,
|
Manifest::DESIGN,
|
||||||
calls: [
|
calls: [
|
||||||
new Call(GlobalPreset::class . '::get_data', 0),
|
// Read by the preset tools as well as by the design system tool.
|
||||||
|
new Call(GlobalPreset::class . '::get_data', 0, [DiviFeature::DesignRead, DiviFeature::PresetWrite]),
|
||||||
new Call(GlobalPreset::class . '::prepare_data', 1),
|
new Call(GlobalPreset::class . '::prepare_data', 1),
|
||||||
new Call(GlobalPreset::class . '::validate_preset_count', 3),
|
new Call(GlobalPreset::class . '::validate_preset_count', 3),
|
||||||
new Call(GlobalPreset::class . '::get_deleted_preset_ids', 3),
|
new Call(GlobalPreset::class . '::get_deleted_preset_ids', 3),
|
||||||
@@ -52,8 +54,17 @@ class DesignSystemWriter implements DiviAdapter
|
|||||||
new Call(GlobalPreset::class . '::save_data', 1),
|
new Call(GlobalPreset::class . '::save_data', 1),
|
||||||
new Call(Conversion::class . '::get_preset_attrs_mapping', 1),
|
new Call(Conversion::class . '::get_preset_attrs_mapping', 1),
|
||||||
new Call(Conversion::class . '::get_preset_attrs', 3),
|
new Call(Conversion::class . '::get_preset_attrs', 3),
|
||||||
new Call(GlobalPreset::class . '::get_group_preset_default_attr', 1),
|
// groupsOf(): also used by the composer for group presets (insert/update/assign), so Catalog too.
|
||||||
new Call(ModuleRegistration::class . '::get_module_settings', 1),
|
new Call(
|
||||||
|
GlobalPreset::class . '::get_group_preset_default_attr',
|
||||||
|
1,
|
||||||
|
[DiviFeature::Catalog, DiviFeature::PresetWrite]
|
||||||
|
),
|
||||||
|
new Call(
|
||||||
|
ModuleRegistration::class . '::get_module_settings',
|
||||||
|
1,
|
||||||
|
[DiviFeature::Catalog, DiviFeature::PresetWrite]
|
||||||
|
),
|
||||||
new Call(UserRole::class . '::can_current_user_use_visual_builder', 0),
|
new Call(UserRole::class . '::can_current_user_use_visual_builder', 0),
|
||||||
],
|
],
|
||||||
mirrors: [
|
mirrors: [
|
||||||
@@ -65,7 +76,8 @@ class DesignSystemWriter implements DiviAdapter
|
|||||||
'Speicherablauf der Presets (prepare_data, validate_preset_count, Bereinigung der Divi-4-Option '
|
'Speicherablauf der Presets (prepare_data, validate_preset_count, Bereinigung der Divi-4-Option '
|
||||||
. 'beim Löschen, save_data)'
|
. 'beim Löschen, save_data)'
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
features: [DiviFeature::PresetWrite]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\Library;
|
|||||||
use LIT\MCP\Abilities\WriteFailed;
|
use LIT\MCP\Abilities\WriteFailed;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
@@ -55,7 +56,14 @@ class DiviLibraryStore implements DiviAdapter
|
|||||||
{
|
{
|
||||||
return new Manifest(
|
return new Manifest(
|
||||||
Manifest::LIBRARY,
|
Manifest::LIBRARY,
|
||||||
calls: [new Call(self::GLOBAL_LAYOUT . '::is_global_layout_template', 1)],
|
calls: [
|
||||||
|
// isGlobal() serves listing and describing as well as the global edits.
|
||||||
|
new Call(
|
||||||
|
self::GLOBAL_LAYOUT . '::is_global_layout_template',
|
||||||
|
1,
|
||||||
|
[DiviFeature::LibraryRead, DiviFeature::LibraryWrite]
|
||||||
|
),
|
||||||
|
],
|
||||||
postTypes: [self::POST_TYPE],
|
postTypes: [self::POST_TYPE],
|
||||||
taxonomies: [...self::TAXONOMIES, 'layout_category', 'layout_tag'],
|
taxonomies: [...self::TAXONOMIES, 'layout_category', 'layout_tag'],
|
||||||
keys: [self::MODULE_TYPE, self::EXCLUDED_OPTIONS, self::BUILT_FOR, self::TEMPLATE_TYPE],
|
keys: [self::MODULE_TYPE, self::EXCLUDED_OPTIONS, self::BUILT_FOR, self::TEMPLATE_TYPE],
|
||||||
@@ -68,7 +76,8 @@ class DiviLibraryStore implements DiviAdapter
|
|||||||
'Terme und Meta neuer Bibliothekseinträge (scope, layout_type, module_width, _et_pb_module_type, '
|
'Terme und Meta neuer Bibliothekseinträge (scope, layout_type, module_width, _et_pb_module_type, '
|
||||||
. '_et_pb_excluded_global_options, _et_pb_built_for_post_type, _et_pb_template_type)'
|
. '_et_pb_excluded_global_options, _et_pb_built_for_post_type, _et_pb_template_type)'
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
features: [DiviFeature::LibraryWrite]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use Closure;
|
|||||||
use LIT\MCP\Abilities\Errors;
|
use LIT\MCP\Abilities\Errors;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
@@ -33,7 +34,11 @@ class DiviRoles implements DiviAdapter
|
|||||||
|
|
||||||
public static function manifest(): Manifest
|
public static function manifest(): Manifest
|
||||||
{
|
{
|
||||||
return new Manifest(Manifest::LIBRARY, calls: [new Call('et_pb_is_allowed', 1)]);
|
return new Manifest(
|
||||||
|
Manifest::LIBRARY,
|
||||||
|
calls: [new Call('et_pb_is_allowed', 1)],
|
||||||
|
features: [DiviFeature::Roles]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param (Closure(string): bool)|null $check Test seam; null = Divi's et_pb_is_allowed(). */
|
/** @param (Closure(string): bool)|null $check Test seam; null = Divi's et_pb_is_allowed(). */
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace LIT\MCP\Integrations\Divi\Pages;
|
|||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
@@ -36,7 +37,12 @@ final class DiviPages implements DiviAdapter
|
|||||||
return new Manifest(
|
return new Manifest(
|
||||||
Manifest::PAGES,
|
Manifest::PAGES,
|
||||||
calls: [
|
calls: [
|
||||||
new Call('ET_Core_PageResource::remove_static_resources', 3),
|
// Also called by DiviThemeBuilder::clearSiteCaches() – a break locks both features.
|
||||||
|
new Call(
|
||||||
|
'ET_Core_PageResource::remove_static_resources',
|
||||||
|
3,
|
||||||
|
[DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite]
|
||||||
|
),
|
||||||
new Call('et_builder_get_public_post_types', 0),
|
new Call('et_builder_get_public_post_types', 0),
|
||||||
],
|
],
|
||||||
keys: ['_et_pb_use_divi_5', '_et_pb_use_builder', '_et_pb_show_page_creation', 'et_fb'],
|
keys: ['_et_pb_use_divi_5', '_et_pb_use_builder', '_et_pb_show_page_creation', 'et_fb'],
|
||||||
@@ -48,7 +54,8 @@ final class DiviPages implements DiviAdapter
|
|||||||
'SyncToServerController',
|
'SyncToServerController',
|
||||||
'Builder-Kennzeichen einer Seite (_et_pb_use_divi_5, _et_pb_use_builder, _et_pb_show_page_creation)'
|
'Builder-Kennzeichen einer Seite (_et_pb_use_divi_5, _et_pb_use_builder, _et_pb_show_page_creation)'
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
features: [DiviFeature::PageWrite]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
|||||||
use LIT\MCP\Integrations\Divi\Compose\DynamicContentRules;
|
use LIT\MCP\Integrations\Divi\Compose\DynamicContentRules;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Log;
|
use LIT\MCP\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@@ -46,7 +47,11 @@ class DynamicContentMap implements DiviAdapter
|
|||||||
|
|
||||||
public static function manifest(): Manifest
|
public static function manifest(): Manifest
|
||||||
{
|
{
|
||||||
return new Manifest(Manifest::CATALOG, calls: [new Call(self::REGISTRY . '::get_options', 2)]);
|
return new Manifest(
|
||||||
|
Manifest::CATALOG,
|
||||||
|
calls: [new Call(self::REGISTRY . '::get_options', 2)],
|
||||||
|
features: [DiviFeature::DynamicSources]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<string, array<string, string>>|null Null = use DynamicContentRules' default map. */
|
/** @return array<string, array<string, string>>|null Null = use DynamicContentRules' default map. */
|
||||||
|
|||||||
@@ -10,11 +10,17 @@ defined('ABSPATH') || exit;
|
|||||||
* One Divi function or static method an adapter calls (spec §4.1), with the number of arguments the plugin passes.
|
* One Divi function or static method an adapter calls (spec §4.1), with the number of arguments the plugin passes.
|
||||||
* $args null: only its existence matters (a function Divi itself needs, e.g. et_() inside store_template).
|
* $args null: only its existence matters (a function Divi itself needs, e.g. et_() inside store_template).
|
||||||
*/
|
*/
|
||||||
final class Call
|
final class Call implements Requirement
|
||||||
{
|
{
|
||||||
/** @param string $symbol "function_name" or "Fully\Qualified\Class::method". */
|
/**
|
||||||
public function __construct(public readonly string $symbol, public readonly ?int $args = null)
|
* @param string $symbol "function_name" or "Fully\Qualified\Class::method".
|
||||||
{
|
* @param list<DiviFeature> $features [] = the default of the manifest.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly string $symbol,
|
||||||
|
public readonly ?int $args = null,
|
||||||
|
private readonly array $features = []
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function className(): ?string
|
public function className(): ?string
|
||||||
@@ -27,6 +33,11 @@ final class Call
|
|||||||
return str_contains($this->symbol, '::') ? explode('::', $this->symbol, 2)[1] : $this->symbol;
|
return str_contains($this->symbol, '::') ? explode('::', $this->symbol, 2)[1] : $this->symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function features(): array
|
||||||
|
{
|
||||||
|
return $this->features;
|
||||||
|
}
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
$class = $this->className();
|
$class = $this->className();
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/** A Divi constant an adapter reads (spec §4.1); it must be defined. */
|
||||||
|
final class ConstantRequirement implements Requirement
|
||||||
|
{
|
||||||
|
/** @param list<DiviFeature> $features [] = the default of the manifest. */
|
||||||
|
public function __construct(public readonly string $name, private readonly array $features = [])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return 'Konstante ' . $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function features(): array
|
||||||
|
{
|
||||||
|
return $this->features;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
// The installed PHPCompatibility sniffs predate enums (PHP 8.1) and read their methods as plain functions.
|
||||||
|
// phpcs:disable PHPCompatibility.Variables.ForbiddenThisUseContexts.OutsideObjectContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What the plugin can do with Divi, as far as a Divi update may break it separately: every manifest entry (call,
|
||||||
|
* mirror, constant) belongs to one or more features, and the release gate decides per feature.
|
||||||
|
*/
|
||||||
|
enum DiviFeature: string
|
||||||
|
{
|
||||||
|
case PageRead = 'page-read';
|
||||||
|
case PageWrite = 'page-write';
|
||||||
|
case Catalog = 'catalog';
|
||||||
|
case DynamicSources = 'dynamic-sources';
|
||||||
|
case DesignRead = 'design-read';
|
||||||
|
case PresetWrite = 'preset-write';
|
||||||
|
case LibraryRead = 'library-read';
|
||||||
|
case LibraryWrite = 'library-write';
|
||||||
|
case Roles = 'roles';
|
||||||
|
case ThemeBuilderRead = 'tb-read';
|
||||||
|
case ThemeBuilderWrite = 'tb-write';
|
||||||
|
|
||||||
|
/** Whether the feature changes data (the four *Write cases). */
|
||||||
|
public function isWrite(): bool
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PageWrite, self::PresetWrite, self::LibraryWrite, self::ThemeBuilderWrite => true,
|
||||||
|
default => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Name for messages, the status tool and the admin tab (German). */
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PageRead => 'Seiten lesen',
|
||||||
|
self::PageWrite => 'Seiten schreiben',
|
||||||
|
self::Catalog => 'Modulkatalog',
|
||||||
|
self::DynamicSources => 'Dynamische Inhalte',
|
||||||
|
self::DesignRead => 'Designsystem lesen',
|
||||||
|
self::PresetWrite => 'Presets schreiben',
|
||||||
|
self::LibraryRead => 'Bibliothek lesen',
|
||||||
|
self::LibraryWrite => 'Bibliothek schreiben',
|
||||||
|
self::Roles => 'Divi-Rollen',
|
||||||
|
self::ThemeBuilderRead => 'Theme Builder lesen',
|
||||||
|
self::ThemeBuilderWrite => 'Theme Builder schreiben',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,27 +18,56 @@ final class Manifest
|
|||||||
public const DESIGN = 'Designsystem';
|
public const DESIGN = 'Designsystem';
|
||||||
public const CATALOG = 'Katalog';
|
public const CATALOG = 'Katalog';
|
||||||
|
|
||||||
|
/** @var list<ConstantRequirement> */
|
||||||
|
public readonly array $constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $area One of the constants above (the five areas of spec §3).
|
* @param string $area One of the constants above (the five areas of spec §3).
|
||||||
* @param list<Call> $calls Divi functions and static methods the adapter calls.
|
* @param list<Call> $calls Divi functions and static methods the adapter calls.
|
||||||
* @param list<string> $constants Divi constants it reads.
|
* @param list<string|ConstantRequirement> $constants Divi constants it reads; a plain name stands for a
|
||||||
* @param list<string> $postTypes Divi post types it needs registered.
|
* ConstantRequirement without features of its own.
|
||||||
* @param list<string> $taxonomies Divi taxonomies it needs registered.
|
* @param list<string> $postTypes Divi post types it needs registered.
|
||||||
* @param list<string> $blockTypes Divi block types it needs (registered or in Divi's module metadata).
|
* @param list<string> $taxonomies Divi taxonomies it needs registered.
|
||||||
* @param list<string> $keys Other Divi names it uses as strings: meta keys, options, internal post
|
* @param list<string> $blockTypes Divi block types it needs (registered or in Divi's
|
||||||
* types that need no registration check, query arguments.
|
* module metadata).
|
||||||
* @param list<Mirror> $mirrors Divi functions whose storage logic it repeats.
|
* @param list<string> $keys Other Divi names it uses as strings: meta keys, options,
|
||||||
|
* internal post types that need no registration check,
|
||||||
|
* query arguments.
|
||||||
|
* @param list<Mirror> $mirrors Divi functions whose storage logic it repeats.
|
||||||
|
* @param list<DiviFeature> $features The features of every call, mirror and constant that names
|
||||||
|
* none itself (featuresOf()).
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $area,
|
public readonly string $area,
|
||||||
public readonly array $calls = [],
|
public readonly array $calls = [],
|
||||||
public readonly array $constants = [],
|
array $constants = [],
|
||||||
public readonly array $postTypes = [],
|
public readonly array $postTypes = [],
|
||||||
public readonly array $taxonomies = [],
|
public readonly array $taxonomies = [],
|
||||||
public readonly array $blockTypes = [],
|
public readonly array $blockTypes = [],
|
||||||
public readonly array $keys = [],
|
public readonly array $keys = [],
|
||||||
public readonly array $mirrors = []
|
public readonly array $mirrors = [],
|
||||||
|
public readonly array $features = []
|
||||||
) {
|
) {
|
||||||
|
$this->constants = array_map(
|
||||||
|
static fn (string|ConstantRequirement $constant): ConstantRequirement => is_string($constant)
|
||||||
|
? new ConstantRequirement($constant)
|
||||||
|
: $constant,
|
||||||
|
array_values($constants)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<Requirement> Calls, constants and mirrors – everything the release gate checks as code. */
|
||||||
|
public function requirements(): array
|
||||||
|
{
|
||||||
|
return [...$this->calls, ...$this->constants, ...$this->mirrors];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<DiviFeature> The features $requirement names itself, else this manifest's default. */
|
||||||
|
public function featuresOf(Requirement $requirement): array
|
||||||
|
{
|
||||||
|
$own = $requirement->features();
|
||||||
|
|
||||||
|
return $own !== [] ? $own : $this->features;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,11 +80,12 @@ final class Manifest
|
|||||||
foreach ($this->calls as $call) {
|
foreach ($this->calls as $call) {
|
||||||
$names[] = $call->className() ?? $call->member();
|
$names[] = $call->className() ?? $call->member();
|
||||||
}
|
}
|
||||||
|
$constants = array_map(static fn (ConstantRequirement $constant): string => $constant->name, $this->constants);
|
||||||
$mirrored = array_map(static fn (Mirror $mirror): string => $mirror->function, $this->mirrors);
|
$mirrored = array_map(static fn (Mirror $mirror): string => $mirror->function, $this->mirrors);
|
||||||
|
|
||||||
return array_values(array_unique(array_merge(
|
return array_values(array_unique(array_merge(
|
||||||
$names,
|
$names,
|
||||||
$this->constants,
|
$constants,
|
||||||
$this->postTypes,
|
$this->postTypes,
|
||||||
$this->taxonomies,
|
$this->taxonomies,
|
||||||
$this->keys,
|
$this->keys,
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ class ManifestCheck
|
|||||||
$checks[$manifest->area . ': ' . $call->label()] = static fn (): bool => self::accepts($call);
|
$checks[$manifest->area . ': ' . $call->label()] = static fn (): bool => self::accepts($call);
|
||||||
}
|
}
|
||||||
foreach ($manifest->constants as $constant) {
|
foreach ($manifest->constants as $constant) {
|
||||||
$checks[$manifest->area . ': Konstante ' . $constant] = static fn (): bool => defined($constant);
|
$checks[$manifest->area . ': ' . $constant->label()] =
|
||||||
|
static fn (): bool => defined($constant->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,25 +10,33 @@ defined('ABSPATH') || exit;
|
|||||||
* A Divi function whose storage logic an adapter repeats instead of calling it (spec §4.1 "mirrors"). Its
|
* A Divi function whose storage logic an adapter repeats instead of calling it (spec §4.1 "mirrors"). Its
|
||||||
* fingerprint (SourceFingerprint) must match the one in fingerprints.json, else Divi changed what we copy.
|
* fingerprint (SourceFingerprint) must match the one in fingerprints.json, else Divi changed what we copy.
|
||||||
*/
|
*/
|
||||||
final class Mirror
|
final class Mirror implements Requirement
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string $id Key in fingerprints.json and name of tests/Fixtures/divi/reference/<id>.txt.
|
* @param string $id Key in fingerprints.json and name of tests/Fixtures/divi/reference/<id>.txt.
|
||||||
* @param string $file Divi file, relative to the Divi theme directory.
|
* @param string $file Divi file, relative to the Divi theme directory.
|
||||||
* @param string $function Function or method name.
|
* @param string $function Function or method name.
|
||||||
* @param string|null $class Class of a method – the short name is enough (the file fixes where it
|
* @param string|null $class Class of a method – the short name is enough (the file fixes where it
|
||||||
* lives, and a short name is no Divi name for DiviBoundaryTest); null for a function.
|
* lives, and a short name is no Divi name for DiviBoundaryTest); null for a
|
||||||
* @param string $what What the plugin repeats (German, for reports).
|
* function.
|
||||||
|
* @param string $what What the plugin repeats (German, for reports).
|
||||||
|
* @param list<DiviFeature> $features [] = the default of the manifest.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $id,
|
public readonly string $id,
|
||||||
public readonly string $file,
|
public readonly string $file,
|
||||||
public readonly string $function,
|
public readonly string $function,
|
||||||
public readonly ?string $class,
|
public readonly ?string $class,
|
||||||
public readonly string $what
|
public readonly string $what,
|
||||||
|
private readonly array $features = []
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function features(): array
|
||||||
|
{
|
||||||
|
return $this->features;
|
||||||
|
}
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
$class = $this->class === null ? '' : SourceFingerprint::shortName($this->class) . '::';
|
$class = $this->class === null ? '' : SourceFingerprint::shortName($this->class) . '::';
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/** One thing an adapter needs from Divi (a call, a mirrored function or a constant) and the features it serves. */
|
||||||
|
interface Requirement
|
||||||
|
{
|
||||||
|
/** Name for reports, e.g. "GlobalPreset::get_data()" (without the manifest area). */
|
||||||
|
public function label(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The features this requirement serves itself; [] = the default of the manifest that lists it
|
||||||
|
* (Manifest::featuresOf() resolves that).
|
||||||
|
*
|
||||||
|
* @return list<DiviFeature>
|
||||||
|
*/
|
||||||
|
public function features(): array;
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use LIT\MCP\Integrations\Divi\BlockTree\GlobalInstance;
|
|||||||
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||||
|
|
||||||
@@ -46,7 +47,8 @@ class DiviModules implements DiviAdapter
|
|||||||
],
|
],
|
||||||
constants: ['ET_BUILDER_PRODUCT_VERSION'],
|
constants: ['ET_BUILDER_PRODUCT_VERSION'],
|
||||||
blockTypes: [GlobalInstance::BLOCK],
|
blockTypes: [GlobalInstance::BLOCK],
|
||||||
keys: ['et_pb_canvas', '_et_pb_speculation']
|
keys: ['et_pb_canvas', '_et_pb_speculation'],
|
||||||
|
features: [DiviFeature::Catalog]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace LIT\MCP\Integrations\Divi\ThemeBuilder;
|
|||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
@@ -84,16 +85,31 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
return new Manifest(
|
return new Manifest(
|
||||||
Manifest::THEME_BUILDER,
|
Manifest::THEME_BUILDER,
|
||||||
calls: [
|
calls: [
|
||||||
new Call('et_theme_builder_get_theme_builder_post_id', 2),
|
// liveId(): reads the live post, or creates it like Divi's first save.
|
||||||
|
new Call(
|
||||||
|
'et_theme_builder_get_theme_builder_post_id',
|
||||||
|
2,
|
||||||
|
[DiviFeature::ThemeBuilderRead, DiviFeature::ThemeBuilderWrite]
|
||||||
|
),
|
||||||
new Call('et_theme_builder_get_theme_builder_template_ids', 2),
|
new Call('et_theme_builder_get_theme_builder_template_ids', 2),
|
||||||
new Call('et_theme_builder_get_template', 1),
|
new Call('et_theme_builder_get_template', 1),
|
||||||
new Call('et_theme_builder_insert_layout', 1),
|
new Call('et_theme_builder_insert_layout', 1, [DiviFeature::ThemeBuilderWrite]),
|
||||||
new Call('et_theme_builder_store_template', 3),
|
new Call('et_theme_builder_store_template', 3, [DiviFeature::ThemeBuilderWrite]),
|
||||||
new Call('et_theme_builder_get_flat_template_settings_options', 0),
|
new Call('et_theme_builder_get_flat_template_settings_options', 0),
|
||||||
new Call('et_theme_builder_clear_wp_cache', 1),
|
// clearSiteCaches() also runs after every page save with a site-wide scope (DiviSaver).
|
||||||
|
new Call(
|
||||||
|
'et_theme_builder_clear_wp_cache',
|
||||||
|
1,
|
||||||
|
[DiviFeature::ThemeBuilderWrite, DiviFeature::PageWrite]
|
||||||
|
),
|
||||||
// store_template needs Divi's et_() helper (E15).
|
// store_template needs Divi's et_() helper (E15).
|
||||||
new Call('et_'),
|
new Call('et_', null, [DiviFeature::ThemeBuilderWrite]),
|
||||||
new Call('ET_Core_PageResource::remove_static_resources', 3),
|
// Also called by DiviPages::clearPageCss() – a break locks both features.
|
||||||
|
new Call(
|
||||||
|
'ET_Core_PageResource::remove_static_resources',
|
||||||
|
3,
|
||||||
|
[DiviFeature::ThemeBuilderWrite, DiviFeature::PageWrite]
|
||||||
|
),
|
||||||
],
|
],
|
||||||
postTypes: self::postTypes(),
|
postTypes: self::postTypes(),
|
||||||
keys: [
|
keys: [
|
||||||
@@ -106,23 +122,27 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
'et_theme_builder_api_save',
|
'et_theme_builder_api_save',
|
||||||
null,
|
null,
|
||||||
'Vorlagenliste des Live-Theme-Builders (_et_template mit Sicherung et_tb_templates_backup_) und '
|
'Vorlagenliste des Live-Theme-Builders (_et_template mit Sicherung et_tb_templates_backup_) und '
|
||||||
. '„ungenutzt“-Markierung'
|
. '„ungenutzt“-Markierung',
|
||||||
|
[DiviFeature::ThemeBuilderWrite]
|
||||||
),
|
),
|
||||||
new Mirror(
|
new Mirror(
|
||||||
'tb-store-template',
|
'tb-store-template',
|
||||||
$dir . 'theme-builder.php',
|
$dir . 'theme-builder.php',
|
||||||
'et_theme_builder_store_template',
|
'et_theme_builder_store_template',
|
||||||
null,
|
null,
|
||||||
'Bedingungen (_et_use_on, _et_exclude_from) und Aktiv-Schalter (_et_enabled) einer Vorlage'
|
'Bedingungen (_et_use_on, _et_exclude_from) und Aktiv-Schalter (_et_enabled) einer Vorlage',
|
||||||
|
[DiviFeature::ThemeBuilderWrite]
|
||||||
),
|
),
|
||||||
new Mirror(
|
new Mirror(
|
||||||
'tb-load-setting-options',
|
'tb-load-setting-options',
|
||||||
$dir . 'theme-builder.php',
|
$dir . 'theme-builder.php',
|
||||||
'et_theme_builder_load_template_setting_options',
|
'et_theme_builder_load_template_setting_options',
|
||||||
null,
|
null,
|
||||||
'Zerlegen der Bedingungs-IDs (TemplateConditions)'
|
'Zerlegen der Bedingungs-IDs (TemplateConditions)',
|
||||||
|
[DiviFeature::ThemeBuilderRead, DiviFeature::ThemeBuilderWrite]
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
features: [DiviFeature::ThemeBuilderRead]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||||
|
use LIT\MCP\Integrations\Divi\Library\DiviRoles;
|
||||||
|
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
||||||
|
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
|
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||||
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every manifest entry of every adapter names the Divi features it serves (D2 of the plan "Divi-Fähigkeiten"), and
|
||||||
|
* the assignment is pinned here: a changed tag shows up in review as a changed row of this table.
|
||||||
|
*/
|
||||||
|
final class DiviFeatureAssignmentTest extends TestCase
|
||||||
|
{
|
||||||
|
/** Adapter => requirement ("Nachbau " before a mirror's label) => features (sorted values). */
|
||||||
|
private const EXPECTED = [
|
||||||
|
DiviPages::class => [
|
||||||
|
'ET_Core_PageResource::remove_static_resources()' => ['page-write', 'tb-write'],
|
||||||
|
'et_builder_get_public_post_types()' => ['page-write'],
|
||||||
|
'Nachbau SyncToServerController::update()' => ['page-write'],
|
||||||
|
],
|
||||||
|
DiviLibraryStore::class => [
|
||||||
|
'GlobalLayout::is_global_layout_template()' => ['library-read', 'library-write'],
|
||||||
|
'Nachbau et_pb_submit_layout()' => ['library-write'],
|
||||||
|
],
|
||||||
|
DiviRoles::class => [
|
||||||
|
'et_pb_is_allowed()' => ['roles'],
|
||||||
|
],
|
||||||
|
DiviThemeBuilder::class => [
|
||||||
|
'et_theme_builder_get_theme_builder_post_id()' => ['tb-read', 'tb-write'],
|
||||||
|
'et_theme_builder_get_theme_builder_template_ids()' => ['tb-read'],
|
||||||
|
'et_theme_builder_get_template()' => ['tb-read'],
|
||||||
|
'et_theme_builder_insert_layout()' => ['tb-write'],
|
||||||
|
'et_theme_builder_store_template()' => ['tb-write'],
|
||||||
|
'et_theme_builder_get_flat_template_settings_options()' => ['tb-read'],
|
||||||
|
'et_theme_builder_clear_wp_cache()' => ['page-write', 'tb-write'],
|
||||||
|
'et_()' => ['tb-write'],
|
||||||
|
'ET_Core_PageResource::remove_static_resources()' => ['page-write', 'tb-write'],
|
||||||
|
'Nachbau et_theme_builder_api_save()' => ['tb-write'],
|
||||||
|
'Nachbau et_theme_builder_store_template()' => ['tb-write'],
|
||||||
|
'Nachbau et_theme_builder_load_template_setting_options()' => ['tb-read', 'tb-write'],
|
||||||
|
],
|
||||||
|
DesignSystemReader::class => [
|
||||||
|
'GlobalData::get_global_colors()' => ['design-read', 'page-read'],
|
||||||
|
'GlobalData::get_global_variables()' => ['design-read', 'page-read'],
|
||||||
|
'GlobalPreset::get_data()' => ['design-read', 'page-read'],
|
||||||
|
],
|
||||||
|
DesignSystemWriter::class => [
|
||||||
|
'GlobalPreset::get_data()' => ['design-read', 'preset-write'],
|
||||||
|
'GlobalPreset::prepare_data()' => ['preset-write'],
|
||||||
|
'GlobalPreset::validate_preset_count()' => ['preset-write'],
|
||||||
|
'GlobalPreset::get_deleted_preset_ids()' => ['preset-write'],
|
||||||
|
'GlobalPreset::remove_preset_from_legacy_option()' => ['preset-write'],
|
||||||
|
'GlobalPreset::save_data()' => ['preset-write'],
|
||||||
|
'Conversion::get_preset_attrs_mapping()' => ['preset-write'],
|
||||||
|
'Conversion::get_preset_attrs()' => ['preset-write'],
|
||||||
|
'GlobalPreset::get_group_preset_default_attr()' => ['catalog', 'preset-write'],
|
||||||
|
'ModuleRegistration::get_module_settings()' => ['catalog', 'preset-write'],
|
||||||
|
'UserRole::can_current_user_use_visual_builder()' => ['preset-write'],
|
||||||
|
'Nachbau GlobalPresetController::sync()' => ['preset-write'],
|
||||||
|
],
|
||||||
|
DiviModules::class => [
|
||||||
|
'ModuleRegistration::get_all_core_modules_metadata()' => ['catalog'],
|
||||||
|
'ModuleRegistration::get_default_attrs()' => ['catalog'],
|
||||||
|
'et_builder_enabled_for_post_type()' => ['catalog'],
|
||||||
|
'Konstante ET_BUILDER_PRODUCT_VERSION' => ['catalog'],
|
||||||
|
],
|
||||||
|
DynamicContentMap::class => [
|
||||||
|
'DynamicContentOptions::get_options()' => ['dynamic-sources'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function testEveryRequirementOfEveryAdapterServesAtLeastOneFeature(): void
|
||||||
|
{
|
||||||
|
$bare = [];
|
||||||
|
foreach (DiviAdapters::manifests() as $class => $manifest) {
|
||||||
|
foreach ($manifest->requirements() as $requirement) {
|
||||||
|
$features = $manifest->featuresOf($requirement);
|
||||||
|
if ($features === [] || array_unique($features, SORT_REGULAR) !== $features) {
|
||||||
|
$bare[] = $class . ': ' . $requirement->label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::assertSame([], $bare, "Anforderungen ohne (eindeutige) Fähigkeit:\n" . implode("\n", $bare));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryFeatureIsServedByAtLeastOneRequirement(): void
|
||||||
|
{
|
||||||
|
$served = [];
|
||||||
|
foreach (DiviAdapters::manifests() as $manifest) {
|
||||||
|
foreach ($manifest->requirements() as $requirement) {
|
||||||
|
foreach ($manifest->featuresOf($requirement) as $feature) {
|
||||||
|
$served[$feature->value] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$missing = array_values(array_diff(
|
||||||
|
array_map(static fn (DiviFeature $feature): string => $feature->value, DiviFeature::cases()),
|
||||||
|
array_keys($served)
|
||||||
|
));
|
||||||
|
|
||||||
|
self::assertSame([], $missing, 'Fähigkeiten ohne Anforderung in einem Manifest');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheAssignmentIsThePinnedTable(): void
|
||||||
|
{
|
||||||
|
$actual = [];
|
||||||
|
foreach (DiviAdapters::manifests() as $class => $manifest) {
|
||||||
|
foreach ($manifest->requirements() as $requirement) {
|
||||||
|
$values = array_map(
|
||||||
|
static fn (DiviFeature $feature): string => $feature->value,
|
||||||
|
$manifest->featuresOf($requirement)
|
||||||
|
);
|
||||||
|
sort($values);
|
||||||
|
$name = ($requirement instanceof Mirror ? 'Nachbau ' : '') . $requirement->label();
|
||||||
|
$actual[$class][$name] = $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::assertSame(self::EXPECTED, $actual);
|
||||||
|
self::assertSame(array_keys(self::EXPECTED), DiviAdapters::CLASSES, 'all eight adapters, in report order');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReadingAdaptersCarryNoWriteFeature(): void
|
||||||
|
{
|
||||||
|
$readingOnly = [DesignSystemReader::class, DiviRoles::class, DiviModules::class, DynamicContentMap::class];
|
||||||
|
|
||||||
|
foreach ($readingOnly as $class) {
|
||||||
|
$manifest = DiviAdapters::manifests()[$class];
|
||||||
|
foreach ($manifest->requirements() as $requirement) {
|
||||||
|
foreach ($manifest->featuresOf($requirement) as $feature) {
|
||||||
|
self::assertFalse($feature->isWrite(), $class . ': ' . $requirement->label());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryMirrorServesAWriteFeature(): void
|
||||||
|
{
|
||||||
|
foreach (DiviAdapters::manifests() as $class => $manifest) {
|
||||||
|
foreach ($manifest->mirrors as $mirror) {
|
||||||
|
$writes = array_filter(
|
||||||
|
$manifest->featuresOf($mirror),
|
||||||
|
static fn (DiviFeature $feature): bool => $feature->isWrite()
|
||||||
|
);
|
||||||
|
self::assertNotSame([], $writes, $class . ': Nachbau ' . $mirror->id . ' dient keinem Schreib-Feature');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class DiviFeatureTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testTheElevenFeaturesAndTheirValues(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
[
|
||||||
|
'PageRead' => 'page-read',
|
||||||
|
'PageWrite' => 'page-write',
|
||||||
|
'Catalog' => 'catalog',
|
||||||
|
'DynamicSources' => 'dynamic-sources',
|
||||||
|
'DesignRead' => 'design-read',
|
||||||
|
'PresetWrite' => 'preset-write',
|
||||||
|
'LibraryRead' => 'library-read',
|
||||||
|
'LibraryWrite' => 'library-write',
|
||||||
|
'Roles' => 'roles',
|
||||||
|
'ThemeBuilderRead' => 'tb-read',
|
||||||
|
'ThemeBuilderWrite' => 'tb-write',
|
||||||
|
],
|
||||||
|
array_column(array_map(
|
||||||
|
static fn (DiviFeature $feature): array => [$feature->name, $feature->value],
|
||||||
|
DiviFeature::cases()
|
||||||
|
), 1, 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnlyTheFourWriteFeaturesWrite(): void
|
||||||
|
{
|
||||||
|
$writing = array_values(array_map(
|
||||||
|
static fn (DiviFeature $feature): string => $feature->value,
|
||||||
|
array_filter(DiviFeature::cases(), static fn (DiviFeature $feature): bool => $feature->isWrite())
|
||||||
|
));
|
||||||
|
|
||||||
|
self::assertSame(['page-write', 'preset-write', 'library-write', 'tb-write'], $writing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEveryFeatureHasAUniqueGermanLabel(): void
|
||||||
|
{
|
||||||
|
$labels = array_map(static fn (DiviFeature $feature): string => $feature->label(), DiviFeature::cases());
|
||||||
|
|
||||||
|
self::assertCount(count(DiviFeature::cases()), array_unique($labels));
|
||||||
|
self::assertSame('Seiten schreiben', DiviFeature::PageWrite->label());
|
||||||
|
self::assertSame('Theme Builder lesen', DiviFeature::ThemeBuilderRead->label());
|
||||||
|
foreach ($labels as $label) {
|
||||||
|
self::assertNotSame('', trim($label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValuesResolveBackToTheFeature(): void
|
||||||
|
{
|
||||||
|
self::assertSame(DiviFeature::ThemeBuilderWrite, DiviFeature::from('tb-write'));
|
||||||
|
self::assertNull(DiviFeature::tryFrom('page'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ConstantRequirement;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -53,4 +55,61 @@ final class ManifestTest extends TestCase
|
|||||||
);
|
);
|
||||||
self::assertSame('Bibliothek', $manifest->area);
|
self::assertSame('Bibliothek', $manifest->area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPlainConstantNamesBecomeRequirementsWithALabel(): void
|
||||||
|
{
|
||||||
|
$manifest = new Manifest(
|
||||||
|
Manifest::CATALOG,
|
||||||
|
constants: ['ET_BUILDER_PRODUCT_VERSION', new ConstantRequirement('ET_OTHER', [DiviFeature::Roles])]
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
['ET_BUILDER_PRODUCT_VERSION', 'ET_OTHER'],
|
||||||
|
array_map(static fn (ConstantRequirement $constant): string => $constant->name, $manifest->constants)
|
||||||
|
);
|
||||||
|
self::assertSame('Konstante ET_OTHER', $manifest->constants[1]->label());
|
||||||
|
self::assertSame([], $manifest->constants[0]->features());
|
||||||
|
self::assertSame([DiviFeature::Roles], $manifest->constants[1]->features());
|
||||||
|
self::assertSame(['ET_BUILDER_PRODUCT_VERSION', 'ET_OTHER'], $manifest->names());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequirementsAreCallsConstantsAndMirrorsInThatOrder(): void
|
||||||
|
{
|
||||||
|
$call = new Call('et_pb_is_allowed', 1);
|
||||||
|
$mirror = new Mirror('m', 'a.php', 'a', null, 'Terme');
|
||||||
|
$manifest = new Manifest(Manifest::LIBRARY, calls: [$call], constants: ['ET_X'], mirrors: [$mirror]);
|
||||||
|
|
||||||
|
$requirements = $manifest->requirements();
|
||||||
|
|
||||||
|
self::assertCount(3, $requirements);
|
||||||
|
self::assertSame($call, $requirements[0]);
|
||||||
|
self::assertSame('Konstante ET_X', $requirements[1]->label());
|
||||||
|
self::assertSame($mirror, $requirements[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFeaturesOfPrefersTheOwnFeaturesOverTheManifestDefault(): void
|
||||||
|
{
|
||||||
|
$own = new Call('et_a', null, [DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite]);
|
||||||
|
$inherited = new Call('et_b');
|
||||||
|
$mirror = new Mirror('m', 'a.php', 'a', null, 'x', [DiviFeature::LibraryWrite]);
|
||||||
|
$manifest = new Manifest(
|
||||||
|
Manifest::PAGES,
|
||||||
|
calls: [$own, $inherited],
|
||||||
|
constants: ['ET_X'],
|
||||||
|
mirrors: [$mirror],
|
||||||
|
features: [DiviFeature::PageRead]
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame([DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite], $manifest->featuresOf($own));
|
||||||
|
self::assertSame([DiviFeature::PageRead], $manifest->featuresOf($inherited));
|
||||||
|
self::assertSame([DiviFeature::PageRead], $manifest->featuresOf($manifest->constants[0]));
|
||||||
|
self::assertSame([DiviFeature::LibraryWrite], $manifest->featuresOf($mirror));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithoutAnyFeaturesAnEntryHasNone(): void
|
||||||
|
{
|
||||||
|
$call = new Call('et_a');
|
||||||
|
|
||||||
|
self::assertSame([], (new Manifest(Manifest::PAGES, calls: [$call]))->featuresOf($call));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Schema;
|
|||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ConstantRequirement;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||||
@@ -35,7 +36,10 @@ final class DiviModulesTest extends TestCase
|
|||||||
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
||||||
);
|
);
|
||||||
self::assertSame(['divi/global-layout'], $manifest->blockTypes);
|
self::assertSame(['divi/global-layout'], $manifest->blockTypes);
|
||||||
self::assertSame(['ET_BUILDER_PRODUCT_VERSION'], $manifest->constants);
|
self::assertSame(
|
||||||
|
['ET_BUILDER_PRODUCT_VERSION'],
|
||||||
|
array_map(static fn (ConstantRequirement $constant): string => $constant->name, $manifest->constants)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInternalPostTypesAreDivisOwn(): void
|
public function testInternalPostTypesAreDivisOwn(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user