| @@ -11,12 +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.15 | |
| 18 | + * Version: 2.7.2 | |
| 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 |
| @@ -30,20 +31,41 @@ | ||
| 30 | 31 | |
| 31 | 32 | // Composer classmap autoloader — loads all plugin classes on demand. |
| 32 | 33 | require_once __DIR__ . '/vendor/autoload.php'; |
| 33 | 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 | + | |
| 34 | 56 | /** |
| 35 | 57 | * Currently plugin version. |
| 36 | 58 | * Start at version 1.0.0 and use SemVer - https://semver.org |
| 37 | 59 | * Rename this for your plugin and update it as you release new versions. |
| 38 | 60 | */ |
| 39 | -$metasync_version = '2.6.15'; | |
| 61 | +$metasync_version = '2.7.2'; | |
| 40 | 62 | define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9'); |
| 41 | 63 | /** |
| 42 | 64 | * Define the current required php version |
| 43 | 65 | * This will be used to validate whether the user can install the plugin or not |
| 44 | 66 | */ |
| 45 | -define('METASYNC_MIN_PHP', '8.2'); | |
| 67 | +define('METASYNC_MIN_PHP', '8.1'); | |
| 46 | 68 | |
| 47 | 69 | /** |
| 48 | 70 | * Define the current required php version |
| 49 | 71 | * This will be used to validate whether the user can install the plugin or not |
| @@ -73,8 +95,18 @@ | ||
| 73 | 95 | define('METASYNC_GA4_MEASUREMENT_ID', 'G-SBLWW1EMTJ'); |
| 74 | 96 | define('METASYNC_GA4_API_SECRET', 'nMGs22mxQ3qVUy-aInqfZA'); |
| 75 | 97 | |
| 76 | 98 | /** |
| 99 | + * PostHog product analytics configuration. | |
| 100 | + * The write key is a public project key embedded by design (same precedent | |
| 101 | + * as the GA4 secret above); set METASYNC_POSTHOG_API_KEY in the environment | |
| 102 | + * to override it - including to an empty string, which disables PostHog. | |
| 103 | + */ | |
| 104 | +define('METASYNC_POSTHOG_HOST', 'https://posthog.internal.searchatlas.com'); | |
| 105 | +$metasync_posthog_key = getenv('METASYNC_POSTHOG_API_KEY'); | |
| 106 | +define('METASYNC_POSTHOG_API_KEY', false !== $metasync_posthog_key ? $metasync_posthog_key : 'phc_ntSayCJE7Ad9eBHRG6ygBaY4BJMa8rFjXcXESun9ftkh'); // gitleaks:allow -- public write-only PostHog project key | |
| 107 | + | |
| 108 | +/** | |
| 77 | 109 | * Define whether to show the plugin status in WordPress admin top navigation bar |
| 78 | 110 | * Set to false to hide the status indicator |
| 79 | 111 | */ |
| 80 | 112 | define('METASYNC_SHOW_ADMIN_BAR_STATUS', true); |
| @@ -107,9 +139,9 @@ | ||
| 107 | 139 | return $sanitized; |
| 108 | 140 | } |
| 109 | 141 | } |
| 110 | 142 | |
| 111 | -// Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449 / WP-227). | |
| 143 | +// Skip heavy MetaSync init on admin-ajax requests that don't target our own actions (Sentry issue 7441226449). | |
| 112 | 144 | if (!function_exists('metasync_is_non_metasync_admin_ajax')) { |
| 113 | 145 | function metasync_is_non_metasync_admin_ajax() { |
| 114 | 146 | static $result = null; |
| 115 | 147 | if ($result !== null) { |
| @@ -114,13 +146,19 @@ | ||
| 114 | 146 | static $result = null; |
| 115 | 147 | if ($result !== null) { |
| 116 | 148 | return $result; |
| 117 | 149 | } |
| 150 | + // The MCP bridge intentionally boots WordPress with DOING_AJAX set, but it | |
| 151 | + // is a MetaSync entry point rather than an unrelated admin AJAX request. | |
| 152 | + // The constant is only ever defined as true by the bridge bootstraps. | |
| 153 | + if (defined('METASYNC_MCP_BRIDGE')) { | |
| 154 | + return ($result = false); | |
| 155 | + } | |
| 118 | 156 | if (!defined('DOING_AJAX') || !DOING_AJAX) { |
| 119 | 157 | return ($result = false); |
| 120 | 158 | } |
| 121 | - $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) | |
| 122 | - : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''); | |
| 159 | + $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 | |
| 160 | + : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only action-name prefix check during boot | |
| 123 | 161 | if ($action === '') { |
| 124 | 162 | return ($result = true); |
| 125 | 163 | } |
| 126 | 164 | if (strpos($action, 'metasync_') === 0 || strpos($action, 'meta_sync_') === 0 || $action === 'sample-permalink') { |
| @@ -132,9 +170,9 @@ | ||
| 132 | 170 | |
| 133 | 171 | // Lazy-load guard: only initialise the MCP server when the request is actually targeting |
| 134 | 172 | // the MCP REST route (or its sibling SEO-inventory route, which depends on $metasync_mcp_server |
| 135 | 173 | // for its permission callback). Saves ~2-5 MB / 10-50 ms on the 99.9% of requests that never |
| 136 | -// touch MCP. See WP-255. | |
| 174 | +// touch MCP. | |
| 137 | 175 | if (!function_exists('metasync_is_mcp_rest_request')) { |
| 138 | 176 | function metasync_is_mcp_rest_request(): bool { |
| 139 | 177 | $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ''; |
| 140 | 178 | if ($uri === '') { |
| @@ -160,10 +198,20 @@ | ||
| 160 | 198 | |
| 161 | 199 | // Phase 2 — these files have file-level side effects (add_action outside class body) |
| 162 | 200 | // and must remain as explicit require_once until refactored. |
| 163 | 201 | require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php'; |
| 202 | +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-data-store.php'; | |
| 164 | 203 | require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-error-logger.php'; |
| 165 | 204 | |
| 205 | +// Shared helpers (custom/LPS page detection + query exclusion) — loaded in all | |
| 206 | +// request contexts (admin, REST, MCP, AJAX) so every SEO surface uses one rule. | |
| 207 | +require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-helpers.php'; | |
| 208 | + | |
| 209 | +// Shared "results per page" selector helper used by the Redirections, 404 | |
| 210 | +// Monitor, Changes Log and Media Library admin list tables. Loaded early so | |
| 211 | +// the helper class is available before any list table instantiates. | |
| 212 | +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-per-page-helper.php'; | |
| 213 | + | |
| 166 | 214 | /** |
| 167 | 215 | * Include the Otto Pixel Php Code |
| 168 | 216 | */ |
| 169 | 217 | if (!metasync_is_non_metasync_admin_ajax()) { |
| @@ -215,9 +263,9 @@ | ||
| 215 | 263 | #craft the message |
| 216 | 264 | sprintf( |
| 217 | 265 | '%s requires WordPress version %s or later. You are currently using version %s. Please update WordPress to activate this plugin.', |
| 218 | 266 | esc_html($plugin_name), |
| 219 | - METASYNC_MIN_WP, | |
| 267 | + esc_html(METASYNC_MIN_WP), | |
| 220 | 268 | esc_html($wp_version) |
| 221 | 269 | ), |
| 222 | 270 | |
| 223 | 271 | #the plugin title as page title |
| @@ -237,9 +285,9 @@ | ||
| 237 | 285 | #craft the message |
| 238 | 286 | sprintf( |
| 239 | 287 | '%s requires PHP version %s or later. You are currently using version %s. Please update PHP to activate this plugin.', |
| 240 | 288 | esc_html($plugin_name), |
| 241 | - METASYNC_MIN_PHP, | |
| 289 | + esc_html(METASYNC_MIN_PHP), | |
| 242 | 290 | esc_html($php_version) |
| 243 | 291 | ), |
| 244 | 292 | |
| 245 | 293 | #the plugin title as page title |
| @@ -256,9 +304,9 @@ | ||
| 256 | 304 | |
| 257 | 305 | // Set initial version |
| 258 | 306 | update_option('metasync_version', METASYNC_VERSION); |
| 259 | 307 | |
| 260 | - // WP-315: Record activation timestamp so Divi CSS fix transients | |
| 308 | + // Record activation timestamp so Divi CSS fix transients | |
| 261 | 309 | // auto-invalidate after plugin deactivate/reactivate cycles. |
| 262 | 310 | update_option('metasync_activated_at', (string) time()); |
| 263 | 311 | |
| 264 | 312 | // Clear all cache plugins to ensure fresh start |
| @@ -263,10 +311,14 @@ | ||
| 263 | 311 | |
| 264 | 312 | // Clear all cache plugins to ensure fresh start |
| 265 | 313 | Metasync_Cache_Purge::purge_all('plugin_activation'); |
| 266 | 314 | |
| 267 | - // WP-332: Migrate physical sitemap files on activation. | |
| 315 | + // Migrate physical sitemap files on activation. | |
| 268 | 316 | metasync_migrate_physical_sitemaps(); |
| 317 | + | |
| 318 | + // Consent-gated analytics: the activation event only fires if the user | |
| 319 | + // previously opted in (re-activation) — a brand-new install is OFF. | |
| 320 | + Metasync_PostHog::get_instance()->track('plugin_activated', array()); | |
| 269 | 321 | } |
| 270 | 322 | |
| 271 | 323 | // Log-sync removed - error monitoring now handled by Sentry |
| 272 | 324 | // require_once plugin_dir_path(__FILE__) . 'log-sync/log-sync.php'; |
| @@ -300,9 +352,9 @@ | ||
| 300 | 352 | register_activation_hook(__FILE__, 'activate_metasync'); |
| 301 | 353 | register_deactivation_hook(__FILE__, 'deactivate_metasync'); |
| 302 | 354 | |
| 303 | 355 | /** |
| 304 | - * WP-332: Migrate physical sitemap .xml files into transients and delete them. | |
| 356 | + * Migrate physical sitemap .xml files into transients and delete them. | |
| 305 | 357 | * |
| 306 | 358 | * Physical files in ABSPATH cause nginx/Plesk to 403 before WordPress can |
| 307 | 359 | * serve them. Called from both the activation hook and the version-gate |
| 308 | 360 | * migration so it covers fresh activations and auto-updates. |
| @@ -331,19 +383,90 @@ | ||
| 331 | 383 | if (empty($candidates)) { |
| 332 | 384 | return; |
| 333 | 385 | } |
| 334 | 386 | |
| 387 | + // Ownership gate: other plugins (Yoast and friends) write files with the | |
| 388 | + // exact same names (sitemap_index.xml, sitemap1.xml, ...), so a name match | |
| 389 | + // alone must never authorize deleting a physical file. A file is only | |
| 390 | + // migrated when this plugin left evidence it wrote it. | |
| 391 | + $tracked_names = []; | |
| 392 | + $tracked_files = get_option('metasync_sitemap_files', []); | |
| 393 | + if (is_array($tracked_files)) { | |
| 394 | + foreach ($tracked_files as $entry) { | |
| 395 | + $name = is_array($entry) && isset($entry['filename']) ? $entry['filename'] : $entry; | |
| 396 | + if (is_string($name) && '' !== $name) { | |
| 397 | + $tracked_names[] = $name; | |
| 398 | + } | |
| 399 | + } | |
| 400 | + } | |
| 335 | 401 | $virtual_index = get_option('metasync_sitemap_virtual_index', []); |
| 402 | + if (!is_array($virtual_index)) { | |
| 403 | + $virtual_index = []; | |
| 404 | + } | |
| 405 | + $parked_names = array_keys($virtual_index); | |
| 406 | + $generator_ran = !empty($tracked_names) && false !== get_option('metasync_sitemap_last_generated'); | |
| 407 | + | |
| 408 | + $ours = []; | |
| 409 | + $metasync_files_found = false; | |
| 410 | + foreach ($candidates as $file) { | |
| 411 | + $bn = basename($file); | |
| 412 | + | |
| 413 | + // A parked name proves MetaSync owned the file at an earlier point, but | |
| 414 | + // an expired transient no longer proves that a file currently on disk | |
| 415 | + // is ours. A different plugin may have recreated the same basename. | |
| 416 | + if (in_array($bn, $parked_names, true)) { | |
| 417 | + if (false !== get_transient('metasync_vsm_' . md5($bn))) { | |
| 418 | + continue; | |
| 419 | + } | |
| 420 | + continue; | |
| 421 | + } | |
| 422 | + | |
| 423 | + // Chunk, index, news, and video files are migratable only when their | |
| 424 | + // exact basename is recorded by MetaSync's generator/configuration. | |
| 425 | + if (in_array($bn, $tracked_names, true) && $generator_ran) { | |
| 426 | + $ours[] = $file; | |
| 427 | + $metasync_files_found = true; | |
| 428 | + continue; | |
| 429 | + } | |
| 430 | + | |
| 431 | + if ($bn === 'news-sitemap.xml' && null !== get_option('metasync_news_sitemap_settings', null)) { | |
| 432 | + $ours[] = $file; | |
| 433 | + $metasync_files_found = true; | |
| 434 | + continue; | |
| 435 | + } | |
| 436 | + if ($bn === 'video-sitemap.xml' && null !== get_option('metasync_video_sitemap_settings', null)) { | |
| 437 | + $ours[] = $file; | |
| 438 | + $metasync_files_found = true; | |
| 439 | + } | |
| 440 | + } | |
| 441 | + | |
| 442 | + // Do not infer ownership of sitemap_index.xml from another sitemap: Yoast, | |
| 443 | + // Rank Math, and other SEO plugins commonly use the same root filename. | |
| 444 | + // It must be explicitly recorded in MetaSync's tracked file list. | |
| 445 | + $ours = array_values(array_unique($ours)); | |
| 446 | + | |
| 447 | + if (empty($ours)) { | |
| 448 | + return; | |
| 449 | + } | |
| 450 | + | |
| 336 | 451 | $migrated_files = []; |
| 337 | 452 | |
| 338 | - foreach ($candidates as $file) { | |
| 453 | + foreach ($ours as $file) { | |
| 339 | 454 | $bn = basename($file); |
| 340 | 455 | $content = @file_get_contents($file); |
| 341 | 456 | if (false !== $content) { |
| 342 | 457 | $tkey = 'metasync_vsm_' . md5($bn); |
| 343 | - set_transient($tkey, $content, 30 * DAY_IN_SECONDS); | |
| 458 | + // The news sitemap's entries are only valid inside Google News' | |
| 459 | + // 48-hour window, so it must not be migrated in under a 30-day | |
| 460 | + // TTL — that would re-introduce the staleness the generator's | |
| 461 | + // own bounded TTL exists to prevent. Regenerate-on-miss rebuilds | |
| 462 | + // it with a fresh date_query when this expires. | |
| 463 | + $ttl = ('news-sitemap.xml' === $bn) ? (int) (DAY_IN_SECONDS / 4) : 30 * DAY_IN_SECONDS; | |
| 464 | + set_transient($tkey, $content, $ttl); | |
| 344 | 465 | if (false !== get_transient($tkey)) { |
| 345 | - @unlink($file); | |
| 466 | + // The parked transient doubles as a 30-day backup of the | |
| 467 | + // content before the file is removed from disk. | |
| 468 | + @wp_delete_file($file); | |
| 346 | 469 | $virtual_index[$bn] = $tkey; |
| 347 | 470 | if ($bn !== 'sitemap_index.xml' && $bn !== 'news-sitemap.xml' && $bn !== 'video-sitemap.xml') { |
| 348 | 471 | $migrated_files[] = [ |
| 349 | 472 | 'filename' => $bn, |
| @@ -374,10 +497,17 @@ | ||
| 374 | 497 | |
| 375 | 498 | $current_version = get_option('metasync_version', '0.0.0'); |
| 376 | 499 | $plugin_version = METASYNC_VERSION; |
| 377 | 500 | |
| 378 | - // If versions don't match, run migration | |
| 379 | - if (version_compare($current_version, $plugin_version, '<')) { | |
| 501 | + // If versions don't match, run migration. Dev builds store the 9.9.9 | |
| 502 | + // placeholder version, which compares greater than every real release — | |
| 503 | + // once stored, a plain "<" comparison never fires again and migrations | |
| 504 | + // are silently skipped forever. Also enter when the stored version is the | |
| 505 | + // placeholder but the running version is a real release, so the migration | |
| 506 | + // runner's dev-build escape hatch becomes reachable and the stored version | |
| 507 | + // is corrected to the real release. | |
| 508 | + if (version_compare($current_version, $plugin_version, '<') | |
| 509 | + || ('9.9.9' === $current_version && '9.9.9' !== $plugin_version)) { | |
| 380 | 510 | // Import whitelabel settings only if the JSON file is new or changed |
| 381 | 511 | // (prevents overwriting admin UI changes on every version check) |
| 382 | 512 | Metasync_Activator::check_whitelabel_settings_update(); |
| 383 | 513 | |
| @@ -383,8 +513,13 @@ | ||
| 383 | 513 | |
| 384 | 514 | // Run version-specific migrations first |
| 385 | 515 | MetaSync_DBMigration::run_version_migrations($current_version, $plugin_version); |
| 386 | 516 | |
| 517 | + // The developer endpoint-switching panel has been removed. Reset its | |
| 518 | + // persisted staging mode so upgrades cannot keep routing API requests | |
| 519 | + // to staging without a visible warning or a supported recovery path. | |
| 520 | + delete_option('metasync_dev_endpoint_mode'); | |
| 521 | + | |
| 387 | 522 | // Migration for v2.7.0+: Remove AI Agent, switch to plugin auth token, make MCP always-on |
| 388 | 523 | if (version_compare($current_version, '2.7.0', '<')) { |
| 389 | 524 | // Remove old MCP API key option |
| 390 | 525 | delete_option('metasync_mcp_api_key'); |
| @@ -404,9 +539,9 @@ | ||
| 404 | 539 | update_option('metasync_options', $options); |
| 405 | 540 | } |
| 406 | 541 | } |
| 407 | 542 | |
| 408 | - // WP-299: One-time purge of stale OTTO SEO cron backlog. | |
| 543 | + // One-time purge of stale OTTO SEO cron backlog. | |
| 409 | 544 | // Prior versions could accumulate thousands of metasync_process_seo_job and |
| 410 | 545 | // metasync_process_otto_crawl_url_job events due to unbounded rescheduling. |
| 411 | 546 | // Clear the backlog once on update; the new code prevents re-accumulation. |
| 412 | 547 | if (!get_option('metasync_wp299_cron_cleanup_done')) { |
| @@ -415,9 +550,39 @@ | ||
| 415 | 550 | wp_unschedule_hook('metasync_process_otto_batch_cache_job'); |
| 416 | 551 | update_option('metasync_wp299_cron_cleanup_done', true, false); |
| 417 | 552 | } |
| 418 | 553 | |
| 419 | - // WP-332: Migrate physical sitemap files on version update. | |
| 554 | + // One-time cleanup of canonical values corrupted to the literal | |
| 555 | + // "Array" (emitted as http://Array once the 2.6.16 canonical filters | |
| 556 | + // started reading them). The sanitizer prevents new corruption; this | |
| 557 | + // repairs the rows already in the database. Cache purge below pushes | |
| 558 | + // the clean pages live. Claimed via add_option() — it fails if the row | |
| 559 | + // already exists, so concurrent requests can't run the cleanup twice, | |
| 560 | + // and the claim lands BEFORE the work: everything inside is idempotent | |
| 561 | + // and the read-side sanitizer already protects output if a run is | |
| 562 | + // interrupted. | |
| 563 | + if (false === get_option('metasync_canonical_cleanup_done') | |
| 564 | + && add_option('metasync_canonical_cleanup_done', 'running', '', false)) { | |
| 565 | + MetaSync_DBMigration::cleanup_corrupted_canonicals(); | |
| 566 | + update_option('metasync_canonical_cleanup_done', 'done', false); | |
| 567 | + } | |
| 568 | + | |
| 569 | + // One-time repair of Local Business logos corrupted to "http://<id>". | |
| 570 | + // The sanitizer fix stopped new corruption; this restores the | |
| 571 | + // attachment ID on sites that saved a logo before it. The corrupted | |
| 572 | + // value encodes the original ID exactly, so the rewrite is lossless. | |
| 573 | + // Claimed via add_option() — it fails when the row already exists, so | |
| 574 | + // concurrent requests can't run the repair twice, and the claim lands | |
| 575 | + // BEFORE the work: the repair itself is idempotent, and the read-side | |
| 576 | + // normalisation in the schema output and admin preview already | |
| 577 | + // protects output if a run is interrupted. | |
| 578 | + if (false === get_option('metasync_local_seo_logo_repair_done') | |
| 579 | + && add_option('metasync_local_seo_logo_repair_done', 'running', '', false)) { | |
| 580 | + MetaSync_DBMigration::repair_corrupted_local_seo_logo(); | |
| 581 | + update_option('metasync_local_seo_logo_repair_done', 'done', false); | |
| 582 | + } | |
| 583 | + | |
| 584 | + // Migrate physical sitemap files on version update. | |
| 420 | 585 | metasync_migrate_physical_sitemaps(); |
| 421 | 586 | |
| 422 | 587 | // Run full migration to ensure all tables are up to date |
| 423 | 588 | MetaSync_DBMigration::run_migrations(); |
| @@ -449,9 +614,9 @@ | ||
| 449 | 614 | if (!isset($hook_extra['type']) || $hook_extra['type'] !== 'plugin') { |
| 450 | 615 | return; |
| 451 | 616 | } |
| 452 | 617 | |
| 453 | - // WP-427: By the time upgrader_process_complete fires, the upgrader may have | |
| 618 | + // By the time upgrader_process_complete fires, the upgrader may have | |
| 454 | 619 | // deleted the directory this (old, still-in-memory) copy of the plugin was |
| 455 | 620 | // loaded from — e.g. when the installed dir name differs from the package's |
| 456 | 621 | // root dir ('metasync-develop' vs 'metasync'). The Composer classmap then |
| 457 | 622 | // points at files that no longer exist, so autoloading Metasync_Activator |
| @@ -517,9 +682,9 @@ | ||
| 517 | 682 | } |
| 518 | 683 | } |
| 519 | 684 | |
| 520 | 685 | if ($should_import) { |
| 521 | - Metasync_Activator::check_whitelabel_settings_update(); | |
| 686 | + Metasync_Activator::check_whitelabel_settings_update(true); | |
| 522 | 687 | } |
| 523 | 688 | } |
| 524 | 689 | |
| 525 | 690 | // Hook into WordPress upgrader to detect plugin updates |
| @@ -528,26 +693,31 @@ | ||
| 528 | 693 | /** |
| 529 | 694 | * Fallback: Check for whitelabel file changes on admin pages |
| 530 | 695 | * This handles edge cases where upgrader_process_complete doesn't fire |
| 531 | 696 | * (e.g., FTP uploads, manual file replacements) |
| 532 | - * Only checks once per admin session to minimize performance impact | |
| 697 | + * Only checks once per admin request to minimize performance impact | |
| 533 | 698 | */ |
| 534 | -// function metasync_check_whitelabel_on_admin() | |
| 535 | -// { | |
| 536 | -// // Only check once per admin session to avoid overhead | |
| 537 | -// static $checked = false; | |
| 538 | -// if ($checked) { | |
| 539 | -// return; | |
| 540 | -// } | |
| 541 | -// $checked = true; | |
| 699 | +function metasync_check_whitelabel_on_admin() | |
| 700 | +{ | |
| 701 | + static $checked = false; | |
| 702 | + if ($checked || !current_user_can('manage_options')) { | |
| 703 | + return; | |
| 704 | + } | |
| 705 | + $checked = true; | |
| 542 | 706 | |
| 543 | -// require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php'; | |
| 544 | -// Metasync_Activator::check_whitelabel_settings_update(); | |
| 545 | -// } | |
| 707 | + require_once plugin_dir_path(__FILE__) . 'includes/class-metasync-activator.php'; | |
| 546 | 708 | |
| 547 | -// Check for whitelabel changes on admin pages (fallback for edge cases) | |
| 548 | -// add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1); | |
| 709 | + // Surface a persistently failing White Label import instead of | |
| 710 | + // retrying it silently on every admin request. | |
| 711 | + add_action('admin_notices', array('Metasync_Activator', 'render_whitelabel_import_failure_notice')); | |
| 549 | 712 | |
| 713 | + Metasync_Activator::check_whitelabel_settings_update(); | |
| 714 | +} | |
| 715 | + | |
| 716 | +// Retry package imports on an authorized admin request when an update completed | |
| 717 | +// through the public version-check or stale-classmap fallback path. | |
| 718 | +add_action('admin_init', 'metasync_check_whitelabel_on_admin', 1); | |
| 719 | + | |
| 550 | 720 | // Include Media Optimization Module |
| 551 | 721 | if (!metasync_is_non_metasync_admin_ajax()) { |
| 552 | 722 | require_once plugin_dir_path(__FILE__) . 'media-optimization/media-optimization-loader.php'; |
| 553 | 723 | } |
| @@ -564,277 +734,12 @@ | ||
| 564 | 734 | $plugin->run(); |
| 565 | 735 | } |
| 566 | 736 | run_metasync(); |
| 567 | 737 | |
| 568 | -/** | |
| 569 | - * Initialize WordPress MCP Server | |
| 570 | - * | |
| 571 | - * Safely register a single MCP tool class, logging failures without aborting | |
| 572 | - * subsequent registrations. Extracted as a named function so both production | |
| 573 | - * code and unit tests exercise the same logic (WP-265). | |
| 574 | - * | |
| 575 | - * @param object $server The MCP server instance (must have register_tool()). | |
| 576 | - * @param string $class_name Fully-qualified tool class name. | |
| 577 | - */ | |
| 578 | -function metasync_safe_register_mcp_tool($server, $class_name) { | |
| 579 | - static $logged_missing = []; | |
| 580 | - if (!class_exists($class_name)) { | |
| 581 | - if (empty($logged_missing[$class_name])) { | |
| 582 | - error_log('MetaSync MCP: ' . $class_name . ' class not found — skipping registration. Run composer dump-autoload to regenerate the classmap.'); | |
| 583 | - $logged_missing[$class_name] = true; | |
| 584 | - } | |
| 585 | - return; | |
| 586 | - } | |
| 587 | - try { | |
| 588 | - $server->register_tool(new $class_name()); | |
| 589 | - } catch (\Throwable $e) { | |
| 590 | - error_log('MetaSync MCP: Failed to register tool ' . $class_name . ': ' . $e->getMessage()); | |
| 591 | - } | |
| 592 | -} | |
| 738 | +// MCP server bootstrap (server + tool registration) extracted to keep this entry file lean. | |
| 739 | +require_once plugin_dir_path( __FILE__ ) . 'includes/mcp-server-bootstrap.php'; | |
| 593 | 740 | |
| 594 | 741 | /** |
| 595 | - * Creates and configures the MCP server instance, | |
| 596 | - * registers all available MCP tools for WordPress operations. | |
| 597 | - * Hooked to 'init' for proper WordPress lifecycle integration. | |
| 598 | - */ | |
| 599 | -function metasync_init_mcp_server() { | |
| 600 | - if (metasync_is_non_metasync_admin_ajax()) { | |
| 601 | - return; | |
| 602 | - } | |
| 603 | - // Skip the heavy MCP boot (server + sync logger + REST inventory + 100+ tool objects) | |
| 604 | - // unless this request is actually targeting the MCP REST route or was loaded via | |
| 605 | - // the stdio/HTTP bridge. WP-255. | |
| 606 | - if (!metasync_is_mcp_rest_request() && !defined('METASYNC_MCP_BRIDGE')) { | |
| 607 | - return; | |
| 608 | - } | |
| 609 | - // Initialize MCP server | |
| 610 | - global $metasync_mcp_server; | |
| 611 | - | |
| 612 | - try { | |
| 613 | - $metasync_mcp_server = new Metasync_MCP_Server(); | |
| 614 | - | |
| 615 | - // Attach MCP sync logger so all write tool calls are recorded in Sync History. | |
| 616 | - new Metasync_MCP_Sync_Logger(); | |
| 617 | - | |
| 618 | - // SEO Inventory: shared builder + standalone REST endpoint (WP-135) | |
| 619 | - new Metasync_REST_SEO_Inventory(); | |
| 620 | - } catch (\Throwable $e) { | |
| 621 | - error_log('MCP Server Initialization Error: ' . $e->getMessage()); | |
| 622 | - return; | |
| 623 | - } | |
| 624 | - | |
| 625 | - // Helper: register a single tool, logging failures without aborting subsequent registrations | |
| 626 | - $safe_register = function($class_name) use ($metasync_mcp_server) { | |
| 627 | - metasync_safe_register_mcp_tool($metasync_mcp_server, $class_name); | |
| 628 | - }; | |
| 629 | - | |
| 630 | - // Register MCP Tools (Total: 92 existing + 8 new = 100 tools total!) | |
| 631 | - // NEW in v2.8.0: +4 Taxonomy Meta tools, +4 Bulk Alt Text tools | |
| 632 | - | |
| 633 | - // Post Meta Operations (4 tools) | |
| 634 | - $safe_register('MCP_Tool_Update_Post_Meta'); | |
| 635 | - $safe_register('MCP_Tool_Get_Post_Meta'); | |
| 636 | - $safe_register('MCP_Tool_Get_SEO_Meta'); | |
| 637 | - $safe_register('MCP_Tool_Get_Hreflang_Links'); | |
| 638 | - | |
| 639 | - // Post Operations (4 tools) | |
| 640 | - $safe_register('MCP_Tool_Get_Post'); | |
| 641 | - $safe_register('MCP_Tool_Get_Post_By_URL'); | |
| 642 | - $safe_register('MCP_Tool_List_Posts'); | |
| 643 | - $safe_register('MCP_Tool_Update_Post'); | |
| 644 | - $safe_register('MCP_Tool_Get_Post_Types'); | |
| 645 | - | |
| 646 | - // SEO Analysis (3 tools) | |
| 647 | - $safe_register('MCP_Tool_Analyze_SEO'); | |
| 648 | - $safe_register('MCP_Tool_Check_Indexability'); | |
| 649 | - $safe_register('MCP_Tool_SEO_Health_Report'); | |
| 650 | - | |
| 651 | - // Search Operations (3 tools) | |
| 652 | - $safe_register('MCP_Tool_Search_Posts'); | |
| 653 | - $safe_register('MCP_Tool_Search_By_Keyword'); | |
| 654 | - $safe_register('MCP_Tool_Find_Missing_Meta'); | |
| 655 | - | |
| 656 | - // Redirect Management (5 tools) | |
| 657 | - $safe_register('MCP_Tool_Create_Redirect'); | |
| 658 | - $safe_register('MCP_Tool_List_Redirects'); | |
| 659 | - $safe_register('MCP_Tool_Delete_Redirect'); | |
| 660 | - $safe_register('MCP_Tool_Update_Redirect'); | |
| 661 | - $safe_register('MCP_Tool_Check_Redirects_Health'); | |
| 662 | - | |
| 663 | - // 404 Error Monitoring (5 tools) | |
| 664 | - $safe_register('MCP_Tool_List_404_Errors'); | |
| 665 | - $safe_register('MCP_Tool_Get_404_Stats'); | |
| 666 | - $safe_register('MCP_Tool_Delete_404_Error'); | |
| 667 | - $safe_register('MCP_Tool_Clear_404_Errors'); | |
| 668 | - $safe_register('MCP_Tool_Create_Redirect_From_404'); | |
| 669 | - | |
| 670 | - // Robots.txt & Sitemap Management (11 tools - 7 existing + 4 new) | |
| 671 | - $safe_register('MCP_Tool_Get_Robots_Txt'); | |
| 672 | - $safe_register('MCP_Tool_Update_Robots_Txt'); | |
| 673 | - $safe_register('MCP_Tool_Get_Sitemap_Status'); | |
| 674 | - $safe_register('MCP_Tool_Regenerate_Sitemap'); | |
| 675 | - $safe_register('MCP_Tool_Exclude_From_Sitemap'); | |
| 676 | - $safe_register('MCP_Tool_Add_Robots_Rule'); | |
| 677 | - $safe_register('MCP_Tool_Remove_Robots_Rule'); | |
| 678 | - $safe_register('MCP_Tool_Parse_Robots_Txt'); | |
| 679 | - $safe_register('MCP_Tool_Validate_Robots_Txt'); | |
| 680 | - $safe_register('MCP_Tool_Get_News_Sitemap'); | |
| 681 | - $safe_register('MCP_Tool_Get_Video_Sitemap'); | |
| 682 | - | |
| 683 | - // Plugin Settings Management (4 tools) | |
| 684 | - $safe_register('MCP_Tool_Get_Plugin_Settings'); | |
| 685 | - $safe_register('MCP_Tool_Update_Plugin_Settings'); | |
| 686 | - $safe_register('MCP_Tool_List_Plugin_Settings_Schema'); | |
| 687 | - $safe_register('MCP_Tool_Get_MCP_Settings'); | |
| 688 | - | |
| 689 | - // Schema Markup Management (7 tools) | |
| 690 | - $safe_register('MCP_Tool_Get_Schema_Markup'); | |
| 691 | - $safe_register('MCP_Tool_Update_Schema_Markup'); | |
| 692 | - $safe_register('MCP_Tool_Add_Schema_Type'); | |
| 693 | - $safe_register('MCP_Tool_Remove_Schema_Type'); | |
| 694 | - $safe_register('MCP_Tool_Validate_Schema'); | |
| 695 | - $safe_register('MCP_Tool_Get_Schema_Content'); | |
| 696 | - $safe_register('MCP_Tool_Set_Schema_Content'); | |
| 697 | - | |
| 698 | - // Google Instant Index (6 tools) | |
| 699 | - $safe_register('MCP_Tool_Instant_Index_Update'); | |
| 700 | - $safe_register('MCP_Tool_Instant_Index_Delete'); | |
| 701 | - $safe_register('MCP_Tool_Instant_Index_Status'); | |
| 702 | - $safe_register('MCP_Tool_Instant_Index_Bulk_Update'); | |
| 703 | - $safe_register('MCP_Tool_Get_Instant_Index_Settings'); | |
| 704 | - $safe_register('MCP_Tool_Update_Instant_Index_Settings'); | |
| 705 | - | |
| 706 | - // Custom HTML Pages (6 tools) | |
| 707 | - $safe_register('MCP_Tool_Create_Custom_Page'); | |
| 708 | - $safe_register('MCP_Tool_Get_Custom_Page'); | |
| 709 | - $safe_register('MCP_Tool_List_Custom_Pages'); | |
| 710 | - $safe_register('MCP_Tool_Update_Custom_Page'); | |
| 711 | - $safe_register('MCP_Tool_Delete_Custom_Page'); | |
| 712 | - $safe_register('MCP_Tool_Import_LPS_Page'); | |
| 713 | - | |
| 714 | - // HTML to Builder Converter (3 tools) | |
| 715 | - $safe_register('MCP_Tool_Convert_HTML_To_Builder'); | |
| 716 | - $safe_register('MCP_Tool_Create_Builder_Page_From_HTML'); | |
| 717 | - $safe_register('MCP_Tool_Convert_Custom_Page_To_Builder'); | |
| 718 | - | |
| 719 | - // Code Snippets (6 tools) | |
| 720 | - $safe_register('MCP_Tool_Get_Header_Snippet'); | |
| 721 | - $safe_register('MCP_Tool_Update_Header_Snippet'); | |
| 722 | - $safe_register('MCP_Tool_Get_Footer_Snippet'); | |
| 723 | - $safe_register('MCP_Tool_Update_Footer_Snippet'); | |
| 724 | - $safe_register('MCP_Tool_Get_Post_Snippets'); | |
| 725 | - $safe_register('MCP_Tool_Update_Post_Snippets'); | |
| 726 | - | |
| 727 | - // Categories & Taxonomies (15 tools) | |
| 728 | - $safe_register('MCP_Tool_List_Categories'); | |
| 729 | - $safe_register('MCP_Tool_Get_Category'); | |
| 730 | - $safe_register('MCP_Tool_Create_Category'); | |
| 731 | - $safe_register('MCP_Tool_Update_Category'); | |
| 732 | - $safe_register('MCP_Tool_Delete_Category'); | |
| 733 | - $safe_register('MCP_Tool_Get_Post_Categories'); | |
| 734 | - $safe_register('MCP_Tool_Set_Post_Categories'); | |
| 735 | - | |
| 736 | - // Tags (8 tools) | |
| 737 | - $safe_register('MCP_Tool_List_Tags'); | |
| 738 | - $safe_register('MCP_Tool_Get_Tag'); | |
| 739 | - $safe_register('MCP_Tool_Create_Tag'); | |
| 740 | - $safe_register('MCP_Tool_Update_Tag'); | |
| 741 | - $safe_register('MCP_Tool_Delete_Tag'); | |
| 742 | - $safe_register('MCP_Tool_Get_Post_Tags'); | |
| 743 | - $safe_register('MCP_Tool_Set_Post_Tags'); | |
| 744 | - | |
| 745 | - // Featured Images & Media (6 tools) | |
| 746 | - $safe_register('MCP_Tool_Get_Featured_Image'); | |
| 747 | - $safe_register('MCP_Tool_Set_Featured_Image'); | |
| 748 | - $safe_register('MCP_Tool_Upload_Featured_Image'); | |
| 749 | - $safe_register('MCP_Tool_Remove_Featured_Image'); | |
| 750 | - $safe_register('MCP_Tool_List_Media'); | |
| 751 | - $safe_register('MCP_Tool_Get_Media_Details'); | |
| 752 | - | |
| 753 | - // Post CRUD Operations (1 tool - delete/restore disabled for safety) | |
| 754 | - $safe_register('MCP_Tool_Create_Post'); | |
| 755 | - // $safe_register('MCP_Tool_Delete_Post'); // DISABLED - safety | |
| 756 | - // $safe_register('MCP_Tool_Restore_Post'); // DISABLED - safety | |
| 757 | - | |
| 758 | - // Bulk Operations (3 tools - bulk delete disabled for safety) | |
| 759 | - $safe_register('MCP_Tool_Bulk_Update_Meta'); | |
| 760 | - $safe_register('MCP_Tool_Bulk_Set_Categories'); | |
| 761 | - $safe_register('MCP_Tool_Bulk_Change_Status'); | |
| 762 | - // $safe_register('MCP_Tool_Bulk_Delete_Posts'); // DISABLED - safety | |
| 763 | - | |
| 764 | - // WordPress Core SEO Settings (10 tools) | |
| 765 | - $safe_register('MCP_Tool_Get_Site_Info'); | |
| 766 | - $safe_register('MCP_Tool_Update_Site_Info'); | |
| 767 | - $safe_register('MCP_Tool_Get_Permalink_Structure'); | |
| 768 | - $safe_register('MCP_Tool_Update_Permalink_Structure'); | |
| 769 | - $safe_register('MCP_Tool_Get_Reading_Settings'); | |
| 770 | - $safe_register('MCP_Tool_Update_Reading_Settings'); | |
| 771 | - $safe_register('MCP_Tool_Get_Search_Visibility'); | |
| 772 | - $safe_register('MCP_Tool_Update_Search_Visibility'); | |
| 773 | - $safe_register('MCP_Tool_Get_Date_Format'); | |
| 774 | - $safe_register('MCP_Tool_Get_Discussion_Settings'); | |
| 775 | - | |
| 776 | - // Taxonomy Meta Operations (4 tools - NEW in v2.8.0) | |
| 777 | - $safe_register('MCP_Tool_Get_Term_Meta'); | |
| 778 | - $safe_register('MCP_Tool_Update_Term_Meta'); | |
| 779 | - $safe_register('MCP_Tool_Bulk_Update_Term_Meta'); | |
| 780 | - $safe_register('MCP_Tool_List_Terms_With_Meta'); | |
| 781 | - | |
| 782 | - // Bulk Alt Text Operations (4 tools - NEW in v2.8.0) | |
| 783 | - $safe_register('MCP_Tool_Audit_Alt_Text'); | |
| 784 | - $safe_register('MCP_Tool_Bulk_Update_Alt_Text'); | |
| 785 | - $safe_register('MCP_Tool_Generate_Alt_Text'); | |
| 786 | - $safe_register('MCP_Tool_Validate_Alt_Text'); | |
| 787 | - | |
| 788 | - // OTTO Persistence Settings (2 tools) | |
| 789 | - $safe_register('MCP_Tool_Get_Otto_Persistence_Settings'); | |
| 790 | - $safe_register('MCP_Tool_Update_Otto_Persistence_Settings'); | |
| 791 | - | |
| 792 | - // OTTO Pipeline Tools (3 tools) | |
| 793 | - $safe_register('MCP_Tool_Trigger_Otto_Optimization'); | |
| 794 | - $safe_register('MCP_Tool_Get_Otto_Status'); | |
| 795 | - $safe_register('MCP_Tool_Verify_SEO_Output'); | |
| 796 | - | |
| 797 | - // System Diagnostics & Plugin Info (4 tools) | |
| 798 | - $safe_register('MCP_Tool_System_Diagnostics'); | |
| 799 | - $safe_register('MCP_Tool_List_All_Plugins'); | |
| 800 | - $safe_register('MCP_Tool_Get_Cron_Jobs'); | |
| 801 | - $safe_register('MCP_Tool_Get_WP_Option'); | |
| 802 | - | |
| 803 | - // SEO Inventory (1 tool — WP-135) | |
| 804 | - $safe_register('MCP_Tool_List_Posts_SEO_Inventory'); | |
| 805 | - | |
| 806 | - // Read-Only Database Access (3 tools) | |
| 807 | - $safe_register('MCP_Tool_DB_Tables'); | |
| 808 | - $safe_register('MCP_Tool_DB_Describe'); | |
| 809 | - $safe_register('MCP_Tool_DB_Select'); | |
| 810 | - | |
| 811 | - // Breadcrumb Tools (1 tool) | |
| 812 | - $safe_register('MCP_Tool_Get_Breadcrumb_Path'); | |
| 813 | - | |
| 814 | - // Cache Purge (2 tools) | |
| 815 | - $safe_register('MCP_Tool_Cache_Purge_All'); | |
| 816 | - $safe_register('MCP_Tool_Cache_Purge_URL'); | |
| 817 | - | |
| 818 | - // LLMs.txt Tools (5 tools) | |
| 819 | - $safe_register('MCP_Tool_Get_LLMs_Txt'); | |
| 820 | - $safe_register('MCP_Tool_Regenerate_LLMs_Txt'); | |
| 821 | - $safe_register('MCP_Tool_Get_LLMs_Txt_Settings'); | |
| 822 | - $safe_register('MCP_Tool_Update_LLMs_Txt_Settings'); | |
| 823 | - $safe_register('MCP_Tool_Get_Post_Markdown'); | |
| 824 | - | |
| 825 | - // SEO Plugin Audit (4 tools — WP-202) | |
| 826 | - $safe_register('MCP_Tool_Read_SEO_Plugin_Data'); | |
| 827 | - $safe_register('MCP_Tool_SEO_Plugin_Diff'); | |
| 828 | - $safe_register('MCP_Tool_Sync_To_Active_Plugins'); | |
| 829 | - $safe_register('MCP_Tool_Detect_SEO_Conflicts'); | |
| 830 | - | |
| 831 | - // Allow other plugins/themes to register tools | |
| 832 | - do_action('metasync_mcp_register_tools', $metasync_mcp_server); | |
| 833 | -} | |
| 834 | -add_action('init', 'metasync_init_mcp_server', 5); | |
| 835 | - | |
| 836 | -/** | |
| 837 | 742 | * Schedule a daily cron event to auto-purge Sync History records older than 90 days. |
| 838 | 743 | */ |
| 839 | 744 | function metasync_schedule_sync_log_cleanup() { |
| 840 | 745 | if (!wp_next_scheduled('metasync_sync_log_daily_cleanup')) { |
| @@ -861,85 +766,49 @@ | ||
| 861 | 766 | echo '<script>window.__SA_DYO_INITIALIZED__=true;</script>' . "\n"; |
| 862 | 767 | } |
| 863 | 768 | add_action('wp_head', 'metasync_output_dyo_init_flag', 1); |
| 864 | 769 | |
| 865 | -/** | |
| 866 | - * Initialize GA4 Analytics for Admin Area | |
| 867 | - * Hooked to admin_init for proper WordPress lifecycle integration | |
| 868 | - * Only loads after WordPress, plugins, and themes are fully loaded | |
| 869 | - */ | |
| 870 | -function metasync_init_analytics() { | |
| 871 | - // Only initialize in admin area (excludes AJAX and REST API requests) | |
| 872 | - if (is_admin() && !wp_doing_ajax() && !defined('REST_REQUEST')) { | |
| 873 | - Metasync_GA4::get_instance(); | |
| 874 | - } | |
| 875 | -} | |
| 876 | -add_action('admin_init', 'metasync_init_analytics', 10); | |
| 770 | +// Shared analytics helper is loaded in all request contexts so server-side feature | |
| 771 | +// events can use the same consent gate as admin analytics. | |
| 772 | +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-posthog.php'; | |
| 773 | +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-analytics-consent-notice.php'; | |
| 877 | 774 | |
| 878 | 775 | /** |
| 879 | - * Initialize API Backoff System | |
| 880 | - * Hooked to init for proper WordPress lifecycle integration | |
| 881 | - * Monitors API responses and manages exponential backoff for rate limiting | |
| 882 | - * @since 2.7.1 | |
| 776 | + * Runtime feature initialisers (GA4, API backoff, review notice, JWT accessor, | |
| 777 | + * debug mode) extracted to keep this entry file lean. | |
| 883 | 778 | */ |
| 884 | -function metasync_init_api_backoff() { | |
| 885 | - if (metasync_is_non_metasync_admin_ajax()) { | |
| 886 | - return; | |
| 887 | - } | |
| 888 | - // Initialize backoff manager (registers HTTP response filters) | |
| 889 | - Metasync_API_Backoff_Manager::get_instance(); | |
| 779 | +require_once plugin_dir_path( __FILE__ ) . 'includes/metasync-runtime-init.php'; | |
| 890 | 780 | |
| 891 | - // Initialize admin notices (only in admin area) | |
| 892 | - if (is_admin()) { | |
| 893 | - Metasync_API_Backoff_Notices::get_instance(); | |
| 894 | - } | |
| 895 | -} | |
| 896 | -add_action('init', 'metasync_init_api_backoff', 5); | |
| 897 | - | |
| 898 | 781 | /** |
| 899 | - * Initialize Review Notice | |
| 900 | - * Shows a dismissible notice asking users to rate the plugin after a usage period | |
| 901 | - * @since 2.8.0 | |
| 782 | + * Append a "Website Studio" post state to LPS-synced / MetaSync custom pages in | |
| 783 | + * the admin Pages list, so site owners can tell at a glance which pages are | |
| 784 | + * managed by Website Studio and shouldn't be hand-edited. | |
| 785 | + * | |
| 786 | + * Hooks WordPress core's display_post_states filter — the same mechanism that | |
| 787 | + * renders the grey inline tags like "— Front Page" / "— Draft" — so the label | |
| 788 | + * is native-styled and only appears next to relevant page titles, with no | |
| 789 | + * custom admin column. | |
| 790 | + * | |
| 791 | + * @param string[] $post_states Existing post-state labels keyed by slug. | |
| 792 | + * @param WP_Post $post The post being listed. | |
| 793 | + * @return string[] Possibly-augmented post states. | |
| 902 | 794 | */ |
| 903 | -function metasync_init_review_notice() { | |
| 904 | - if (metasync_is_non_metasync_admin_ajax()) { | |
| 905 | - return; | |
| 795 | +function metasync_add_lps_post_state($post_states, $post) { | |
| 796 | + // metasync_is_custom_or_lps_page() lives in otto/otto_pixel.php, which is NOT | |
| 797 | + // loaded on non-MetaSync admin-ajax requests (e.g. Quick Edit's inline-save, | |
| 798 | + // where this filter still fires), so guard against the undefined function. | |
| 799 | + if (!function_exists('metasync_is_custom_or_lps_page')) { | |
| 800 | + return $post_states; | |
| 906 | 801 | } |
| 907 | - if (is_admin()) { | |
| 908 | - Metasync_Review_Notice::get_instance(); | |
| 802 | + if (!metasync_is_custom_or_lps_page($post->ID)) { | |
| 803 | + return $post_states; | |
| 909 | 804 | } |
| 805 | + $post_states['metasync_website_studio'] = __('Website Studio', 'metasync'); | |
| 806 | + return $post_states; | |
| 910 | 807 | } |
| 911 | -add_action('init', 'metasync_init_review_notice'); | |
| 912 | 808 | |
| 913 | -/** | |
| 914 | - * Global convenience function to get active JWT token | |
| 915 | - * Can be called from anywhere in WordPress (themes, other plugins, etc.) | |
| 916 | - * | |
| 917 | - * @param bool $force_refresh Force generation of new token even if cached one exists | |
| 918 | - * @return string|false JWT token on success, false on failure | |
| 919 | - */ | |
| 920 | -if (!function_exists('metasync_get_jwt_token')) { | |
| 921 | - function metasync_get_jwt_token($force_refresh = false) | |
| 922 | - { | |
| 923 | - return Metasync::get_jwt_token($force_refresh); | |
| 924 | - } | |
| 925 | -} | |
| 926 | 809 | |
| 927 | - | |
| 928 | 810 | /** |
| 929 | - * Initialize Debug Mode Manager | |
| 930 | - * Hooked to 'init' for proper WordPress lifecycle integration | |
| 931 | - */ | |
| 932 | -function metasync_init_debug_mode_manager() { | |
| 933 | - if (metasync_is_non_metasync_admin_ajax()) { | |
| 934 | - return; | |
| 935 | - } | |
| 936 | - // Initialize the Debug Mode Manager singleton | |
| 937 | - Metasync_Debug_Mode_Manager::get_instance(); | |
| 938 | -} | |
| 939 | -add_action('init', 'metasync_init_debug_mode_manager', 10); | |
| 940 | - | |
| 941 | -/** | |
| 942 | 811 | * Oxygen Builder Compatibility |
| 943 | 812 | * Auto re-signs [oxygen] dynamic-data shortcodes when their HMAC signatures |
| 944 | 813 | * are invalid (e.g. after design-set import or site migration). |
| 945 | 814 | * Runs once on admin_init; skips entirely when Oxygen is inactive. |
| @@ -945,5 +814,6 @@ | ||
| 945 | 814 | * Runs once on admin_init; skips entirely when Oxygen is inactive. |
| 946 | 815 | */ |
| 947 | 816 | if (is_admin()) { |
| 948 | 817 | add_action('admin_init', ['Metasync_Oxygen_Compat', 'maybe_resign_shortcodes'], 20); |
| 818 | + add_filter('display_post_states', 'metasync_add_lps_post_state', 10, 2); | |
| 949 | 819 | } |