refactor: Divi pages adapter replaces DiviFlags
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ defined('ABSPATH') || exit;
|
||||
/**
|
||||
* Divi short form for lit/content-create (spec §6.2): the sections become a Divi 5 layout in
|
||||
* exactly one divi/placeholder – inserted by TreeEditor like the first section of an empty page
|
||||
* (ruling S9) – stored through DiviSaver's kses window, with the builder flags (DiviFlags, S11).
|
||||
* (ruling S9) – stored through DiviSaver's kses window, with the builder flags (DiviPages, S11).
|
||||
*
|
||||
* Allow map (ruling S22) for an entry that does not exist yet: check() runs before the insert
|
||||
* with the composition bound to post ID 0 – Divi's own registry for "no entry" (get_options(0)
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Pages;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Marks an entry as a Divi 5 builder layout, like the Visual Builder's own save does
|
||||
* (Divi 5.2.1 SyncToServerController.php:382-387: _et_pb_use_divi_5 = "on", legacy
|
||||
* _et_pb_use_builder = "on", and _et_pb_show_page_creation = "off" so the builder does not show
|
||||
* its page creation flow on the first open). Ruling S11: update_post_meta() returns false for
|
||||
* an unchanged value too, so success is checked by reading the flags back. Used by DiviSaver
|
||||
* (inside FollowUp) and the Divi content creation.
|
||||
*/
|
||||
final class DiviFlags
|
||||
{
|
||||
public const FLAGS = [
|
||||
'_et_pb_use_divi_5' => 'on',
|
||||
'_et_pb_use_builder' => 'on',
|
||||
'_et_pb_show_page_creation' => 'off',
|
||||
];
|
||||
|
||||
/** @throws RuntimeException When a flag does not read back as set. */
|
||||
public static function set(int $postId): void
|
||||
{
|
||||
foreach (self::FLAGS as $key => $value) {
|
||||
update_post_meta($postId, $key, $value);
|
||||
}
|
||||
|
||||
foreach (self::FLAGS as $key => $value) {
|
||||
if (get_post_meta($postId, $key, true) !== $value) {
|
||||
throw new RuntimeException(sprintf('Divi flag %s of post %d is not "%s".', $key, $postId, $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,6 @@ final class DiviPage
|
||||
/** The entry opened in the Divi Visual Builder – the edit_url of every Divi write (review I-1). */
|
||||
public static function builderUrl(WP_Post|int $post): string
|
||||
{
|
||||
return (string) add_query_arg('et_fb', '1', (string) get_permalink($post));
|
||||
return DiviPages::builderUrl($post);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Pages;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||
use RuntimeException;
|
||||
use WP_Post;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Adapter "Seiten" (spec §3): the Divi 5 builder flags of an entry as the Visual Builder's own save sets them
|
||||
* (mirror vb-sync-flags: _et_pb_use_divi_5 = "on", legacy _et_pb_use_builder = "on", _et_pb_show_page_creation =
|
||||
* "off" so the builder skips its page creation flow), whether an entry uses the builder, its builder URL, the
|
||||
* static CSS of one page and Divi's list of linkable post types. Ruling S11: update_post_meta() returns false for an
|
||||
* unchanged value too, so setFlags() checks by reading the flags back. Used by DiviSaver, the Divi content creation,
|
||||
* WpLinkTargets and PostPresenter.
|
||||
*/
|
||||
final class DiviPages implements DiviAdapter
|
||||
{
|
||||
public const FLAGS = [
|
||||
'_et_pb_use_divi_5' => 'on',
|
||||
'_et_pb_use_builder' => 'on',
|
||||
'_et_pb_show_page_creation' => 'off',
|
||||
];
|
||||
|
||||
public static function manifest(): Manifest
|
||||
{
|
||||
return new Manifest(
|
||||
Manifest::PAGES,
|
||||
calls: [
|
||||
new Call('ET_Core_PageResource::remove_static_resources', 3),
|
||||
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'],
|
||||
mirrors: [
|
||||
new Mirror(
|
||||
'vb-sync-flags',
|
||||
'includes/builder-5/server/VisualBuilder/REST/SyncToServer/SyncToServerController.php',
|
||||
'update',
|
||||
'SyncToServerController',
|
||||
'Builder-Kennzeichen einer Seite (_et_pb_use_divi_5, _et_pb_use_builder, _et_pb_show_page_creation)'
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @throws RuntimeException When a flag does not read back as set. */
|
||||
public static function setFlags(int $postId): void
|
||||
{
|
||||
foreach (self::FLAGS as $key => $value) {
|
||||
update_post_meta($postId, $key, $value);
|
||||
}
|
||||
|
||||
foreach (self::FLAGS as $key => $value) {
|
||||
if (get_post_meta($postId, $key, true) !== $value) {
|
||||
throw new RuntimeException(sprintf('Divi flag %s of post %d is not "%s".', $key, $postId, $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether Divi's builder flag marks $postId as a builder layout. */
|
||||
public static function usesBuilder(int $postId): bool
|
||||
{
|
||||
return get_post_meta($postId, '_et_pb_use_builder', true) === 'on';
|
||||
}
|
||||
|
||||
/** The entry opened in the Divi Visual Builder: its permalink with et_fb=1. */
|
||||
public static function builderUrl(WP_Post|int $post): string
|
||||
{
|
||||
return (string) add_query_arg('et_fb', '1', (string) get_permalink($post));
|
||||
}
|
||||
|
||||
/** Removes the static CSS files Divi generated for $postId (spec §5.6 step 5). */
|
||||
public static function clearPageCss(int $postId): void
|
||||
{
|
||||
if (class_exists('ET_Core_PageResource')) {
|
||||
\ET_Core_PageResource::remove_static_resources($postId, 'all', true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether Divi offers a post_link_url_<type> option for $postType. Fail closed: without Divi's list, none. */
|
||||
public static function isLinkable(string $postType): bool
|
||||
{
|
||||
if (! function_exists('et_builder_get_public_post_types')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$types = et_builder_get_public_post_types();
|
||||
|
||||
return is_array($types) && array_key_exists($postType, $types);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Save pipeline of spec §5.6, steps 3–5: restore point and wp_update_post() with wp_slash() (the
|
||||
* status stays), Divi 5 flags (DiviFlags), Divi's static CSS cache. A failure after saving is
|
||||
* status stays), Divi 5 flags and the page's static CSS (DiviPages). A failure after saving is
|
||||
* only a warning (FollowUp); a filter that changed the stored content is reported as well.
|
||||
*
|
||||
* kses (spec D7): for users without unfiltered_html WordPress' kses would run
|
||||
@@ -45,7 +45,7 @@ defined('ABSPATH') || exit;
|
||||
* for its single wp_update_post().
|
||||
*
|
||||
* Divi's own save filters are NOT kses and are never removed (ruling S7, defense in depth):
|
||||
* Security::initialize() (Divi 5.2.1 Security.php:74-81) registers on wp_insert_post_data
|
||||
* Security::initialize() (Divi's Security.php) registers on wp_insert_post_data
|
||||
* AttributeSecurity (priority 5, always), HtmlSecurity (priority 6, by capability) and
|
||||
* DynamicContentFixes (priority 10: strips enable_html for users without unfiltered_html). The first
|
||||
* two re-serialize the whole page when it holds custom attributes or htmlBefore/htmlAfter/elementType, so on such
|
||||
@@ -98,7 +98,7 @@ class DiviSaver
|
||||
$warnings = [];
|
||||
if ($scope->setsFlags()) {
|
||||
FollowUp::run(
|
||||
static fn () => DiviFlags::set($postId),
|
||||
static fn () => DiviPages::setFlags($postId),
|
||||
'divi builder flags',
|
||||
'Die Divi-Kennzeichen der Seite konnten nicht gesetzt werden; der Inhalt ist gespeichert – die Seite '
|
||||
. 'einmal im Divi-Builder öffnen und speichern.',
|
||||
@@ -107,9 +107,7 @@ class DiviSaver
|
||||
}
|
||||
FollowUp::run(
|
||||
static function () use ($postId, $scope): void {
|
||||
if (class_exists('ET_Core_PageResource')) {
|
||||
\ET_Core_PageResource::remove_static_resources($postId, 'all', true);
|
||||
}
|
||||
DiviPages::clearPageCss($postId);
|
||||
// A global library entry is part of every page that shows it (spec §5.6 step 5).
|
||||
if ($scope->clearsSiteCss()) {
|
||||
DiviThemeBuilder::clearSiteCaches();
|
||||
@@ -149,7 +147,7 @@ class DiviSaver
|
||||
}
|
||||
|
||||
if ($scope->setsFlags()) {
|
||||
DiviFlags::set($postId);
|
||||
DiviPages::setFlags($postId);
|
||||
}
|
||||
|
||||
$stored = get_post($postId);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace LIT\MCP\Integrations\Divi\Pages;
|
||||
|
||||
use Closure;
|
||||
use LIT\MCP\Integrations\Divi\Compose\LinkTargets;
|
||||
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||
use WP_Post;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
@@ -22,18 +23,19 @@ final class WpLinkTargets implements LinkTargets
|
||||
private const DEAD = ['trash', 'auto-draft', 'inherit'];
|
||||
|
||||
/** Never link targets, whatever Divi lists: attachments and the Divi Library (ruling Q14). */
|
||||
private const NEVER = ['attachment', 'et_pb_layout'];
|
||||
private const NEVER = ['attachment', DiviLibraryStore::POST_TYPE];
|
||||
|
||||
/** @var Closure(string): bool */
|
||||
private Closure $linkable;
|
||||
|
||||
/**
|
||||
* @param (Closure(string): bool)|null $linkable Whether Divi offers post_link_url_<type> (tests);
|
||||
* null = Divi's et_builder_get_public_post_types(), the list its link option uses.
|
||||
* null = DiviPages::isLinkable() (Divi's et_builder_get_public_post_types(), the list its
|
||||
* link option uses).
|
||||
*/
|
||||
public function __construct(?Closure $linkable = null)
|
||||
{
|
||||
$this->linkable = $linkable ?? self::diviLinkable(...);
|
||||
$this->linkable = $linkable ?? DiviPages::isLinkable(...);
|
||||
}
|
||||
|
||||
public function postTypeOf(int $id): ?string
|
||||
@@ -50,16 +52,4 @@ final class WpLinkTargets implements LinkTargets
|
||||
|
||||
return current_user_can('read_post', $id) ? $type : null;
|
||||
}
|
||||
|
||||
/** Fail closed: without Divi's list no type has a link option. */
|
||||
private static function diviLinkable(string $type): bool
|
||||
{
|
||||
if (! function_exists('et_builder_get_public_post_types')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$types = et_builder_get_public_post_types();
|
||||
|
||||
return is_array($types) && array_key_exists($type, $types);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Release;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||
|
||||
@@ -14,6 +15,7 @@ final class DiviAdapters
|
||||
{
|
||||
/** @var list<class-string<DiviAdapter>> */
|
||||
public const CLASSES = [
|
||||
DiviPages::class,
|
||||
DiviModules::class,
|
||||
DynamicContentMap::class,
|
||||
];
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace LIT\MCP\Integrations\WordPress\Content;
|
||||
|
||||
use LIT\MCP\Abilities\JsonMap;
|
||||
use LIT\MCP\Abilities\Rev;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\FieldDefinition;
|
||||
@@ -95,7 +96,7 @@ class PostPresenter
|
||||
|
||||
public static function usesDivi(WP_Post $post): bool
|
||||
{
|
||||
return get_post_meta((int) $post->ID, '_et_pb_use_builder', true) === 'on'
|
||||
return DiviPages::usesBuilder((int) $post->ID)
|
||||
|| str_contains((string) $post->post_content, '<!-- wp:divi/');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user