PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.7
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.7
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 2.5.24 All 137 releases
metasync / metasync.php

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

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