| @@ -11,13 +11,13 @@ | ||
| 11 | 11 | * @link https://searchatlas.com/ |
| 12 | 12 | * @since 1.0.0 |
| 13 | 13 | * |
| 14 | 14 | * @wordpress-plugin |
| 15 | - * Plugin Name: Search Atlas: The Premier AI SEO Plugin for Instant Optimization | |
| 15 | + * Plugin Name: Search Atlas SEO - OTTO AI SEO Automation for WordPress | |
| 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.6.16 | |
| 19 | - * Requires PHP: 8.2 | |
| 18 | + * Version: 2.7.1 | |
| 19 | + * Requires PHP: 8.1 | |
| 20 | 20 | * Author: Search Atlas |
| 21 | 21 | * Author URI: https://searchatlas.com |
| 22 | 22 | * License: GPL v3 |
| 23 | 23 | * License URI: https://www.gnu.org/licenses/gpl-3.0.txt |
| @@ -31,20 +31,41 @@ | ||
| 31 | 31 | |
| 32 | 32 | // Composer classmap autoloader — loads all plugin classes on demand. |
| 33 | 33 | require_once __DIR__ . '/vendor/autoload.php'; |
| 34 | 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 | +// Consent gate and write-once backup for third-party SEO storage — required | |
| 46 | +// explicitly for the same reason: every writer into Yoast / Rank Math / AIOSEO | |
| 47 | +// consults it, and a class that fails to load would read as "no consent" and | |
| 48 | +// silently stop those writes. | |
| 49 | +require_once __DIR__ . '/includes/class-metasync-seo-backup.php'; | |
| 50 | + | |
| 51 | +// Loaded beside the backup class because it is the only reader of what that | |
| 52 | +// class writes. Restore is inert until something calls it, so loading it early | |
| 53 | +// costs nothing and keeps the pair together. | |
| 54 | +require_once __DIR__ . '/includes/class-metasync-seo-restore.php'; | |
| 55 | + | |
| 35 | 56 | /** |
| 36 | 57 | * Currently plugin version. |
| 37 | 58 | * Start at version 1.0.0 and use SemVer - https://semver.org |
| 38 | 59 | * Rename this for your plugin and update it as you release new versions. |
| 39 | 60 | */ |
| 40 | -$metasync_version = '2.6.16'; | |
| 61 | +$metasync_version = '2.7.1'; | |
| 41 | 62 | define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9'); |
| 42 | 63 | /** |
| 43 | 64 | * Define the current required php version |
| 44 | 65 | * This will be used to validate whether the user can install the plugin or not |
| 45 | 66 | */ |
| 46 | -define('METASYNC_MIN_PHP', '8.2'); | |
| 67 | +define('METASYNC_MIN_PHP', '8.1'); | |
| 47 | 68 | |
| 48 | 69 | /** |
| 49 | 70 | * Define the current required php version |
| 50 | 71 | * This will be used to validate whether the user can install the plugin or not |
| @@ -108,9 +129,9 @@ | ||
| 108 | 129 | return $sanitized; |
| 109 | 130 | } |
| 110 | 131 | } |
| 111 | 132 | |
| 112 | -// Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449 / WP-227). | |
| 133 | +// Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449). | |
| 113 | 134 | if (!function_exists('metasync_is_non_metasync_admin_ajax')) { |
| 114 | 135 | function metasync_is_non_metasync_admin_ajax() { |
| 115 | 136 | static $result = null; |
| 116 | 137 | if ($result !== null) { |
| @@ -115,13 +136,19 @@ | ||
| 115 | 136 | static $result = null; |
| 116 | 137 | if ($result !== null) { |
| 117 | 138 | return $result; |
| 118 | 139 | } |
| 140 | + // The MCP bridge intentionally boots WordPress with DOING_AJAX set, but it | |
| 141 | + // is a MetaSync entry point rather than an unrelated admin AJAX request. | |
| 142 | + // The constant is only ever defined as true by the bridge bootstraps. | |
| 143 | + if (defined('METASYNC_MCP_BRIDGE')) { | |
| 144 | + return ($result = false); | |
| 145 | + } | |
| 119 | 146 | if (!defined('DOING_AJAX') || !DOING_AJAX) { |
| 120 | 147 | return ($result = false); |
| 121 | 148 | } |
| 122 | - $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) | |
| 123 | - : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''); | |
| 149 | + $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- read-only action-name prefix check during boot | |
| 150 | + : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only action-name prefix check during boot | |
| 124 | 151 | if ($action === '') { |
| 125 | 152 | return ($result = true); |
| 126 | 153 | } |
| 127 | 154 | if (strpos($action, 'metasync_') === 0 || strpos($action, 'meta_sync_') === 0 || $action === 'sample-permalink') { |
| @@ -133,9 +160,9 @@ | ||
| 133 | 160 | |
| 134 | 161 | // Lazy-load guard: only initialise the MCP server when the request is actually targeting |
| 135 | 162 | // the MCP REST route (or its sibling SEO-inventory route, which depends on $metasync_mcp_server |
| 136 | 163 | // 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. | |
| 164 | +// touch MCP. | |
| 138 | 165 | if (!function_exists('metasync_is_mcp_rest_request')) { |
| 139 | 166 | function metasync_is_mcp_rest_request(): bool { |
| 140 | 167 | $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ''; |
| 141 | 168 | if ($uri === '') { |
| @@ -161,8 +188,9 @@ | ||
| 161 | 188 | |
| 162 | 189 | // Phase 2 — these files have file-level side effects (add_action outside class body) |
| 163 | 190 | // and must remain as explicit require_once until refactored. |
| 164 | 191 | require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php'; |
| 192 | +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-data-store.php'; | |
| 165 | 193 | require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-error-logger.php'; |
| 166 | 194 | |
| 167 | 195 | // Shared helpers (custom/LPS page detection + query exclusion) — loaded in all |
| 168 | 196 | // request contexts (admin, REST, MCP, AJAX) so every SEO surface uses one rule. |
| @@ -167,8 +195,13 @@ | ||
| 167 | 195 | // Shared helpers (custom/LPS page detection + query exclusion) — loaded in all |
| 168 | 196 | // request contexts (admin, REST, MCP, AJAX) so every SEO surface uses one rule. |
| 169 | 197 | require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-helpers.php'; |
| 170 | 198 | |
| 199 | +// Shared "results per page" selector helper used by the Redirections, 404 | |
| 200 | +// Monitor, Changes Log and Media Library admin list tables. Loaded early so | |
| 201 | +// the helper class is available before any list table instantiates. | |
| 202 | +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-per-page-helper.php'; | |
| 203 | + | |
| 171 | 204 | /** |
| 172 | 205 | * Include the Otto Pixel Php Code |
| 173 | 206 | */ |
| 174 | 207 | if (!metasync_is_non_metasync_admin_ajax()) { |
| @@ -261,9 +294,9 @@ | ||
| 261 | 294 | |
| 262 | 295 | // Set initial version |
| 263 | 296 | update_option('metasync_version', METASYNC_VERSION); |
| 264 | 297 | |
| 265 | - // WP-315: Record activation timestamp so Divi CSS fix transients | |
| 298 | + // Record activation timestamp so Divi CSS fix transients | |
| 266 | 299 | // auto-invalidate after plugin deactivate/reactivate cycles. |
| 267 | 300 | update_option('metasync_activated_at', (string) time()); |
| 268 | 301 | |
| 269 | 302 | // Clear all cache plugins to ensure fresh start |
| @@ -268,9 +301,9 @@ | ||
| 268 | 301 | |
| 269 | 302 | // Clear all cache plugins to ensure fresh start |
| 270 | 303 | Metasync_Cache_Purge::purge_all('plugin_activation'); |
| 271 | 304 | |
| 272 | - // WP-332: Migrate physical sitemap files on activation. | |
| 305 | + // Migrate physical sitemap files on activation. | |
| 273 | 306 | metasync_migrate_physical_sitemaps(); |
| 274 | 307 | } |
| 275 | 308 | |
| 276 | 309 | // Log-sync removed - error monitoring now handled by Sentry |
| @@ -305,9 +338,9 @@ | ||
| 305 | 338 | register_activation_hook(__FILE__, 'activate_metasync'); |
| 306 | 339 | register_deactivation_hook(__FILE__, 'deactivate_metasync'); |
| 307 | 340 | |
| 308 | 341 | /** |
| 309 | - * WP-332: Migrate physical sitemap .xml files into transients and delete them. | |
| 342 | + * Migrate physical sitemap .xml files into transients and delete them. | |
| 310 | 343 | * |
| 311 | 344 | * Physical files in ABSPATH cause nginx/Plesk to 403 before WordPress can |
| 312 | 345 | * serve them. Called from both the activation hook and the version-gate |
| 313 | 346 | * migration so it covers fresh activations and auto-updates. |
| @@ -336,19 +369,90 @@ | ||
| 336 | 369 | if (empty($candidates)) { |
| 337 | 370 | return; |
| 338 | 371 | } |
| 339 | 372 | |
| 373 | + // Ownership gate: other plugins (Yoast and friends) write files with the | |
| 374 | + // exact same names (sitemap_index.xml, sitemap1.xml, ...), so a name match | |
| 375 | + // alone must never authorize deleting a physical file. A file is only | |
| 376 | + // migrated when this plugin left evidence it wrote it. | |
| 377 | + $tracked_names = []; | |
| 378 | + $tracked_files = get_option('metasync_sitemap_files', []); | |
| 379 | + if (is_array($tracked_files)) { | |
| 380 | + foreach ($tracked_files as $entry) { | |
| 381 | + $name = is_array($entry) && isset($entry['filename']) ? $entry['filename'] : $entry; | |
| 382 | + if (is_string($name) && '' !== $name) { | |
| 383 | + $tracked_names[] = $name; | |
| 384 | + } | |
| 385 | + } | |
| 386 | + } | |
| 340 | 387 | $virtual_index = get_option('metasync_sitemap_virtual_index', []); |
| 388 | + if (!is_array($virtual_index)) { | |
| 389 | + $virtual_index = []; | |
| 390 | + } | |
| 391 | + $parked_names = array_keys($virtual_index); | |
| 392 | + $generator_ran = !empty($tracked_names) && false !== get_option('metasync_sitemap_last_generated'); | |
| 393 | + | |
| 394 | + $ours = []; | |
| 395 | + $metasync_files_found = false; | |
| 396 | + foreach ($candidates as $file) { | |
| 397 | + $bn = basename($file); | |
| 398 | + | |
| 399 | + // A parked name proves MetaSync owned the file at an earlier point, but | |
| 400 | + // an expired transient no longer proves that a file currently on disk | |
| 401 | + // is ours. A different plugin may have recreated the same basename. | |
| 402 | + if (in_array($bn, $parked_names, true)) { | |
| 403 | + if (false !== get_transient('metasync_vsm_' . md5($bn))) { | |
| 404 | + continue; | |
| 405 | + } | |
| 406 | + continue; | |
| 407 | + } | |
| 408 | + | |
| 409 | + // Chunk, index, news, and video files are migratable only when their | |
| 410 | + // exact basename is recorded by MetaSync's generator/configuration. | |
| 411 | + if (in_array($bn, $tracked_names, true) && $generator_ran) { | |
| 412 | + $ours[] = $file; | |
| 413 | + $metasync_files_found = true; | |
| 414 | + continue; | |
| 415 | + } | |
| 416 | + | |
| 417 | + if ($bn === 'news-sitemap.xml' && null !== get_option('metasync_news_sitemap_settings', null)) { | |
| 418 | + $ours[] = $file; | |
| 419 | + $metasync_files_found = true; | |
| 420 | + continue; | |
| 421 | + } | |
| 422 | + if ($bn === 'video-sitemap.xml' && null !== get_option('metasync_video_sitemap_settings', null)) { | |
| 423 | + $ours[] = $file; | |
| 424 | + $metasync_files_found = true; | |
| 425 | + } | |
| 426 | + } | |
| 427 | + | |
| 428 | + // Do not infer ownership of sitemap_index.xml from another sitemap: Yoast, | |
| 429 | + // Rank Math, and other SEO plugins commonly use the same root filename. | |
| 430 | + // It must be explicitly recorded in MetaSync's tracked file list. | |
| 431 | + $ours = array_values(array_unique($ours)); | |
| 432 | + | |
| 433 | + if (empty($ours)) { | |
| 434 | + return; | |
| 435 | + } | |
| 436 | + | |
| 341 | 437 | $migrated_files = []; |
| 342 | 438 | |
| 343 | - foreach ($candidates as $file) { | |
| 439 | + foreach ($ours as $file) { | |
| 344 | 440 | $bn = basename($file); |
| 345 | 441 | $content = @file_get_contents($file); |
| 346 | 442 | if (false !== $content) { |
| 347 | 443 | $tkey = 'metasync_vsm_' . md5($bn); |
| 348 | - set_transient($tkey, $content, 30 * DAY_IN_SECONDS); | |
| 444 | + // The news sitemap's entries are only valid inside Google News' | |
| 445 | + // 48-hour window, so it must not be migrated in under a 30-day | |
| 446 | + // TTL — that would re-introduce the staleness the generator's | |
| 447 | + // own bounded TTL exists to prevent. Regenerate-on-miss rebuilds | |
| 448 | + // it with a fresh date_query when this expires. | |
| 449 | + $ttl = ('news-sitemap.xml' === $bn) ? (int) (DAY_IN_SECONDS / 4) : 30 * DAY_IN_SECONDS; | |
| 450 | + set_transient($tkey, $content, $ttl); | |
| 349 | 451 | if (false !== get_transient($tkey)) { |
| 350 | - @unlink($file); | |
| 452 | + // The parked transient doubles as a 30-day backup of the | |
| 453 | + // content before the file is removed from disk. | |
| 454 | + @wp_delete_file($file); | |
| 351 | 455 | $virtual_index[$bn] = $tkey; |
| 352 | 456 | if ($bn !== 'sitemap_index.xml' && $bn !== 'news-sitemap.xml' && $bn !== 'video-sitemap.xml') { |
| 353 | 457 | $migrated_files[] = [ |
| 354 | 458 | 'filename' => $bn, |
| @@ -379,10 +483,17 @@ | ||
| 379 | 483 | |
| 380 | 484 | $current_version = get_option('metasync_version', '0.0.0'); |
| 381 | 485 | $plugin_version = METASYNC_VERSION; |
| 382 | 486 | |
| 383 | - // If versions don't match, run migration | |
| 384 | - if (version_compare($current_version, $plugin_version, '<')) { | |
| 487 | + // If versions don't match, run migration. Dev builds store the 9.9.9 | |
| 488 | + // placeholder version, which compares greater than every real release — | |
| 489 | + // once stored, a plain "<" comparison never fires again and migrations | |
| 490 | + // are silently skipped forever. Also enter when the stored version is the | |
| 491 | + // placeholder but the running version is a real release, so the migration | |
| 492 | + // runner's dev-build escape hatch becomes reachable and the stored version | |
| 493 | + // is corrected to the real release. | |
| 494 | + if (version_compare($current_version, $plugin_version, '<') | |
| 495 | + || ('9.9.9' === $current_version && '9.9.9' !== $plugin_version)) { | |
| 385 | 496 | // Import whitelabel settings only if the JSON file is new or changed |
| 386 | 497 | // (prevents overwriting admin UI changes on every version check) |
| 387 | 498 | Metasync_Activator::check_whitelabel_settings_update(); |
| 388 | 499 | |
| @@ -409,9 +520,9 @@ | ||
| 409 | 520 | update_option('metasync_options', $options); |
| 410 | 521 | } |
| 411 | 522 | } |
| 412 | 523 | |
| 413 | - // WP-299: One-time purge of stale OTTO SEO cron backlog. | |
| 524 | + // One-time purge of stale OTTO SEO cron backlog. | |
| 414 | 525 | // Prior versions could accumulate thousands of metasync_process_seo_job and |
| 415 | 526 | // metasync_process_otto_crawl_url_job events due to unbounded rescheduling. |
| 416 | 527 | // Clear the backlog once on update; the new code prevents re-accumulation. |
| 417 | 528 | if (!get_option('metasync_wp299_cron_cleanup_done')) { |
| @@ -420,9 +531,39 @@ | ||
| 420 | 531 | wp_unschedule_hook('metasync_process_otto_batch_cache_job'); |
| 421 | 532 | update_option('metasync_wp299_cron_cleanup_done', true, false); |
| 422 | 533 | } |
| 423 | 534 | |
| 424 | - // WP-332: Migrate physical sitemap files on version update. | |
| 535 | + // One-time cleanup of canonical values corrupted to the literal | |
| 536 | + // "Array" (emitted as http://Array once the 2.6.16 canonical filters | |
| 537 | + // started reading them). The sanitizer prevents new corruption; this | |
| 538 | + // repairs the rows already in the database. Cache purge below pushes | |
| 539 | + // the clean pages live. Claimed via add_option() — it fails if the row | |
| 540 | + // already exists, so concurrent requests can't run the cleanup twice, | |
| 541 | + // and the claim lands BEFORE the work: everything inside is idempotent | |
| 542 | + // and the read-side sanitizer already protects output if a run is | |
| 543 | + // interrupted. | |
| 544 | + if (false === get_option('metasync_canonical_cleanup_done') | |
| 545 | + && add_option('metasync_canonical_cleanup_done', 'running', '', false)) { | |
| 546 | + MetaSync_DBMigration::cleanup_corrupted_canonicals(); | |
| 547 | + update_option('metasync_canonical_cleanup_done', 'done', false); | |
| 548 | + } | |
| 549 | + | |
| 550 | + // One-time repair of Local Business logos corrupted to "http://<id>". | |
| 551 | + // The sanitizer fix stopped new corruption; this restores the | |
| 552 | + // attachment ID on sites that saved a logo before it. The corrupted | |
| 553 | + // value encodes the original ID exactly, so the rewrite is lossless. | |
| 554 | + // Claimed via add_option() — it fails when the row already exists, so | |
| 555 | + // concurrent requests can't run the repair twice, and the claim lands | |
| 556 | + // BEFORE the work: the repair itself is idempotent, and the read-side | |
| 557 | + // normalisation in the schema output and admin preview already | |
| 558 | + // protects output if a run is interrupted. | |
| 559 | + if (false === get_option('metasync_local_seo_logo_repair_done') | |
| 560 | + && add_option('metasync_local_seo_logo_repair_done', 'running', '', false)) { | |
| 561 | + MetaSync_DBMigration::repair_corrupted_local_seo_logo(); | |
| 562 | + update_option('metasync_local_seo_logo_repair_done', 'done', false); | |
| 563 | + } | |
| 564 | + | |
| 565 | + // Migrate physical sitemap files on version update. | |
| 425 | 566 | metasync_migrate_physical_sitemaps(); |
| 426 | 567 | |
| 427 | 568 | // Run full migration to ensure all tables are up to date |
| 428 | 569 | MetaSync_DBMigration::run_migrations(); |
| @@ -454,9 +595,9 @@ | ||
| 454 | 595 | if (!isset($hook_extra['type']) || $hook_extra['type'] !== 'plugin') { |
| 455 | 596 | return; |
| 456 | 597 | } |
| 457 | 598 | |
| 458 | - // WP-427: By the time upgrader_process_complete fires, the upgrader may have | |
| 599 | + // By the time upgrader_process_complete fires, the upgrader may have | |
| 459 | 600 | // deleted the directory this (old, still-in-memory) copy of the plugin was |
| 460 | 601 | // loaded from — e.g. when the installed dir name differs from the package's |
| 461 | 602 | // root dir ('metasync-develop' vs 'metasync'). The Composer classmap then |
| 462 | 603 | // points at files that no longer exist, so autoloading Metasync_Activator |
| @@ -522,9 +663,9 @@ | ||
| 522 | 663 | } |
| 523 | 664 | } |
| 524 | 665 | |
| 525 | 666 | if ($should_import) { |
| 526 | - Metasync_Activator::check_whitelabel_settings_update(); | |
| 667 | + Metasync_Activator::check_whitelabel_settings_update(true); | |
| 527 | 668 | } |
| 528 | 669 | } |
| 529 | 670 | |
| 530 | 671 | // Hook into WordPress upgrader to detect plugin updates |
| @@ -533,26 +674,31 @@ | ||
| 533 | 674 | /** |
| 534 | 675 | * Fallback: Check for whitelabel file changes on admin pages |
| 535 | 676 | * This handles edge cases where upgrader_process_complete doesn't fire |
| 536 | 677 | * (e.g., FTP uploads, manual file replacements) |
| 537 | - * Only checks once per admin session to minimize performance impact | |
| 678 | + * Only checks once per admin request to minimize performance impact | |
| 538 | 679 | */ |
| 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; | |
| 680 | +function metasync_check_whitelabel_on_admin() | |
| 681 | +{ | |
| 682 | + static $checked = false; | |
| 683 | + if ($checked || !current_user_can('manage_options')) { | |
| 684 | + return; | |
| 685 | + } | |
| 686 | + $checked = true; | |
| 547 | 687 | |
| 548 | -// require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php'; | |
| 549 | -// Metasync_Activator::check_whitelabel_settings_update(); | |
| 550 | -// } | |
| 688 | + require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php'; | |
| 551 | 689 | |
| 552 | -// Check for whitelabel changes on admin pages (fallback for edge cases) | |
| 553 | -// add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1); | |
| 690 | + // Surface a persistently failing White Label import instead of | |
| 691 | + // retrying it silently on every admin request. | |
| 692 | + add_action('admin_notices', array('Metasync_Activator', 'render_whitelabel_import_failure_notice')); | |
| 554 | 693 | |
| 694 | + Metasync_Activator::check_whitelabel_settings_update(); | |
| 695 | +} | |
| 696 | + | |
| 697 | +// Retry package imports on an authorized admin request when an update completed | |
| 698 | +// through the public version-check or stale-classmap fallback path. | |
| 699 | +add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1); | |
| 700 | + | |
| 555 | 701 | // Include Media Optimization Module |
| 556 | 702 | if (!metasync_is_non_metasync_admin_ajax()) { |
| 557 | 703 | require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php'; |
| 558 | 704 | } |
| @@ -569,9 +715,9 @@ | ||
| 569 | 715 | $plugin->run(); |
| 570 | 716 | } |
| 571 | 717 | run_metasync(); |
| 572 | 718 | |
| 573 | -// WP-530: MCP server bootstrap (server + tool registration) extracted to keep this entry file lean. | |
| 719 | +// MCP server bootstrap (server + tool registration) extracted to keep this entry file lean. | |
| 574 | 720 | require_once plugin_dir_path( __FILE__ ) . 'includes/mcp-server-bootstrap.php'; |
| 575 | 721 | |
| 576 | 722 | /** |
| 577 | 723 | * Schedule a daily cron event to auto-purge Sync History records older than 90 days. |
| @@ -601,15 +747,15 @@ | ||
| 601 | 747 | echo '<script>window.__SA_DYO_INITIALIZED__=true;</script>' . "\n"; |
| 602 | 748 | } |
| 603 | 749 | add_action('wp_head', 'metasync_output_dyo_init_flag', 1); |
| 604 | 750 | |
| 605 | -// WP-530: Runtime feature initialisers (GA4, API backoff, review notice, JWT accessor, debug mode) extracted to keep this entry file lean. | |
| 751 | +// Runtime feature initialisers (GA4, API backoff, review notice, JWT accessor, debug mode) extracted to keep this entry file lean. | |
| 606 | 752 | require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-runtime-init.php'; |
| 607 | 753 | |
| 608 | 754 | /** |
| 609 | 755 | * Append a "Website Studio" post state to LPS-synced / MetaSync custom pages in |
| 610 | 756 | * 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). | |
| 757 | + * managed by Website Studio and shouldn't be hand-edited. | |
| 612 | 758 | * |
| 613 | 759 | * Hooks WordPress core's display_post_states filter — the same mechanism that |
| 614 | 760 | * renders the grey inline tags like "— Front Page" / "— Draft" — so the label |
| 615 | 761 | * is native-styled and only appears next to relevant page titles, with no |