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

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