PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
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
← All changes | metasync.php +135 -361 2.6.11trunk View file →
@@ -14,9 +14,10 @@
14 14 * @wordpress-plugin
15 15 * Plugin Name: Search Atlas: The Premier AI SEO Plugin for Instant Optimization
16 16 * Plugin URI: https://searchatlas.com/
17 17 * Description: Search Atlas SEO is an intuitive WordPress Plugin that transforms the most complicated, most labor-intensive SEO tasks into streamlined, straightforward processes. With a few clicks, the meta-bulk update feature automates the re-optimization of meta tags using AI to increase clicks. Stay up-to-date with the freshest Google Search data for your entire site or targeted URLs within the Meta Sync plug-in page.
18 - * Version: 2.6.11
18 + * Version: 2.6.26
19 + * Requires PHP: 8.1
19 20 * Author: Search Atlas
20 21 * Author URI: https://searchatlas.com
21 22 * License: GPL v3
22 23 * License URI: https://www.gnu.org/licenses/gpl-3.0.txt
@@ -30,20 +31,30 @@
30 31
31 32 // Composer classmap autoloader — loads all plugin classes on demand.
32 33 require_once __DIR__ . '/vendor/autoload.php';
33 34
35 +// Canonical sanitizer — required explicitly (not via the committed classmap)
36 +// so it is guaranteed loadable everywhere canonicals are read or written.
37 +require_once __DIR__ . '/includes/class-metasync-canonical-sanitizer.php';
38 +
39 +// Editor-settings feature flags — required explicitly for the same reason: they
40 +// gate output in the OTTO buffer and in third-party filter callbacks, both of
41 +// which can run before the classmap has resolved anything else.
42 +require_once __DIR__ . '/includes/class-metasync-feature-flags.php';
43 +Metasync_Feature_Flags::register_invalidation();
44 +
34 45 /**
35 46 * Currently plugin version.
36 47 * Start at version 1.0.0 and use SemVer - https://semver.org
37 48 * Rename this for your plugin and update it as you release new versions.
38 49 */
39 -$metasync_version = '2.6.11';
50 +$metasync_version = '2.6.26';
40 51 define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9');
41 52 /**
42 53 * Define the current required php version
43 54 * This will be used to validate whether the user can install the plugin or not
44 55 */
45 -define('METASYNC_MIN_PHP', '8.2');
56 +define('METASYNC_MIN_PHP', '8.1');
46 57
47 58 /**
48 59 * Define the current required php version
49 60 * This will be used to validate whether the user can install the plugin or not
@@ -107,9 +118,9 @@
107 118 return $sanitized;
108 119 }
109 120 }
110 121
111 -// Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449 / WP-227).
122 +// Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449).
112 123 if (!function_exists('metasync_is_non_metasync_admin_ajax')) {
113 124 function metasync_is_non_metasync_admin_ajax() {
114 125 static $result = null;
115 126 if ($result !== null) {
@@ -114,8 +125,14 @@
114 125 static $result = null;
115 126 if ($result !== null) {
116 127 return $result;
117 128 }
129 + // The MCP bridge intentionally boots WordPress with DOING_AJAX set, but it
130 + // is a MetaSync entry point rather than an unrelated admin AJAX request.
131 + // The constant is only ever defined as true by the bridge bootstraps.
132 + if (defined('METASYNC_MCP_BRIDGE')) {
133 + return ($result = false);
134 + }
118 135 if (!defined('DOING_AJAX') || !DOING_AJAX) {
119 136 return ($result = false);
120 137 }
121 138 $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action']))
@@ -132,9 +149,9 @@
132 149
133 150 // Lazy-load guard: only initialise the MCP server when the request is actually targeting
134 151 // the MCP REST route (or its sibling SEO-inventory route, which depends on $metasync_mcp_server
135 152 // for its permission callback). Saves ~2-5 MB / 10-50 ms on the 99.9% of requests that never
136 -// touch MCP. See WP-255.
153 +// touch MCP.
137 154 if (!function_exists('metasync_is_mcp_rest_request')) {
138 155 function metasync_is_mcp_rest_request(): bool {
139 156 $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
140 157 if ($uri === '') {
@@ -162,8 +179,17 @@
162 179 // and must remain as explicit require_once until refactored.
163 180 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php';
164 181 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-error-logger.php';
165 182
183 +// Shared helpers (custom/LPS page detection + query exclusion) — loaded in all
184 +// request contexts (admin, REST, MCP, AJAX) so every SEO surface uses one rule.
185 +require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-helpers.php';
186 +
187 +// Shared "results per page" selector helper used by the Redirections, 404
188 +// Monitor, Changes Log and Media Library admin list tables. Loaded early so
189 +// the helper class is available before any list table instantiates.
190 +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-per-page-helper.php';
191 +
166 192 /**
167 193 * Include the Otto Pixel Php Code
168 194 */
169 195 if (!metasync_is_non_metasync_admin_ajax()) {
@@ -256,9 +282,9 @@
256 282
257 283 // Set initial version
258 284 update_option('metasync_version', METASYNC_VERSION);
259 285
260 - // WP-315: Record activation timestamp so Divi CSS fix transients
286 + // Record activation timestamp so Divi CSS fix transients
261 287 // auto-invalidate after plugin deactivate/reactivate cycles.
262 288 update_option('metasync_activated_at', (string) time());
263 289
264 290 // Clear all cache plugins to ensure fresh start
@@ -263,9 +289,9 @@
263 289
264 290 // Clear all cache plugins to ensure fresh start
265 291 Metasync_Cache_Purge::purge_all('plugin_activation');
266 292
267 - // WP-332: Migrate physical sitemap files on activation.
293 + // Migrate physical sitemap files on activation.
268 294 metasync_migrate_physical_sitemaps();
269 295 }
270 296
271 297 // Log-sync removed - error monitoring now handled by Sentry
@@ -300,9 +326,9 @@
300 326 register_activation_hook(__FILE__, 'activate_metasync');
301 327 register_deactivation_hook(__FILE__, 'deactivate_metasync');
302 328
303 329 /**
304 - * WP-332: Migrate physical sitemap .xml files into transients and delete them.
330 + * Migrate physical sitemap .xml files into transients and delete them.
305 331 *
306 332 * Physical files in ABSPATH cause nginx/Plesk to 403 before WordPress can
307 333 * serve them. Called from both the activation hook and the version-gate
308 334 * migration so it covers fresh activations and auto-updates.
@@ -339,9 +365,15 @@
339 365 $bn = basename($file);
340 366 $content = @file_get_contents($file);
341 367 if (false !== $content) {
342 368 $tkey = 'metasync_vsm_' . md5($bn);
343 - set_transient($tkey, $content, 30 * DAY_IN_SECONDS);
369 + // The news sitemap's entries are only valid inside Google News'
370 + // 48-hour window, so it must not be migrated in under a 30-day
371 + // TTL — that would re-introduce the staleness the generator's
372 + // own bounded TTL exists to prevent. Regenerate-on-miss rebuilds
373 + // it with a fresh date_query when this expires.
374 + $ttl = ('news-sitemap.xml' === $bn) ? (int) (DAY_IN_SECONDS / 4) : 30 * DAY_IN_SECONDS;
375 + set_transient($tkey, $content, $ttl);
344 376 if (false !== get_transient($tkey)) {
345 377 @unlink($file);
346 378 $virtual_index[$bn] = $tkey;
347 379 if ($bn !== 'sitemap_index.xml' && $bn !== 'news-sitemap.xml' && $bn !== 'video-sitemap.xml') {
@@ -404,9 +436,9 @@
404 436 update_option('metasync_options', $options);
405 437 }
406 438 }
407 439
408 - // WP-299: One-time purge of stale OTTO SEO cron backlog.
440 + // One-time purge of stale OTTO SEO cron backlog.
409 441 // Prior versions could accumulate thousands of metasync_process_seo_job and
410 442 // metasync_process_otto_crawl_url_job events due to unbounded rescheduling.
411 443 // Clear the backlog once on update; the new code prevents re-accumulation.
412 444 if (!get_option('metasync_wp299_cron_cleanup_done')) {
@@ -415,9 +447,39 @@
415 447 wp_unschedule_hook('metasync_process_otto_batch_cache_job');
416 448 update_option('metasync_wp299_cron_cleanup_done', true, false);
417 449 }
418 450
419 - // WP-332: Migrate physical sitemap files on version update.
451 + // One-time cleanup of canonical values corrupted to the literal
452 + // "Array" (emitted as http://Array once the 2.6.16 canonical filters
453 + // started reading them). The sanitizer prevents new corruption; this
454 + // repairs the rows already in the database. Cache purge below pushes
455 + // the clean pages live. Claimed via add_option() — it fails if the row
456 + // already exists, so concurrent requests can't run the cleanup twice,
457 + // and the claim lands BEFORE the work: everything inside is idempotent
458 + // and the read-side sanitizer already protects output if a run is
459 + // interrupted.
460 + if (false === get_option('metasync_canonical_cleanup_done')
461 + && add_option('metasync_canonical_cleanup_done', 'running', '', false)) {
462 + MetaSync_DBMigration::cleanup_corrupted_canonicals();
463 + update_option('metasync_canonical_cleanup_done', 'done', false);
464 + }
465 +
466 + // One-time repair of Local Business logos corrupted to "http://<id>".
467 + // The sanitizer fix stopped new corruption; this restores the
468 + // attachment ID on sites that saved a logo before it. The corrupted
469 + // value encodes the original ID exactly, so the rewrite is lossless.
470 + // Claimed via add_option() — it fails when the row already exists, so
471 + // concurrent requests can't run the repair twice, and the claim lands
472 + // BEFORE the work: the repair itself is idempotent, and the read-side
473 + // normalisation in the schema output and admin preview already
474 + // protects output if a run is interrupted.
475 + if (false === get_option('metasync_local_seo_logo_repair_done')
476 + && add_option('metasync_local_seo_logo_repair_done', 'running', '', false)) {
477 + MetaSync_DBMigration::repair_corrupted_local_seo_logo();
478 + update_option('metasync_local_seo_logo_repair_done', 'done', false);
479 + }
480 +
481 + // Migrate physical sitemap files on version update.
420 482 metasync_migrate_physical_sitemaps();
421 483
422 484 // Run full migration to ensure all tables are up to date
423 485 MetaSync_DBMigration::run_migrations();
@@ -449,8 +511,23 @@
449 511 if (!isset($hook_extra['type']) || $hook_extra['type'] !== 'plugin') {
450 512 return;
451 513 }
452 514
515 + // By the time upgrader_process_complete fires, the upgrader may have
516 + // deleted the directory this (old, still-in-memory) copy of the plugin was
517 + // loaded from — e.g. when the installed dir name differs from the package's
518 + // root dir ('metasync-develop' vs 'metasync'). The Composer classmap then
519 + // points at files that no longer exist, so autoloading Metasync_Activator
520 + // below would fatal. Bail instead; the whitelabel re-import runs on the next
521 + // request via check_metasync_updates() once the new copy is active.
522 + if (!class_exists('Metasync_Activator', false)) {
523 + $activator = __DIR__ . '/includes/class-metasync-activator.php';
524 + if (!is_file($activator)) {
525 + return;
526 + }
527 + require_once $activator;
528 + }
529 +
453 530 // Only process install and update actions
454 531 if (!isset($hook_extra['action']) || !in_array($hook_extra['action'], ['install', 'update'], true)) {
455 532 return;
456 533 }
@@ -502,9 +579,9 @@
502 579 }
503 580 }
504 581
505 582 if ($should_import) {
506 - Metasync_Activator::check_whitelabel_settings_update();
583 + Metasync_Activator::check_whitelabel_settings_update(true);
507 584 }
508 585 }
509 586
510 587 // Hook into WordPress upgrader to detect plugin updates
@@ -513,26 +590,31 @@
513 590 /**
514 591 * Fallback: Check for whitelabel file changes on admin pages
515 592 * This handles edge cases where upgrader_process_complete doesn't fire
516 593 * (e.g., FTP uploads, manual file replacements)
517 - * Only checks once per admin session to minimize performance impact
594 + * Only checks once per admin request to minimize performance impact
518 595 */
519 -// function metasync_check_whitelabel_on_admin()
520 -// {
521 -// // Only check once per admin session to avoid overhead
522 -// static $checked = false;
523 -// if ($checked) {
524 -// return;
525 -// }
526 -// $checked = true;
596 +function metasync_check_whitelabel_on_admin()
597 +{
598 + static $checked = false;
599 + if ($checked || !current_user_can('manage_options')) {
600 + return;
601 + }
602 + $checked = true;
527 603
528 -// require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
529 -// Metasync_Activator::check_whitelabel_settings_update();
530 -// }
604 + require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
531 605
532 -// Check for whitelabel changes on admin pages (fallback for edge cases)
533 -// add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1);
606 + // Surface a persistently failing White Label import instead of
607 + // retrying it silently on every admin request.
608 + add_action('admin_notices', array('Metasync_Activator', 'render_whitelabel_import_failure_notice'));
534 609
610 + Metasync_Activator::check_whitelabel_settings_update();
611 +}
612 +
613 +// Retry package imports on an authorized admin request when an update completed
614 +// through the public version-check or stale-classmap fallback path.
615 +add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1);
616 +
535 617 // Include Media Optimization Module
536 618 if (!metasync_is_non_metasync_admin_ajax()) {
537 619 require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php';
538 620 }
@@ -549,277 +631,12 @@
549 631 $plugin->run();
550 632 }
551 633 run_metasync();
552 634
553 -/**
554 - * Initialize WordPress MCP Server
555 - *
556 - * Safely register a single MCP tool class, logging failures without aborting
557 - * subsequent registrations. Extracted as a named function so both production
558 - * code and unit tests exercise the same logic (WP-265).
559 - *
560 - * @param object $server The MCP server instance (must have register_tool()).
561 - * @param string $class_name Fully-qualified tool class name.
562 - */
563 -function metasync_safe_register_mcp_tool($server, $class_name) {
564 - static $logged_missing = [];
565 - if (!class_exists($class_name)) {
566 - if (empty($logged_missing[$class_name])) {
567 - error_log('MetaSync MCP: ' . $class_name . ' class not found — skipping registration. Run composer dump-autoload to regenerate the classmap.');
568 - $logged_missing[$class_name] = true;
569 - }
570 - return;
571 - }
572 - try {
573 - $server->register_tool(new $class_name());
574 - } catch (\Throwable $e) {
575 - error_log('MetaSync MCP: Failed to register tool ' . $class_name . ': ' . $e->getMessage());
576 - }
577 -}
635 +// MCP server bootstrap (server + tool registration) extracted to keep this entry file lean.
636 +require_once plugin_dir_path( __FILE__ ) . 'includes/mcp-server-bootstrap.php';
578 637
579 638 /**
580 - * Creates and configures the MCP server instance,
581 - * registers all available MCP tools for WordPress operations.
582 - * Hooked to 'init' for proper WordPress lifecycle integration.
583 - */
584 -function metasync_init_mcp_server() {
585 - if (metasync_is_non_metasync_admin_ajax()) {
586 - return;
587 - }
588 - // Skip the heavy MCP boot (server + sync logger + REST inventory + 100+ tool objects)
589 - // unless this request is actually targeting the MCP REST route or was loaded via
590 - // the stdio/HTTP bridge. WP-255.
591 - if (!metasync_is_mcp_rest_request() && !defined('METASYNC_MCP_BRIDGE')) {
592 - return;
593 - }
594 - // Initialize MCP server
595 - global $metasync_mcp_server;
596 -
597 - try {
598 - $metasync_mcp_server = new Metasync_MCP_Server();
599 -
600 - // Attach MCP sync logger so all write tool calls are recorded in Sync History.
601 - new Metasync_MCP_Sync_Logger();
602 -
603 - // SEO Inventory: shared builder + standalone REST endpoint (WP-135)
604 - new Metasync_REST_SEO_Inventory();
605 - } catch (\Throwable $e) {
606 - error_log('MCP Server Initialization Error: ' . $e->getMessage());
607 - return;
608 - }
609 -
610 - // Helper: register a single tool, logging failures without aborting subsequent registrations
611 - $safe_register = function($class_name) use ($metasync_mcp_server) {
612 - metasync_safe_register_mcp_tool($metasync_mcp_server, $class_name);
613 - };
614 -
615 - // Register MCP Tools (Total: 92 existing + 8 new = 100 tools total!)
616 - // NEW in v2.8.0: +4 Taxonomy Meta tools, +4 Bulk Alt Text tools
617 -
618 - // Post Meta Operations (4 tools)
619 - $safe_register('MCP_Tool_Update_Post_Meta');
620 - $safe_register('MCP_Tool_Get_Post_Meta');
621 - $safe_register('MCP_Tool_Get_SEO_Meta');
622 - $safe_register('MCP_Tool_Get_Hreflang_Links');
623 -
624 - // Post Operations (4 tools)
625 - $safe_register('MCP_Tool_Get_Post');
626 - $safe_register('MCP_Tool_Get_Post_By_URL');
627 - $safe_register('MCP_Tool_List_Posts');
628 - $safe_register('MCP_Tool_Update_Post');
629 - $safe_register('MCP_Tool_Get_Post_Types');
630 -
631 - // SEO Analysis (3 tools)
632 - $safe_register('MCP_Tool_Analyze_SEO');
633 - $safe_register('MCP_Tool_Check_Indexability');
634 - $safe_register('MCP_Tool_SEO_Health_Report');
635 -
636 - // Search Operations (3 tools)
637 - $safe_register('MCP_Tool_Search_Posts');
638 - $safe_register('MCP_Tool_Search_By_Keyword');
639 - $safe_register('MCP_Tool_Find_Missing_Meta');
640 -
641 - // Redirect Management (5 tools)
642 - $safe_register('MCP_Tool_Create_Redirect');
643 - $safe_register('MCP_Tool_List_Redirects');
644 - $safe_register('MCP_Tool_Delete_Redirect');
645 - $safe_register('MCP_Tool_Update_Redirect');
646 - $safe_register('MCP_Tool_Check_Redirects_Health');
647 -
648 - // 404 Error Monitoring (5 tools)
649 - $safe_register('MCP_Tool_List_404_Errors');
650 - $safe_register('MCP_Tool_Get_404_Stats');
651 - $safe_register('MCP_Tool_Delete_404_Error');
652 - $safe_register('MCP_Tool_Clear_404_Errors');
653 - $safe_register('MCP_Tool_Create_Redirect_From_404');
654 -
655 - // Robots.txt & Sitemap Management (11 tools - 7 existing + 4 new)
656 - $safe_register('MCP_Tool_Get_Robots_Txt');
657 - $safe_register('MCP_Tool_Update_Robots_Txt');
658 - $safe_register('MCP_Tool_Get_Sitemap_Status');
659 - $safe_register('MCP_Tool_Regenerate_Sitemap');
660 - $safe_register('MCP_Tool_Exclude_From_Sitemap');
661 - $safe_register('MCP_Tool_Add_Robots_Rule');
662 - $safe_register('MCP_Tool_Remove_Robots_Rule');
663 - $safe_register('MCP_Tool_Parse_Robots_Txt');
664 - $safe_register('MCP_Tool_Validate_Robots_Txt');
665 - $safe_register('MCP_Tool_Get_News_Sitemap');
666 - $safe_register('MCP_Tool_Get_Video_Sitemap');
667 -
668 - // Plugin Settings Management (4 tools)
669 - $safe_register('MCP_Tool_Get_Plugin_Settings');
670 - $safe_register('MCP_Tool_Update_Plugin_Settings');
671 - $safe_register('MCP_Tool_List_Plugin_Settings_Schema');
672 - $safe_register('MCP_Tool_Get_MCP_Settings');
673 -
674 - // Schema Markup Management (7 tools)
675 - $safe_register('MCP_Tool_Get_Schema_Markup');
676 - $safe_register('MCP_Tool_Update_Schema_Markup');
677 - $safe_register('MCP_Tool_Add_Schema_Type');
678 - $safe_register('MCP_Tool_Remove_Schema_Type');
679 - $safe_register('MCP_Tool_Validate_Schema');
680 - $safe_register('MCP_Tool_Get_Schema_Content');
681 - $safe_register('MCP_Tool_Set_Schema_Content');
682 -
683 - // Google Instant Index (6 tools)
684 - $safe_register('MCP_Tool_Instant_Index_Update');
685 - $safe_register('MCP_Tool_Instant_Index_Delete');
686 - $safe_register('MCP_Tool_Instant_Index_Status');
687 - $safe_register('MCP_Tool_Instant_Index_Bulk_Update');
688 - $safe_register('MCP_Tool_Get_Instant_Index_Settings');
689 - $safe_register('MCP_Tool_Update_Instant_Index_Settings');
690 -
691 - // Custom HTML Pages (6 tools)
692 - $safe_register('MCP_Tool_Create_Custom_Page');
693 - $safe_register('MCP_Tool_Get_Custom_Page');
694 - $safe_register('MCP_Tool_List_Custom_Pages');
695 - $safe_register('MCP_Tool_Update_Custom_Page');
696 - $safe_register('MCP_Tool_Delete_Custom_Page');
697 - $safe_register('MCP_Tool_Import_LPS_Page');
698 -
699 - // HTML to Builder Converter (3 tools)
700 - $safe_register('MCP_Tool_Convert_HTML_To_Builder');
701 - $safe_register('MCP_Tool_Create_Builder_Page_From_HTML');
702 - $safe_register('MCP_Tool_Convert_Custom_Page_To_Builder');
703 -
704 - // Code Snippets (6 tools)
705 - $safe_register('MCP_Tool_Get_Header_Snippet');
706 - $safe_register('MCP_Tool_Update_Header_Snippet');
707 - $safe_register('MCP_Tool_Get_Footer_Snippet');
708 - $safe_register('MCP_Tool_Update_Footer_Snippet');
709 - $safe_register('MCP_Tool_Get_Post_Snippets');
710 - $safe_register('MCP_Tool_Update_Post_Snippets');
711 -
712 - // Categories & Taxonomies (15 tools)
713 - $safe_register('MCP_Tool_List_Categories');
714 - $safe_register('MCP_Tool_Get_Category');
715 - $safe_register('MCP_Tool_Create_Category');
716 - $safe_register('MCP_Tool_Update_Category');
717 - $safe_register('MCP_Tool_Delete_Category');
718 - $safe_register('MCP_Tool_Get_Post_Categories');
719 - $safe_register('MCP_Tool_Set_Post_Categories');
720 -
721 - // Tags (8 tools)
722 - $safe_register('MCP_Tool_List_Tags');
723 - $safe_register('MCP_Tool_Get_Tag');
724 - $safe_register('MCP_Tool_Create_Tag');
725 - $safe_register('MCP_Tool_Update_Tag');
726 - $safe_register('MCP_Tool_Delete_Tag');
727 - $safe_register('MCP_Tool_Get_Post_Tags');
728 - $safe_register('MCP_Tool_Set_Post_Tags');
729 -
730 - // Featured Images & Media (6 tools)
731 - $safe_register('MCP_Tool_Get_Featured_Image');
732 - $safe_register('MCP_Tool_Set_Featured_Image');
733 - $safe_register('MCP_Tool_Upload_Featured_Image');
734 - $safe_register('MCP_Tool_Remove_Featured_Image');
735 - $safe_register('MCP_Tool_List_Media');
736 - $safe_register('MCP_Tool_Get_Media_Details');
737 -
738 - // Post CRUD Operations (1 tool - delete/restore disabled for safety)
739 - $safe_register('MCP_Tool_Create_Post');
740 - // $safe_register('MCP_Tool_Delete_Post'); // DISABLED - safety
741 - // $safe_register('MCP_Tool_Restore_Post'); // DISABLED - safety
742 -
743 - // Bulk Operations (3 tools - bulk delete disabled for safety)
744 - $safe_register('MCP_Tool_Bulk_Update_Meta');
745 - $safe_register('MCP_Tool_Bulk_Set_Categories');
746 - $safe_register('MCP_Tool_Bulk_Change_Status');
747 - // $safe_register('MCP_Tool_Bulk_Delete_Posts'); // DISABLED - safety
748 -
749 - // WordPress Core SEO Settings (10 tools)
750 - $safe_register('MCP_Tool_Get_Site_Info');
751 - $safe_register('MCP_Tool_Update_Site_Info');
752 - $safe_register('MCP_Tool_Get_Permalink_Structure');
753 - $safe_register('MCP_Tool_Update_Permalink_Structure');
754 - $safe_register('MCP_Tool_Get_Reading_Settings');
755 - $safe_register('MCP_Tool_Update_Reading_Settings');
756 - $safe_register('MCP_Tool_Get_Search_Visibility');
757 - $safe_register('MCP_Tool_Update_Search_Visibility');
758 - $safe_register('MCP_Tool_Get_Date_Format');
759 - $safe_register('MCP_Tool_Get_Discussion_Settings');
760 -
761 - // Taxonomy Meta Operations (4 tools - NEW in v2.8.0)
762 - $safe_register('MCP_Tool_Get_Term_Meta');
763 - $safe_register('MCP_Tool_Update_Term_Meta');
764 - $safe_register('MCP_Tool_Bulk_Update_Term_Meta');
765 - $safe_register('MCP_Tool_List_Terms_With_Meta');
766 -
767 - // Bulk Alt Text Operations (4 tools - NEW in v2.8.0)
768 - $safe_register('MCP_Tool_Audit_Alt_Text');
769 - $safe_register('MCP_Tool_Bulk_Update_Alt_Text');
770 - $safe_register('MCP_Tool_Generate_Alt_Text');
771 - $safe_register('MCP_Tool_Validate_Alt_Text');
772 -
773 - // OTTO Persistence Settings (2 tools)
774 - $safe_register('MCP_Tool_Get_Otto_Persistence_Settings');
775 - $safe_register('MCP_Tool_Update_Otto_Persistence_Settings');
776 -
777 - // OTTO Pipeline Tools (3 tools)
778 - $safe_register('MCP_Tool_Trigger_Otto_Optimization');
779 - $safe_register('MCP_Tool_Get_Otto_Status');
780 - $safe_register('MCP_Tool_Verify_SEO_Output');
781 -
782 - // System Diagnostics & Plugin Info (4 tools)
783 - $safe_register('MCP_Tool_System_Diagnostics');
784 - $safe_register('MCP_Tool_List_All_Plugins');
785 - $safe_register('MCP_Tool_Get_Cron_Jobs');
786 - $safe_register('MCP_Tool_Get_WP_Option');
787 -
788 - // SEO Inventory (1 tool — WP-135)
789 - $safe_register('MCP_Tool_List_Posts_SEO_Inventory');
790 -
791 - // Read-Only Database Access (3 tools)
792 - $safe_register('MCP_Tool_DB_Tables');
793 - $safe_register('MCP_Tool_DB_Describe');
794 - $safe_register('MCP_Tool_DB_Select');
795 -
796 - // Breadcrumb Tools (1 tool)
797 - $safe_register('MCP_Tool_Get_Breadcrumb_Path');
798 -
799 - // Cache Purge (2 tools)
800 - $safe_register('MCP_Tool_Cache_Purge_All');
801 - $safe_register('MCP_Tool_Cache_Purge_URL');
802 -
803 - // LLMs.txt Tools (5 tools)
804 - $safe_register('MCP_Tool_Get_LLMs_Txt');
805 - $safe_register('MCP_Tool_Regenerate_LLMs_Txt');
806 - $safe_register('MCP_Tool_Get_LLMs_Txt_Settings');
807 - $safe_register('MCP_Tool_Update_LLMs_Txt_Settings');
808 - $safe_register('MCP_Tool_Get_Post_Markdown');
809 -
810 - // SEO Plugin Audit (4 tools — WP-202)
811 - $safe_register('MCP_Tool_Read_SEO_Plugin_Data');
812 - $safe_register('MCP_Tool_SEO_Plugin_Diff');
813 - $safe_register('MCP_Tool_Sync_To_Active_Plugins');
814 - $safe_register('MCP_Tool_Detect_SEO_Conflicts');
815 -
816 - // Allow other plugins/themes to register tools
817 - do_action('metasync_mcp_register_tools', $metasync_mcp_server);
818 -}
819 -add_action('init', 'metasync_init_mcp_server', 5);
820 -
821 -/**
822 639 * Schedule a daily cron event to auto-purge Sync History records older than 90 days.
823 640 */
824 641 function metasync_schedule_sync_log_cleanup() {
825 642 if (!wp_next_scheduled('metasync_sync_log_daily_cleanup')) {
@@ -846,85 +663,41 @@
846 663 echo '<script>window.__SA_DYO_INITIALIZED__=true;</script>' . "\n";
847 664 }
848 665 add_action('wp_head', 'metasync_output_dyo_init_flag', 1);
849 666
850 -/**
851 - * Initialize GA4 Analytics for Admin Area
852 - * Hooked to admin_init for proper WordPress lifecycle integration
853 - * Only loads after WordPress, plugins, and themes are fully loaded
854 - */
855 -function metasync_init_analytics() {
856 - // Only initialize in admin area (excludes AJAX and REST API requests)
857 - if (is_admin() && !wp_doing_ajax() && !defined('REST_REQUEST')) {
858 - Metasync_GA4::get_instance();
859 - }
860 -}
861 -add_action('admin_init', 'metasync_init_analytics', 10);
667 +// Runtime feature initialisers (GA4, API backoff, review notice, JWT accessor, debug mode) extracted to keep this entry file lean.
668 +require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-runtime-init.php';
862 669
863 670 /**
864 - * Initialize API Backoff System
865 - * Hooked to init for proper WordPress lifecycle integration
866 - * Monitors API responses and manages exponential backoff for rate limiting
867 - * @since 2.7.1
671 + * Append a "Website Studio" post state to LPS-synced / MetaSync custom pages in
672 + * the admin Pages list, so site owners can tell at a glance which pages are
673 + * managed by Website Studio and shouldn't be hand-edited.
674 + *
675 + * Hooks WordPress core's display_post_states filter — the same mechanism that
676 + * renders the grey inline tags like "— Front Page" / "— Draft" — so the label
677 + * is native-styled and only appears next to relevant page titles, with no
678 + * custom admin column.
679 + *
680 + * @param string[] $post_states Existing post-state labels keyed by slug.
681 + * @param WP_Post $post The post being listed.
682 + * @return string[] Possibly-augmented post states.
868 683 */
869 -function metasync_init_api_backoff() {
870 - if (metasync_is_non_metasync_admin_ajax()) {
871 - return;
684 +function metasync_add_lps_post_state($post_states, $post) {
685 + // metasync_is_custom_or_lps_page() lives in otto/otto_pixel.php, which is NOT
686 + // loaded on non-MetaSync admin-ajax requests (e.g. Quick Edit's inline-save,
687 + // where this filter still fires), so guard against the undefined function.
688 + if (!function_exists('metasync_is_custom_or_lps_page')) {
689 + return $post_states;
872 690 }
873 - // Initialize backoff manager (registers HTTP response filters)
874 - Metasync_API_Backoff_Manager::get_instance();
875 -
876 - // Initialize admin notices (only in admin area)
877 - if (is_admin()) {
878 - Metasync_API_Backoff_Notices::get_instance();
691 + if (!metasync_is_custom_or_lps_page($post->ID)) {
692 + return $post_states;
879 693 }
694 + $post_states['metasync_website_studio'] = __('Website Studio', 'metasync');
695 + return $post_states;
880 696 }
881 -add_action('init', 'metasync_init_api_backoff', 5);
882 697
883 -/**
884 - * Initialize Review Notice
885 - * Shows a dismissible notice asking users to rate the plugin after a usage period
886 - * @since 2.8.0
887 - */
888 -function metasync_init_review_notice() {
889 - if (metasync_is_non_metasync_admin_ajax()) {
890 - return;
891 - }
892 - if (is_admin()) {
893 - Metasync_Review_Notice::get_instance();
894 - }
895 -}
896 -add_action('init', 'metasync_init_review_notice');
897 698
898 699 /**
899 - * Global convenience function to get active JWT token
900 - * Can be called from anywhere in WordPress (themes, other plugins, etc.)
901 - *
902 - * @param bool $force_refresh Force generation of new token even if cached one exists
903 - * @return string|false JWT token on success, false on failure
904 - */
905 -if (!function_exists('metasync_get_jwt_token')) {
906 - function metasync_get_jwt_token($force_refresh = false)
907 - {
908 - return Metasync::get_jwt_token($force_refresh);
909 - }
910 -}
911 -
912 -
913 -/**
914 - * Initialize Debug Mode Manager
915 - * Hooked to 'init' for proper WordPress lifecycle integration
916 - */
917 -function metasync_init_debug_mode_manager() {
918 - if (metasync_is_non_metasync_admin_ajax()) {
919 - return;
920 - }
921 - // Initialize the Debug Mode Manager singleton
922 - Metasync_Debug_Mode_Manager::get_instance();
923 -}
924 -add_action('init', 'metasync_init_debug_mode_manager', 10);
925 -
926 -/**
927 700 * Oxygen Builder Compatibility
928 701 * Auto re-signs [oxygen] dynamic-data shortcodes when their HMAC signatures
929 702 * are invalid (e.g. after design-set import or site migration).
930 703 * Runs once on admin_init; skips entirely when Oxygen is inactive.
@@ -930,5 +703,6 @@
930 703 * Runs once on admin_init; skips entirely when Oxygen is inactive.
931 704 */
932 705 if (is_admin()) {
933 706 add_action('admin_init', ['Metasync_Oxygen_Compat', 'maybe_resign_shortcodes'], 20);
707 + add_filter('display_post_states', 'metasync_add_lps_post_state', 10, 2);
934 708 }