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

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