| @@ -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); | |