PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.6
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.6
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.6, at metasync.php

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