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-elementor-metabox.php

class-elementor-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-elementor-metabox.php

160 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Elementor editor integration for the ThinkRank SEO metabox.
4 *
5 * Elementor's editor is a standalone SPA: it does not render WordPress
6 * metaboxes and it does not submit the #post form, so the classic/block metabox
7 * (which relies on hidden inputs + save_post) never appears or saves there.
8 *
9 * This class mounts the SAME React metabox app inside the Elementor editor:
10 * - enqueues a dedicated `elementor` bundle and localizes the same
11 * `thinkrankMetabox` data (via Metabox_Manager::get_localized_data()),
12 * augmented with the existing metadata + content preview that the classic
13 * editor would otherwise expose through hidden inputs;
14 * - outputs a mount node in the editor footer;
15 * - the bundle adds a launcher to Elementor's panel footer that opens the
16 * existing drawer, and saves through the `thinkrank_save_metabox` AJAX route.
17 *
18 * @package ThinkRank
19 * @since 1.0.0
20 */
21
22 namespace ThinkRank\Admin;
23
24 if (!defined('ABSPATH')) {
25 exit;
26 }
27
28 /**
29 * Wires the React metabox into the Elementor editor.
30 */
31 class Elementor_Metabox {
32
33 /**
34 * Shared metabox manager (data builder + supported post types).
35 *
36 * @var Metabox_Manager
37 */
38 private Metabox_Manager $metabox;
39
40 /**
41 * Constructor.
42 *
43 * @param Metabox_Manager $metabox Shared metabox manager instance.
44 */
45 public function __construct(Metabox_Manager $metabox) {
46 $this->metabox = $metabox;
47 }
48
49 /**
50 * Register Elementor editor hooks.
51 *
52 * The `elementor/editor/*` actions only fire when Elementor is active, so
53 * registering them unconditionally is harmless when it isn't installed.
54 *
55 * @return void
56 */
57 public function init(): void {
58 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueue_editor_assets']);
59 add_action('elementor/editor/footer', [$this, 'render_root']);
60 }
61
62 /**
63 * Resolve the post currently open in the Elementor editor.
64 *
65 * @return int Post ID, or 0 if it cannot be determined.
66 */
67 private function get_post_id(): int {
68 if (class_exists('\\Elementor\\Plugin')) {
69 $documents = \Elementor\Plugin::$instance->documents ?? null;
70 if ($documents) {
71 $document = $documents->get_current();
72 if ($document) {
73 return (int) $document->get_main_id();
74 }
75 }
76 }
77
78 // Fallback: Elementor editor loads as ?post=ID&action=elementor.
79 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only context resolution, no state change
80 return isset($_GET['post']) ? absint($_GET['post']) : 0;
81 }
82
83 /**
84 * Enqueue the Elementor metabox bundle and localize its data.
85 *
86 * @return void
87 */
88 public function enqueue_editor_assets(): void {
89 $post_id = $this->get_post_id();
90 if (!$post_id) {
91 return;
92 }
93
94 $post = get_post($post_id);
95 if (!$post || !in_array($post->post_type, $this->metabox->get_supported_post_types(), true)) {
96 return;
97 }
98
99 // wp.media powers the social-image picker inside the drawer.
100 wp_enqueue_media();
101
102 $asset_file = THINKRANK_PLUGIN_DIR . 'assets/elementor.asset.php';
103 $asset = file_exists($asset_file) ? include $asset_file : [
104 'dependencies' => ['react', 'wp-element', 'wp-i18n', 'wp-api-fetch', 'wp-components'],
105 'version' => THINKRANK_VERSION,
106 ];
107
108 $dependencies = $asset['dependencies'];
109
110 // Elementor v2 app-bar package — exposes window.elementorV2.editorAppBar
111 // used to add the ThinkRank logo to the editor's top-right utilities
112 // menu. Depend on it (when present) so the global loads first; the JS
113 // gracefully falls back to a floating launcher when it isn't available.
114 if (wp_script_is('elementor-v2-editor-app-bar', 'registered')
115 || wp_script_is('elementor-v2-editor-app-bar', 'enqueued')) {
116 $dependencies[] = 'elementor-v2-editor-app-bar';
117 }
118
119 wp_enqueue_script(
120 'thinkrank-elementor',
121 THINKRANK_PLUGIN_URL . 'assets/elementor.js',
122 $dependencies,
123 $asset['version'],
124 true
125 );
126
127 // Reuse the exact metabox config, then add the data the classic editor
128 // would normally hand the React app through hidden inputs.
129 $data = $this->metabox->get_localized_data($post_id);
130 $data['context'] = 'elementor';
131 $data['existingMetadata'] = $this->metabox->get_post_metadata($post_id);
132 $data['contentPreview'] = $this->metabox->get_content_preview($post);
133 $data['postTitle'] = get_the_title($post_id);
134 // AJAX route used to persist all fields (no #post form in Elementor).
135 $data['saveAction'] = 'thinkrank_save_metabox';
136
137 wp_localize_script('thinkrank-elementor', 'thinkrankMetabox', $data);
138
139 // Depend on wp-components so the @wordpress/components controls (toggles,
140 // checkboxes, text/select inputs) inside the drawer keep their styling.
141 // Without it, Elementor's global `input { width: 100% }` reset wins and
142 // the checkboxes/fields render broken.
143 wp_enqueue_style(
144 'thinkrank-elementor',
145 THINKRANK_PLUGIN_URL . 'assets/elementor.css',
146 ['wp-components'],
147 THINKRANK_VERSION
148 );
149 }
150
151 /**
152 * Output the React mount node into the Elementor editor footer.
153 *
154 * @return void
155 */
156 public function render_root(): void {
157 echo '<div id="thinkrank-elementor-root" class="thinkrank-metabox"></div>';
158 }
159 }
160