PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 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 All 141 releases
← All changes | admin/class-metasync-post-meta-setting.php +147 -48 2.6.16 → 2.7.2 View file →
@@ -17,8 +17,19 @@
17 17 }
18 18
19 19 class Metasync_Post_Meta_Settings
20 20 {
21 + /**
22 + * Description written onto every redirection row this meta box owns.
23 + *
24 + * Rows created here live in the shared redirections table alongside rules
25 + * added by hand on the Redirections screen, and carry no other marker. The
26 + * prefix is therefore how the redirect engine tells the two apart, so that
27 + * switching the Redirection feature off stops these rows without touching
28 + * anyone's manually-added rules.
29 + */
30 + const POST_REDIRECT_DESCRIPTION_PREFIX = 'Post redirection meta box for post #';
31 +
21 32 private $common;
22 33
23 34 public function __construct()
24 35 {
@@ -46,12 +57,12 @@
46 57 $post_types = array_diff($post_types, ['attachment']);
47 58
48 59 // Resolve the current post ID in admin_init context: $_GET['post'] on the
49 60 // edit screen, $_POST['post_ID'] on save. Mirrors the sidebar enqueue.
50 - $post_id = isset($_GET['post']) ? intval($_GET['post']) : (isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0);
61 + $post_id = isset($_GET['post']) ? intval($_GET['post']) : (isset($_POST['post_ID']) ? intval($_POST['post_ID']) : 0); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- read-only post ID to pick meta boxes
51 62
52 63 // LPS / custom-HTML pages carry their own baked SEO served before wp_head,
53 - // so the editable SEO fields here do nothing and mislead editors (WP-486).
64 + // so the editable SEO fields here do nothing and mislead editors.
54 65 // otto_pixel.php (where metasync_is_custom_or_lps_page lives) is only
55 66 // required conditionally, so the function_exists guard is needed at runtime.
56 67 $is_custom_or_lps_page = function_exists('metasync_is_custom_or_lps_page') && $post_id > 0 && metasync_is_custom_or_lps_page($post_id);
57 68
@@ -58,9 +69,9 @@
58 69 // Only add meta boxes if not disabled in settings.
59 70 // On LPS / custom-HTML pages all of these MetaSync boxes are suppressed: they do
60 71 // nothing useful there (their output goes into wp_head, which never runs for these
61 72 // pages — serve_raw_html() exits first — and a redirect would only break the
62 - // published page). The single SEO read-only notice below carries the messaging. (WP-486)
73 + // published page). The single SEO read-only notice below carries the messaging.
63 74 if (empty($general_settings['disable_common_robots_metabox']) && !$is_custom_or_lps_page) {
64 75 add_meta_box('common-robots-meta', "Common Robots Meta by $plugin_name", [$this, 'common_robots_meta_box_display'], $post_types, 'normal', 'default');
65 76 }
66 77
@@ -112,16 +123,8 @@
112 123 wp_nonce_field('metasync_common_robots_nonce', 'metasync_common_robots_nonce');
113 124 ?>
114 125 <ul class="checkbox-list">
115 126 <li>
116 - <input type="checkbox" name="common_robots_meta[index]" id="robots_common1" value="index" <?php isset($common_robots['index']) ? checked('index', $common_robots['index']) : '' ?>>
117 - <label for="robots_common1">Index </br>
118 - <span class="description">
119 - <span>Search engines to index and show these pages in the search results.</span>
120 - </span>
121 - </label>
122 - </li>
123 - <li>
124 127 <input type="checkbox" name="common_robots_meta[noindex]" id="robots_common2" value="noindex" <?php isset($common_robots['noindex']) ? checked('noindex', $common_robots['noindex']) : '' ?>>
125 128 <label for="robots_common2">No Index </br>
126 129 <span class="description">
127 130 <span>Prevents search engines from indexing and displaying these pages in search results.</span>
@@ -204,9 +207,9 @@
204 207 <li>
205 208 <label for="advanced_robots_image">
206 209 <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)) ?>>
207 210 Image Preview </br>
208 - <select class="input-length" name="advanced_robots_meta[max-image-preview][length]' ?>" id="advanced_robots_image_value">
211 + <select class="input-length" name="advanced_robots_meta[max-image-preview][length]" id="advanced_robots_image_value">
209 212 <option value="large" <?php selected(esc_attr($image_advance_robots_length), 'large'); ?>>Large</option>
210 213 <option value="standard" <?php selected(esc_attr($image_advance_robots_length), 'standard'); ?>>Standard</option>
211 214 <option value="none" <?php selected(esc_attr($image_advance_robots_length), 'none'); ?>>None</option>
212 215 </select>
@@ -253,22 +256,31 @@
253 256 public function post_canonical_display()
254 257 {
255 258 global $post;
256 259
257 - $post_canonical = get_post_meta($post->ID, 'meta_canonical', true) ?? '';
258 - // Fix legacy array values stored by sanitize_array()
259 - if (is_array($post_canonical)) {
260 - $post_canonical = reset($post_canonical) ?: '';
261 - // Repair the stored value so it won't recur
262 - if (!empty($post_canonical)) {
263 - update_post_meta($post->ID, 'meta_canonical', (string) $post_canonical);
260 + $raw_canonical = get_post_meta($post->ID, 'meta_canonical', true);
261 + $post_canonical = Metasync_Canonical_Sanitizer::sanitize($raw_canonical);
262 +
263 + // Repair legacy array rows and rows corrupted to the literal "Array"
264 + // A nested array here previously became the string "Array"
265 + // via a single reset() + (string) cast, permanently corrupting the row.
266 + if (is_array($raw_canonical) || Metasync_Canonical_Sanitizer::is_corrupted($raw_canonical)) {
267 + if ($post_canonical !== '') {
268 + update_post_meta($post->ID, 'meta_canonical', $post_canonical);
269 + } else {
270 + delete_post_meta($post->ID, 'meta_canonical');
264 271 }
272 + } elseif ($post_canonical === '' && is_string($raw_canonical) && trim($raw_canonical) !== '') {
273 + // Stored value the validator doesn't recognize (and is not a
274 + // corruption artifact): show it as-is so a routine post save
275 + // doesn't silently wipe it. It is not emitted while invalid.
276 + $post_canonical = trim($raw_canonical);
265 277 }
266 278 wp_nonce_field('metasync_post_canonical_nonce', 'metasync_post_canonical_nonce');
267 279 ?>
268 280 <ul>
269 281 <li> Canonical URL:
270 - <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); ?>">
282 + <input type="text" class="regular-text" name="post_canonical_url_meta" placeholder="<?php echo esc_url(get_permalink($post->ID)); ?>" value="<?php echo esc_attr($post_canonical); ?>">
271 283 </li>
272 284 </ul>
273 285 <?php
274 286 }
@@ -277,9 +289,9 @@
277 289 {
278 290 if (!current_user_can('edit_post', $post_id))
279 291 return;
280 292
281 - // WP-197: When saving from Gutenberg (REST API context) and the sidebar
293 + // When saving from Gutenberg (REST API context) and the sidebar
282 294 // JSON exists, skip — the sidebar auto-save is the source of truth.
283 295 // Classic editor form submits (non-REST) always proceed so classic-only
284 296 // users can still save via the meta boxes.
285 297 if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) {
@@ -285,13 +297,16 @@
285 297 if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) {
286 298 return;
287 299 }
288 300
289 - $post_data = metasync_sanitize_input_array($_POST);
301 + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write
290 302 // Check for new field name first, then old for backward compatibility
291 303 $field_name = isset($post_data['common_robots_meta']) ? 'common_robots_meta' : 'common_robots_mata';
292 304
293 - 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'))
305 + // Unchecked checkboxes are omitted from the request. Require the nonce,
306 + // but allow an absent checkbox group so a user can clear the last saved
307 + // directive (for example, by unticking No Index).
308 + if (!isset($post_data['metasync_common_robots_nonce']) || !wp_verify_nonce($post_data['metasync_common_robots_nonce'], 'metasync_common_robots_nonce'))
294 309 return;
295 310
296 311 $old_common_robots = get_post_meta($post_id, 'metasync_common_robots', true);
297 312
@@ -310,15 +325,15 @@
310 325 {
311 326 if (!current_user_can('edit_post', $post_id))
312 327 return;
313 328
314 - // WP-197: When saving from Gutenberg (REST API context) and the sidebar
329 + // When saving from Gutenberg (REST API context) and the sidebar
315 330 // JSON exists, skip — the sidebar auto-save is the source of truth.
316 331 if (defined('REST_REQUEST') && REST_REQUEST && !empty(get_post_meta($post_id, '_metasync_robots_advanced', true))) {
317 332 return;
318 333 }
319 334
320 - $post_data = metasync_sanitize_input_array($_POST);
335 + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write
321 336 // Check for new field name first, then old for backward compatibility
322 337 $field_name = isset($post_data['advanced_robots_meta']) ? 'advanced_robots_meta' : 'advanced_robots_mata';
323 338
324 339 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'))
@@ -338,9 +353,9 @@
338 353 {
339 354 if (!current_user_can('edit_post', $post_id))
340 355 return;
341 356
342 - $post_data = metasync_sanitize_input_array($_POST);
357 + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write
343 358 // Check for new field name first, then old for backward compatibility
344 359 $field_name = isset($post_data['post_redirect_meta']) ? 'post_redirect_meta' : 'post_redirect_mata';
345 360
346 361 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'))
@@ -355,13 +370,13 @@
355 370 update_post_meta($post_id, 'metasync_post_redirection_meta', $post_redirection_meta);
356 371 else
357 372 delete_post_meta($post_id, 'metasync_post_redirection_meta', $old_post_redirection_meta);
358 373
359 - // WP-540: sync the actual redirect into the shared redirection table (exact
374 + // sync the actual redirect into the shared redirection table (exact
360 375 // match) using the same DB layer as ?page=...-redirections&action=add. This
361 376 // keeps a single redirect code path — the existing handle_template_redirect()
362 377 // serves it — instead of a duplicate per-post frontend handler.
363 - $this->sync_post_redirect_rule($post_id, is_array($post_redirection_meta) ? $post_redirection_meta : []);
378 + self::sync_post_redirect_rule($post_id, is_array($post_redirection_meta) ? $post_redirection_meta : []);
364 379 }
365 380
366 381 /**
367 382 * Create, update, or remove a row in the shared redirection table so a redirect
@@ -367,24 +382,59 @@
367 382 * Create, update, or remove a row in the shared redirection table so a redirect
368 383 * configured on the post-edit "Redirection" meta box is served by the same engine
369 384 * as manually-added redirections (exact match on the post's own URL). The row id we
370 385 * create is remembered in post meta so repeated saves update — never duplicate — it,
371 - * and disabling the meta box removes it. (WP-540)
386 + * and disabling the meta box removes it.
372 387 *
388 + * Static: besides the metabox save hook, the REST handlers call this after
389 + * writing metasync_post_redirection_meta, because platform/Gutenberg saves
390 + * never pass through the classic nonce-gated save path.
391 + *
373 392 * @param int $post_id The post being saved.
374 393 * @param array $meta Sanitized meta box values (enable/type/url).
375 394 */
376 - private function sync_post_redirect_rule($post_id, $meta)
395 + /**
396 + * Whether a redirection row was created by the per-post Redirection meta box.
397 + *
398 + * @param object $row Row from the redirections table.
399 + * @return bool True for rows this meta box owns.
400 + */
401 + public static function owns_redirect_row($row)
377 402 {
403 + // A row missing the column, or carrying a non-string, is not ours.
404 + // isset() on a non-object is simply false, so no type check is needed.
405 + if (!isset($row->description) || !is_string($row->description)) {
406 + return false;
407 + }
408 +
409 + return strncmp(
410 + $row->description,
411 + self::POST_REDIRECT_DESCRIPTION_PREFIX,
412 + strlen(self::POST_REDIRECT_DESCRIPTION_PREFIX)
413 + ) === 0;
414 + }
415 +
416 + public static function sync_post_redirect_rule($post_id, $meta)
417 + {
378 418 // Never act on autosaves or revisions — their permalink isn't the public URL.
379 419 if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) || !class_exists('Metasync_Redirection_Database')) {
380 420 return;
381 421 }
382 422
423 + // Redirection feature switched off. Return before touching the table so a
424 + // disabled feature neither creates new rows nor deletes the row a post
425 + // already owns — re-enabling must restore the redirect exactly as it was.
426 + if (Metasync_Feature_Flags::is_disabled(Metasync_Feature_Flags::REDIRECTION)) {
427 + return;
428 + }
429 +
383 430 $db = new Metasync_Redirection_Database();
384 431 $existing_row_id = (int) get_post_meta($post_id, '_metasync_post_redirect_row_id', true);
385 432
386 - $enabled = isset($meta['enable']) && $meta['enable'] === 'true';
433 + // The metabox posts the literal string 'true', but REST/platform
434 + // writes pass through sanitize_text_field() and may arrive as '1',
435 + // 'on', or boolean true. Normalize so every spelling behaves alike.
436 + $enabled = isset($meta['enable']) && in_array($meta['enable'], ['true', '1', 1, true, 'on', 'yes'], true);
387 437 $type = isset($meta['type']) ? (int) $meta['type'] : 301;
388 438 $dest = isset($meta['url']) ? trim((string) $meta['url']) : '';
389 439 $is_gone = in_array($type, [410, 451], true);
390 440
@@ -405,8 +455,38 @@
405 455 }
406 456 $parsed = wp_parse_url($permalink);
407 457 $source_path = isset($parsed['path']) ? $parsed['path'] : '/';
408 458
459 + // Same loop guard as the admin form — creating or updating this post's
460 + // rule must not wire its permalink into a cycle (e.g. another rule
461 + // already redirects the chosen destination back here). On a loop we
462 + // keep the previously stored rule and skip the write. The guard above
463 + // already returned when a non-410/451 type had an empty destination,
464 + // so $dest is known non-empty here.
465 + if (!$is_gone) {
466 + // Destination guard — same checks as the admin form and MCP tools.
467 + // Backslashes and protocol-relative hosts bypass host validation in
468 + // browsers while looking internal to wp_validate_redirect. On a
469 + // bad destination we keep the previously stored rule and skip the
470 + // write, same skip-write semantics as the loop guard below.
471 + if (!class_exists('Metasync_Redirection_Validator')) {
472 + require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection-validator.php';
473 + }
474 + if (!Metasync_Redirection_Validator::is_safe_destination_syntax($dest)
475 + || (!get_option('metasync_allow_external_redirects', 0) && wp_validate_redirect($dest, '') !== $dest)) {
476 + return;
477 + }
478 +
479 + if (!class_exists('Metasync_Redirection')) {
480 + require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection.php';
481 + }
482 + $db_ref = $db;
483 + $redirection_helper = new Metasync_Redirection($db_ref);
484 + if ($redirection_helper->validate_no_loop($source_path, $dest) !== null) {
485 + return;
486 + }
487 + }
488 +
409 489 $data = [
410 490 'sources_from' => serialize([$source_path => 'exact']),
411 491 'url_redirect_to' => $is_gone ? '' : $dest,
412 492 'http_code' => $type,
@@ -412,9 +492,9 @@
412 492 'http_code' => $type,
413 493 'status' => 'active',
414 494 'pattern_type' => 'exact',
415 495 'regex_pattern' => null,
416 - 'description' => sprintf('Post redirection meta box for post #%d', $post_id),
496 + 'description' => self::POST_REDIRECT_DESCRIPTION_PREFIX . $post_id,
417 497 ];
418 498
419 499 // Update the row we own if it still exists; otherwise create it and remember its id.
420 500 if ($existing_row_id > 0 && $db->find($existing_row_id)) {
@@ -431,9 +511,9 @@
431 511 {
432 512 if (!current_user_can('edit_post', $post_id))
433 513 return;
434 514
435 - $post_data = metasync_sanitize_input_array($_POST);
515 + $post_data = metasync_sanitize_input_array($_POST); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- wp_verify_nonce() runs before any meta write
436 516 // Check for new field name first, then old for backward compatibility
437 517 $field_name = isset($post_data['post_canonical_url_meta']) ? 'post_canonical_url_meta' : 'post_canonical_url_mata';
438 518
439 519 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'))
@@ -440,14 +520,13 @@
440 520 return;
441 521
442 522 $old_post_canonical_meta = get_post_meta($post_id, 'meta_canonical', true);
443 523
444 - // Canonical is a URL string — sanitize as URL, not array
445 - $raw_value = $post_data[$field_name];
446 - if (is_array($raw_value)) {
447 - $raw_value = reset($raw_value); // extract first element if array
448 - }
449 - $post_canonical_meta = esc_url_raw(trim((string) $raw_value));
524 + // Canonical is a URL string — extract the first scalar from nested
525 + // arrays, normalize schemeless input, and validate. Unusable input
526 + // (including the corruption literal "Array") clears the meta instead
527 + // of persisting garbage like "http://Array".
528 + $post_canonical_meta = Metasync_Canonical_Sanitizer::sanitize_for_save($post_data[$field_name]);
450 529
451 530 if (!empty($post_canonical_meta))
452 531 update_post_meta($post_id, 'meta_canonical', $post_canonical_meta);
453 532 else
@@ -559,9 +638,9 @@
559 638
560 639 /**
561 640 * Display a read-only notice in place of the SEO meta box on LPS / custom-HTML
562 641 * pages, whose SEO is baked into their own HTML bundle and managed by WebStudio.
563 - * No inputs, nonce, or editable fields are rendered (WP-486).
642 + * No inputs, nonce, or editable fields are rendered.
564 643 */
565 644 public function seo_lps_notice_meta_box_display()
566 645 {
567 646 echo '<p><strong>The SEO for this page is managed by WebStudio.</strong></p>';
@@ -578,24 +657,42 @@
578 657 {
579 658 global $post;
580 659 $seo_title = get_post_meta($post->ID, '_metasync_seo_title', true);
581 660 $seo_desc = get_post_meta($post->ID, '_metasync_seo_desc', true);
582 - $otto_title = get_post_meta($post->ID, '_metasync_otto_title', true);
583 - $otto_desc = get_post_meta($post->ID, '_metasync_otto_description', true);
584 661
662 + // Values brought in by the external SEO importer apply only where OTTO
663 + // has no suggestion, so show them as the placeholder in exactly that
664 + // case. Without this the field looks empty after an import and the user
665 + // concludes nothing was imported. Which tier fills the placeholder is
666 + // Metasync_Seo_Precedence's call, not this screen's.
667 + $title_fallback = Metasync_Seo_Precedence::fallback($post->ID, Metasync_Seo_Precedence::FIELD_TITLE);
668 + $desc_fallback = Metasync_Seo_Precedence::fallback($post->ID, Metasync_Seo_Precedence::FIELD_DESCRIPTION);
669 +
670 + $title_placeholder = $title_fallback['value'];
671 + $desc_placeholder = $desc_fallback['value'];
672 +
673 + $title_is_imported = $title_fallback['source'] === Metasync_Seo_Precedence::SOURCE_IMPORTED;
674 +
585 675 wp_nonce_field('metasync_seo_meta_nonce', 'metasync_seo_meta_nonce');
586 676 ?>
587 677 <p style="color: #666; margin-bottom: 12px;">
678 + <?php /* translators: %s: OTTO name (whitelabel). */ ?>
588 679 <?php printf(esc_html__('Set the SEO Title and Meta Description for this post. Leave the field blank to use %s suggestion.', 'metasync'), esc_html(Metasync::get_whitelabel_otto_name())); ?>
589 680 </p>
681 + <?php if ($title_is_imported) : ?>
682 + <p style="color: #666; margin-bottom: 12px;">
683 + <?php /* translators: %s: OTTO name (whitelabel). */ ?>
684 + <?php printf(esc_html__('The greyed-out value below was imported from another SEO plugin. It is used only until %s has a suggestion for this page.', 'metasync'), esc_html(Metasync::get_whitelabel_otto_name())); ?>
685 + </p>
686 + <?php endif; ?>
590 687 <table class="form-table" style="margin: 0;">
591 688 <tr>
592 689 <th scope="row"><label for="metasync_seo_title"><?php esc_html_e('SEO Title', 'metasync'); ?></label></th>
593 - <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>
690 + <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($title_placeholder); ?>" /></td>
594 691 </tr>
595 692 <tr>
596 693 <th scope="row"><label for="metasync_seo_desc"><?php esc_html_e('Meta Description', 'metasync'); ?></label></th>
597 - <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>
694 + <td><textarea id="metasync_seo_desc" name="metasync_seo_desc" class="large-text" rows="3" placeholder="<?php echo esc_attr($desc_placeholder); ?>"><?php echo esc_textarea($seo_desc); ?></textarea></td>
598 695 </tr>
599 696 </table>
600 697 <?php
601 698 }
@@ -624,11 +721,13 @@
624 721 return;
625 722 }
626 723
627 724 if (isset($_POST['metasync_seo_title'])) {
628 - $seo_title = sanitize_text_field($_POST['metasync_seo_title']);
725 + $seo_title = sanitize_text_field(wp_unslash($_POST['metasync_seo_title']));
629 726 if (!empty($seo_title)) {
630 - update_post_meta($post_id, '_metasync_seo_title', $seo_title);
727 + // update_post_meta() unslashes again, so re-slash to keep
728 + // literal backslashes and quotes in the stored value.
729 + update_post_meta($post_id, '_metasync_seo_title', wp_slash($seo_title));
631 730 } else {
632 731 delete_post_meta($post_id, '_metasync_seo_title');
633 732 }
634 733 }
@@ -633,11 +732,11 @@
633 732 }
634 733 }
635 734
636 735 if (isset($_POST['metasync_seo_desc'])) {
637 - $seo_desc = sanitize_textarea_field($_POST['metasync_seo_desc']);
736 + $seo_desc = sanitize_textarea_field(wp_unslash($_POST['metasync_seo_desc']));
638 737 if (!empty($seo_desc)) {
639 - update_post_meta($post_id, '_metasync_seo_desc', $seo_desc);
738 + update_post_meta($post_id, '_metasync_seo_desc', wp_slash($seo_desc));
640 739 } else {
641 740 delete_post_meta($post_id, '_metasync_seo_desc');
642 741 }
643 742 }