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

698 lines 26.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is read by WordPress to generate the plugin information in the plugin
5 * admin area. This file also includes all of the dependencies used by the plugin,
6 * registers the activation and deactivation functions, and defines a function
7 * that starts the plugin.
8 *
9 * @package Search Atlas SEO
10 * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com
11 * @link https://searchatlas.com/
12 * @since 1.0.0
13 *
14 * @wordpress-plugin
15 * Plugin Name: Search Atlas: The Premier AI SEO Plugin for Instant Optimization
16 * Plugin URI: https://searchatlas.com/
17 * Description: Search Atlas SEO is an intuitive WordPress Plugin that transforms the most complicated, most labor-intensive SEO tasks into streamlined, straightforward processes. With a few clicks, the meta-bulk update feature automates the re-optimization of meta tags using AI to increase clicks. Stay up-to-date with the freshest Google Search data for your entire site or targeted URLs within the Meta Sync plug-in page.
18 * Version: 2.6.25
19 * Requires PHP: 8.1
20 * Author: Search Atlas
21 * Author URI: https://searchatlas.com
22 * License: GPL v3
23 * License URI: https://www.gnu.org/licenses/gpl-3.0.txt
24 * Text Domain: metasync
25 */
26
27 // If this file is called directly, abort.
28 if (!defined('WPINC')) {
29 die;
30 }
31
32 // Composer classmap autoloader — loads all plugin classes on demand.
33 require_once __DIR__ . '/vendor/autoload.php';
34
35 // Canonical sanitizer — required explicitly (not via the committed classmap)
36 // so it is guaranteed loadable everywhere canonicals are read or written.
37 require_once __DIR__ . '/includes/class-metasync-canonical-sanitizer.php';
38
39 // Editor-settings feature flags — required explicitly for the same reason: they
40 // gate output in the OTTO buffer and in third-party filter callbacks, both of
41 // which can run before the classmap has resolved anything else.
42 require_once __DIR__ . '/includes/class-metasync-feature-flags.php';
43 Metasync_Feature_Flags::register_invalidation();
44
45 /**
46 * Currently plugin version.
47 * Start at version 1.0.0 and use SemVer - https://semver.org
48 * Rename this for your plugin and update it as you release new versions.
49 */
50 $metasync_version = '2.6.25';
51 define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9');
52 /**
53 * Define the current required php version
54 * This will be used to validate whether the user can install the plugin or not
55 */
56 define('METASYNC_MIN_PHP', '8.1');
57
58 /**
59 * Define the current required php version
60 * This will be used to validate whether the user can install the plugin or not
61 */
62 define('METASYNC_MIN_WP', '5.2');
63
64 /**
65 * Telemetry Configuration Constants
66 * These replace the old database options for better security and consistency
67 */
68 define('METASYNC_SENTRY_PROJECT_ID', '4509950439849985');
69 define('METASYNC_SENTRY_ENVIRONMENT', 'production');
70 define('METASYNC_SENTRY_RELEASE', METASYNC_VERSION);
71 define('METASYNC_SENTRY_SAMPLE_RATE', 1.0);
72
73 /**
74 * GA4 Analytics Configuration
75 * Measurement ID for Google Analytics 4 event tracking (format: G-XXXXXXXX)
76 *
77 * IMPORTANT: This constant is REQUIRED for GA4 tracking to function.
78 * If not defined or empty, all GA4 analytics tracking will be disabled.
79 *
80 * GA4_API_SECRET is required for server-side Measurement Protocol events
81 * (Content Genius, OTTO optimization). Generate it in GA4:
82 * Admin → Data Streams → (stream) → Measurement Protocol → Create
83 */
84 define('METASYNC_GA4_MEASUREMENT_ID', 'G-SBLWW1EMTJ');
85 define('METASYNC_GA4_API_SECRET', 'nMGs22mxQ3qVUy-aInqfZA');
86
87 /**
88 * Define whether to show the plugin status in WordPress admin top navigation bar
89 * Set to false to hide the status indicator
90 */
91 define('METASYNC_SHOW_ADMIN_BAR_STATUS', true);
92
93 /**
94 * Sanitize POST/GET/REQUEST data recursively
95 *
96 * @param array $data Data to sanitize
97 * @return array Sanitized data
98 */
99 if (!function_exists('metasync_sanitize_input_array')) {
100 function metasync_sanitize_input_array($data) {
101 if (!is_array($data)) {
102 return sanitize_text_field($data);
103 }
104
105 $sanitized = [];
106 foreach ($data as $key => $value) {
107 if (is_array($value)) {
108 $sanitized[$key] = metasync_sanitize_input_array($value);
109 } else {
110 // Check if it's a URL
111 if (filter_var($value, FILTER_VALIDATE_URL)) {
112 $sanitized[$key] = esc_url_raw($value);
113 } else {
114 $sanitized[$key] = sanitize_text_field($value);
115 }
116 }
117 }
118 return $sanitized;
119 }
120 }
121
122 // Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449).
123 if (!function_exists('metasync_is_non_metasync_admin_ajax')) {
124 function metasync_is_non_metasync_admin_ajax() {
125 static $result = null;
126 if ($result !== null) {
127 return $result;
128 }
129 // The MCP bridge intentionally boots WordPress with DOING_AJAX set, but it
130 // is a MetaSync entry point rather than an unrelated admin AJAX request.
131 // The constant is only ever defined as true by the bridge bootstraps.
132 if (defined('METASYNC_MCP_BRIDGE')) {
133 return ($result = false);
134 }
135 if (!defined('DOING_AJAX') || !DOING_AJAX) {
136 return ($result = false);
137 }
138 $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action']))
139 : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '');
140 if ($action === '') {
141 return ($result = true);
142 }
143 if (strpos($action, 'metasync_') === 0 || strpos($action, 'meta_sync_') === 0 || $action === 'sample-permalink') {
144 return ($result = false);
145 }
146 return ($result = true);
147 }
148 }
149
150 // Lazy-load guard: only initialise the MCP server when the request is actually targeting
151 // the MCP REST route (or its sibling SEO-inventory route, which depends on $metasync_mcp_server
152 // for its permission callback). Saves ~2-5 MB / 10-50 ms on the 99.9% of requests that never
153 // touch MCP.
154 if (!function_exists('metasync_is_mcp_rest_request')) {
155 function metasync_is_mcp_rest_request(): bool {
156 $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
157 if ($uri === '') {
158 return false;
159 }
160 $prefix = function_exists('rest_get_url_prefix') ? rest_get_url_prefix() : 'wp-json';
161 $needles = [
162 '/' . $prefix . '/metasync/v1/mcp',
163 '/' . $prefix . '/metasync/v1/seo-inventory',
164 'rest_route=/metasync/v1/mcp',
165 'rest_route=/metasync/v1/seo-inventory',
166 'rest_route=%2Fmetasync%2Fv1%2Fmcp',
167 'rest_route=%2Fmetasync%2Fv1%2Fseo-inventory',
168 ];
169 foreach ($needles as $needle) {
170 if (stripos($uri, $needle) !== false) {
171 return true;
172 }
173 }
174 return false;
175 }
176 }
177
178 // Phase 2 — these files have file-level side effects (add_action outside class body)
179 // and must remain as explicit require_once until refactored.
180 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php';
181 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-error-logger.php';
182
183 // Shared helpers (custom/LPS page detection + query exclusion) — loaded in all
184 // request contexts (admin, REST, MCP, AJAX) so every SEO surface uses one rule.
185 require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-helpers.php';
186
187 // Shared "results per page" selector helper used by the Redirections, 404
188 // Monitor, Changes Log and Media Library admin list tables. Loaded early so
189 // the helper class is available before any list table instantiates.
190 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-per-page-helper.php';
191
192 /**
193 * Include the Otto Pixel Php Code
194 */
195 if (!metasync_is_non_metasync_admin_ajax()) {
196 require_once plugin_dir_path( __FILE__ ) . '/otto/otto_pixel.php';
197 require_once plugin_dir_path( __FILE__ ) . '/otto/class-metasync-otto-clone-meta-cleaner.php';
198 Metasync_Otto_Clone_Meta_Cleaner::register();
199 }
200
201
202 /**
203 * Initialize OTTO Persistence Settings (registers REST API endpoints)
204 */
205 add_action('init', function() {
206 if (metasync_is_non_metasync_admin_ajax()) {
207 return;
208 }
209 Metasync_Otto_Persistence_Settings::init();
210 }, 5);
211
212 /**
213 * The code that runs during plugin activation.
214 * This action is documented in includes/class-metasync-activator.php
215 */
216 function activate_metasync()
217 {
218 # Include WordPress plugin functions to access plugin metadata
219 if (!function_exists('get_plugin_data')) {
220 require_once ABSPATH . 'wp-admin/includes/plugin.php';
221 }
222
223 # Get plugin data
224 $plugin_data = get_plugin_data(__FILE__);
225
226 # Get the plugin name
227 $plugin_name = $plugin_data['Name'];
228
229 # Get the current WordPress
230 global $wp_version;
231
232 # Get the current php version
233 $php_version = PHP_VERSION;
234
235 # Check for incompatible WordPress version
236 if (version_compare($wp_version, METASYNC_MIN_WP, '<')) {
237
238 #show error
239 wp_die(
240
241 #craft the message
242 sprintf(
243 '%s requires WordPress version %s or later. You are currently using version %s. Please update WordPress to activate this plugin.',
244 esc_html($plugin_name),
245 METASYNC_MIN_WP,
246 esc_html($wp_version)
247 ),
248
249 #the plugin title as page title
250 esc_html($plugin_name).'Plugin Activation Error',
251
252 #include the back link
253 ['back_link' => true]
254 );
255 }
256
257 # Check for incompatible PHP version
258 if (version_compare($php_version, METASYNC_MIN_PHP, '<')) {
259
260 #show error message
261 wp_die(
262
263 #craft the message
264 sprintf(
265 '%s requires PHP version %s or later. You are currently using version %s. Please update PHP to activate this plugin.',
266 esc_html($plugin_name),
267 METASYNC_MIN_PHP,
268 esc_html($php_version)
269 ),
270
271 #the plugin title as page title
272 esc_html($plugin_name).'Plugin Activation Error',
273
274 #include the back link
275 ['back_link' => true]
276 );
277 }
278
279 Metasync_Activator::activate();
280 // class name is changed at class-db-migrations.php
281 MetaSync_DBMigration::activation();
282
283 // Set initial version
284 update_option('metasync_version', METASYNC_VERSION);
285
286 // Record activation timestamp so Divi CSS fix transients
287 // auto-invalidate after plugin deactivate/reactivate cycles.
288 update_option('metasync_activated_at', (string) time());
289
290 // Clear all cache plugins to ensure fresh start
291 Metasync_Cache_Purge::purge_all('plugin_activation');
292
293 // Migrate physical sitemap files on activation.
294 metasync_migrate_physical_sitemaps();
295 }
296
297 // Log-sync removed - error monitoring now handled by Sentry
298 // require_once plugin_dir_path(__FILE__) . 'log-sync/log-sync.php';
299
300 /**
301 * Initialize telemetry system for error monitoring and Sentry integration
302 */
303 if (!metasync_is_non_metasync_admin_ajax()) {
304 require_once plugin_dir_path(__FILE__) . 'telemetry/telemetry-init.php';
305 }
306
307 /**
308 * The code that runs during plugin deactivation.
309 * This action is documented in includes/class-metasync-deactivator.php
310 */
311 function deactivate_metasync()
312 {
313 Metasync_Deactivator::deactivate();
314 // class name is changed at class-db-migrations.php
315 MetaSync_DBMigration::deactivation();
316
317 // Clear news/video sitemap caches
318 delete_transient('metasync_vsm_' . md5('news-sitemap.xml'));
319 delete_transient('metasync_vsm_' . md5('video-sitemap.xml'));
320 delete_option('metasync_sitemap_virtual_index');
321
322 // Clear OTTO JS detection cache so re-activation gets a fresh check
323 delete_transient('metasync_otto_js_detected');
324 }
325
326 register_activation_hook(__FILE__, 'activate_metasync');
327 register_deactivation_hook(__FILE__, 'deactivate_metasync');
328
329 /**
330 * Migrate physical sitemap .xml files into transients and delete them.
331 *
332 * Physical files in ABSPATH cause nginx/Plesk to 403 before WordPress can
333 * serve them. Called from both the activation hook and the version-gate
334 * migration so it covers fresh activations and auto-updates.
335 */
336 function metasync_migrate_physical_sitemaps()
337 {
338 update_option('metasync_sitemap_virtual_mode', true, false);
339
340 $candidates = [];
341 $globbed = glob(ABSPATH . 'sitemap*.xml');
342 if (is_array($globbed)) {
343 foreach ($globbed as $file) {
344 $basename = basename($file);
345 if ($basename === 'sitemap_index.xml' || preg_match('/^sitemap\d*\.xml$/', $basename)) {
346 $candidates[] = $file;
347 }
348 }
349 }
350 foreach (['news-sitemap.xml', 'video-sitemap.xml'] as $extra) {
351 $path = ABSPATH . $extra;
352 if (file_exists($path)) {
353 $candidates[] = $path;
354 }
355 }
356
357 if (empty($candidates)) {
358 return;
359 }
360
361 $virtual_index = get_option('metasync_sitemap_virtual_index', []);
362 $migrated_files = [];
363
364 foreach ($candidates as $file) {
365 $bn = basename($file);
366 $content = @file_get_contents($file);
367 if (false !== $content) {
368 $tkey = 'metasync_vsm_' . md5($bn);
369 set_transient($tkey, $content, 30 * DAY_IN_SECONDS);
370 if (false !== get_transient($tkey)) {
371 @unlink($file);
372 $virtual_index[$bn] = $tkey;
373 if ($bn !== 'sitemap_index.xml' && $bn !== 'news-sitemap.xml' && $bn !== 'video-sitemap.xml') {
374 $migrated_files[] = [
375 'filename' => $bn,
376 'url' => home_url('/' . $bn),
377 'lastmod' => current_time('mysql', true),
378 ];
379 }
380 }
381 }
382 }
383
384 update_option('metasync_sitemap_virtual_index', $virtual_index, false);
385
386 if (!empty($migrated_files)) {
387 update_option('metasync_sitemap_files', $migrated_files);
388 update_option('metasync_sitemap_last_generated', current_time('mysql'));
389 }
390 }
391
392 /**
393 * Check for plugin updates and run migrations if needed
394 */
395 function check_metasync_updates()
396 {
397 static $checked = false;
398 if ($checked) return;
399 $checked = true;
400
401 $current_version = get_option('metasync_version', '0.0.0');
402 $plugin_version = METASYNC_VERSION;
403
404 // If versions don't match, run migration
405 if (version_compare($current_version, $plugin_version, '<')) {
406 // Import whitelabel settings only if the JSON file is new or changed
407 // (prevents overwriting admin UI changes on every version check)
408 Metasync_Activator::check_whitelabel_settings_update();
409
410 // Run version-specific migrations first
411 MetaSync_DBMigration::run_version_migrations($current_version, $plugin_version);
412
413 // Migration for v2.7.0+: Remove AI Agent, switch to plugin auth token, make MCP always-on
414 if (version_compare($current_version, '2.7.0', '<')) {
415 // Remove old MCP API key option
416 delete_option('metasync_mcp_api_key');
417
418 // Remove MCP enabled/disabled toggle option (MCP is now always enabled)
419 delete_option('metasync_mcp_enabled');
420
421 // Remove AI Agent settings (AI Agent has been removed)
422 delete_option('metasync_ai_agent_mcp_config');
423 delete_option('metasync_ai_agent_ai_config');
424 delete_option('metasync_ai_agent_enabled');
425
426 // Ensure plugin auth token exists
427 $options = get_option('metasync_options', []);
428 if (empty($options['general']['apikey'])) {
429 $options['general']['apikey'] = wp_generate_password(32, false, false);
430 update_option('metasync_options', $options);
431 }
432 }
433
434 // One-time purge of stale OTTO SEO cron backlog.
435 // Prior versions could accumulate thousands of metasync_process_seo_job and
436 // metasync_process_otto_crawl_url_job events due to unbounded rescheduling.
437 // Clear the backlog once on update; the new code prevents re-accumulation.
438 if (!get_option('metasync_wp299_cron_cleanup_done')) {
439 wp_unschedule_hook('metasync_process_seo_job');
440 wp_unschedule_hook('metasync_process_otto_crawl_url_job');
441 wp_unschedule_hook('metasync_process_otto_batch_cache_job');
442 update_option('metasync_wp299_cron_cleanup_done', true, false);
443 }
444
445 // One-time cleanup of canonical values corrupted to the literal
446 // "Array" (emitted as http://Array once the 2.6.16 canonical filters
447 // started reading them). The sanitizer prevents new corruption; this
448 // repairs the rows already in the database. Cache purge below pushes
449 // the clean pages live. Claimed via add_option() — it fails if the row
450 // already exists, so concurrent requests can't run the cleanup twice,
451 // and the claim lands BEFORE the work: everything inside is idempotent
452 // and the read-side sanitizer already protects output if a run is
453 // interrupted.
454 if (false === get_option('metasync_canonical_cleanup_done')
455 && add_option('metasync_canonical_cleanup_done', 'running', '', false)) {
456 MetaSync_DBMigration::cleanup_corrupted_canonicals();
457 update_option('metasync_canonical_cleanup_done', 'done', false);
458 }
459
460 // One-time repair of Local Business logos corrupted to "http://<id>".
461 // The sanitizer fix stopped new corruption; this restores the
462 // attachment ID on sites that saved a logo before it. The corrupted
463 // value encodes the original ID exactly, so the rewrite is lossless.
464 // Claimed via add_option() — it fails when the row already exists, so
465 // concurrent requests can't run the repair twice, and the claim lands
466 // BEFORE the work: the repair itself is idempotent, and the read-side
467 // normalisation in the schema output and admin preview already
468 // protects output if a run is interrupted.
469 if (false === get_option('metasync_local_seo_logo_repair_done')
470 && add_option('metasync_local_seo_logo_repair_done', 'running', '', false)) {
471 MetaSync_DBMigration::repair_corrupted_local_seo_logo();
472 update_option('metasync_local_seo_logo_repair_done', 'done', false);
473 }
474
475 // Migrate physical sitemap files on version update.
476 metasync_migrate_physical_sitemaps();
477
478 // Run full migration to ensure all tables are up to date
479 MetaSync_DBMigration::run_migrations();
480
481 // Update stored version
482 update_option('metasync_version', $plugin_version);
483
484 // Clear all cache plugins after update
485 Metasync_Cache_Purge::purge_all('plugin_update');
486
487 // Log the update
488 //error_log("MetaSync: Plugin updated from {$current_version} to {$plugin_version}. Database migration completed.");
489 }
490 }
491
492 // Hook into WordPress init to check for updates
493 add_action('init', 'check_metasync_updates', 1);
494
495 /**
496 * Handle whitelabel settings import after plugin is updated via WordPress admin
497 * This hook fires when plugins are installed/updated through the WordPress updater
498 *
499 * @param WP_Upgrader $upgrader WP_Upgrader instance
500 * @param array $hook_extra Extra arguments passed to hooked filters
501 */
502 function metasync_handle_plugin_upgrade($upgrader, $hook_extra)
503 {
504 // Only process plugin updates/installs
505 if (!isset($hook_extra['type']) || $hook_extra['type'] !== 'plugin') {
506 return;
507 }
508
509 // By the time upgrader_process_complete fires, the upgrader may have
510 // deleted the directory this (old, still-in-memory) copy of the plugin was
511 // loaded from — e.g. when the installed dir name differs from the package's
512 // root dir ('metasync-develop' vs 'metasync'). The Composer classmap then
513 // points at files that no longer exist, so autoloading Metasync_Activator
514 // below would fatal. Bail instead; the whitelabel re-import runs on the next
515 // request via check_metasync_updates() once the new copy is active.
516 if (!class_exists('Metasync_Activator', false)) {
517 $activator = __DIR__ . '/includes/class-metasync-activator.php';
518 if (!is_file($activator)) {
519 return;
520 }
521 require_once $activator;
522 }
523
524 // Only process install and update actions
525 if (!isset($hook_extra['action']) || !in_array($hook_extra['action'], ['install', 'update'], true)) {
526 return;
527 }
528
529 $this_plugin = plugin_basename(__FILE__);
530 $this_plugin_slug = dirname($this_plugin); // Get 'metasync' from 'metasync/metasync.php'
531 $should_import = false;
532
533 // Handle bulk updates
534 if (isset($hook_extra['bulk']) && $hook_extra['bulk'] === true && isset($hook_extra['plugins'])) {
535 foreach ($hook_extra['plugins'] as $plugin) {
536 // Match by exact path OR by plugin slug/directory
537 if ($plugin === $this_plugin || dirname($plugin) === $this_plugin_slug) {
538 $should_import = true;
539 break;
540 }
541 }
542 }
543
544 // Handle single plugin update/install
545 if (isset($hook_extra['plugin'])) {
546 $plugin = $hook_extra['plugin'];
547 // Match by exact path OR by plugin slug/directory
548 if ($plugin === $this_plugin || dirname($plugin) === $this_plugin_slug) {
549 $should_import = true;
550 }
551 }
552
553 // SPECIAL CASE: When uploading plugin via "Add New > Upload Plugin",
554 // WordPress doesn't set the 'plugin' parameter during 'install' action.
555 // Check if we can get the plugin info from the upgrader result or whitelabel file exists.
556 if (!$should_import && $hook_extra['action'] === 'install') {
557 // Check upgrader result for destination
558 if (isset($upgrader->result) && isset($upgrader->result['destination'])) {
559 $destination = $upgrader->result['destination'];
560 // Check if destination contains our plugin slug
561 if (strpos($destination, $this_plugin_slug) !== false) {
562 $should_import = true;
563 }
564 }
565
566 // Fallback: Check if whitelabel file exists in our plugin directory
567 // This means our plugin was just installed/updated with whitelabel settings
568 if (!$should_import) {
569 $whitelabel_file = Metasync_Activator::get_whitelabel_settings_file();
570 if ($whitelabel_file !== false) {
571 $should_import = true;
572 }
573 }
574 }
575
576 if ($should_import) {
577 Metasync_Activator::check_whitelabel_settings_update(true);
578 }
579 }
580
581 // Hook into WordPress upgrader to detect plugin updates
582 add_action('upgrader_process_complete', 'metasync_handle_plugin_upgrade', 10, 2);
583
584 /**
585 * Fallback: Check for whitelabel file changes on admin pages
586 * This handles edge cases where upgrader_process_complete doesn't fire
587 * (e.g., FTP uploads, manual file replacements)
588 * Only checks once per admin request to minimize performance impact
589 */
590 function metasync_check_whitelabel_on_admin()
591 {
592 static $checked = false;
593 if ($checked || !current_user_can('manage_options')) {
594 return;
595 }
596 $checked = true;
597
598 require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
599 Metasync_Activator::check_whitelabel_settings_update();
600 }
601
602 // Retry package imports on an authorized admin request when an update completed
603 // through the public version-check or stale-classmap fallback path.
604 add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1);
605
606 // Include Media Optimization Module
607 if (!metasync_is_non_metasync_admin_ajax()) {
608 require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php';
609 }
610
611 // Include Code Minification & Delivery Module
612 if (!metasync_is_non_metasync_admin_ajax()) {
613 require_once plugin_dir_path(__FILE__) . 'code-minification/code-minification-loader.php';
614 }
615
616
617 function run_metasync()
618 {
619 $plugin = new Metasync();
620 $plugin->run();
621 }
622 run_metasync();
623
624 // MCP server bootstrap (server + tool registration) extracted to keep this entry file lean.
625 require_once plugin_dir_path( __FILE__ ) . 'includes/mcp-server-bootstrap.php';
626
627 /**
628 * Schedule a daily cron event to auto-purge Sync History records older than 90 days.
629 */
630 function metasync_schedule_sync_log_cleanup() {
631 if (!wp_next_scheduled('metasync_sync_log_daily_cleanup')) {
632 wp_schedule_event(time(), 'daily', 'metasync_sync_log_daily_cleanup');
633 }
634 }
635 add_action('wp', 'metasync_schedule_sync_log_cleanup');
636
637 /**
638 * Cron callback: delete Sync History records older than 90 days.
639 */
640 function metasync_sync_log_cleanup_handler() {
641 $sync_db = new Metasync_Sync_History_Database();
642 $sync_db->delete_older_than_days(90);
643 }
644 add_action('metasync_sync_log_daily_cleanup', 'metasync_sync_log_cleanup_handler');
645
646 /**
647 * Output DYO initialization flag to the frontend
648 * Makes window.__SA_DYO_INITIALIZED__ = true available in the DOM
649 * This indicates the Search Atlas plugin is active and initialized
650 */
651 function metasync_output_dyo_init_flag() {
652 echo '<script>window.__SA_DYO_INITIALIZED__=true;</script>' . "\n";
653 }
654 add_action('wp_head', 'metasync_output_dyo_init_flag', 1);
655
656 // Runtime feature initialisers (GA4, API backoff, review notice, JWT accessor, debug mode) extracted to keep this entry file lean.
657 require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-runtime-init.php';
658
659 /**
660 * Append a "Website Studio" post state to LPS-synced / MetaSync custom pages in
661 * the admin Pages list, so site owners can tell at a glance which pages are
662 * managed by Website Studio and shouldn't be hand-edited.
663 *
664 * Hooks WordPress core's display_post_states filter — the same mechanism that
665 * renders the grey inline tags like "— Front Page" / "— Draft" — so the label
666 * is native-styled and only appears next to relevant page titles, with no
667 * custom admin column.
668 *
669 * @param string[] $post_states Existing post-state labels keyed by slug.
670 * @param WP_Post $post The post being listed.
671 * @return string[] Possibly-augmented post states.
672 */
673 function metasync_add_lps_post_state($post_states, $post) {
674 // metasync_is_custom_or_lps_page() lives in otto/otto_pixel.php, which is NOT
675 // loaded on non-MetaSync admin-ajax requests (e.g. Quick Edit's inline-save,
676 // where this filter still fires), so guard against the undefined function.
677 if (!function_exists('metasync_is_custom_or_lps_page')) {
678 return $post_states;
679 }
680 if (!metasync_is_custom_or_lps_page($post->ID)) {
681 return $post_states;
682 }
683 $post_states['metasync_website_studio'] = __('Website Studio', 'metasync');
684 return $post_states;
685 }
686
687
688 /**
689 * Oxygen Builder Compatibility
690 * Auto re-signs [oxygen] dynamic-data shortcodes when their HMAC signatures
691 * are invalid (e.g. after design-set import or site migration).
692 * Runs once on admin_init; skips entirely when Oxygen is inactive.
693 */
694 if (is_admin()) {
695 add_action('admin_init', ['Metasync_Oxygen_Compat', 'maybe_resign_shortcodes'], 20);
696 add_filter('display_post_states', 'metasync_add_lps_post_state', 10, 2);
697 }
698