PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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-bricks-metabox.php

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

224 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bricks Builder integration for the ThinkRank SEO metabox.
4 *
5 * Bricks is a theme-based builder whose editor is a front-end overlay: it loads
6 * the post on its own permalink with `?bricks=run`, renders the canvas in a
7 * child iframe and paints its own chrome (toolbar, panels) in the top window.
8 * It does NOT render WordPress metaboxes and does not submit the #post form, so
9 * the classic/block metabox never appears or saves there.
10 *
11 * That makes this the Divi shape, not the Oxygen one. Bricks runs the ordinary
12 * `wp_enqueue_scripts` / `wp_footer` flow inside the builder — which is how
13 * both AIOSEO and SureRank mount their own panels — so no hand-printed builder
14 * head/footer is needed and the SAME React metabox app used everywhere else is
15 * reused:
16 * - enqueues a dedicated `bricks` bundle and localizes the same
17 * `thinkrankMetabox` data (via Metabox_Manager::get_localized_data()),
18 * augmented with the metadata + content preview the classic editor exposes
19 * through hidden inputs;
20 * - outputs a mount node in the footer;
21 * - the bundle injects a ThinkRank logo into the Bricks toolbar that opens the
22 * drawer, and saves through the `thinkrank_save_metabox` AJAX route (Bricks
23 * keeps its own layout in postmeta via its own Save button — we never touch
24 * it).
25 *
26 * Detection is Bricks' own `bricks_is_builder_main()`, the same public helper
27 * the competing integrations gate on, so every callback no-ops when Bricks
28 * isn't the active theme. `_main` matters: Bricks loads the canvas in a second
29 * request (`bricks_is_builder_iframe()`), and mounting there would give the
30 * page two drawers.
31 *
32 * @package ThinkRank
33 * @since 2.2.1
34 */
35
36 declare(strict_types=1);
37
38 namespace ThinkRank\Admin;
39
40 if (!defined('ABSPATH')) {
41 exit;
42 }
43
44 /**
45 * Wires the React metabox into the Bricks builder.
46 */
47 class Bricks_Metabox {
48
49 /**
50 * Shared metabox manager (data builder + supported post types).
51 *
52 * @var Metabox_Manager
53 */
54 private Metabox_Manager $metabox;
55
56 /**
57 * Constructor.
58 *
59 * @param Metabox_Manager $metabox Shared metabox manager instance.
60 */
61 public function __construct(Metabox_Manager $metabox) {
62 $this->metabox = $metabox;
63 }
64
65 /**
66 * Register the Bricks builder hooks.
67 *
68 * Both callbacks gate on `is_builder()`, so registering unconditionally is
69 * harmless when Bricks isn't the active theme: the detector returns false.
70 *
71 * The enqueue runs at 9999 because Bricks dequeues front-end assets it
72 * doesn't recognise while setting up the builder; registering after that
73 * pass is what keeps the bundle on the page.
74 *
75 * @return void
76 */
77 public function init(): void {
78 add_action('wp_enqueue_scripts', [$this, 'enqueue_editor_assets'], 9999);
79 add_action('wp_footer', [$this, 'render_root']);
80 }
81
82 /**
83 * Whether the current request is the main Bricks builder window.
84 *
85 * @return bool
86 */
87 private function is_builder(): bool {
88 // Bricks' own helper is authoritative and is the documented way to
89 // detect the builder; `_main` excludes the canvas iframe, which is a
90 // separate request that would otherwise mount a second drawer.
91 if (function_exists('bricks_is_builder_main')) {
92 return (bool) \bricks_is_builder_main();
93 }
94
95 return false;
96 }
97
98 /**
99 * Resolve the post currently open in the builder.
100 *
101 * Bricks loads on the post's own permalink, so the queried object is the
102 * post being edited.
103 *
104 * @return int Post ID, or 0 if it cannot be determined.
105 */
106 private function get_post_id(): int {
107 $queried = get_queried_object_id();
108 if ($queried) {
109 return (int) $queried;
110 }
111
112 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only context resolution, no state change
113 foreach (['post_id', 'postId', 'p', 'page_id'] as $key) {
114 if (isset($_GET[$key])) {
115 $candidate = absint(wp_unslash($_GET[$key]));
116 if ($candidate) {
117 return $candidate;
118 }
119 }
120 }
121 // phpcs:enable WordPress.Security.NonceVerification.Recommended
122
123 return 0;
124 }
125
126 /**
127 * The post this request may mount the drawer for, if any.
128 *
129 * Shared by the enqueue and the mount node so the two cannot disagree:
130 * printing the root div for a post the bundle then declines to load for
131 * would leave an orphan element in Bricks' footer.
132 *
133 * @return int Post ID, or 0 when the drawer must not mount.
134 */
135 private function eligible_post_id(): int {
136 if (!$this->is_builder()) {
137 return 0;
138 }
139
140 $post_id = $this->get_post_id();
141 if (!$post_id) {
142 return 0;
143 }
144
145 // Editing SEO from the builder must respect the same capability the
146 // classic metabox save enforces.
147 if (!current_user_can('edit_post', $post_id)) {
148 return 0;
149 }
150
151 $post = get_post($post_id);
152 if (!$post || !in_array($post->post_type, $this->metabox->get_supported_post_types(), true)) {
153 return 0;
154 }
155
156 return $post_id;
157 }
158
159 /**
160 * Enqueue the Bricks metabox bundle and localize its data.
161 *
162 * @return void
163 */
164 public function enqueue_editor_assets(): void {
165 $post_id = $this->eligible_post_id();
166 if (!$post_id) {
167 return;
168 }
169
170 $post = get_post($post_id);
171
172 // wp.media powers the social-image picker inside the drawer.
173 wp_enqueue_media();
174
175 $asset_file = THINKRANK_PLUGIN_DIR . 'assets/bricks.asset.php';
176 $asset = file_exists($asset_file) ? include $asset_file : [
177 'dependencies' => ['react', 'react-dom', 'wp-element', 'wp-i18n', 'wp-api-fetch', 'wp-components'],
178 'version' => THINKRANK_VERSION,
179 ];
180
181 wp_enqueue_script(
182 'thinkrank-bricks',
183 THINKRANK_PLUGIN_URL . 'assets/bricks.js',
184 $asset['dependencies'],
185 $asset['version'],
186 true
187 );
188
189 // Reuse the exact metabox config, then add the data the classic editor
190 // would normally hand the React app through hidden inputs.
191 $data = $this->metabox->get_localized_data($post_id);
192 $data['context'] = 'bricks';
193 $data['existingMetadata'] = $this->metabox->get_post_metadata($post_id);
194 $data['contentPreview'] = $this->metabox->get_content_preview($post);
195 $data['postTitle'] = get_the_title($post_id);
196 // AJAX route used to persist all fields (no #post form in the builder).
197 $data['saveAction'] = 'thinkrank_save_metabox';
198
199 wp_localize_script('thinkrank-bricks', 'thinkrankMetabox', $data);
200
201 // Depend on wp-components so the @wordpress/components controls inside
202 // the drawer keep their styling against the builder's global resets.
203 wp_enqueue_style(
204 'thinkrank-bricks',
205 THINKRANK_PLUGIN_URL . 'assets/bricks.css',
206 ['wp-components'],
207 THINKRANK_VERSION
208 );
209 }
210
211 /**
212 * Output the React mount node into the builder footer.
213 *
214 * @return void
215 */
216 public function render_root(): void {
217 if (!$this->eligible_post_id()) {
218 return;
219 }
220
221 echo '<div id="thinkrank-bricks-root" class="thinkrank-metabox"></div>';
222 }
223 }
224