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 | includes/class-metasync.php +98 -5 2.7.1 → 2.7.2 View file →
@@ -297,8 +297,17 @@
297 297 // `metasync_enable_seo_suite` filter.
298 298 require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-seo-suite.php';
299 299 new Metasync_Seo_Suite();
300 300
301 + // Classic-editor save path for the three fields that exist only as
302 + // Gutenberg sidebar (REST) meta otherwise: breadcrumb title, language
303 + // alternates, primary category. The Suite renders the first two and
304 + // emits the nonce; this handler persists them from the classic form.
305 + // It guards itself against block-editor and managed-page context, so
306 + // it is safe to register unconditionally.
307 + require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-metasync-classic-seo-fields.php';
308 + new Metasync_Classic_Seo_Fields();
309 +
301 310 // SEO Health CSV export: must run on admin_init (before output).
302 311 // Cheap $_GET check avoids loading the class on every admin page.
303 312 if (
304 313 isset($_GET['page'], $_GET['export'], $_GET['_wpnonce']) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing check, the handler verifies nonce and capability
@@ -343,13 +352,8 @@
343 352 if (class_exists('Metasync_Breadcrumbs_Schema')) {
344 353 new Metasync_Breadcrumbs_Schema($this->get_plugin_name(), $this->get_version());
345 354 }
346 355
347 - // Initialize Developer Panel (for endpoint switching)
348 - if (class_exists('Metasync_Dev_Panel')) {
349 - $dev_panel = new Metasync_Dev_Panel($this->get_plugin_name(), $this->get_version());
350 - }
351 -
352 356 // Initialize Site Health integration
353 357 if (class_exists('Metasync_Site_Health')) {
354 358 $site_health = new Metasync_Site_Health();
355 359 $site_health->register_tests();
@@ -568,8 +572,20 @@
568 572 // One-time upgrade: regenerate sitemap to remove any Beaver Builder template entries
569 573 if ( ! get_option( 'metasync_sitemap_bb_exclusion_applied' ) ) {
570 574 $this->loader->add_action('init', $this, 'maybe_regenerate_sitemap_after_upgrade');
571 575 }
576 +
577 + // One-time upgrade: delete the legacy Otto crawl-data option. Nothing
578 + // reads or writes it anymore (the webhook and SEO sync job stopped
579 + // persisting crawl data), and on sites where OTTO crawled many URLs
580 + // the row could grow to megabytes while being autoloaded on every
581 + // request before WordPress 6.6. The guard below runs on every request,
582 + // so the flag must be autoloaded (like the sitemap flag above): a
583 + // boolean true costs ~2 bytes inside the alloptions query WordPress
584 + // already always runs, keeping the steady-state cost a cache hit.
585 + if ( ! get_option( 'metasync_otto_crawldata_removed' ) ) {
586 + $this->loader->add_action('init', $this, 'maybe_delete_legacy_otto_crawl_data');
587 + }
572 588 }
573 589
574 590 /**
575 591 * One-time upgrade routine: regenerate the XML sitemap so that Beaver Builder
@@ -599,8 +615,40 @@
599 615 // so that enabling sitemaps later will still clean up BB templates.
600 616 }
601 617
602 618 /**
619 + * One-time upgrade routine: delete the legacy Otto crawl-data option.
620 + *
621 + * The option stored every URL Otto ever sent and grew without bound;
622 + * nothing writes it anymore and nothing in production reads it (per-URL
623 + * state lives in Metasync_Otto_Job_Status, delivery receipts in
624 + * Metasync_Otto_Webhook_Audit, both bounded). This drops the leftover
625 + * row — which on long-running sites can be megabytes of autoloaded data.
626 + *
627 + * Runs once on 'init' and sets a flag so it never runs again. The flag
628 + * is autoloaded deliberately: the registration guard reads it on every
629 + * request, and an autoloaded boolean is a free cache hit inside the
630 + * alloptions query WordPress always performs — a non-autoloaded flag
631 + * would cost a dedicated SELECT per request forever, the exact waste
632 + * this routine exists to remove. delete_option() is called directly
633 + * without reading the option first: loading a multi-megabyte value into
634 + * memory just to delete it would be the one way this cleanup could hurt
635 + * a site, and the SQL DELETE never needs the value.
636 + */
637 + public function maybe_delete_legacy_otto_crawl_data() {
638 + $done_key = 'metasync_otto_crawldata_removed';
639 + if ( get_option( $done_key ) ) {
640 + return;
641 + }
642 +
643 + // No "feature in use" conditional here, unlike the sitemap routine:
644 + // the row is dead weight whether or not Otto is configured, and
645 + // delete_option() on an absent row is a cheap no-op.
646 + delete_option( 'metasync_otto_crawldata' );
647 + update_option( $done_key, true );
648 + }
649 +
650 + /**
603 651 * Initialize endpoint URL filtering for staging mode
604 652 * Intercepts HTTP requests and replaces production URLs with staging URLs
605 653 */
606 654 private function init_endpoint_filtering() {
@@ -903,8 +951,52 @@
903 951 return is_string($value) && strncmp($value, self::API_KEY_ENC_PREFIX, strlen(self::API_KEY_ENC_PREFIX)) === 0;
904 952 }
905 953
906 954 /**
955 + * Whether a value is a strictly valid UUID (versions 1-8).
956 + *
957 + * The single shared validator for every write path that persists
958 + * `general.otto_pixel_uuid` (SSO push callback, pull-based connect,
959 + * heartbeat writes, settings sanitizer). The OTTO Pixel UUID must never
960 + * hold arbitrary text — usernames such as `raza.imran` landing in it
961 + * break OTTO SSR authentication — so every write path funnels through
962 + * this check instead of a scattered one-off regex.
963 + *
964 + * Deliberately stricter than wp_is_uuid(), which only checks the
965 + * 8-4-4-4-12 hex shape and would accept placeholder values such as
966 + * `11111111-2222-3333-4444-555555555555`. The version nibble (first hex
967 + * of the third group) must be 1-8 — RFC 4122 defined 1-5 and RFC 9562
968 + * added 6/7/8, which modern generators increasingly emit — and the
969 + * variant nibble (first hex of the fourth group) must be 8/9/a/b. That
970 + * keeps behaviour identical with and without the WP helper loaded (unit
971 + * tests run without it).
972 + *
973 + * An empty string is NOT valid: clearing the field is a separate concern
974 + * owned by the disconnect flow (unset + write_options_without_stored_merge),
975 + * so '' must not masquerade as a usable UUID here.
976 + *
977 + * @param mixed $value
978 + * @return bool True only for a canonical 8-4-4-4-12 hex UUID of version 1-8
979 + * with an RFC-4122 variant (8/9/a/b) in the fourth group.
980 + */
981 + public static function is_valid_uuid($value)
982 + {
983 + if (!is_string($value)) {
984 + return false;
985 + }
986 +
987 + $value = trim($value);
988 + if ($value === '') {
989 + return false;
990 + }
991 +
992 + return (bool) preg_match(
993 + '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
994 + $value
995 + );
996 + }
997 +
998 + /**
907 999 * Encrypt a plaintext Search Atlas API key for storage at rest.
908 1000 *
909 1001 * Uses AES-256-GCM (authenticated) with a random 12-byte IV. The IV, the
910 1002 * 16-byte GCM tag and the ciphertext are concatenated and base64-encoded
@@ -1494,8 +1586,9 @@
1494 1586 * @param string $text Plain-English help text (escaped internally).
1495 1587 */
1496 1588 public static function render_tooltip_icon($tooltip_id, $text)
1497 1589 {
1590 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_tooltip_icon_html() escapes every interpolated value internally (esc_attr / esc_html)
1498 1591 echo self::get_tooltip_icon_html($tooltip_id, $text);
1499 1592 }
1500 1593
1501 1594 /**