PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.32.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.32.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / admin / class-divi-metabox.php

class-divi-metabox.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.32.0, at includes/admin/class-divi-metabox.php

199 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Divi Visual Builder integration for the ThinkRank SEO metabox.
4 *
5 * Divi's Visual Builder is a front-end editing overlay: it loads the page on
6 * its own permalink with `?et_fb=1` and mounts a React app over the themed
7 * page. It does NOT render WordPress metaboxes and does not submit the #post
8 * form, so the classic/block metabox never appears or saves there.
9 *
10 * Unlike the Oxygen/Breakdance builder (a standalone template with no
11 * `wp_head()`/`wp_footer()`), the Divi VB is a normal themed front-end request,
12 * so the ordinary enqueue flow works. This class mounts the SAME React metabox
13 * app used elsewhere:
14 * - enqueues a dedicated `divi` bundle and localizes the same `thinkrankMetabox`
15 * data (via Metabox_Manager::get_localized_data()), augmented with the
16 * existing metadata + content preview the classic editor exposes via hidden
17 * inputs;
18 * - outputs a mount node in the footer;
19 * - the bundle injects a ThinkRank logo into the VB page bar tools
20 * (`.et-vb-page-bar-tools`) that opens the drawer, and saves through the
21 * `thinkrank_save_metabox` AJAX route (Divi keeps its own content in
22 * post_content via its own Save button — we never touch it).
23 *
24 * Scope: Divi 5 Visual Builder (the installed architecture). Detection is by
25 * Divi's own `et_core_is_fb_enabled()` helper, so it no-ops when Divi isn't the
26 * active theme/builder.
27 *
28 * @package ThinkRank
29 * @since 1.24.0
30 */
31
32 namespace ThinkRank\Admin;
33
34 if (!defined('ABSPATH')) {
35 exit;
36 }
37
38 /**
39 * Wires the React metabox into the Divi Visual Builder.
40 */
41 class Divi_Metabox {
42
43 /**
44 * Shared metabox manager (data builder + supported post types).
45 *
46 * @var Metabox_Manager
47 */
48 private Metabox_Manager $metabox;
49
50 /**
51 * Constructor.
52 *
53 * @param Metabox_Manager $metabox Shared metabox manager instance.
54 */
55 public function __construct(Metabox_Manager $metabox) {
56 $this->metabox = $metabox;
57 }
58
59 /**
60 * Register the Divi Visual Builder hooks.
61 *
62 * The VB is a themed front-end request, so we hook the front-end enqueue +
63 * footer actions and gate every callback on `is_visual_builder()`.
64 * Registering unconditionally is harmless when Divi isn't active: the
65 * detector simply returns false.
66 *
67 * @return void
68 */
69 public function init(): void {
70 add_action('wp_enqueue_scripts', [$this, 'enqueue_editor_assets']);
71 add_action('wp_footer', [$this, 'render_root']);
72 }
73
74 /**
75 * Whether the current request is the Divi Visual Builder.
76 *
77 * @return bool
78 */
79 private function is_visual_builder(): bool {
80 // Divi's own detector is authoritative when present.
81 if (function_exists('et_core_is_fb_enabled') && et_core_is_fb_enabled()) {
82 return true;
83 }
84 if (function_exists('et_fb_is_enabled') && et_fb_is_enabled()) {
85 return true;
86 }
87
88 // Fallback: the VB always loads as ?et_fb=1.
89 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request classification, no state change
90 return !empty($_GET['et_fb']);
91 }
92
93 /**
94 * Resolve the post currently open in the Visual Builder.
95 *
96 * The VB loads on the post's own permalink, so the queried object is the
97 * post being edited.
98 *
99 * @return int Post ID, or 0 if it cannot be determined.
100 */
101 private function get_post_id(): int {
102 $queried = get_queried_object_id();
103 if ($queried) {
104 return (int) $queried;
105 }
106
107 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only context resolution, no state change
108 foreach (['post', 'post_id', 'page_id', 'et_post_id'] as $key) {
109 if (isset($_GET[$key])) {
110 $candidate = absint(wp_unslash($_GET[$key]));
111 if ($candidate) {
112 return $candidate;
113 }
114 }
115 }
116 // phpcs:enable WordPress.Security.NonceVerification.Recommended
117
118 return 0;
119 }
120
121 /**
122 * Enqueue the Divi metabox bundle and localize its data.
123 *
124 * @return void
125 */
126 public function enqueue_editor_assets(): void {
127 if (!$this->is_visual_builder()) {
128 return;
129 }
130
131 $post_id = $this->get_post_id();
132 if (!$post_id) {
133 return;
134 }
135
136 // Editing SEO from the builder must respect the same capability the
137 // classic metabox save enforces.
138 if (!current_user_can('edit_post', $post_id)) {
139 return;
140 }
141
142 $post = get_post($post_id);
143 if (!$post || !in_array($post->post_type, $this->metabox->get_supported_post_types(), true)) {
144 return;
145 }
146
147 // wp.media powers the social-image picker inside the drawer.
148 wp_enqueue_media();
149
150 $asset_file = THINKRANK_PLUGIN_DIR . 'assets/divi.asset.php';
151 $asset = file_exists($asset_file) ? include $asset_file : [
152 'dependencies' => ['react', 'react-dom', 'wp-element', 'wp-i18n', 'wp-api-fetch', 'wp-components'],
153 'version' => THINKRANK_VERSION,
154 ];
155
156 wp_enqueue_script(
157 'thinkrank-divi',
158 THINKRANK_PLUGIN_URL . 'assets/divi.js',
159 $asset['dependencies'],
160 $asset['version'],
161 true
162 );
163
164 // Reuse the exact metabox config, then add the data the classic editor
165 // would normally hand the React app through hidden inputs.
166 $data = $this->metabox->get_localized_data($post_id);
167 $data['context'] = 'divi';
168 $data['existingMetadata'] = $this->metabox->get_post_metadata($post_id);
169 $data['contentPreview'] = $this->metabox->get_content_preview($post);
170 $data['postTitle'] = get_the_title($post_id);
171 // AJAX route used to persist all fields (no #post form in the builder).
172 $data['saveAction'] = 'thinkrank_save_metabox';
173
174 wp_localize_script('thinkrank-divi', 'thinkrankMetabox', $data);
175
176 // Depend on wp-components so the @wordpress/components controls inside
177 // the drawer keep their styling against the builder's global resets.
178 wp_enqueue_style(
179 'thinkrank-divi',
180 THINKRANK_PLUGIN_URL . 'assets/divi.css',
181 ['wp-components'],
182 THINKRANK_VERSION
183 );
184 }
185
186 /**
187 * Output the React mount node into the Visual Builder footer.
188 *
189 * @return void
190 */
191 public function render_root(): void {
192 if (!$this->is_visual_builder()) {
193 return;
194 }
195
196 echo '<div id="thinkrank-divi-root" class="thinkrank-metabox"></div>';
197 }
198 }
199