PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.8
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.8
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 / metasync.php

metasync.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.8, at metasync.php

861 lines 30.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is read by WordPress to generate the plugin information in the plugin
5 * admin area. This file also includes all of the dependencies used by the plugin,
6 * registers the activation and deactivation functions, and defines a function
7 * that starts the plugin.
8 *
9 * @package Search Atlas SEO
10 * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com
11 * @link https://searchatlas.com/
12 * @since 1.0.0
13 *
14 * @wordpress-plugin
15 * Plugin Name: Search Atlas: The Premier AI SEO Plugin for Instant Optimization
16 * Plugin URI: https://searchatlas.com/
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.8
19 * Author: Search Atlas
20 * Author URI: https://searchatlas.com
21 * License: GPL v3
22 * License URI: https://www.gnu.org/licenses/gpl-3.0.txt
23 * Text Domain: metasync
24 */
25
26 // If this file is called directly, abort.
27 if (!defined('WPINC')) {
28 die;
29 }
30
31 // Composer classmap autoloader — loads all plugin classes on demand.
32 require_once __DIR__ . '/vendor/autoload.php';
33
34 /**
35 * Currently plugin version.
36 * Start at version 1.0.0 and use SemVer - https://semver.org
37 * Rename this for your plugin and update it as you release new versions.
38 */
39 $metasync_version = '2.6.8';
40 define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9');
41 /**
42 * Define the current required php version
43 * This will be used to validate whether the user can install the plugin or not
44 */
45 define('METASYNC_MIN_PHP', '8.2');
46
47 /**
48 * Define the current required php version
49 * This will be used to validate whether the user can install the plugin or not
50 */
51 define('METASYNC_MIN_WP', '5.2');
52
53 /**
54 * Telemetry Configuration Constants
55 * These replace the old database options for better security and consistency
56 */
57 define('METASYNC_SENTRY_PROJECT_ID', '4509950439849985');
58 define('METASYNC_SENTRY_ENVIRONMENT', 'production');
59 define('METASYNC_SENTRY_RELEASE', METASYNC_VERSION);
60 define('METASYNC_SENTRY_SAMPLE_RATE', 1.0);
61
62 /**
63 * GA4 Analytics Configuration
64 * Measurement ID for Google Analytics 4 event tracking (format: G-XXXXXXXX)
65 *
66 * IMPORTANT: This constant is REQUIRED for GA4 tracking to function.
67 * If not defined or empty, all GA4 analytics tracking will be disabled.
68 *
69 * GA4_API_SECRET is required for server-side Measurement Protocol events
70 * (Content Genius, OTTO optimization). Generate it in GA4:
71 * Admin → Data Streams → (stream) → Measurement Protocol → Create
72 */
73 define('METASYNC_GA4_MEASUREMENT_ID', 'G-SBLWW1EMTJ');
74 define('METASYNC_GA4_API_SECRET', 'nMGs22mxQ3qVUy-aInqfZA');
75
76 /**
77 * Define whether to show the plugin status in WordPress admin top navigation bar
78 * Set to false to hide the status indicator
79 */
80 define('METASYNC_SHOW_ADMIN_BAR_STATUS', true);
81
82 /**
83 * Sanitize POST/GET/REQUEST data recursively
84 *
85 * @param array $data Data to sanitize
86 * @return array Sanitized data
87 */
88 if (!function_exists('metasync_sanitize_input_array')) {
89 function metasync_sanitize_input_array($data) {
90 if (!is_array($data)) {
91 return sanitize_text_field($data);
92 }
93
94 $sanitized = [];
95 foreach ($data as $key => $value) {
96 if (is_array($value)) {
97 $sanitized[$key] = metasync_sanitize_input_array($value);
98 } else {
99 // Check if it's a URL
100 if (filter_var($value, FILTER_VALIDATE_URL)) {
101 $sanitized[$key] = esc_url_raw($value);
102 } else {
103 $sanitized[$key] = sanitize_text_field($value);
104 }
105 }
106 }
107 return $sanitized;
108 }
109 }
110
111 // Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449 / WP-227).
112 if (!function_exists('metasync_is_non_metasync_admin_ajax')) {
113 function metasync_is_non_metasync_admin_ajax() {
114 static $result = null;
115 if ($result !== null) {
116 return $result;
117 }
118 if (!defined('DOING_AJAX') || !DOING_AJAX) {
119 return ($result = false);
120 }
121 $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action']))
122 : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '');
123 if ($action === '') {
124 return ($result = true);
125 }
126 if (strpos($action, 'metasync_') === 0 || strpos($action, 'meta_sync_') === 0 || $action === 'sample-permalink') {
127 return ($result = false);
128 }
129 return ($result = true);
130 }
131 }
132
133 // Lazy-load guard: only initialise the MCP server when the request is actually targeting
134 // the MCP REST route (or its sibling SEO-inventory route, which depends on $metasync_mcp_server
135 // 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.
137 if (!function_exists('metasync_is_mcp_rest_request')) {
138 function metasync_is_mcp_rest_request(): bool {
139 $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
140 if ($uri === '') {
141 return false;
142 }
143 $prefix = function_exists('rest_get_url_prefix') ? rest_get_url_prefix() : 'wp-json';
144 $needles = [
145 '/' . $prefix . '/metasync/v1/mcp',
146 '/' . $prefix . '/metasync/v1/seo-inventory',
147 'rest_route=/metasync/v1/mcp',
148 'rest_route=/metasync/v1/seo-inventory',
149 'rest_route=%2Fmetasync%2Fv1%2Fmcp',
150 'rest_route=%2Fmetasync%2Fv1%2Fseo-inventory',
151 ];
152 foreach ($needles as $needle) {
153 if (stripos($uri, $needle) !== false) {
154 return true;
155 }
156 }
157 return false;
158 }
159 }
160
161 // Phase 2 — these files have file-level side effects (add_action outside class body)
162 // and must remain as explicit require_once until refactored.
163 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php';
164 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-error-logger.php';
165
166 /**
167 * Include the Otto Pixel Php Code
168 */
169 if (!metasync_is_non_metasync_admin_ajax()) {
170 require_once plugin_dir_path( __FILE__ ) . '/otto/otto_pixel.php';
171 require_once plugin_dir_path( __FILE__ ) . '/otto/class-metasync-otto-clone-meta-cleaner.php';
172 Metasync_Otto_Clone_Meta_Cleaner::register();
173 }
174
175
176 /**
177 * Initialize OTTO Persistence Settings (registers REST API endpoints)
178 */
179 add_action('init', function() {
180 if (metasync_is_non_metasync_admin_ajax()) {
181 return;
182 }
183 Metasync_Otto_Persistence_Settings::init();
184 }, 5);
185
186 /**
187 * The code that runs during plugin activation.
188 * This action is documented in includes/class-metasync-activator.php
189 */
190 function activate_metasync()
191 {
192 # Include WordPress plugin functions to access plugin metadata
193 if (!function_exists('get_plugin_data')) {
194 require_once ABSPATH . 'wp-admin/includes/plugin.php';
195 }
196
197 # Get plugin data
198 $plugin_data = get_plugin_data(__FILE__);
199
200 # Get the plugin name
201 $plugin_name = $plugin_data['Name'];
202
203 # Get the current WordPress
204 global $wp_version;
205
206 # Get the current php version
207 $php_version = PHP_VERSION;
208
209 # Check for incompatible WordPress version
210 if (version_compare($wp_version, METASYNC_MIN_WP, '<')) {
211
212 #show error
213 wp_die(
214
215 #craft the message
216 sprintf(
217 '%s requires WordPress version %s or later. You are currently using version %s. Please update WordPress to activate this plugin.',
218 esc_html($plugin_name),
219 METASYNC_MIN_WP,
220 esc_html($wp_version)
221 ),
222
223 #the plugin title as page title
224 esc_html($plugin_name).'Plugin Activation Error',
225
226 #include the back link
227 ['back_link' => true]
228 );
229 }
230
231 # Check for incompatible PHP version
232 if (version_compare($php_version, METASYNC_MIN_PHP, '<')) {
233
234 #show error message
235 wp_die(
236
237 #craft the message
238 sprintf(
239 '%s requires PHP version %s or later. You are currently using version %s. Please update PHP to activate this plugin.',
240 esc_html($plugin_name),
241 METASYNC_MIN_PHP,
242 esc_html($php_version)
243 ),
244
245 #the plugin title as page title
246 esc_html($plugin_name).'Plugin Activation Error',
247
248 #include the back link
249 ['back_link' => true]
250 );
251 }
252
253 Metasync_Activator::activate();
254 // class name is changed at class-db-migrations.php
255 MetaSync_DBMigration::activation();
256
257 // Set initial version
258 update_option('metasync_version', METASYNC_VERSION);
259
260 // Clear all cache plugins to ensure fresh start
261 Metasync_Cache_Purge::purge_all('plugin_activation');
262 }
263
264 // Log-sync removed - error monitoring now handled by Sentry
265 // require_once plugin_dir_path(__FILE__) . 'log-sync/log-sync.php';
266
267 /**
268 * Initialize telemetry system for error monitoring and Sentry integration
269 */
270 if (!metasync_is_non_metasync_admin_ajax()) {
271 require_once plugin_dir_path(__FILE__) . 'telemetry/telemetry-init.php';
272 }
273
274 /**
275 * The code that runs during plugin deactivation.
276 * This action is documented in includes/class-metasync-deactivator.php
277 */
278 function deactivate_metasync()
279 {
280 Metasync_Deactivator::deactivate();
281 // class name is changed at class-db-migrations.php
282 MetaSync_DBMigration::deactivation();
283
284 // Clear news/video sitemap caches
285 delete_transient('metasync_vsm_' . md5('news-sitemap.xml'));
286 delete_transient('metasync_vsm_' . md5('video-sitemap.xml'));
287 delete_option('metasync_sitemap_virtual_index');
288
289 // Clear OTTO JS detection cache so re-activation gets a fresh check
290 delete_transient('metasync_otto_js_detected');
291 }
292
293 register_activation_hook(__FILE__, 'activate_metasync');
294 register_deactivation_hook(__FILE__, 'deactivate_metasync');
295
296 /**
297 * Check for plugin updates and run migrations if needed
298 */
299 function check_metasync_updates()
300 {
301 static $checked = false;
302 if ($checked) return;
303 $checked = true;
304
305 $current_version = get_option('metasync_version', '0.0.0');
306 $plugin_version = METASYNC_VERSION;
307
308 // If versions don't match, run migration
309 if (version_compare($current_version, $plugin_version, '<')) {
310 // Import whitelabel settings only if the JSON file is new or changed
311 // (prevents overwriting admin UI changes on every version check)
312 Metasync_Activator::check_whitelabel_settings_update();
313
314 // Run version-specific migrations first
315 MetaSync_DBMigration::run_version_migrations($current_version, $plugin_version);
316
317 // Migration for v2.7.0+: Remove AI Agent, switch to plugin auth token, make MCP always-on
318 if (version_compare($current_version, '2.7.0', '<')) {
319 // Remove old MCP API key option
320 delete_option('metasync_mcp_api_key');
321
322 // Remove MCP enabled/disabled toggle option (MCP is now always enabled)
323 delete_option('metasync_mcp_enabled');
324
325 // Remove AI Agent settings (AI Agent has been removed)
326 delete_option('metasync_ai_agent_mcp_config');
327 delete_option('metasync_ai_agent_ai_config');
328 delete_option('metasync_ai_agent_enabled');
329
330 // Ensure plugin auth token exists
331 $options = get_option('metasync_options', []);
332 if (empty($options['general']['apikey'])) {
333 $options['general']['apikey'] = wp_generate_password(32, false, false);
334 update_option('metasync_options', $options);
335 }
336 }
337
338 // WP-299: One-time purge of stale OTTO SEO cron backlog.
339 // Prior versions could accumulate thousands of metasync_process_seo_job and
340 // metasync_process_otto_crawl_url_job events due to unbounded rescheduling.
341 // Clear the backlog once on update; the new code prevents re-accumulation.
342 if (!get_option('metasync_wp299_cron_cleanup_done')) {
343 wp_unschedule_hook('metasync_process_seo_job');
344 wp_unschedule_hook('metasync_process_otto_crawl_url_job');
345 wp_unschedule_hook('metasync_process_otto_batch_cache_job');
346 update_option('metasync_wp299_cron_cleanup_done', true, false);
347 }
348
349 // Run full migration to ensure all tables are up to date
350 MetaSync_DBMigration::run_migrations();
351
352 // Update stored version
353 update_option('metasync_version', $plugin_version);
354
355 // Clear all cache plugins after update
356 Metasync_Cache_Purge::purge_all('plugin_update');
357
358 // Log the update
359 //error_log("MetaSync: Plugin updated from {$current_version} to {$plugin_version}. Database migration completed.");
360 }
361 }
362
363 // Hook into WordPress init to check for updates
364 add_action('init', 'check_metasync_updates', 1);
365
366 /**
367 * Handle whitelabel settings import after plugin is updated via WordPress admin
368 * This hook fires when plugins are installed/updated through the WordPress updater
369 *
370 * @param WP_Upgrader $upgrader WP_Upgrader instance
371 * @param array $hook_extra Extra arguments passed to hooked filters
372 */
373 function metasync_handle_plugin_upgrade($upgrader, $hook_extra)
374 {
375 // Only process plugin updates/installs
376 if (!isset($hook_extra['type']) || $hook_extra['type'] !== 'plugin') {
377 return;
378 }
379
380 // Only process install and update actions
381 if (!isset($hook_extra['action']) || !in_array($hook_extra['action'], ['install', 'update'], true)) {
382 return;
383 }
384
385 $this_plugin = plugin_basename(__FILE__);
386 $this_plugin_slug = dirname($this_plugin); // Get 'metasync' from 'metasync/metasync.php'
387 $should_import = false;
388
389 // Handle bulk updates
390 if (isset($hook_extra['bulk']) && $hook_extra['bulk'] === true && isset($hook_extra['plugins'])) {
391 foreach ($hook_extra['plugins'] as $plugin) {
392 // Match by exact path OR by plugin slug/directory
393 if ($plugin === $this_plugin || dirname($plugin) === $this_plugin_slug) {
394 $should_import = true;
395 break;
396 }
397 }
398 }
399
400 // Handle single plugin update/install
401 if (isset($hook_extra['plugin'])) {
402 $plugin = $hook_extra['plugin'];
403 // Match by exact path OR by plugin slug/directory
404 if ($plugin === $this_plugin || dirname($plugin) === $this_plugin_slug) {
405 $should_import = true;
406 }
407 }
408
409 // SPECIAL CASE: When uploading plugin via "Add New > Upload Plugin",
410 // WordPress doesn't set the 'plugin' parameter during 'install' action.
411 // Check if we can get the plugin info from the upgrader result or whitelabel file exists.
412 if (!$should_import && $hook_extra['action'] === 'install') {
413 // Check upgrader result for destination
414 if (isset($upgrader->result) && isset($upgrader->result['destination'])) {
415 $destination = $upgrader->result['destination'];
416 // Check if destination contains our plugin slug
417 if (strpos($destination, $this_plugin_slug) !== false) {
418 $should_import = true;
419 }
420 }
421
422 // Fallback: Check if whitelabel file exists in our plugin directory
423 // This means our plugin was just installed/updated with whitelabel settings
424 if (!$should_import) {
425 $whitelabel_file = Metasync_Activator::get_whitelabel_settings_file();
426 if ($whitelabel_file !== false) {
427 $should_import = true;
428 }
429 }
430 }
431
432 if ($should_import) {
433 Metasync_Activator::check_whitelabel_settings_update();
434 }
435 }
436
437 // Hook into WordPress upgrader to detect plugin updates
438 add_action('upgrader_process_complete', 'metasync_handle_plugin_upgrade', 10, 2);
439
440 /**
441 * Fallback: Check for whitelabel file changes on admin pages
442 * This handles edge cases where upgrader_process_complete doesn't fire
443 * (e.g., FTP uploads, manual file replacements)
444 * Only checks once per admin session to minimize performance impact
445 */
446 // function metasync_check_whitelabel_on_admin()
447 // {
448 // // Only check once per admin session to avoid overhead
449 // static $checked = false;
450 // if ($checked) {
451 // return;
452 // }
453 // $checked = true;
454
455 // require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
456 // Metasync_Activator::check_whitelabel_settings_update();
457 // }
458
459 // Check for whitelabel changes on admin pages (fallback for edge cases)
460 // add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1);
461
462 // Include Media Optimization Module
463 if (!metasync_is_non_metasync_admin_ajax()) {
464 require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php';
465 }
466
467 // Include Code Minification & Delivery Module
468 if (!metasync_is_non_metasync_admin_ajax()) {
469 require_once plugin_dir_path(__FILE__) . 'code-minification/code-minification-loader.php';
470 }
471
472
473 function run_metasync()
474 {
475 $plugin = new Metasync();
476 $plugin->run();
477 }
478 run_metasync();
479
480 /**
481 * Initialize WordPress MCP Server
482 *
483 * Safely register a single MCP tool class, logging failures without aborting
484 * subsequent registrations. Extracted as a named function so both production
485 * code and unit tests exercise the same logic (WP-265).
486 *
487 * @param object $server The MCP server instance (must have register_tool()).
488 * @param string $class_name Fully-qualified tool class name.
489 */
490 function metasync_safe_register_mcp_tool($server, $class_name) {
491 static $logged_missing = [];
492 if (!class_exists($class_name)) {
493 if (empty($logged_missing[$class_name])) {
494 error_log('MetaSync MCP: ' . $class_name . ' class not found — skipping registration. Run composer dump-autoload to regenerate the classmap.');
495 $logged_missing[$class_name] = true;
496 }
497 return;
498 }
499 try {
500 $server->register_tool(new $class_name());
501 } catch (\Throwable $e) {
502 error_log('MetaSync MCP: Failed to register tool ' . $class_name . ': ' . $e->getMessage());
503 }
504 }
505
506 /**
507 * Creates and configures the MCP server instance,
508 * registers all available MCP tools for WordPress operations.
509 * Hooked to 'init' for proper WordPress lifecycle integration.
510 */
511 function metasync_init_mcp_server() {
512 if (metasync_is_non_metasync_admin_ajax()) {
513 return;
514 }
515 // Skip the heavy MCP boot (server + sync logger + REST inventory + 100+ tool objects)
516 // unless this request is actually targeting the MCP REST route or was loaded via
517 // the stdio/HTTP bridge. WP-255.
518 if (!metasync_is_mcp_rest_request() && !defined('METASYNC_MCP_BRIDGE')) {
519 return;
520 }
521 // Initialize MCP server
522 global $metasync_mcp_server;
523
524 try {
525 $metasync_mcp_server = new Metasync_MCP_Server();
526
527 // Attach MCP sync logger so all write tool calls are recorded in Sync History.
528 new Metasync_MCP_Sync_Logger();
529
530 // SEO Inventory: shared builder + standalone REST endpoint (WP-135)
531 new Metasync_REST_SEO_Inventory();
532 } catch (\Throwable $e) {
533 error_log('MCP Server Initialization Error: ' . $e->getMessage());
534 return;
535 }
536
537 // Helper: register a single tool, logging failures without aborting subsequent registrations
538 $safe_register = function($class_name) use ($metasync_mcp_server) {
539 metasync_safe_register_mcp_tool($metasync_mcp_server, $class_name);
540 };
541
542 // Register MCP Tools (Total: 92 existing + 8 new = 100 tools total!)
543 // NEW in v2.8.0: +4 Taxonomy Meta tools, +4 Bulk Alt Text tools
544
545 // Post Meta Operations (4 tools)
546 $safe_register('MCP_Tool_Update_Post_Meta');
547 $safe_register('MCP_Tool_Get_Post_Meta');
548 $safe_register('MCP_Tool_Get_SEO_Meta');
549 $safe_register('MCP_Tool_Get_Hreflang_Links');
550
551 // Post Operations (4 tools)
552 $safe_register('MCP_Tool_Get_Post');
553 $safe_register('MCP_Tool_Get_Post_By_URL');
554 $safe_register('MCP_Tool_List_Posts');
555 $safe_register('MCP_Tool_Update_Post');
556 $safe_register('MCP_Tool_Get_Post_Types');
557
558 // SEO Analysis (3 tools)
559 $safe_register('MCP_Tool_Analyze_SEO');
560 $safe_register('MCP_Tool_Check_Indexability');
561 $safe_register('MCP_Tool_SEO_Health_Report');
562
563 // Search Operations (3 tools)
564 $safe_register('MCP_Tool_Search_Posts');
565 $safe_register('MCP_Tool_Search_By_Keyword');
566 $safe_register('MCP_Tool_Find_Missing_Meta');
567
568 // Redirect Management (5 tools)
569 $safe_register('MCP_Tool_Create_Redirect');
570 $safe_register('MCP_Tool_List_Redirects');
571 $safe_register('MCP_Tool_Delete_Redirect');
572 $safe_register('MCP_Tool_Update_Redirect');
573 $safe_register('MCP_Tool_Check_Redirects_Health');
574
575 // 404 Error Monitoring (5 tools)
576 $safe_register('MCP_Tool_List_404_Errors');
577 $safe_register('MCP_Tool_Get_404_Stats');
578 $safe_register('MCP_Tool_Delete_404_Error');
579 $safe_register('MCP_Tool_Clear_404_Errors');
580 $safe_register('MCP_Tool_Create_Redirect_From_404');
581
582 // Robots.txt & Sitemap Management (11 tools - 7 existing + 4 new)
583 $safe_register('MCP_Tool_Get_Robots_Txt');
584 $safe_register('MCP_Tool_Update_Robots_Txt');
585 $safe_register('MCP_Tool_Get_Sitemap_Status');
586 $safe_register('MCP_Tool_Regenerate_Sitemap');
587 $safe_register('MCP_Tool_Exclude_From_Sitemap');
588 $safe_register('MCP_Tool_Add_Robots_Rule');
589 $safe_register('MCP_Tool_Remove_Robots_Rule');
590 $safe_register('MCP_Tool_Parse_Robots_Txt');
591 $safe_register('MCP_Tool_Validate_Robots_Txt');
592 $safe_register('MCP_Tool_Get_News_Sitemap');
593 $safe_register('MCP_Tool_Get_Video_Sitemap');
594
595 // Plugin Settings Management (4 tools)
596 $safe_register('MCP_Tool_Get_Plugin_Settings');
597 $safe_register('MCP_Tool_Update_Plugin_Settings');
598 $safe_register('MCP_Tool_List_Plugin_Settings_Schema');
599 $safe_register('MCP_Tool_Get_MCP_Settings');
600
601 // Schema Markup Management (7 tools)
602 $safe_register('MCP_Tool_Get_Schema_Markup');
603 $safe_register('MCP_Tool_Update_Schema_Markup');
604 $safe_register('MCP_Tool_Add_Schema_Type');
605 $safe_register('MCP_Tool_Remove_Schema_Type');
606 $safe_register('MCP_Tool_Validate_Schema');
607 $safe_register('MCP_Tool_Get_Schema_Content');
608 $safe_register('MCP_Tool_Set_Schema_Content');
609
610 // Google Instant Index (6 tools)
611 $safe_register('MCP_Tool_Instant_Index_Update');
612 $safe_register('MCP_Tool_Instant_Index_Delete');
613 $safe_register('MCP_Tool_Instant_Index_Status');
614 $safe_register('MCP_Tool_Instant_Index_Bulk_Update');
615 $safe_register('MCP_Tool_Get_Instant_Index_Settings');
616 $safe_register('MCP_Tool_Update_Instant_Index_Settings');
617
618 // Custom HTML Pages (5 tools)
619 $safe_register('MCP_Tool_Create_Custom_Page');
620 $safe_register('MCP_Tool_Get_Custom_Page');
621 $safe_register('MCP_Tool_List_Custom_Pages');
622 $safe_register('MCP_Tool_Update_Custom_Page');
623 $safe_register('MCP_Tool_Delete_Custom_Page');
624
625 // HTML to Builder Converter (3 tools)
626 $safe_register('MCP_Tool_Convert_HTML_To_Builder');
627 $safe_register('MCP_Tool_Create_Builder_Page_From_HTML');
628 $safe_register('MCP_Tool_Convert_Custom_Page_To_Builder');
629
630 // Code Snippets (6 tools)
631 $safe_register('MCP_Tool_Get_Header_Snippet');
632 $safe_register('MCP_Tool_Update_Header_Snippet');
633 $safe_register('MCP_Tool_Get_Footer_Snippet');
634 $safe_register('MCP_Tool_Update_Footer_Snippet');
635 $safe_register('MCP_Tool_Get_Post_Snippets');
636 $safe_register('MCP_Tool_Update_Post_Snippets');
637
638 // Categories & Taxonomies (15 tools)
639 $safe_register('MCP_Tool_List_Categories');
640 $safe_register('MCP_Tool_Get_Category');
641 $safe_register('MCP_Tool_Create_Category');
642 $safe_register('MCP_Tool_Update_Category');
643 $safe_register('MCP_Tool_Delete_Category');
644 $safe_register('MCP_Tool_Get_Post_Categories');
645 $safe_register('MCP_Tool_Set_Post_Categories');
646
647 // Tags (8 tools)
648 $safe_register('MCP_Tool_List_Tags');
649 $safe_register('MCP_Tool_Get_Tag');
650 $safe_register('MCP_Tool_Create_Tag');
651 $safe_register('MCP_Tool_Update_Tag');
652 $safe_register('MCP_Tool_Delete_Tag');
653 $safe_register('MCP_Tool_Get_Post_Tags');
654 $safe_register('MCP_Tool_Set_Post_Tags');
655
656 // Featured Images & Media (6 tools)
657 $safe_register('MCP_Tool_Get_Featured_Image');
658 $safe_register('MCP_Tool_Set_Featured_Image');
659 $safe_register('MCP_Tool_Upload_Featured_Image');
660 $safe_register('MCP_Tool_Remove_Featured_Image');
661 $safe_register('MCP_Tool_List_Media');
662 $safe_register('MCP_Tool_Get_Media_Details');
663
664 // Post CRUD Operations (1 tool - delete/restore disabled for safety)
665 $safe_register('MCP_Tool_Create_Post');
666 // $safe_register('MCP_Tool_Delete_Post'); // DISABLED - safety
667 // $safe_register('MCP_Tool_Restore_Post'); // DISABLED - safety
668
669 // Bulk Operations (3 tools - bulk delete disabled for safety)
670 $safe_register('MCP_Tool_Bulk_Update_Meta');
671 $safe_register('MCP_Tool_Bulk_Set_Categories');
672 $safe_register('MCP_Tool_Bulk_Change_Status');
673 // $safe_register('MCP_Tool_Bulk_Delete_Posts'); // DISABLED - safety
674
675 // WordPress Core SEO Settings (10 tools)
676 $safe_register('MCP_Tool_Get_Site_Info');
677 $safe_register('MCP_Tool_Update_Site_Info');
678 $safe_register('MCP_Tool_Get_Permalink_Structure');
679 $safe_register('MCP_Tool_Update_Permalink_Structure');
680 $safe_register('MCP_Tool_Get_Reading_Settings');
681 $safe_register('MCP_Tool_Update_Reading_Settings');
682 $safe_register('MCP_Tool_Get_Search_Visibility');
683 $safe_register('MCP_Tool_Update_Search_Visibility');
684 $safe_register('MCP_Tool_Get_Date_Format');
685 $safe_register('MCP_Tool_Get_Discussion_Settings');
686
687 // Taxonomy Meta Operations (4 tools - NEW in v2.8.0)
688 $safe_register('MCP_Tool_Get_Term_Meta');
689 $safe_register('MCP_Tool_Update_Term_Meta');
690 $safe_register('MCP_Tool_Bulk_Update_Term_Meta');
691 $safe_register('MCP_Tool_List_Terms_With_Meta');
692
693 // Bulk Alt Text Operations (4 tools - NEW in v2.8.0)
694 $safe_register('MCP_Tool_Audit_Alt_Text');
695 $safe_register('MCP_Tool_Bulk_Update_Alt_Text');
696 $safe_register('MCP_Tool_Generate_Alt_Text');
697 $safe_register('MCP_Tool_Validate_Alt_Text');
698
699 // OTTO Persistence Settings (2 tools)
700 $safe_register('MCP_Tool_Get_Otto_Persistence_Settings');
701 $safe_register('MCP_Tool_Update_Otto_Persistence_Settings');
702
703 // OTTO Pipeline Tools (3 tools)
704 $safe_register('MCP_Tool_Trigger_Otto_Optimization');
705 $safe_register('MCP_Tool_Get_Otto_Status');
706 $safe_register('MCP_Tool_Verify_SEO_Output');
707
708 // System Diagnostics & Plugin Info (4 tools)
709 $safe_register('MCP_Tool_System_Diagnostics');
710 $safe_register('MCP_Tool_List_All_Plugins');
711 $safe_register('MCP_Tool_Get_Cron_Jobs');
712 $safe_register('MCP_Tool_Get_WP_Option');
713
714 // SEO Inventory (1 tool — WP-135)
715 $safe_register('MCP_Tool_List_Posts_SEO_Inventory');
716
717 // Read-Only Database Access (3 tools)
718 $safe_register('MCP_Tool_DB_Tables');
719 $safe_register('MCP_Tool_DB_Describe');
720 $safe_register('MCP_Tool_DB_Select');
721
722 // Breadcrumb Tools (1 tool)
723 $safe_register('MCP_Tool_Get_Breadcrumb_Path');
724
725 // Cache Purge (2 tools)
726 $safe_register('MCP_Tool_Cache_Purge_All');
727 $safe_register('MCP_Tool_Cache_Purge_URL');
728
729 // LLMs.txt Tools (5 tools)
730 $safe_register('MCP_Tool_Get_LLMs_Txt');
731 $safe_register('MCP_Tool_Regenerate_LLMs_Txt');
732 $safe_register('MCP_Tool_Get_LLMs_Txt_Settings');
733 $safe_register('MCP_Tool_Update_LLMs_Txt_Settings');
734 $safe_register('MCP_Tool_Get_Post_Markdown');
735
736 // SEO Plugin Audit (4 tools — WP-202)
737 $safe_register('MCP_Tool_Read_SEO_Plugin_Data');
738 $safe_register('MCP_Tool_SEO_Plugin_Diff');
739 $safe_register('MCP_Tool_Sync_To_Active_Plugins');
740 $safe_register('MCP_Tool_Detect_SEO_Conflicts');
741
742 // Allow other plugins/themes to register tools
743 do_action('metasync_mcp_register_tools', $metasync_mcp_server);
744 }
745 add_action('init', 'metasync_init_mcp_server', 5);
746
747 /**
748 * Schedule a daily cron event to auto-purge Sync History records older than 90 days.
749 */
750 function metasync_schedule_sync_log_cleanup() {
751 if (!wp_next_scheduled('metasync_sync_log_daily_cleanup')) {
752 wp_schedule_event(time(), 'daily', 'metasync_sync_log_daily_cleanup');
753 }
754 }
755 add_action('wp', 'metasync_schedule_sync_log_cleanup');
756
757 /**
758 * Cron callback: delete Sync History records older than 90 days.
759 */
760 function metasync_sync_log_cleanup_handler() {
761 $sync_db = new Metasync_Sync_History_Database();
762 $sync_db->delete_older_than_days(90);
763 }
764 add_action('metasync_sync_log_daily_cleanup', 'metasync_sync_log_cleanup_handler');
765
766 /**
767 * Output DYO initialization flag to the frontend
768 * Makes window.__SA_DYO_INITIALIZED__ = true available in the DOM
769 * This indicates the Search Atlas plugin is active and initialized
770 */
771 function metasync_output_dyo_init_flag() {
772 echo '<script>window.__SA_DYO_INITIALIZED__=true;</script>' . "\n";
773 }
774 add_action('wp_head', 'metasync_output_dyo_init_flag', 1);
775
776 /**
777 * Initialize GA4 Analytics for Admin Area
778 * Hooked to admin_init for proper WordPress lifecycle integration
779 * Only loads after WordPress, plugins, and themes are fully loaded
780 */
781 function metasync_init_analytics() {
782 // Only initialize in admin area (excludes AJAX and REST API requests)
783 if (is_admin() && !wp_doing_ajax() && !defined('REST_REQUEST')) {
784 Metasync_GA4::get_instance();
785 }
786 }
787 add_action('admin_init', 'metasync_init_analytics', 10);
788
789 /**
790 * Initialize API Backoff System
791 * Hooked to init for proper WordPress lifecycle integration
792 * Monitors API responses and manages exponential backoff for rate limiting
793 * @since 2.7.1
794 */
795 function metasync_init_api_backoff() {
796 if (metasync_is_non_metasync_admin_ajax()) {
797 return;
798 }
799 // Initialize backoff manager (registers HTTP response filters)
800 Metasync_API_Backoff_Manager::get_instance();
801
802 // Initialize admin notices (only in admin area)
803 if (is_admin()) {
804 Metasync_API_Backoff_Notices::get_instance();
805 }
806 }
807 add_action('init', 'metasync_init_api_backoff', 5);
808
809 /**
810 * Initialize Review Notice
811 * Shows a dismissible notice asking users to rate the plugin after a usage period
812 * @since 2.8.0
813 */
814 function metasync_init_review_notice() {
815 if (metasync_is_non_metasync_admin_ajax()) {
816 return;
817 }
818 if (is_admin()) {
819 Metasync_Review_Notice::get_instance();
820 }
821 }
822 add_action('init', 'metasync_init_review_notice');
823
824 /**
825 * Global convenience function to get active JWT token
826 * Can be called from anywhere in WordPress (themes, other plugins, etc.)
827 *
828 * @param bool $force_refresh Force generation of new token even if cached one exists
829 * @return string|false JWT token on success, false on failure
830 */
831 if (!function_exists('metasync_get_jwt_token')) {
832 function metasync_get_jwt_token($force_refresh = false)
833 {
834 return Metasync::get_jwt_token($force_refresh);
835 }
836 }
837
838
839 /**
840 * Initialize Debug Mode Manager
841 * Hooked to 'init' for proper WordPress lifecycle integration
842 */
843 function metasync_init_debug_mode_manager() {
844 if (metasync_is_non_metasync_admin_ajax()) {
845 return;
846 }
847 // Initialize the Debug Mode Manager singleton
848 Metasync_Debug_Mode_Manager::get_instance();
849 }
850 add_action('init', 'metasync_init_debug_mode_manager', 10);
851
852 /**
853 * Oxygen Builder Compatibility
854 * Auto re-signs [oxygen] dynamic-data shortcodes when their HMAC signatures
855 * are invalid (e.g. after design-set import or site migration).
856 * Runs once on admin_init; skips entirely when Oxygen is inactive.
857 */
858 if (is_admin()) {
859 add_action('admin_init', ['Metasync_Oxygen_Compat', 'maybe_resign_shortcodes'], 20);
860 }
861