PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.12
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.12
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / admin / class-metasync-post-meta-setting.php

class-metasync-post-meta-setting.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.12, at admin/class-metasync-post-meta-setting.php

544 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The header and footer code snippets functionality of the plugin.
5 *
6 *
7 * @link https://searchatlas.com
8 * @since 1.0.0
9 * @package Metasync
10 * @subpackage Metasync/admin
11 * @author Engineering Team <support@searchatlas.com>
12 */
13
14 // Abort if this file is accessed directly.
15 if (!defined('ABSPATH')) {
16 exit;
17 }
18
19 class Metasync_Post_Meta_Settings
20 {
21 private $common;
22
23 public function __construct()
24 {
25 $this->common = new Metasync_Common();
26
27 add_action('admin_init', [$this, 'add_post_meta_data'], 2);
28 add_action('save_post', [$this, 'common_robots_meta_box_save']);
29 add_action('save_post', [$this, 'advance_robots_meta_box_save']);
30 add_action('save_post', [$this, 'redirection_meta_box_save']);
31 add_action('save_post', [$this, 'canonical_meta_box_save']);
32 add_action('save_post', [$this, 'video_sitemap_meta_box_save']);
33 add_action('save_post', [$this, 'seo_meta_box_save']);
34 }
35
36 public function add_post_meta_data()
37 {
38 // Don't show meta boxes if user's role doesn't have plugin access
39 if (!Metasync::current_user_has_plugin_access()) {
40 return;
41 }
42
43 $plugin_name = Metasync::get_effective_plugin_name();
44 $general_settings = Metasync::get_option('general', []);
45 $post_types = array_values(get_post_types(['public' => true], 'names'));
46 $post_types = array_diff($post_types, ['attachment']);
47
48 // Only add meta boxes if not disabled in settings
49 if (empty($general_settings['disable_common_robots_metabox'])) {
50 add_meta_box('common-robots-meta', "Common Robots Meta by $plugin_name", [$this, 'common_robots_meta_box_display'], $post_types, 'normal', 'default');
51 }
52
53 if (empty($general_settings['disable_advance_robots_metabox'])) {
54 add_meta_box('advance-robots-meta', "Advance Robots Meta by $plugin_name", [$this, 'advance_robots_meta_box_display'], $post_types, 'normal', 'default');
55 }
56
57 if (empty($general_settings['disable_redirection_metabox'])) {
58 add_meta_box('post-redirection-meta', "Redirection by $plugin_name", [$this, 'post_redirection_display'], $post_types, 'normal', 'default');
59 }
60
61 if (empty($general_settings['disable_canonical_metabox'])) {
62 add_meta_box('post-canonical-meta', "Canonical by $plugin_name", [$this, 'post_canonical_display'], $post_types, 'normal', 'default');
63 }
64
65 if (empty($general_settings['disable_seo_metabox'])) {
66 add_meta_box('metasync-seo-meta', "SEO by $plugin_name", [$this, 'seo_meta_box_display'], $post_types, 'normal', 'default', ['__back_compat_meta_box' => true]);
67 }
68
69 // Video Sitemap meta box — only if video sitemap is enabled
70 $video_settings = get_option('metasync_video_sitemap_settings', []);
71 if (!empty($video_settings['enabled'])) {
72 $video_post_types = !empty($video_settings['post_types']) ? (array) $video_settings['post_types'] : ['post', 'page'];
73 add_meta_box(
74 'metasync-video-sitemap-meta',
75 "Video Sitemap by $plugin_name",
76 [$this, 'video_sitemap_meta_box_display'],
77 $video_post_types,
78 'normal',
79 'default'
80 );
81 }
82 }
83
84 public function common_robots_meta_box_display()
85 {
86 global $post;
87 $post_meta_robots = get_post_meta($post->ID, 'metasync_common_robots', true);
88 // Check new spelling first, fall back to old for backward compatibility
89 $common_meta_robots = Metasync::get_option('common_robots_meta') ?? Metasync::get_option('common_robots_mata') ?? '';
90 $common_robots = $post_meta_robots ? $post_meta_robots : $common_meta_robots;
91 wp_nonce_field('metasync_common_robots_nonce', 'metasync_common_robots_nonce');
92 ?>
93 <ul class="checkbox-list">
94 <li>
95 <input type="checkbox" name="common_robots_meta[index]" id="robots_common1" value="index" <?php isset($common_robots['index']) ? checked('index', $common_robots['index']) : '' ?>>
96 <label for="robots_common1">Index </br>
97 <span class="description">
98 <span>Search engines to index and show these pages in the search results.</span>
99 </span>
100 </label>
101 </li>
102 <li>
103 <input type="checkbox" name="common_robots_meta[noindex]" id="robots_common2" value="noindex" <?php isset($common_robots['noindex']) ? checked('noindex', $common_robots['noindex']) : '' ?>>
104 <label for="robots_common2">No Index </br>
105 <span class="description">
106 <span>Prevents search engines from indexing and displaying these pages in search results.</span>
107 </span>
108 </label>
109 </li>
110 <li>
111 <input type="checkbox" name="common_robots_meta[nofollow]" id="robots_common3" value="nofollow" <?php isset($common_robots['nofollow']) ? checked('nofollow', $common_robots['nofollow']) : '' ?>>
112 <label for="robots_common3">No Follow </br>
113 <span class="description">
114 <span>Prevents search engines from following the links on the pages.</span>
115 </span>
116 </label>
117 </li>
118 <li>
119 <input type="checkbox" name="common_robots_meta[noarchive]" id="robots_common4" value="noarchive" <?php isset($common_robots['noarchive']) ? checked('noarchive', $common_robots['noarchive']) : '' ?>>
120 <label for="robots_common4">No Archive </br>
121 <span class="description">
122 <span>Prevents search engines from showing cached links for pages.</span>
123 </span>
124 </label>
125 </li>
126 <li>
127 <input type="checkbox" name="common_robots_meta[noimageindex]" id="robots_common5" value="noimageindex" <?php isset($common_robots['noimageindex']) ? checked('noimageindex', $common_robots['noimageindex']) : '' ?>>
128 <label for="robots_common5">No Image Index </br>
129 <span class="description">
130 <span>Prevents your pages from appearing as the referring page for images in image search results.</span>
131 </span>
132 </label>
133 </li>
134 <li>
135 <input type="checkbox" name="common_robots_meta[nosnippet]" id="robots_common6" value="nosnippet" <?php isset($common_robots['nosnippet']) ? checked('nosnippet', $common_robots['nosnippet']) : '' ?>>
136 <label for="robots_common6">No Snippet </br>
137 <span class="description">
138 <span>Prevents search engines from showing a snippet in the search results.</span>
139 </span>
140 </label>
141 </li>
142 </ul>
143
144 <?php
145 }
146
147 public function advance_robots_meta_box_display()
148 {
149 global $post;
150 $post_meta_robots = get_post_meta($post->ID, 'metasync_advance_robots', true);
151 // Check new spelling first, fall back to old for backward compatibility
152 $common_meta_robots = Metasync::get_option('advance_robots_meta') ?? Metasync::get_option('advance_robots_mata') ?? '';
153 $advance_robots = $post_meta_robots ? $post_meta_robots : $common_meta_robots;
154 $snippet_advance_robots_enable = $advance_robots['max-snippet']['enable'] ?? '';
155 $snippet_advance_robots_length = $advance_robots['max-snippet']['length'] ?? '';
156 $video_advance_robots_enable = $advance_robots['max-video-preview']['enable'] ?? '';
157 $video_advance_robots_length = $advance_robots['max-video-preview']['length'] ?? '';
158 $image_advance_robots_enable = $advance_robots['max-image-preview']['enable'] ?? '';
159 $image_advance_robots_length = $advance_robots['max-image-preview']['length'] ?? '';
160 wp_nonce_field('metasync_advance_robots_nonce', 'metasync_advance_robots_nonce');
161 ?>
162 <ul class="checkbox-list">
163 <li>
164 <label for="advanced_robots_snippet">
165 <input type="checkbox" name="advanced_robots_meta[max-snippet][enable]" id="advanced_robots_snippet" value="1" <?php checked('1', esc_attr($snippet_advance_robots_enable)) ?>>
166 Snippet </br>
167 <input type="number" class="input-length" name="advanced_robots_meta[max-snippet][length]" id="advanced_robots_snippet_value" value="<?php echo esc_attr($snippet_advance_robots_length); ?>" min="-1"> </br>
168 <span class="description">
169 <span>Add maximum text-length, in characters, of a snippet for your page.</span>
170 </span>
171 </label>
172 </li>
173 <li>
174 <label for="advanced_robots_video">
175 <input type="checkbox" name="advanced_robots_meta[max-video-preview][enable]" id="advanced_robots_video" value="1" <?php checked('1', esc_attr($video_advance_robots_enable)) ?>>
176 Video Preview </br>
177 <input type="number" class="input-length" name="advanced_robots_meta[max-video-preview][length]" id="advanced_robots_video_value" value="<?php echo esc_attr($video_advance_robots_length); ?>" min="-1"> </br>
178 <span class="description">
179 <span>Add maximum duration in seconds of an animated video preview.</span>
180 </span>
181 </label>
182 </li>
183 <li>
184 <label for="advanced_robots_image">
185 <input type="checkbox" name="advanced_robots_meta[max-image-preview][enable]" id="advanced_robots_image" value="1" <?php checked('1', esc_attr($image_advance_robots_enable)) ?>>
186 Image Preview </br>
187 <select class="input-length" name="advanced_robots_meta[max-image-preview][length]' ?>" id="advanced_robots_image_value">
188 <option value="large" <?php selected(esc_attr($image_advance_robots_length), 'large'); ?>>Large</option>
189 <option value="standard" <?php selected(esc_attr($image_advance_robots_length), 'standard'); ?>>Standard</option>
190 <option value="none" <?php selected(esc_attr($image_advance_robots_length), 'none'); ?>>None</option>
191 </select>
192 </br>
193 <span class="description">
194 <span>Add maximum size of image preview to show the images on this page.</span>
195 </span>
196 </label>
197 </li>
198 </ul>
199 <?php
200 }
201
202 public function post_redirection_display()
203 {
204 global $post;
205 $post_redirection = get_post_meta($post->ID, 'metasync_post_redirection_meta', true) ?? '';
206 $enable = $post_redirection['enable'] ?? '';
207 $type = $post_redirection['type'] ?? '';
208 $url = $post_redirection['url'] ?? '';
209 wp_nonce_field('metasync_post_redirection_nonce', 'metasync_post_redirection_nonce');
210 ?>
211 <ul class="checkbox-list">
212 <li>
213 <input type="checkbox" name="post_redirect_meta[enable]" id="post_redirection" value="true" <?php checked('true', esc_attr($enable)); ?>>
214 <label for="post_redirection">Redirection</label>
215 </li>
216 <li class="hide"> Redirection Type:
217 <select class="regular-text" name="post_redirect_meta[type]" id="post_redirection_type">
218 <option value="301" <?php selected(esc_attr($type), '301'); ?>>301 Permanent Move</option>
219 <option value="302" <?php selected(esc_attr($type), '302'); ?>>302 Temporary Move</option>
220 <option value="307" <?php selected(esc_attr($type), '307'); ?>>307 Temporary Redirect</option>
221 <option value="410" <?php selected(esc_attr($type), '410'); ?>>410 Content Deleted</option>
222 <option value="451" <?php selected(esc_attr($type), '451'); ?>>451 Content Unavailable</option>
223 </select>
224 </li>
225 <li class="hide" id="post_redirect_url"> Destination URL:
226 <input type="text" class="regular-text" name="post_redirect_meta[url]" id="post_redirect_url_val" value="<?php echo esc_attr($url); ?>">
227 </li>
228 </ul>
229 <?php
230 }
231
232 public function post_canonical_display()
233 {
234 global $post;
235
236 $post_canonical = get_post_meta($post->ID, 'meta_canonical', true) ?? '';
237 // Fix legacy array values stored by sanitize_array()
238 if (is_array($post_canonical)) {
239 $post_canonical = reset($post_canonical) ?: '';
240 // Repair the stored value so it won't recur
241 if (!empty($post_canonical)) {
242 update_post_meta($post->ID, 'meta_canonical', (string) $post_canonical);
243 }
244 }
245 wp_nonce_field('metasync_post_canonical_nonce', 'metasync_post_canonical_nonce');
246 ?>
247 <ul>
248 <li> Canonical URL:
249 <input type="text" class="regular-text" name="post_canonical_url_meta" placeholder="<?php echo get_permalink($post->ID) ?>" value="<?php echo esc_attr($post_canonical); ?>">
250 </li>
251 </ul>
252 <?php
253 }
254
255 public function common_robots_meta_box_save($post_id)
256 {
257 if (!current_user_can('edit_post', $post_id))
258 return;
259
260 // WP-197: When saving from Gutenberg (REST API context) and the sidebar
261 // JSON exists, skip — the sidebar auto-save is the source of truth.
262 // Classic editor form submits (non-REST) always proceed so classic-only
263 // users can still save via the meta boxes.
264 if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) {
265 return;
266 }
267
268 $post_data = metasync_sanitize_input_array($_POST);
269 // Check for new field name first, then old for backward compatibility
270 $field_name = isset($post_data['common_robots_meta']) ? 'common_robots_meta' : 'common_robots_mata';
271
272 if (!isset($post_data['metasync_common_robots_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_common_robots_nonce'], 'metasync_common_robots_nonce'))
273 return;
274
275 $old_common_robots = get_post_meta($post_id, 'metasync_common_robots', true);
276
277 $common_robots = [];
278 if (!empty($post_data[$field_name])) {
279 $common_robots = $this->common->sanitize_array($post_data[$field_name]);
280 }
281
282 if (!empty($common_robots))
283 update_post_meta($post_id, 'metasync_common_robots', $common_robots);
284 elseif (empty($common_robots) && $old_common_robots)
285 delete_post_meta($post_id, 'metasync_common_robots', $old_common_robots);
286 }
287
288 public function advance_robots_meta_box_save($post_id)
289 {
290 if (!current_user_can('edit_post', $post_id))
291 return;
292
293 // WP-197: When saving from Gutenberg (REST API context) and the sidebar
294 // JSON exists, skip — the sidebar auto-save is the source of truth.
295 if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) {
296 return;
297 }
298
299 $post_data = metasync_sanitize_input_array($_POST);
300 // Check for new field name first, then old for backward compatibility
301 $field_name = isset($post_data['advanced_robots_meta']) ? 'advanced_robots_meta' : 'advanced_robots_mata';
302
303 if (!isset($post_data['metasync_advance_robots_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_advance_robots_nonce'], 'metasync_advance_robots_nonce'))
304 return;
305
306 $old_advance_robots = get_post_meta($post_id, 'metasync_advance_robots', true);
307
308 $advance_robots = $this->common->sanitize_array($post_data[$field_name]);
309
310 if (!empty($advance_robots))
311 update_post_meta($post_id, 'metasync_advance_robots', $advance_robots);
312 elseif (empty($advance_robots) && $old_advance_robots)
313 delete_post_meta($post_id, 'metasync_advance_robots', $old_advance_robots);
314 }
315
316 public function redirection_meta_box_save($post_id)
317 {
318 if (!current_user_can('edit_post', $post_id))
319 return;
320
321 $post_data = metasync_sanitize_input_array($_POST);
322 // Check for new field name first, then old for backward compatibility
323 $field_name = isset($post_data['post_redirect_meta']) ? 'post_redirect_meta' : 'post_redirect_mata';
324
325 if (!isset($post_data['metasync_post_redirection_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_post_redirection_nonce'], 'metasync_post_redirection_nonce'))
326 return;
327
328 $old_post_redirection_meta = get_post_meta($post_id, 'metasync_post_redirection_meta', true);
329
330 $post_redirection_meta = $this->common->sanitize_array($post_data[$field_name]);
331
332 if (isset($post_redirection_meta['enable']))
333 update_post_meta($post_id, 'metasync_post_redirection_meta', $post_redirection_meta);
334 else
335 delete_post_meta($post_id, 'metasync_post_redirection_meta', $old_post_redirection_meta);
336 }
337
338 public function canonical_meta_box_save($post_id)
339 {
340 if (!current_user_can('edit_post', $post_id))
341 return;
342
343 $post_data = metasync_sanitize_input_array($_POST);
344 // Check for new field name first, then old for backward compatibility
345 $field_name = isset($post_data['post_canonical_url_meta']) ? 'post_canonical_url_meta' : 'post_canonical_url_mata';
346
347 if (!isset($post_data['metasync_post_canonical_nonce'], $post_data[$field_name]) || !wp_verify_nonce($post_data['metasync_post_canonical_nonce'], 'metasync_post_canonical_nonce'))
348 return;
349
350 $old_post_canonical_meta = get_post_meta($post_id, 'meta_canonical', true);
351
352 // Canonical is a URL string — sanitize as URL, not array
353 $raw_value = $post_data[$field_name];
354 if (is_array($raw_value)) {
355 $raw_value = reset($raw_value); // extract first element if array
356 }
357 $post_canonical_meta = esc_url_raw(trim((string) $raw_value));
358
359 if (!empty($post_canonical_meta))
360 update_post_meta($post_id, 'meta_canonical', $post_canonical_meta);
361 else
362 delete_post_meta($post_id, 'meta_canonical', $old_post_canonical_meta);
363 }
364
365 public function show_top_admin_bar() {
366 if ( Metasync::current_user_has_plugin_access() ) {
367 show_admin_bar( true );
368 }
369 }
370
371 /**
372 * Display the Video Sitemap meta box in post editor.
373 */
374 public function video_sitemap_meta_box_display()
375 {
376 global $post;
377 $video_url = get_post_meta($post->ID, '_metasync_video_url', true);
378 $video_thumbnail = get_post_meta($post->ID, '_metasync_video_thumbnail', true);
379 $video_title = get_post_meta($post->ID, '_metasync_video_title', true);
380 $video_desc = get_post_meta($post->ID, '_metasync_video_description', true);
381 $video_duration = get_post_meta($post->ID, '_metasync_video_duration', true);
382
383 wp_nonce_field('metasync_video_sitemap_meta_nonce', 'metasync_video_sitemap_meta_nonce');
384 ?>
385 <p style="color: #666; margin-bottom: 12px;">
386 <?php esc_html_e('Override auto-detected video data for this post. Leave fields empty to use auto-detection.', 'metasync'); ?>
387 </p>
388 <table class="form-table" style="margin: 0;">
389 <tr>
390 <th scope="row"><label for="metasync_video_url"><?php esc_html_e('Video URL', 'metasync'); ?></label></th>
391 <td><input type="url" id="metasync_video_url" name="metasync_video_url" value="<?php echo esc_attr($video_url); ?>" class="large-text" placeholder="https://www.youtube.com/watch?v=..." /></td>
392 </tr>
393 <tr>
394 <th scope="row"><label for="metasync_video_thumbnail"><?php esc_html_e('Thumbnail URL', 'metasync'); ?></label></th>
395 <td><input type="url" id="metasync_video_thumbnail" name="metasync_video_thumbnail" value="<?php echo esc_attr($video_thumbnail); ?>" class="large-text" placeholder="https://img.youtube.com/vi/.../hqdefault.jpg" /></td>
396 </tr>
397 <tr>
398 <th scope="row"><label for="metasync_video_title"><?php esc_html_e('Video Title', 'metasync'); ?></label></th>
399 <td><input type="text" id="metasync_video_title" name="metasync_video_title" value="<?php echo esc_attr($video_title); ?>" class="large-text" placeholder="<?php esc_attr_e('Defaults to post title', 'metasync'); ?>" /></td>
400 </tr>
401 <tr>
402 <th scope="row"><label for="metasync_video_description"><?php esc_html_e('Video Description', 'metasync'); ?></label></th>
403 <td><textarea id="metasync_video_description" name="metasync_video_description" class="large-text" rows="3" placeholder="<?php esc_attr_e('Defaults to post excerpt', 'metasync'); ?>"><?php echo esc_textarea($video_desc); ?></textarea></td>
404 </tr>
405 <tr>
406 <th scope="row"><label for="metasync_video_duration"><?php esc_html_e('Duration (seconds)', 'metasync'); ?></label></th>
407 <td><input type="number" id="metasync_video_duration" name="metasync_video_duration" value="<?php echo esc_attr($video_duration); ?>" class="small-text" min="0" step="1" placeholder="300" /></td>
408 </tr>
409 </table>
410 <?php
411 }
412
413 /**
414 * Save the Video Sitemap meta box data.
415 *
416 * @param int $post_id The post ID.
417 */
418 public function video_sitemap_meta_box_save($post_id)
419 {
420 if (!isset($_POST['metasync_video_sitemap_meta_nonce']) ||
421 !wp_verify_nonce($_POST['metasync_video_sitemap_meta_nonce'], 'metasync_video_sitemap_meta_nonce')) {
422 return;
423 }
424
425 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
426 return;
427 }
428
429 if (!current_user_can('edit_post', $post_id)) {
430 return;
431 }
432
433 $fields = [
434 'metasync_video_url' => '_metasync_video_url',
435 'metasync_video_thumbnail' => '_metasync_video_thumbnail',
436 'metasync_video_title' => '_metasync_video_title',
437 'metasync_video_description' => '_metasync_video_description',
438 'metasync_video_duration' => '_metasync_video_duration',
439 ];
440
441 foreach ($fields as $form_key => $meta_key) {
442 if (!isset($_POST[$form_key])) {
443 continue;
444 }
445
446 $value = $_POST[$form_key];
447
448 // Sanitize per field type
449 if (in_array($meta_key, ['_metasync_video_url', '_metasync_video_thumbnail'], true)) {
450 $value = esc_url_raw($value);
451 } elseif ($meta_key === '_metasync_video_duration') {
452 $value = absint($value);
453 $value = $value > 0 ? $value : '';
454 } elseif ($meta_key === '_metasync_video_description') {
455 $value = sanitize_textarea_field($value);
456 } else {
457 $value = sanitize_text_field($value);
458 }
459
460 if (!empty($value)) {
461 update_post_meta($post_id, $meta_key, $value);
462 } else {
463 delete_post_meta($post_id, $meta_key);
464 }
465 }
466 }
467
468 /**
469 * Display the SEO meta box (SEO Title & Meta Description) in the Classic editor.
470 *
471 * Reads/writes the same _metasync_seo_title / _metasync_seo_desc keys used by the
472 * Gutenberg sidebar. OTTO values are used only as placeholder hints so the
473 * custom-value > OTTO priority is preserved.
474 */
475 public function seo_meta_box_display()
476 {
477 global $post;
478 $seo_title = get_post_meta($post->ID, '_metasync_seo_title', true);
479 $seo_desc = get_post_meta($post->ID, '_metasync_seo_desc', true);
480 $otto_title = get_post_meta($post->ID, '_metasync_otto_title', true);
481 $otto_desc = get_post_meta($post->ID, '_metasync_otto_description', true);
482
483 wp_nonce_field('metasync_seo_meta_nonce', 'metasync_seo_meta_nonce');
484 ?>
485 <p style="color: #666; margin-bottom: 12px;">
486 <?php esc_html_e('Set the SEO Title and Meta Description for this post. Leave the field blank to use OTTO suggestion.', 'metasync'); ?>
487 </p>
488 <table class="form-table" style="margin: 0;">
489 <tr>
490 <th scope="row"><label for="metasync_seo_title"><?php esc_html_e('SEO Title', 'metasync'); ?></label></th>
491 <td><input type="text" id="metasync_seo_title" name="metasync_seo_title" value="<?php echo esc_attr($seo_title); ?>" class="large-text" placeholder="<?php echo esc_attr($otto_title); ?>" /></td>
492 </tr>
493 <tr>
494 <th scope="row"><label for="metasync_seo_desc"><?php esc_html_e('Meta Description', 'metasync'); ?></label></th>
495 <td><textarea id="metasync_seo_desc" name="metasync_seo_desc" class="large-text" rows="3" placeholder="<?php echo esc_attr($otto_desc); ?>"><?php echo esc_textarea($seo_desc); ?></textarea></td>
496 </tr>
497 </table>
498 <?php
499 }
500
501 /**
502 * Save the SEO meta box data.
503 *
504 * The nonce field is never present in Gutenberg REST saves, so the nonce check
505 * alone prevents this handler from running in that context — leaving the sidebar
506 * as the source of truth there.
507 *
508 * @param int $post_id The post ID.
509 */
510 public function seo_meta_box_save($post_id)
511 {
512 if (!isset($_POST['metasync_seo_meta_nonce']) ||
513 !wp_verify_nonce($_POST['metasync_seo_meta_nonce'], 'metasync_seo_meta_nonce')) {
514 return;
515 }
516
517 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
518 return;
519 }
520
521 if (!current_user_can('edit_post', $post_id)) {
522 return;
523 }
524
525 if (isset($_POST['metasync_seo_title'])) {
526 $seo_title = sanitize_text_field($_POST['metasync_seo_title']);
527 if (!empty($seo_title)) {
528 update_post_meta($post_id, '_metasync_seo_title', $seo_title);
529 } else {
530 delete_post_meta($post_id, '_metasync_seo_title');
531 }
532 }
533
534 if (isset($_POST['metasync_seo_desc'])) {
535 $seo_desc = sanitize_textarea_field($_POST['metasync_seo_desc']);
536 if (!empty($seo_desc)) {
537 update_post_meta($post_id, '_metasync_seo_desc', $seo_desc);
538 } else {
539 delete_post_meta($post_id, '_metasync_seo_desc');
540 }
541 }
542 }
543 }
544