feat: Fähigkeiten color-write/variable-write; GlobalColorsAdapter und GlobalVariablesAdapter (Manifest, Signatur-Referenzen) und DesignRights::colors()/variables()
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
d73144ab36
commit
9b7c3c1d3b
@@ -11,9 +11,10 @@ use WP_Error;
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Who may change the design system (spec §10): presets need edit_theme_options and Divi's role right "Visual Builder
|
* Who may change the design system (spec §10): presets and colors need edit_theme_options and Divi's role right
|
||||||
* verwenden" – what Divi's own preset sync asks since 5.15 (formats "Nachtrag 5.15.1"). Without unfiltered_html a
|
* "Visual Builder verwenden" – what Divi's own preset sync asks since 5.15 (formats "Nachtrag 5.15.1"); variables
|
||||||
* write is refused while presets it saves along hold HTML/CSS that Divi would strip (final review I-2).
|
* also the role right "Variables Manager". Without unfiltered_html a write is refused while presets it saves along
|
||||||
|
* hold HTML/CSS that Divi would strip (final review I-2).
|
||||||
*/
|
*/
|
||||||
final class DesignRights
|
final class DesignRights
|
||||||
{
|
{
|
||||||
@@ -48,15 +49,46 @@ final class DesignRights
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function presets(DesignSystemWriter $writer): ?WP_Error
|
public static function presets(DesignSystemWriter $writer): ?WP_Error
|
||||||
|
{
|
||||||
|
return self::designer($writer, 'Presets');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Colors: the same rights as presets (spec §10 – stricter than Divi, which asks edit_theme_options only). */
|
||||||
|
public static function colors(DesignSystemWriter $writer): ?WP_Error
|
||||||
|
{
|
||||||
|
return self::designer($writer, 'Farben');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Variables: also Divi's role right "Variables Manager" – without it Divi silently saves nothing (plan 4c V7). */
|
||||||
|
public static function variables(DesignSystemWriter $writer, GlobalVariablesAdapter $variables): ?WP_Error
|
||||||
|
{
|
||||||
|
$refusal = self::designer($writer, 'Variablen');
|
||||||
|
if ($refusal !== null || $variables->canManage()) {
|
||||||
|
return $refusal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Errors::forbidden(
|
||||||
|
'Die Divi-Rolle dieses Kontos erlaubt den Variablen-Manager nicht; ohne ihn speichert Divi keine '
|
||||||
|
. 'Variablen.',
|
||||||
|
'Einen Administrator bitten, das Recht unter Divi → Rollen-Editor freizugeben.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** edit_theme_options and Divi's role right "Visual Builder verwenden"; $what names the area in the message. */
|
||||||
|
private static function designer(DesignSystemWriter $writer, string $what): ?WP_Error
|
||||||
{
|
{
|
||||||
if (! current_user_can('edit_theme_options')) {
|
if (! current_user_can('edit_theme_options')) {
|
||||||
return Errors::forbidden(
|
return Errors::forbidden(sprintf(
|
||||||
'Presets ändern erfordert das Recht, das Design der Website anzupassen (edit_theme_options).'
|
'%s ändern erfordert das Recht, das Design der Website anzupassen (edit_theme_options).',
|
||||||
);
|
$what
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if (! $writer->canUseVisualBuilder()) {
|
if (! $writer->canUseVisualBuilder()) {
|
||||||
return Errors::forbidden(
|
return Errors::forbidden(
|
||||||
'Die Divi-Rolle dieses Kontos erlaubt den Visual Builder nicht; ohne ihn ändert LIT MCP keine Presets.',
|
sprintf(
|
||||||
|
'Die Divi-Rolle dieses Kontos erlaubt den Visual Builder nicht; ohne ihn ändert LIT MCP keine %s.',
|
||||||
|
$what
|
||||||
|
),
|
||||||
'Einen Administrator bitten, das Recht unter Divi → Rollen-Editor freizugeben.'
|
'Einen Administrator bitten, das Recht unter Divi → Rollen-Editor freizugeben.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use ET\Builder\Packages\GlobalData\GlobalData;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
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\Manifest;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter "Designsystem" (spec §3) for the global colors (formats §1, plan 4c V3/V4): reads them through
|
||||||
|
* GlobalData::get_global_colors() and writes them through set_global_colors(). The five customizer colors live in
|
||||||
|
* Divi's theme options; get_customizer_colors() caches them in a static variable for the whole request, so read()
|
||||||
|
* lays the stored option values over Divi's answer – a read-back after writing sees the new value. Not final so the
|
||||||
|
* color area can be tested with a mock.
|
||||||
|
*/
|
||||||
|
class GlobalColorsAdapter implements DiviAdapter
|
||||||
|
{
|
||||||
|
/** Divi's theme options (one array option) with the customizer colors. */
|
||||||
|
private const OPTIONS = 'et_divi';
|
||||||
|
|
||||||
|
/** Customizer color ID => its option in et_divi (GlobalData::$customizer_colors). */
|
||||||
|
private const CUSTOMIZER = [
|
||||||
|
'gcid-primary-color' => 'accent_color',
|
||||||
|
'gcid-secondary-color' => 'secondary_accent_color',
|
||||||
|
'gcid-heading-color' => 'header_color',
|
||||||
|
'gcid-body-color' => 'font_color',
|
||||||
|
'gcid-link-color' => 'link_color',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function manifest(): Manifest
|
||||||
|
{
|
||||||
|
return new Manifest(
|
||||||
|
Manifest::DESIGN,
|
||||||
|
calls: [
|
||||||
|
new Call(GlobalData::class . '::get_global_colors', 0),
|
||||||
|
new Call(GlobalData::class . '::sanitize_global_colors_data', 1),
|
||||||
|
new Call(GlobalData::class . '::set_global_colors', 2),
|
||||||
|
],
|
||||||
|
keys: [self::OPTIONS],
|
||||||
|
features: [DiviFeature::ColorWrite]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, array<string, mixed>> get_global_colors() with the customizer values of et_divi. */
|
||||||
|
public function read(): array
|
||||||
|
{
|
||||||
|
$colors = GlobalData::get_global_colors();
|
||||||
|
$options = get_option(self::OPTIONS);
|
||||||
|
foreach (self::CUSTOMIZER as $id => $option) {
|
||||||
|
$value = is_array($options) ? ($options[$option] ?? null) : null;
|
||||||
|
if (is_array($colors[$id] ?? null) && is_string($value) && $value !== '') {
|
||||||
|
$colors[$id]['color'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves checked entries: $replace false merges them into the stored colors (set_global_colors with
|
||||||
|
* "already sanitized"), $replace true replaces all stored colors (restore). Customizer colors among them go to
|
||||||
|
* the theme options (Divi does that itself).
|
||||||
|
*
|
||||||
|
* @param array<string, array<string, mixed>> $entries
|
||||||
|
* @throws DiviLocked When color-write is not released – before anything is written.
|
||||||
|
* @throws RuntimeException When Divi's sanitizing drops an entry; nothing is written then.
|
||||||
|
*/
|
||||||
|
public function save(array $entries, bool $replace): void
|
||||||
|
{
|
||||||
|
ReleaseGate::assertFeature(DiviFeature::ColorWrite);
|
||||||
|
$clean = GlobalData::sanitize_global_colors_data($entries);
|
||||||
|
$dropped = array_diff(array_keys($entries), array_keys($clean));
|
||||||
|
if ($dropped !== []) {
|
||||||
|
throw new RuntimeException('global colors refused: Divi dropped ' . implode(', ', $dropped));
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalData::set_global_colors($clean, ! $replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use ET\Builder\Packages\GlobalData\GlobalData;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
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\Manifest;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
|
use RuntimeException;
|
||||||
|
use WP_Error;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter "Designsystem" (spec §3) for the global variables (formats §2, plan 4c V7): reads them through
|
||||||
|
* GlobalData::get_global_variables() back into the stored form and writes them through set_global_variables() with
|
||||||
|
* the inventory the change was planned on (Divi's own stale check). Without edit_theme_options or Divi's role right
|
||||||
|
* "Variables Manager" Divi silently saves nothing – DesignRights::variables() asks canManage() first. Not final so
|
||||||
|
* the variable area can be tested with a mock.
|
||||||
|
*/
|
||||||
|
class GlobalVariablesAdapter implements DiviAdapter
|
||||||
|
{
|
||||||
|
public static function manifest(): Manifest
|
||||||
|
{
|
||||||
|
return new Manifest(
|
||||||
|
Manifest::DESIGN,
|
||||||
|
calls: [
|
||||||
|
new Call(GlobalData::class . '::get_global_variables', 0),
|
||||||
|
new Call(GlobalData::class . '::set_global_variables', 2),
|
||||||
|
new Call('et_pb_is_allowed', 1),
|
||||||
|
],
|
||||||
|
features: [DiviFeature::VariableWrite]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stored form: type => ID => entry as arrays, without allowedActions, without the customizer fonts and the
|
||||||
|
* group "colors" (both are not stored there), empty groups left out.
|
||||||
|
*
|
||||||
|
* @return array<string, array<string, array<string, mixed>>>
|
||||||
|
*/
|
||||||
|
public function read(): array
|
||||||
|
{
|
||||||
|
$data = [];
|
||||||
|
foreach (GlobalData::get_global_variables() as $type => $items) {
|
||||||
|
if ($type === 'colors' || (! is_array($items) && ! is_object($items))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ((array) $items as $id => $item) {
|
||||||
|
// Divi's sanitizing keeps gvid- IDs only; the customizer fonts (--et_global_*_font) are not stored.
|
||||||
|
if (! str_starts_with((string) $id, 'gvid-') || (! is_array($item) && ! is_object($item))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$entry = (array) $item;
|
||||||
|
unset($entry['allowedActions']);
|
||||||
|
$data[(string) $type][(string) $id] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array<string, mixed>>> $data The whole new inventory (stored form).
|
||||||
|
* @param array<string, array<string, array<string, mixed>>> $expected The inventory the change was planned on.
|
||||||
|
* @throws DiviLocked When variable-write is not released – before anything is written.
|
||||||
|
* @throws RuntimeException When Divi refuses the inventory (stale, missing IDs, invalid entries).
|
||||||
|
*/
|
||||||
|
public function save(array $data, array $expected): void
|
||||||
|
{
|
||||||
|
ReleaseGate::assertFeature(DiviFeature::VariableWrite);
|
||||||
|
$refusal = GlobalData::set_global_variables($data, $expected);
|
||||||
|
if ($refusal instanceof WP_Error) {
|
||||||
|
throw new RuntimeException('global variables refused: ' . $refusal->get_error_code() . ' '
|
||||||
|
. $refusal->get_error_message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Divi's role right "Variables Manager"; fail closed without Divi. */
|
||||||
|
public function canManage(): bool
|
||||||
|
{
|
||||||
|
return function_exists('et_pb_is_allowed') && (bool) et_pb_is_allowed('variables_manager');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ namespace LIT\MCP\Integrations\Divi\Release;
|
|||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||||
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;
|
||||||
@@ -27,6 +29,8 @@ final class DiviAdapters
|
|||||||
DiviThemeBuilder::class,
|
DiviThemeBuilder::class,
|
||||||
DesignSystemReader::class,
|
DesignSystemReader::class,
|
||||||
DesignSystemWriter::class,
|
DesignSystemWriter::class,
|
||||||
|
GlobalColorsAdapter::class,
|
||||||
|
GlobalVariablesAdapter::class,
|
||||||
DiviModules::class,
|
DiviModules::class,
|
||||||
DynamicContentMap::class,
|
DynamicContentMap::class,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -21,17 +21,20 @@ enum DiviFeature: string
|
|||||||
case DynamicSources = 'dynamic-sources';
|
case DynamicSources = 'dynamic-sources';
|
||||||
case DesignRead = 'design-read';
|
case DesignRead = 'design-read';
|
||||||
case PresetWrite = 'preset-write';
|
case PresetWrite = 'preset-write';
|
||||||
|
case ColorWrite = 'color-write';
|
||||||
|
case VariableWrite = 'variable-write';
|
||||||
case LibraryRead = 'library-read';
|
case LibraryRead = 'library-read';
|
||||||
case LibraryWrite = 'library-write';
|
case LibraryWrite = 'library-write';
|
||||||
case Roles = 'roles';
|
case Roles = 'roles';
|
||||||
case ThemeBuilderRead = 'tb-read';
|
case ThemeBuilderRead = 'tb-read';
|
||||||
case ThemeBuilderWrite = 'tb-write';
|
case ThemeBuilderWrite = 'tb-write';
|
||||||
|
|
||||||
/** Whether the feature changes data (the four *Write cases). */
|
/** Whether the feature changes data (the six *Write cases). */
|
||||||
public function isWrite(): bool
|
public function isWrite(): bool
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::PageWrite, self::PresetWrite, self::LibraryWrite, self::ThemeBuilderWrite => true,
|
self::PageWrite, self::PresetWrite, self::ColorWrite, self::VariableWrite, self::LibraryWrite,
|
||||||
|
self::ThemeBuilderWrite => true,
|
||||||
default => false,
|
default => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -46,6 +49,8 @@ enum DiviFeature: string
|
|||||||
self::DynamicSources => 'Dynamische Inhalte',
|
self::DynamicSources => 'Dynamische Inhalte',
|
||||||
self::DesignRead => 'Designsystem lesen',
|
self::DesignRead => 'Designsystem lesen',
|
||||||
self::PresetWrite => 'Presets schreiben',
|
self::PresetWrite => 'Presets schreiben',
|
||||||
|
self::ColorWrite => 'Farben schreiben',
|
||||||
|
self::VariableWrite => 'Variablen schreiben',
|
||||||
self::LibraryRead => 'Bibliothek lesen',
|
self::LibraryRead => 'Bibliothek lesen',
|
||||||
self::LibraryWrite => 'Bibliothek schreiben',
|
self::LibraryWrite => 'Bibliothek schreiben',
|
||||||
self::Roles => 'Divi-Rollen',
|
self::Roles => 'Divi-Rollen',
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
"sig:ET\\Builder\\Packages\\Conversion\\Conversion::get_preset_attrs_mapping": "b5c3403666ff92be1dd7bfa6e6589c32f6662b37282f2fd40922f8d50151745d",
|
"sig:ET\\Builder\\Packages\\Conversion\\Conversion::get_preset_attrs_mapping": "b5c3403666ff92be1dd7bfa6e6589c32f6662b37282f2fd40922f8d50151745d",
|
||||||
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::get_global_colors": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::get_global_colors": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
||||||
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::get_global_variables": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::get_global_variables": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
||||||
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::sanitize_global_colors_data": "94f109150a8d6db57c309de00f4f4e369b4cd4a7e2a0682d5612dddeb65c8861",
|
||||||
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::set_global_colors": "d269cc06168a1898ea999e6df332449cf9bc04c17abefbb8f729a05136c836e6",
|
||||||
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalData::set_global_variables": "a2f422943530dba1afb5133459b25341dc67aa29e0be0a8626cbd60619c5a267",
|
||||||
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalPreset::get_data": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalPreset::get_data": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
||||||
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalPreset::get_deleted_preset_ids": "d36bfe7698116a94f7c6bc5ff00e3f66f3cd4267b68b5e61f0a5ec01e051b450",
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalPreset::get_deleted_preset_ids": "d36bfe7698116a94f7c6bc5ff00e3f66f3cd4267b68b5e61f0a5ec01e051b450",
|
||||||
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalPreset::get_group_preset_default_attr": "427ee58858537cf02a9d6dbbf58aafd779f8424c9c840806b5d7af8f25fbbda5",
|
"sig:ET\\Builder\\Packages\\GlobalData\\GlobalPreset::get_group_preset_default_attr": "427ee58858537cf02a9d6dbbf58aafd779f8424c9c840806b5d7af8f25fbbda5",
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ trap 'rc=$?; cleanup; guard_exit "$rc" || exit $?' EXIT
|
|||||||
|
|
||||||
check_pages() { wp post list --post_type=page --post_status=any --meta_key=_lit_mcp_check_page --format=count; }
|
check_pages() { wp post list --post_type=page --post_status=any --meta_key=_lit_mcp_check_page --format=count; }
|
||||||
|
|
||||||
# The ids of the eleven features (plan decision D1), sorted.
|
# The ids of the thirteen features (plan decision D1, plan 4c V2), sorted.
|
||||||
FEATURE_IDS='["catalog","design-read","dynamic-sources","library-read","library-write","page-read","page-write","preset-write","roles","tb-read","tb-write"]'
|
FEATURE_IDS='["catalog","color-write","design-read","dynamic-sources","library-read","library-write","page-read","page-write","preset-write","roles","tb-read","tb-write","variable-write"]'
|
||||||
|
|
||||||
# Adds a problem to one feature in the cached state, as if its mirror check had failed. Needs the cache entry of
|
# Adds a problem to one feature in the cached state, as if its mirror check had failed. Needs the cache entry of
|
||||||
# ReleaseResolver (format 2: ["format" => 2, "key" => stateKey, "problems" => list of Problem::toArray()]); the key
|
# ReleaseResolver (format 2: ["format" => 2, "key" => stateKey, "problems" => list of Problem::toArray()]); the key
|
||||||
@@ -68,7 +68,7 @@ echo "$STATUS" | jq -e --argjson ids "$FEATURE_IDS" '
|
|||||||
.components[] | select(.id == "divi") | .features
|
.components[] | select(.id == "divi") | .features
|
||||||
| ([.[].id] | sort) == $ids
|
| ([.[].id] | sort) == $ids
|
||||||
and all(.state == "released" and .source == "code" and .problems == [])' >/dev/null \
|
and all(.state == "released" and .source == "code" and .problems == [])' >/dev/null \
|
||||||
|| fail "all eleven features must be released from code without problems: $STATUS"
|
|| fail "all thirteen features must be released from code without problems: $STATUS"
|
||||||
echo "$STATUS" | jq -e '
|
echo "$STATUS" | jq -e '
|
||||||
.components[] | select(.id == "divi") | .features
|
.components[] | select(.id == "divi") | .features
|
||||||
| (.[] | select(.id == "page-write") | .tools | any(. == "lit-divi-insert"))
|
| (.[] | select(.id == "page-write") | .tools | any(. == "lit-divi-insert"))
|
||||||
@@ -76,7 +76,7 @@ echo "$STATUS" | jq -e '
|
|||||||
and (.[] | select(.id == "preset-write") | .tools | any(startswith("lit/divi-presets-")))
|
and (.[] | select(.id == "preset-write") | .tools | any(startswith("lit/divi-presets-")))
|
||||||
and (.[] | select(.id == "page-read") | .label == "Seiten lesen")' >/dev/null \
|
and (.[] | select(.id == "page-read") | .label == "Seiten lesen")' >/dev/null \
|
||||||
|| fail "features must name their tools (direct lit-<name>, indirect lit/<name>): $STATUS"
|
|| fail "features must name their tools (direct lit-<name>, indirect lit/<name>): $STATUS"
|
||||||
pass "lit-site-status: eleven features released (code), each with its tools"
|
pass "lit-site-status: thirteen features released (code), each with its tools"
|
||||||
|
|
||||||
# 2. Admin check through the REST route (application password instead of the cookie nonce).
|
# 2. Admin check through the REST route (application password instead of the cookie nonce).
|
||||||
# X-Forwarded-Proto: https is required here: without it the request to http://localhost with the
|
# X-Forwarded-Proto: https is required here: without it the request to http://localhost with the
|
||||||
@@ -212,8 +212,8 @@ simulate_problem page-write "Smoke: Cache vergiftet"
|
|||||||
CLI=$(wp --user="$ADMIN_LOGIN" lit-mcp divi-check 2>&1) || fail "wp lit-mcp divi-check must succeed (exit 0): $CLI"
|
CLI=$(wp --user="$ADMIN_LOGIN" lit-mcp divi-check 2>&1) || fail "wp lit-mcp divi-check must succeed (exit 0): $CLI"
|
||||||
echo "$CLI" | grep -q '^Fähigkeiten:' || fail "divi-check must print the matrix: $CLI"
|
echo "$CLI" | grep -q '^Fähigkeiten:' || fail "divi-check must print the matrix: $CLI"
|
||||||
echo "$CLI" | grep -q '✓ Seiten schreiben – freigegeben (code)' || fail "divi-check must show page-write released: $CLI"
|
echo "$CLI" | grep -q '✓ Seiten schreiben – freigegeben (code)' || fail "divi-check must show page-write released: $CLI"
|
||||||
[ "$(echo "$CLI" | grep -c ' – freigegeben (')" = "11" ] || fail "divi-check must show eleven released features: $CLI"
|
[ "$(echo "$CLI" | grep -c ' – freigegeben (')" = "13" ] || fail "divi-check must show thirteen released features: $CLI"
|
||||||
[ "$(check_pages)" = "0" ] || fail "the CLI check must delete its page"
|
[ "$(check_pages)" = "0" ] || fail "the CLI check must delete its page"
|
||||||
pass "wp lit-mcp divi-check prints eleven released features, recomputes the state and leaves no page"
|
pass "wp lit-mcp divi-check prints thirteen released features, recomputes the state and leaves no page"
|
||||||
|
|
||||||
finish
|
finish
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
|||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignRights;
|
use LIT\MCP\Integrations\Divi\Design\DesignRights;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
|
||||||
@@ -26,4 +27,50 @@ final class DesignRightsTest extends TestCase
|
|||||||
$refusal = self::assertToolError(DesignRights::presets($writer), 'forbidden');
|
$refusal = self::assertToolError(DesignRights::presets($writer), 'forbidden');
|
||||||
self::assertStringContainsString('edit_theme_options', $refusal['message']);
|
self::assertStringContainsString('edit_theme_options', $refusal['message']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testColorsNeedEditThemeOptionsAndTheVisualBuilder(): void
|
||||||
|
{
|
||||||
|
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||||
|
$writer->allows('canUseVisualBuilder')->andReturn(true, false);
|
||||||
|
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options');
|
||||||
|
|
||||||
|
self::assertNull(DesignRights::colors($writer));
|
||||||
|
$refusal = self::assertToolError(DesignRights::colors($writer), 'forbidden');
|
||||||
|
self::assertStringContainsString('keine Farben', $refusal['message']);
|
||||||
|
|
||||||
|
Functions\when('current_user_can')->justReturn(false);
|
||||||
|
$refusal = self::assertToolError(DesignRights::colors($writer), 'forbidden');
|
||||||
|
self::assertStringContainsString('Farben ändern erfordert', $refusal['message']);
|
||||||
|
self::assertStringContainsString('edit_theme_options', $refusal['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVariablesAlsoNeedTheVariablesManager(): void
|
||||||
|
{
|
||||||
|
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||||
|
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||||
|
$variables = Mockery::mock(GlobalVariablesAdapter::class);
|
||||||
|
$variables->allows('canManage')->andReturn(true, false);
|
||||||
|
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options');
|
||||||
|
|
||||||
|
self::assertNull(DesignRights::variables($writer, $variables));
|
||||||
|
$refusal = self::assertToolError(DesignRights::variables($writer, $variables), 'forbidden');
|
||||||
|
self::assertStringContainsString('Variablen-Manager', $refusal['message']);
|
||||||
|
self::assertStringContainsString('Rollen-Editor', $refusal['hint']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVariablesAskTheThemeRightAndTheVisualBuilderFirst(): void
|
||||||
|
{
|
||||||
|
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||||
|
$writer->allows('canUseVisualBuilder')->andReturn(false);
|
||||||
|
$variables = Mockery::mock(GlobalVariablesAdapter::class);
|
||||||
|
$variables->allows('canManage')->andReturn(true);
|
||||||
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
|
|
||||||
|
$refusal = self::assertToolError(DesignRights::variables($writer, $variables), 'forbidden');
|
||||||
|
self::assertStringContainsString('keine Variablen', $refusal['message']);
|
||||||
|
|
||||||
|
Functions\when('current_user_can')->justReturn(false);
|
||||||
|
$refusal = self::assertToolError(DesignRights::variables($writer, $variables), 'forbidden');
|
||||||
|
self::assertStringContainsString('Variablen ändern erfordert', $refusal['message']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
use Mockery;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divi's GlobalData is replaced by a Mockery alias; the tests that need it run in their own process (an alias
|
||||||
|
* defines the class for the rest of the process).
|
||||||
|
*/
|
||||||
|
final class GlobalColorsAdapterTest extends TestCase
|
||||||
|
{
|
||||||
|
private const GLOBAL_DATA = 'ET\\Builder\\Packages\\GlobalData\\GlobalData';
|
||||||
|
|
||||||
|
public function testIsARegisteredDesignAdapterForColorWrite(): void
|
||||||
|
{
|
||||||
|
$manifest = GlobalColorsAdapter::manifest();
|
||||||
|
|
||||||
|
self::assertContains(GlobalColorsAdapter::class, DiviAdapters::CLASSES);
|
||||||
|
self::assertSame(Manifest::DESIGN, $manifest->area);
|
||||||
|
self::assertSame(
|
||||||
|
['GlobalData::get_global_colors()', 'GlobalData::sanitize_global_colors_data()',
|
||||||
|
'GlobalData::set_global_colors()'],
|
||||||
|
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
||||||
|
);
|
||||||
|
self::assertSame([0, 1, 2], array_map(static fn (Call $call): ?int => $call->args, $manifest->calls));
|
||||||
|
self::assertSame(['et_divi'], $manifest->keys);
|
||||||
|
self::assertSame([DiviFeature::ColorWrite], $manifest->features);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWritesNeedAReleasedDivi(): void
|
||||||
|
{
|
||||||
|
ReleaseGate::useResolver(
|
||||||
|
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(DiviLocked::class);
|
||||||
|
|
||||||
|
(new GlobalColorsAdapter())->save(['gcid-abc' => ['color' => '#fff']], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testReadLaysTheStoredCustomizerValuesOverDivisCache(): void
|
||||||
|
{
|
||||||
|
Mockery::mock('alias:' . self::GLOBAL_DATA)->allows('get_global_colors')->andReturn([
|
||||||
|
'gcid-primary-color' => ['color' => '#2ea3f2', 'folder' => 'customizer', 'status' => 'active'],
|
||||||
|
'gcid-secondary-color' => ['color' => '#2ea3f2', 'folder' => 'customizer', 'status' => 'active'],
|
||||||
|
'gcid-body-color' => ['color' => '#666666', 'folder' => 'customizer', 'status' => 'active'],
|
||||||
|
'gcid-abc1234567' => ['color' => '#123456', 'folder' => '', 'status' => 'active'],
|
||||||
|
]);
|
||||||
|
Functions\when('get_option')->alias(static fn (string $name): mixed => $name === 'et_divi'
|
||||||
|
? ['accent_color' => '#ff0000', 'secondary_accent_color' => '', 'font_color' => 7, 'link_color' => '#00f']
|
||||||
|
: false);
|
||||||
|
|
||||||
|
$colors = (new GlobalColorsAdapter())->read();
|
||||||
|
|
||||||
|
self::assertSame('#ff0000', $colors['gcid-primary-color']['color'], 'stored option wins over the cache');
|
||||||
|
self::assertSame('#2ea3f2', $colors['gcid-secondary-color']['color'], 'an empty option is not laid over');
|
||||||
|
self::assertSame('#666666', $colors['gcid-body-color']['color'], 'a non-string option is not laid over');
|
||||||
|
self::assertArrayNotHasKey('gcid-link-color', $colors, 'no entry is invented');
|
||||||
|
self::assertSame('#123456', $colors['gcid-abc1234567']['color']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testReadWithoutThemeOptionsIsDivisAnswer(): void
|
||||||
|
{
|
||||||
|
$divi = ['gcid-primary-color' => ['color' => '#2ea3f2', 'folder' => 'customizer']];
|
||||||
|
Mockery::mock('alias:' . self::GLOBAL_DATA)->allows('get_global_colors')->andReturn($divi);
|
||||||
|
Functions\when('get_option')->justReturn(false);
|
||||||
|
|
||||||
|
self::assertSame($divi, (new GlobalColorsAdapter())->read());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testSaveSanitizesAndMergesOrReplaces(): void
|
||||||
|
{
|
||||||
|
$entries = ['gcid-abc1234567' => ['color' => '#123456', 'status' => 'active', 'usedInPosts' => []]];
|
||||||
|
$clean = ['gcid-abc1234567' => ['color' => '#123456', 'status' => 'active', 'usedInPosts' => []]];
|
||||||
|
$divi = Mockery::mock('alias:' . self::GLOBAL_DATA);
|
||||||
|
$divi->expects('sanitize_global_colors_data')->twice()->with($entries)->andReturn($clean);
|
||||||
|
// Normal write: merge ("already sanitized" = true); restore: replace all (false).
|
||||||
|
$divi->expects('set_global_colors')->once()->with($clean, true)->ordered();
|
||||||
|
$divi->expects('set_global_colors')->once()->with($clean, false)->ordered();
|
||||||
|
|
||||||
|
(new GlobalColorsAdapter())->save($entries, false);
|
||||||
|
(new GlobalColorsAdapter())->save($entries, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testSaveRefusesWhenDiviDropsAnEntry(): void
|
||||||
|
{
|
||||||
|
$divi = Mockery::mock('alias:' . self::GLOBAL_DATA);
|
||||||
|
$divi->allows('sanitize_global_colors_data')->andReturn(['gcid-abc1234567' => ['color' => '#123456']]);
|
||||||
|
$divi->expects('set_global_colors')->never();
|
||||||
|
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('global colors refused: Divi dropped gcid-empty00000');
|
||||||
|
|
||||||
|
(new GlobalColorsAdapter())->save([
|
||||||
|
'gcid-abc1234567' => ['color' => '#123456'],
|
||||||
|
'gcid-empty00000' => ['color' => ''],
|
||||||
|
], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
use Mockery;
|
||||||
|
use RuntimeException;
|
||||||
|
use WP_Error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divi's GlobalData is replaced by a Mockery alias and et_pb_is_allowed() by Brain Monkey; those tests run in their
|
||||||
|
* own process (both define a symbol for the rest of the process).
|
||||||
|
*/
|
||||||
|
final class GlobalVariablesAdapterTest extends TestCase
|
||||||
|
{
|
||||||
|
private const GLOBAL_DATA = 'ET\\Builder\\Packages\\GlobalData\\GlobalData';
|
||||||
|
|
||||||
|
public function testIsARegisteredDesignAdapterForVariableWrite(): void
|
||||||
|
{
|
||||||
|
$manifest = GlobalVariablesAdapter::manifest();
|
||||||
|
|
||||||
|
self::assertContains(GlobalVariablesAdapter::class, DiviAdapters::CLASSES);
|
||||||
|
self::assertSame(Manifest::DESIGN, $manifest->area);
|
||||||
|
self::assertSame(
|
||||||
|
['GlobalData::get_global_variables()', 'GlobalData::set_global_variables()', 'et_pb_is_allowed()'],
|
||||||
|
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
||||||
|
);
|
||||||
|
self::assertSame([0, 2, 1], array_map(static fn (Call $call): ?int => $call->args, $manifest->calls));
|
||||||
|
self::assertSame([], $manifest->keys);
|
||||||
|
self::assertSame([DiviFeature::VariableWrite], $manifest->features);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWritesNeedAReleasedDivi(): void
|
||||||
|
{
|
||||||
|
ReleaseGate::useResolver(
|
||||||
|
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(DiviLocked::class);
|
||||||
|
|
||||||
|
(new GlobalVariablesAdapter())->save([], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithoutDiviNobodyManagesVariables(): void
|
||||||
|
{
|
||||||
|
// Unit tests have no et_pb_is_allowed(): a missing Divi function never means "allowed".
|
||||||
|
self::assertFalse((new GlobalVariablesAdapter())->canManage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testCanManageAsksTheVariablesManagerRight(): void
|
||||||
|
{
|
||||||
|
Functions\when('et_pb_is_allowed')->alias(static fn (string $cap): bool => $cap === 'variables_manager');
|
||||||
|
|
||||||
|
self::assertTrue((new GlobalVariablesAdapter())->canManage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testReadReturnsTheStoredForm(): void
|
||||||
|
{
|
||||||
|
$actions = ['allowedActions' => ['editLabel' => true, 'editValue' => true, 'remove' => true]];
|
||||||
|
$space = ['label' => 'Abstand', 'value' => '24px', 'order' => '1', 'status' => 'active'];
|
||||||
|
$wave = ['label' => 'Welle', 'value' => 'linear-gradient(#fff,#000)', 'order' => '1', 'status' => 'active'];
|
||||||
|
Mockery::mock('alias:' . self::GLOBAL_DATA)->allows('get_global_variables')->andReturn([
|
||||||
|
'numbers' => (object) ['gvid-space00001' => $space + $actions],
|
||||||
|
'strings' => (object) [],
|
||||||
|
'colors' => (object) ['gcid-x' => ['label' => 'X'] + $actions],
|
||||||
|
'fonts' => (object) [
|
||||||
|
'--et_global_heading_font' => ['label' => 'Überschrift', 'value' => 'Open Sans'] + $actions,
|
||||||
|
'gvid-font000001' => (object) (['label' => 'Fließtext', 'value' => 'Inter'] + $actions),
|
||||||
|
],
|
||||||
|
'gradients' => (object) ['gvid-wave000001' => $wave + $actions],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
[
|
||||||
|
'numbers' => ['gvid-space00001' => $space],
|
||||||
|
'fonts' => ['gvid-font000001' => ['label' => 'Fließtext', 'value' => 'Inter']],
|
||||||
|
'gradients' => ['gvid-wave000001' => $wave],
|
||||||
|
],
|
||||||
|
(new GlobalVariablesAdapter())->read()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testSavePassesThePlannedInventoryAndThrowsOnARefusal(): void
|
||||||
|
{
|
||||||
|
$data = ['numbers' => ['gvid-space00001' => ['label' => 'Abstand', 'value' => '32px']]];
|
||||||
|
$expected = ['numbers' => ['gvid-space00001' => ['label' => 'Abstand', 'value' => '24px']]];
|
||||||
|
Mockery::mock('alias:' . self::GLOBAL_DATA)->expects('set_global_variables')->twice()->with($data, $expected)
|
||||||
|
->andReturn(null, new WP_Error('global_variables_stale', 'Global variables changed.'));
|
||||||
|
|
||||||
|
(new GlobalVariablesAdapter())->save($data, $expected);
|
||||||
|
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('global variables refused: global_variables_stale Global variables changed.');
|
||||||
|
(new GlobalVariablesAdapter())->save($data, $expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
|||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||||
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;
|
||||||
@@ -70,6 +72,16 @@ final class DiviFeatureAssignmentTest extends TestCase
|
|||||||
'UserRole::can_current_user_use_visual_builder()' => ['preset-write'],
|
'UserRole::can_current_user_use_visual_builder()' => ['preset-write'],
|
||||||
'Nachbau GlobalPresetController::sync()' => ['preset-write'],
|
'Nachbau GlobalPresetController::sync()' => ['preset-write'],
|
||||||
],
|
],
|
||||||
|
GlobalColorsAdapter::class => [
|
||||||
|
'GlobalData::get_global_colors()' => ['color-write'],
|
||||||
|
'GlobalData::sanitize_global_colors_data()' => ['color-write'],
|
||||||
|
'GlobalData::set_global_colors()' => ['color-write'],
|
||||||
|
],
|
||||||
|
GlobalVariablesAdapter::class => [
|
||||||
|
'GlobalData::get_global_variables()' => ['variable-write'],
|
||||||
|
'GlobalData::set_global_variables()' => ['variable-write'],
|
||||||
|
'et_pb_is_allowed()' => ['variable-write'],
|
||||||
|
],
|
||||||
DiviModules::class => [
|
DiviModules::class => [
|
||||||
'ModuleRegistration::get_all_core_modules_metadata()' => ['catalog'],
|
'ModuleRegistration::get_all_core_modules_metadata()' => ['catalog'],
|
||||||
'ModuleRegistration::get_default_attrs()' => ['catalog'],
|
'ModuleRegistration::get_default_attrs()' => ['catalog'],
|
||||||
@@ -131,7 +143,7 @@ final class DiviFeatureAssignmentTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
self::assertSame(self::EXPECTED, $actual);
|
self::assertSame(self::EXPECTED, $actual);
|
||||||
self::assertSame(array_keys(self::EXPECTED), DiviAdapters::CLASSES, 'all eight adapters, in report order');
|
self::assertSame(array_keys(self::EXPECTED), DiviAdapters::CLASSES, 'all ten adapters, in report order');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReadingAdaptersCarryNoWriteFeature(): void
|
public function testReadingAdaptersCarryNoWriteFeature(): void
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
final class DiviFeatureTest extends TestCase
|
final class DiviFeatureTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testTheElevenFeaturesAndTheirValues(): void
|
public function testTheThirteenFeaturesAndTheirValues(): void
|
||||||
{
|
{
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
[
|
[
|
||||||
@@ -19,6 +19,8 @@ final class DiviFeatureTest extends TestCase
|
|||||||
'DynamicSources' => 'dynamic-sources',
|
'DynamicSources' => 'dynamic-sources',
|
||||||
'DesignRead' => 'design-read',
|
'DesignRead' => 'design-read',
|
||||||
'PresetWrite' => 'preset-write',
|
'PresetWrite' => 'preset-write',
|
||||||
|
'ColorWrite' => 'color-write',
|
||||||
|
'VariableWrite' => 'variable-write',
|
||||||
'LibraryRead' => 'library-read',
|
'LibraryRead' => 'library-read',
|
||||||
'LibraryWrite' => 'library-write',
|
'LibraryWrite' => 'library-write',
|
||||||
'Roles' => 'roles',
|
'Roles' => 'roles',
|
||||||
@@ -32,14 +34,17 @@ final class DiviFeatureTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testOnlyTheFourWriteFeaturesWrite(): void
|
public function testOnlyTheSixWriteFeaturesWrite(): void
|
||||||
{
|
{
|
||||||
$writing = array_values(array_map(
|
$writing = array_values(array_map(
|
||||||
static fn (DiviFeature $feature): string => $feature->value,
|
static fn (DiviFeature $feature): string => $feature->value,
|
||||||
array_filter(DiviFeature::cases(), static fn (DiviFeature $feature): bool => $feature->isWrite())
|
array_filter(DiviFeature::cases(), static fn (DiviFeature $feature): bool => $feature->isWrite())
|
||||||
));
|
));
|
||||||
|
|
||||||
self::assertSame(['page-write', 'preset-write', 'library-write', 'tb-write'], $writing);
|
self::assertSame(
|
||||||
|
['page-write', 'preset-write', 'color-write', 'variable-write', 'library-write', 'tb-write'],
|
||||||
|
$writing
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEveryFeatureHasAUniqueGermanLabel(): void
|
public function testEveryFeatureHasAUniqueGermanLabel(): void
|
||||||
@@ -49,6 +54,8 @@ final class DiviFeatureTest extends TestCase
|
|||||||
self::assertCount(count(DiviFeature::cases()), array_unique($labels));
|
self::assertCount(count(DiviFeature::cases()), array_unique($labels));
|
||||||
self::assertSame('Seiten schreiben', DiviFeature::PageWrite->label());
|
self::assertSame('Seiten schreiben', DiviFeature::PageWrite->label());
|
||||||
self::assertSame('Theme Builder lesen', DiviFeature::ThemeBuilderRead->label());
|
self::assertSame('Theme Builder lesen', DiviFeature::ThemeBuilderRead->label());
|
||||||
|
self::assertSame('Farben schreiben', DiviFeature::ColorWrite->label());
|
||||||
|
self::assertSame('Variablen schreiben', DiviFeature::VariableWrite->label());
|
||||||
foreach ($labels as $label) {
|
foreach ($labels as $label) {
|
||||||
self::assertNotSame('', trim($label));
|
self::assertNotSame('', trim($label));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user