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

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