PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
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
← All changes | metasync.php +317 -410 2.5.23trunk View file →
@@ -14,9 +14,10 @@
14 14 * @wordpress-plugin
15 15 * Plugin Name: Search Atlas: The Premier AI SEO Plugin for Instant Optimization
16 16 * Plugin URI: https://searchatlas.com/
17 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.5.23
18 + * Version: 2.6.26
19 + * Requires PHP: 8.1
19 20 * Author: Search Atlas
20 21 * Author URI: https://searchatlas.com
21 22 * License: GPL v3
22 23 * License URI: https://www.gnu.org/licenses/gpl-3.0.txt
@@ -27,20 +28,33 @@
27 28 if (!defined('WPINC')) {
28 29 die;
29 30 }
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 +
31 45 /**
32 46 * Currently plugin version.
33 47 * Start at version 1.0.0 and use SemVer - https://semver.org
34 48 * Rename this for your plugin and update it as you release new versions.
35 49 */
36 -$metasync_version = '2.5.23';
50 +$metasync_version = '2.6.26';
37 51 define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9');
38 52 /**
39 53 * Define the current required php version
40 54 * This will be used to validate whether the user can install the plugin or not
41 55 */
42 -define('METASYNC_MIN_PHP', '8.2');
56 +define('METASYNC_MIN_PHP', '8.1');
43 57
44 58 /**
45 59 * Define the current required php version
46 60 * This will be used to validate whether the user can install the plugin or not
@@ -56,19 +70,21 @@
56 70 define('METASYNC_SENTRY_RELEASE', METASYNC_VERSION);
57 71 define('METASYNC_SENTRY_SAMPLE_RATE', 1.0);
58 72
59 73 /**
60 - * Mixpanel Analytics Configuration
61 - * Project token for usage tracking
74 + * GA4 Analytics Configuration
75 + * Measurement ID for Google Analytics 4 event tracking (format: G-XXXXXXXX)
62 76 *
63 - * IMPORTANT: This constant is REQUIRED for Mixpanel tracking to function.
64 - * If not defined or empty, all analytics tracking will be disabled.
77 + * IMPORTANT: This constant is REQUIRED for GA4 tracking to function.
78 + * If not defined or empty, all GA4 analytics tracking will be disabled.
65 79 *
66 - * To disable tracking: Comment out this line or set to empty string
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
67 83 */
84 +define('METASYNC_GA4_MEASUREMENT_ID', 'G-SBLWW1EMTJ');
85 +define('METASYNC_GA4_API_SECRET', 'nMGs22mxQ3qVUy-aInqfZA');
68 86
69 -define('METASYNC_MIXPANEL_TOKEN', '90374a20c197bd2eb8312e0706e3b458');
70 -
71 87 /**
72 88 * Define whether to show the plugin status in WordPress admin top navigation bar
73 89 * Set to false to hide the status indicator
74 90 */
@@ -102,101 +118,95 @@
102 118 return $sanitized;
103 119 }
104 120 }
105 121
106 -/**
107 - * Include the Redirection class early (provides regex pattern utilities used across the plugin)
108 - */
109 -require_once plugin_dir_path( __FILE__ ) . 'redirections/class-metasync-redirection.php';
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 +}
110 149
111 -/**
112 - * Centralized class loading function
113 - */
114 -function metasync_load_class($class_name) {
115 - $class_map = [
116 - 'Metasync_Sync_History_Database' => 'sync-history/class-metasync-sync-history-database.php',
117 - //'Metasync_Admin' => 'admin/class-metasync-admin.php',
118 - //'Metasync_Public' => 'public/class-metasync-public.php',
119 - //'Metasync_Activator' => 'includes/class-metasync-activator.php',
120 - //'MetaSync_DBMigration' => 'database/class-db-migrations.php',
121 - // Add more classes as needed
122 - ];
123 -
124 - if (isset($class_map[$class_name])) {
125 - $file_path = plugin_dir_path(__FILE__) . $class_map[$class_name];
126 - if (file_exists($file_path)) {
127 - require_once $file_path;
128 - }
129 - }
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 + }
130 176 }
131 177
132 -// Register the autoloader
133 -spl_autoload_register('metasync_load_class');
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';
134 182
135 -/**
136 - * Include the Session Helper (must load before other classes that use it)
137 - * @deprecated 2.5.12 - Kept for backward compatibility only
138 - */
139 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-session-helper.php';
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';
140 186
141 -/**
142 - * Include the Auth Manager (WordPress-native authentication without sessions)
143 - * @since 2.5.12
144 - */
145 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-auth-manager.php';
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';
146 191
147 192 /**
148 - * Include the Cache Purge Handler
149 - */
150 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-cache-purge.php';
151 -
152 -/**
153 - * Include the Edge Cache / CDN Purge Handler
154 - */
155 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-edge-cache-purge.php';
156 -
157 -/**
158 - * Include the API Backoff Manager
159 - * Handles exponential backoff for HTTP 429/503 responses
160 - * @since 2.7.1
161 - */
162 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-manager.php';
163 -
164 -/**
165 - * Include the API Backoff Admin Notices
166 - * Displays admin notices when API endpoints are in backoff mode
167 - * @since 2.7.1
168 - */
169 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-notices.php';
170 -
171 -/**
172 - * Include the API Backoff REST API
173 - * Provides REST endpoints for backoff management and monitoring
174 - * @since 2.7.1
175 - */
176 -require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php';
177 -
178 -/**
179 193 * Include the Otto Pixel Php Code
180 194 */
181 -require_once plugin_dir_path( __FILE__ ) . '/otto/otto_pixel.php';
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 +}
182 200
183 -/**
184 - * Include the Otto Persistence Settings
185 - * Handles configuration for which OTTO data should be saved to native WordPress fields
186 - */
187 -require_once plugin_dir_path( __FILE__ ) . '/otto/class-metasync-otto-persistence-settings.php';
188 201
189 202 /**
190 - * Include the Otto Persistence Handler
191 - * Handles actual persistence of OTTO data to native WordPress fields
192 - */
193 -require_once plugin_dir_path( __FILE__ ) . '/otto/class-metasync-otto-persistence-handler.php';
194 -
195 -/**
196 203 * Initialize OTTO Persistence Settings (registers REST API endpoints)
197 204 */
198 205 add_action('init', function() {
206 + if (metasync_is_non_metasync_admin_ajax()) {
207 + return;
208 + }
199 209 Metasync_Otto_Persistence_Settings::init();
200 210 }, 5);
201 211
202 212 /**
@@ -265,10 +275,8 @@
265 275 ['back_link' => true]
266 276 );
267 277 }
268 278
269 - require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
270 - require_once plugin_dir_path(__FILE__) . 'database/class-db-migrations.php';
271 279 Metasync_Activator::activate();
272 280 // class name is changed at class-db-migrations.php
273 281 MetaSync_DBMigration::activation();
274 282
@@ -273,11 +281,18 @@
273 281 MetaSync_DBMigration::activation();
274 282
275 283 // Set initial version
276 284 update_option('metasync_version', METASYNC_VERSION);
277 -
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 +
278 290 // Clear all cache plugins to ensure fresh start
279 291 Metasync_Cache_Purge::purge_all('plugin_activation');
292 +
293 + // Migrate physical sitemap files on activation.
294 + metasync_migrate_physical_sitemaps();
280 295 }
281 296
282 297 // Log-sync removed - error monitoring now handled by Sentry
283 298 // require_once plugin_dir_path(__FILE__) . 'log-sync/log-sync.php';
@@ -284,9 +299,11 @@
284 299
285 300 /**
286 301 * Initialize telemetry system for error monitoring and Sentry integration
287 302 */
288 -require_once plugin_dir_path(__FILE__) . 'telemetry/telemetry-init.php';
303 +if (!metasync_is_non_metasync_admin_ajax()) {
304 + require_once plugin_dir_path(__FILE__) . 'telemetry/telemetry-init.php';
305 +}
289 306
290 307 /**
291 308 * The code that runs during plugin deactivation.
292 309 * This action is documented in includes/class-metasync-deactivator.php
@@ -292,13 +309,19 @@
292 309 * This action is documented in includes/class-metasync-deactivator.php
293 310 */
294 311 function deactivate_metasync()
295 312 {
296 - require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-deactivator.php';
297 - require_once plugin_dir_path(__FILE__) . 'database/class-db-migrations.php';
298 313 Metasync_Deactivator::deactivate();
299 314 // class name is changed at class-db-migrations.php
300 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');
301 324 }
302 325
303 326 register_activation_hook(__FILE__, 'activate_metasync');
304 327 register_deactivation_hook(__FILE__, 'deactivate_metasync');
@@ -303,8 +326,77 @@
303 326 register_activation_hook(__FILE__, 'activate_metasync');
304 327 register_deactivation_hook(__FILE__, 'deactivate_metasync');
305 328
306 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 + // The news sitemap's entries are only valid inside Google News'
370 + // 48-hour window, so it must not be migrated in under a 30-day
371 + // TTL — that would re-introduce the staleness the generator's
372 + // own bounded TTL exists to prevent. Regenerate-on-miss rebuilds
373 + // it with a fresh date_query when this expires.
374 + $ttl = ('news-sitemap.xml' === $bn) ? (int) (DAY_IN_SECONDS / 4) : 30 * DAY_IN_SECONDS;
375 + set_transient($tkey, $content, $ttl);
376 + if (false !== get_transient($tkey)) {
377 + @unlink($file);
378 + $virtual_index[$bn] = $tkey;
379 + if ($bn !== 'sitemap_index.xml' && $bn !== 'news-sitemap.xml' && $bn !== 'video-sitemap.xml') {
380 + $migrated_files[] = [
381 + 'filename' => $bn,
382 + 'url' => home_url('/' . $bn),
383 + 'lastmod' => current_time('mysql', true),
384 + ];
385 + }
386 + }
387 + }
388 + }
389 +
390 + update_option('metasync_sitemap_virtual_index', $virtual_index, false);
391 +
392 + if (!empty($migrated_files)) {
393 + update_option('metasync_sitemap_files', $migrated_files);
394 + update_option('metasync_sitemap_last_generated', current_time('mysql'));
395 + }
396 +}
397 +
398 +/**
307 399 * Check for plugin updates and run migrations if needed
308 400 */
309 401 function check_metasync_updates()
310 402 {
@@ -316,11 +408,8 @@
316 408 $plugin_version = METASYNC_VERSION;
317 409
318 410 // If versions don't match, run migration
319 411 if (version_compare($current_version, $plugin_version, '<')) {
320 - require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
321 - require_once plugin_dir_path(__FILE__) . 'database/class-db-migrations.php';
322 -
323 412 // Import whitelabel settings only if the JSON file is new or changed
324 413 // (prevents overwriting admin UI changes on every version check)
325 414 Metasync_Activator::check_whitelabel_settings_update();
326 415
@@ -347,8 +436,52 @@
347 436 update_option('metasync_options', $options);
348 437 }
349 438 }
350 439
440 + // One-time purge of stale OTTO SEO cron backlog.
441 + // Prior versions could accumulate thousands of metasync_process_seo_job and
442 + // metasync_process_otto_crawl_url_job events due to unbounded rescheduling.
443 + // Clear the backlog once on update; the new code prevents re-accumulation.
444 + if (!get_option('metasync_wp299_cron_cleanup_done')) {
445 + wp_unschedule_hook('metasync_process_seo_job');
446 + wp_unschedule_hook('metasync_process_otto_crawl_url_job');
447 + wp_unschedule_hook('metasync_process_otto_batch_cache_job');
448 + update_option('metasync_wp299_cron_cleanup_done', true, false);
449 + }
450 +
451 + // One-time cleanup of canonical values corrupted to the literal
452 + // "Array" (emitted as http://Array once the 2.6.16 canonical filters
453 + // started reading them). The sanitizer prevents new corruption; this
454 + // repairs the rows already in the database. Cache purge below pushes
455 + // the clean pages live. Claimed via add_option() — it fails if the row
456 + // already exists, so concurrent requests can't run the cleanup twice,
457 + // and the claim lands BEFORE the work: everything inside is idempotent
458 + // and the read-side sanitizer already protects output if a run is
459 + // interrupted.
460 + if (false === get_option('metasync_canonical_cleanup_done')
461 + && add_option('metasync_canonical_cleanup_done', 'running', '', false)) {
462 + MetaSync_DBMigration::cleanup_corrupted_canonicals();
463 + update_option('metasync_canonical_cleanup_done', 'done', false);
464 + }
465 +
466 + // One-time repair of Local Business logos corrupted to "http://<id>".
467 + // The sanitizer fix stopped new corruption; this restores the
468 + // attachment ID on sites that saved a logo before it. The corrupted
469 + // value encodes the original ID exactly, so the rewrite is lossless.
470 + // Claimed via add_option() — it fails when the row already exists, so
471 + // concurrent requests can't run the repair twice, and the claim lands
472 + // BEFORE the work: the repair itself is idempotent, and the read-side
473 + // normalisation in the schema output and admin preview already
474 + // protects output if a run is interrupted.
475 + if (false === get_option('metasync_local_seo_logo_repair_done')
476 + && add_option('metasync_local_seo_logo_repair_done', 'running', '', false)) {
477 + MetaSync_DBMigration::repair_corrupted_local_seo_logo();
478 + update_option('metasync_local_seo_logo_repair_done', 'done', false);
479 + }
480 +
481 + // Migrate physical sitemap files on version update.
482 + metasync_migrate_physical_sitemaps();
483 +
351 484 // Run full migration to ensure all tables are up to date
352 485 MetaSync_DBMigration::run_migrations();
353 486
354 487 // Update stored version
@@ -378,8 +511,23 @@
378 511 if (!isset($hook_extra['type']) || $hook_extra['type'] !== 'plugin') {
379 512 return;
380 513 }
381 514
515 + // By the time upgrader_process_complete fires, the upgrader may have
516 + // deleted the directory this (old, still-in-memory) copy of the plugin was
517 + // loaded from — e.g. when the installed dir name differs from the package's
518 + // root dir ('metasync-develop' vs 'metasync'). The Composer classmap then
519 + // points at files that no longer exist, so autoloading Metasync_Activator
520 + // below would fatal. Bail instead; the whitelabel re-import runs on the next
521 + // request via check_metasync_updates() once the new copy is active.
522 + if (!class_exists('Metasync_Activator', false)) {
523 + $activator = __DIR__ . '/includes/class-metasync-activator.php';
524 + if (!is_file($activator)) {
525 + return;
526 + }
527 + require_once $activator;
528 + }
529 +
382 530 // Only process install and update actions
383 531 if (!isset($hook_extra['action']) || !in_array($hook_extra['action'], ['install', 'update'], true)) {
384 532 return;
385 533 }
@@ -423,9 +571,8 @@
423 571
424 572 // Fallback: Check if whitelabel file exists in our plugin directory
425 573 // This means our plugin was just installed/updated with whitelabel settings
426 574 if (!$should_import) {
427 - require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
428 575 $whitelabel_file = Metasync_Activator::get_whitelabel_settings_file();
429 576 if ($whitelabel_file !== false) {
430 577 $should_import = true;
431 578 }
@@ -432,10 +579,9 @@
432 579 }
433 580 }
434 581
435 582 if ($should_import) {
436 - require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
437 - Metasync_Activator::check_whitelabel_settings_update();
583 + Metasync_Activator::check_whitelabel_settings_update(true);
438 584 }
439 585 }
440 586
441 587 // Hook into WordPress upgrader to detect plugin updates
@@ -444,54 +590,41 @@
444 590 /**
445 591 * Fallback: Check for whitelabel file changes on admin pages
446 592 * This handles edge cases where upgrader_process_complete doesn't fire
447 593 * (e.g., FTP uploads, manual file replacements)
448 - * Only checks once per admin session to minimize performance impact
594 + * Only checks once per admin request to minimize performance impact
449 595 */
450 -// function metasync_check_whitelabel_on_admin()
451 -// {
452 -// // Only check once per admin session to avoid overhead
453 -// static $checked = false;
454 -// if ($checked) {
455 -// return;
456 -// }
457 -// $checked = true;
596 +function metasync_check_whitelabel_on_admin()
597 +{
598 + static $checked = false;
599 + if ($checked || !current_user_can('manage_options')) {
600 + return;
601 + }
602 + $checked = true;
458 603
459 -// require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
460 -// Metasync_Activator::check_whitelabel_settings_update();
461 -// }
604 + require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php';
462 605
463 -// Check for whitelabel changes on admin pages (fallback for edge cases)
464 -// add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1);
606 + // Surface a persistently failing White Label import instead of
607 + // retrying it silently on every admin request.
608 + add_action('admin_notices', array('Metasync_Activator', 'render_whitelabel_import_failure_notice'));
465 609
466 -/**
467 - * The core plugin class that is used to define internationalization,
468 - * admin-specific hooks, and public-facing site hooks.
469 - */
470 -require plugin_dir_path(__FILE__) . 'admin/class-metasync-admin.php';
610 + Metasync_Activator::check_whitelabel_settings_update();
611 +}
471 612
472 -// Include OTTO Debug class for developers
473 -require plugin_dir_path(__FILE__) . 'admin/class-metasync-otto-debug.php';
613 +// Retry package imports on an authorized admin request when an update completed
614 +// through the public version-check or stale-classmap fallback path.
615 +add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1);
474 616
475 -// Include OTTO MCP Integration class for direct MCP tool access
476 -require plugin_dir_path(__FILE__) . 'otto/class-metasync-otto-mcp-integration.php';
477 -
478 -// Include WordPress MCP Server for Model Context Protocol support
479 -require plugin_dir_path(__FILE__) . 'wp-mcp-server/class-metasync-mcp-server.php';
480 -
481 -// Include Mixpanel Analytics Integration
482 -require plugin_dir_path(__FILE__) . 'includes/class-metasync-mixpanel.php';
483 -
484 617 // Include Media Optimization Module
485 -require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php';
618 +if (!metasync_is_non_metasync_admin_ajax()) {
619 + require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php';
620 +}
486 621
487 -try {
488 - require plugin_dir_path(__FILE__) . 'includes/class-metasync.php';
489 -} catch (Exception $e) {
622 +// Include Code Minification & Delivery Module
623 +if (!metasync_is_non_metasync_admin_ajax()) {
624 + require_once plugin_dir_path(__FILE__) . 'code-minification/code-minification-loader.php';
625 +}
490 626
491 - # Log into the default PHP error log and trigger error
492 - error_log($e->getMessage());
493 -}
494 627
495 628 function run_metasync()
496 629 {
497 630 $plugin = new Metasync();
@@ -498,223 +631,30 @@
498 631 $plugin->run();
499 632 }
500 633 run_metasync();
501 634
635 +// MCP server bootstrap (server + tool registration) extracted to keep this entry file lean.
636 +require_once plugin_dir_path( __FILE__ ) . 'includes/mcp-server-bootstrap.php';
637 +
502 638 /**
503 - * Initialize WordPress MCP Server
504 - *
505 - * Creates and configures the MCP server instance,
506 - * registers all available MCP tools for WordPress operations.
507 - * Hooked to 'init' for proper WordPress lifecycle integration.
639 + * Schedule a daily cron event to auto-purge Sync History records older than 90 days.
508 640 */
509 -function metasync_init_mcp_server() {
510 - // Initialize MCP server
511 - global $metasync_mcp_server;
512 - try {
513 - $metasync_mcp_server = new Metasync_MCP_Server();
514 -
515 - // Load tool classes
516 - $tool_path = plugin_dir_path(__FILE__) . 'wp-mcp-server/tools/';
517 - require_once $tool_path . 'class-mcp-tool-post-meta.php';
518 - require_once $tool_path . 'class-mcp-tool-posts.php';
519 - require_once $tool_path . 'class-mcp-tool-seo-analysis.php';
520 - require_once $tool_path . 'class-mcp-tool-search.php';
521 - require_once $tool_path . 'class-mcp-tool-redirects.php';
522 - require_once $tool_path . 'class-mcp-tool-404-monitor.php';
523 - require_once $tool_path . 'class-mcp-tool-robots-sitemap.php';
524 - require_once $tool_path . 'class-mcp-tool-plugin-settings.php';
525 - require_once $tool_path . 'class-mcp-tool-schema-markup.php';
526 - require_once $tool_path . 'class-mcp-tool-instant-index.php';
527 - require_once $tool_path . 'class-mcp-tool-custom-pages.php';
528 - require_once $tool_path . 'class-mcp-tool-html-converter.php';
529 - require_once $tool_path . 'class-mcp-tool-code-snippets.php';
530 - require_once $tool_path . 'class-mcp-tool-taxonomies.php';
531 - require_once $tool_path . 'class-mcp-tool-taxonomy-meta.php';
532 - require_once $tool_path . 'class-mcp-tool-media.php';
533 - require_once $tool_path . 'class-mcp-tool-bulk-alt-text.php';
534 - require_once $tool_path . 'class-mcp-tool-post-crud.php';
535 - require_once $tool_path . 'class-mcp-tool-bulk-operations.php';
536 - require_once $tool_path . 'class-mcp-tool-wordpress-settings.php';
537 - require_once $tool_path . 'class-mcp-tool-otto-persistence.php';
538 - } catch (Exception $e) {
539 - error_log('MCP Server Initialization Error: ' . $e->getMessage());
540 - return;
641 +function metasync_schedule_sync_log_cleanup() {
642 + if (!wp_next_scheduled('metasync_sync_log_daily_cleanup')) {
643 + wp_schedule_event(time(), 'daily', 'metasync_sync_log_daily_cleanup');
541 644 }
645 +}
646 +add_action('wp', 'metasync_schedule_sync_log_cleanup');
542 647
543 - // Helper: register a single tool, logging failures without aborting subsequent registrations
544 - $safe_register = function($tool) use ($metasync_mcp_server) {
545 - try {
546 - $metasync_mcp_server->register_tool($tool);
547 - } catch (Exception $e) {
548 - error_log('MetaSync MCP: Failed to register tool ' . get_class($tool) . ': ' . $e->getMessage());
549 - }
550 - };
551 -
552 - // Register MCP Tools (Total: 92 existing + 8 new = 100 tools total!)
553 - // NEW in v2.8.0: +4 Taxonomy Meta tools, +4 Bulk Alt Text tools
554 -
555 - // Post Meta Operations (3 tools)
556 - $safe_register(new MCP_Tool_Update_Post_Meta());
557 - $safe_register(new MCP_Tool_Get_Post_Meta());
558 - $safe_register(new MCP_Tool_Get_SEO_Meta());
559 -
560 - // Post Operations (4 tools)
561 - $safe_register(new MCP_Tool_Get_Post());
562 - $safe_register(new MCP_Tool_Get_Post_By_URL());
563 - $safe_register(new MCP_Tool_List_Posts());
564 - $safe_register(new MCP_Tool_Update_Post());
565 - $safe_register(new MCP_Tool_Get_Post_Types());
566 -
567 - // SEO Analysis (2 tools)
568 - $safe_register(new MCP_Tool_Analyze_SEO());
569 - $safe_register(new MCP_Tool_Check_Indexability());
570 -
571 - // Search Operations (3 tools)
572 - $safe_register(new MCP_Tool_Search_Posts());
573 - $safe_register(new MCP_Tool_Search_By_Keyword());
574 - $safe_register(new MCP_Tool_Find_Missing_Meta());
575 -
576 - // Redirect Management (4 tools)
577 - $safe_register(new MCP_Tool_Create_Redirect());
578 - $safe_register(new MCP_Tool_List_Redirects());
579 - $safe_register(new MCP_Tool_Delete_Redirect());
580 - $safe_register(new MCP_Tool_Update_Redirect());
581 -
582 - // 404 Error Monitoring (5 tools)
583 - $safe_register(new MCP_Tool_List_404_Errors());
584 - $safe_register(new MCP_Tool_Get_404_Stats());
585 - $safe_register(new MCP_Tool_Delete_404_Error());
586 - $safe_register(new MCP_Tool_Clear_404_Errors());
587 - $safe_register(new MCP_Tool_Create_Redirect_From_404());
588 -
589 - // Robots.txt & Sitemap Management (9 tools - 5 existing + 4 new)
590 - $safe_register(new MCP_Tool_Get_Robots_Txt());
591 - $safe_register(new MCP_Tool_Update_Robots_Txt());
592 - $safe_register(new MCP_Tool_Get_Sitemap_Status());
593 - $safe_register(new MCP_Tool_Regenerate_Sitemap());
594 - $safe_register(new MCP_Tool_Exclude_From_Sitemap());
595 - $safe_register(new MCP_Tool_Add_Robots_Rule());
596 - $safe_register(new MCP_Tool_Remove_Robots_Rule());
597 - $safe_register(new MCP_Tool_Parse_Robots_Txt());
598 - $safe_register(new MCP_Tool_Validate_Robots_Txt());
599 -
600 - // Plugin Settings Management (4 tools)
601 - $safe_register(new MCP_Tool_Get_Plugin_Settings());
602 - $safe_register(new MCP_Tool_Update_Plugin_Settings());
603 - $safe_register(new MCP_Tool_List_Plugin_Settings_Schema());
604 - $safe_register(new MCP_Tool_Get_MCP_Settings());
605 -
606 - // Schema Markup Management (5 tools)
607 - $safe_register(new MCP_Tool_Get_Schema_Markup());
608 - $safe_register(new MCP_Tool_Update_Schema_Markup());
609 - $safe_register(new MCP_Tool_Add_Schema_Type());
610 - $safe_register(new MCP_Tool_Remove_Schema_Type());
611 - $safe_register(new MCP_Tool_Validate_Schema());
612 -
613 - // Google Instant Index (6 tools)
614 - $safe_register(new MCP_Tool_Instant_Index_Update());
615 - $safe_register(new MCP_Tool_Instant_Index_Delete());
616 - $safe_register(new MCP_Tool_Instant_Index_Status());
617 - $safe_register(new MCP_Tool_Instant_Index_Bulk_Update());
618 - $safe_register(new MCP_Tool_Get_Instant_Index_Settings());
619 - $safe_register(new MCP_Tool_Update_Instant_Index_Settings());
620 -
621 - // Custom HTML Pages (5 tools)
622 - $safe_register(new MCP_Tool_Create_Custom_Page());
623 - $safe_register(new MCP_Tool_Get_Custom_Page());
624 - $safe_register(new MCP_Tool_List_Custom_Pages());
625 - $safe_register(new MCP_Tool_Update_Custom_Page());
626 - $safe_register(new MCP_Tool_Delete_Custom_Page());
627 -
628 - // HTML to Builder Converter (3 tools)
629 - $safe_register(new MCP_Tool_Convert_HTML_To_Builder());
630 - $safe_register(new MCP_Tool_Create_Builder_Page_From_HTML());
631 - $safe_register(new MCP_Tool_Convert_Custom_Page_To_Builder());
632 -
633 - // Code Snippets (6 tools)
634 - $safe_register(new MCP_Tool_Get_Header_Snippet());
635 - $safe_register(new MCP_Tool_Update_Header_Snippet());
636 - $safe_register(new MCP_Tool_Get_Footer_Snippet());
637 - $safe_register(new MCP_Tool_Update_Footer_Snippet());
638 - $safe_register(new MCP_Tool_Get_Post_Snippets());
639 - $safe_register(new MCP_Tool_Update_Post_Snippets());
640 -
641 - // Categories & Taxonomies (15 tools)
642 - $safe_register(new MCP_Tool_List_Categories());
643 - $safe_register(new MCP_Tool_Get_Category());
644 - $safe_register(new MCP_Tool_Create_Category());
645 - $safe_register(new MCP_Tool_Update_Category());
646 - $safe_register(new MCP_Tool_Delete_Category());
647 - $safe_register(new MCP_Tool_Get_Post_Categories());
648 - $safe_register(new MCP_Tool_Set_Post_Categories());
649 -
650 - // Tags (8 tools)
651 - $safe_register(new MCP_Tool_List_Tags());
652 - $safe_register(new MCP_Tool_Get_Tag());
653 - $safe_register(new MCP_Tool_Create_Tag());
654 - $safe_register(new MCP_Tool_Update_Tag());
655 - $safe_register(new MCP_Tool_Delete_Tag());
656 - $safe_register(new MCP_Tool_Get_Post_Tags());
657 - $safe_register(new MCP_Tool_Set_Post_Tags());
658 -
659 - // Featured Images & Media (6 tools)
660 - $safe_register(new MCP_Tool_Get_Featured_Image());
661 - $safe_register(new MCP_Tool_Set_Featured_Image());
662 - $safe_register(new MCP_Tool_Upload_Featured_Image());
663 - $safe_register(new MCP_Tool_Remove_Featured_Image());
664 - $safe_register(new MCP_Tool_List_Media());
665 - $safe_register(new MCP_Tool_Get_Media_Details());
666 -
667 - // Post CRUD Operations (1 tool - delete/restore disabled for safety)
668 - $safe_register(new MCP_Tool_Create_Post());
669 - // $safe_register(new MCP_Tool_Delete_Post()); // DISABLED - safety
670 - // $safe_register(new MCP_Tool_Restore_Post()); // DISABLED - safety
671 -
672 - // Bulk Operations (3 tools - bulk delete disabled for safety)
673 - $safe_register(new MCP_Tool_Bulk_Update_Meta());
674 - $safe_register(new MCP_Tool_Bulk_Set_Categories());
675 - $safe_register(new MCP_Tool_Bulk_Change_Status());
676 - // $safe_register(new MCP_Tool_Bulk_Delete_Posts()); // DISABLED - safety
677 -
678 - // WordPress Core SEO Settings (10 tools)
679 - $safe_register(new MCP_Tool_Get_Site_Info());
680 - $safe_register(new MCP_Tool_Update_Site_Info());
681 - $safe_register(new MCP_Tool_Get_Permalink_Structure());
682 - $safe_register(new MCP_Tool_Update_Permalink_Structure());
683 - $safe_register(new MCP_Tool_Get_Reading_Settings());
684 - $safe_register(new MCP_Tool_Update_Reading_Settings());
685 - $safe_register(new MCP_Tool_Get_Search_Visibility());
686 - $safe_register(new MCP_Tool_Update_Search_Visibility());
687 - $safe_register(new MCP_Tool_Get_Date_Format());
688 - $safe_register(new MCP_Tool_Get_Discussion_Settings());
689 -
690 - // Taxonomy Meta Operations (4 tools - NEW in v2.8.0)
691 - $safe_register(new MCP_Tool_Get_Term_Meta());
692 - $safe_register(new MCP_Tool_Update_Term_Meta());
693 - $safe_register(new MCP_Tool_Bulk_Update_Term_Meta());
694 - $safe_register(new MCP_Tool_List_Terms_With_Meta());
695 -
696 - // Bulk Alt Text Operations (4 tools - NEW in v2.8.0)
697 - $safe_register(new MCP_Tool_Audit_Alt_Text());
698 - $safe_register(new MCP_Tool_Bulk_Update_Alt_Text());
699 - $safe_register(new MCP_Tool_Generate_Alt_Text());
700 - $safe_register(new MCP_Tool_Validate_Alt_Text());
701 -
702 - // OTTO Persistence Settings (2 tools)
703 - if (class_exists('MCP_Tool_Get_Otto_Persistence_Settings')) {
704 - $safe_register(new MCP_Tool_Get_Otto_Persistence_Settings());
705 - }
706 - if (class_exists('MCP_Tool_Update_Otto_Persistence_Settings')) {
707 - $safe_register(new MCP_Tool_Update_Otto_Persistence_Settings());
708 - }
709 -
710 - // Allow other plugins/themes to register tools
711 - do_action('metasync_mcp_register_tools', $metasync_mcp_server);
648 +/**
649 + * Cron callback: delete Sync History records older than 90 days.
650 + */
651 +function metasync_sync_log_cleanup_handler() {
652 + $sync_db = new Metasync_Sync_History_Database();
653 + $sync_db->delete_older_than_days(90);
712 654 }
713 -add_action('init', 'metasync_init_mcp_server', 5);
655 +add_action('metasync_sync_log_daily_cleanup', 'metasync_sync_log_cleanup_handler');
714 656
715 -
716 -
717 657 /**
718 658 * Output DYO initialization flag to the frontend
719 659 * Makes window.__SA_DYO_INITIALIZED__ = true available in the DOM
720 660 * This indicates the Search Atlas plugin is active and initialized
@@ -723,79 +663,46 @@
723 663 echo '<script>window.__SA_DYO_INITIALIZED__=true;</script>' . "\n";
724 664 }
725 665 add_action('wp_head', 'metasync_output_dyo_init_flag', 1);
726 666
727 -/**
728 - * Initialize Mixpanel Analytics for Admin Area
729 - * Hooked to admin_init for proper WordPress lifecycle integration
730 - * Only loads after WordPress, plugins, and themes are fully loaded
731 - */
732 -function metasync_init_analytics() {
733 - // Only initialize in admin area (excludes AJAX and REST API requests)
734 - if (is_admin() && !wp_doing_ajax() && !defined('REST_REQUEST')) {
735 - Metasync_Mixpanel::get_instance();
736 - }
737 -}
738 -add_action('admin_init', 'metasync_init_analytics', 10);
667 +// Runtime feature initialisers (GA4, API backoff, review notice, JWT accessor, debug mode) extracted to keep this entry file lean.
668 +require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-runtime-init.php';
739 669
740 670 /**
741 - * Initialize API Backoff System
742 - * Hooked to init for proper WordPress lifecycle integration
743 - * Monitors API responses and manages exponential backoff for rate limiting
744 - * @since 2.7.1
671 + * Append a "Website Studio" post state to LPS-synced / MetaSync custom pages in
672 + * the admin Pages list, so site owners can tell at a glance which pages are
673 + * managed by Website Studio and shouldn't be hand-edited.
674 + *
675 + * Hooks WordPress core's display_post_states filter — the same mechanism that
676 + * renders the grey inline tags like "— Front Page" / "— Draft" — so the label
677 + * is native-styled and only appears next to relevant page titles, with no
678 + * custom admin column.
679 + *
680 + * @param string[] $post_states Existing post-state labels keyed by slug.
681 + * @param WP_Post $post The post being listed.
682 + * @return string[] Possibly-augmented post states.
745 683 */
746 -function metasync_init_api_backoff() {
747 - // Initialize backoff manager (registers HTTP response filters)
748 - Metasync_API_Backoff_Manager::get_instance();
749 -
750 - // Initialize admin notices (only in admin area)
751 - if (is_admin()) {
752 - Metasync_API_Backoff_Notices::get_instance();
684 +function metasync_add_lps_post_state($post_states, $post) {
685 + // metasync_is_custom_or_lps_page() lives in otto/otto_pixel.php, which is NOT
686 + // loaded on non-MetaSync admin-ajax requests (e.g. Quick Edit's inline-save,
687 + // where this filter still fires), so guard against the undefined function.
688 + if (!function_exists('metasync_is_custom_or_lps_page')) {
689 + return $post_states;
753 690 }
754 -}
755 -add_action('init', 'metasync_init_api_backoff', 5);
756 -
757 -/**
758 - * Initialize Review Notice
759 - * Shows a dismissible notice asking users to rate the plugin after a usage period
760 - * @since 2.8.0
761 - */
762 -function metasync_init_review_notice() {
763 - if (is_admin()) {
764 - Metasync_Review_Notice::get_instance();
691 + if (!metasync_is_custom_or_lps_page($post->ID)) {
692 + return $post_states;
765 693 }
694 + $post_states['metasync_website_studio'] = __('Website Studio', 'metasync');
695 + return $post_states;
766 696 }
767 -add_action('init', 'metasync_init_review_notice');
768 697
769 -/**
770 - * Global convenience function to get active JWT token
771 - * Can be called from anywhere in WordPress (themes, other plugins, etc.)
772 - *
773 - * @param bool $force_refresh Force generation of new token even if cached one exists
774 - * @return string|false JWT token on success, false on failure
775 - */
776 -if (!function_exists('metasync_get_jwt_token')) {
777 - function metasync_get_jwt_token($force_refresh = false)
778 - {
779 - return Metasync::get_jwt_token($force_refresh);
780 - }
781 -}
782 698
783 -require plugin_dir_path(__FILE__) . 'MetaSyncDebug.php';
784 -
785 699 /**
786 - * Include Debug Mode Manager
787 - * Handles automatic disable and safety limits for debug mode
788 - * UI integrated into Advanced Settings tab in class-metasync-admin.php
789 - * @since 2.5.15
700 + * Oxygen Builder Compatibility
701 + * Auto re-signs [oxygen] dynamic-data shortcodes when their HMAC signatures
702 + * are invalid (e.g. after design-set import or site migration).
703 + * Runs once on admin_init; skips entirely when Oxygen is inactive.
790 704 */
791 -require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-debug-mode-manager.php';
792 -
793 -/**
794 - * Initialize Debug Mode Manager
795 - * Hooked to 'init' for proper WordPress lifecycle integration
796 - */
797 -function metasync_init_debug_mode_manager() {
798 - // Initialize the Debug Mode Manager singleton
799 - Metasync_Debug_Mode_Manager::get_instance();
705 +if (is_admin()) {
706 + add_action('admin_init', ['Metasync_Oxygen_Compat', 'maybe_resign_shortcodes'], 20);
707 + add_filter('display_post_states', 'metasync_add_lps_post_state', 10, 2);
800 708 }
801 -add_action('init', 'metasync_init_debug_mode_manager', 10);