PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | metasync.php +93 -9 2.6.26 → 2.7.1 View file →
@@ -11,12 +11,12 @@
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.26
18 + * Version: 2.7.1
19 19 * Requires PHP: 8.1
20 20 * Author: Search Atlas
21 21 * Author URI: https://searchatlas.com
22 22 * License: GPL v3
@@ -41,14 +41,25 @@
41 41 // which can run before the classmap has resolved anything else.
42 42 require_once __DIR__ . '/includes/class-metasync-feature-flags.php';
43 43 Metasync_Feature_Flags::register_invalidation();
44 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 +
45 56 /**
46 57 * Currently plugin version.
47 58 * Start at version 1.0.0 and use SemVer - https://semver.org
48 59 * Rename this for your plugin and update it as you release new versions.
49 60 */
50 -$metasync_version = '2.6.26';
61 +$metasync_version = '2.7.1';
51 62 define('METASYNC_VERSION', preg_match('/^\d+\.\d+/', $metasync_version) ? $metasync_version : '9.9.9');
52 63 /**
53 64 * Define the current required php version
54 65 * This will be used to validate whether the user can install the plugin or not
@@ -134,10 +145,10 @@
134 145 }
135 146 if (!defined('DOING_AJAX') || !DOING_AJAX) {
136 147 return ($result = false);
137 148 }
138 - $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action']))
139 - : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '');
149 + $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- read-only action-name prefix check during boot
150 + : (isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only action-name prefix check during boot
140 151 if ($action === '') {
141 152 return ($result = true);
142 153 }
143 154 if (strpos($action, 'metasync_') === 0 || strpos($action, 'meta_sync_') === 0 || $action === 'sample-permalink') {
@@ -177,8 +188,9 @@
177 188
178 189 // Phase 2 — these files have file-level side effects (add_action outside class body)
179 190 // and must remain as explicit require_once until refactored.
180 191 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-api-backoff-rest.php';
192 +require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-data-store.php';
181 193 require_once plugin_dir_path( __FILE__ ) . 'includes/class-metasync-error-logger.php';
182 194
183 195 // Shared helpers (custom/LPS page detection + query exclusion) — loaded in all
184 196 // request contexts (admin, REST, MCP, AJAX) so every SEO surface uses one rule.
@@ -357,12 +369,75 @@
357 369 if (empty($candidates)) {
358 370 return;
359 371 }
360 372
373 + // Ownership gate: other plugins (Yoast and friends) write files with the
374 + // exact same names (sitemap_index.xml, sitemap1.xml, ...), so a name match
375 + // alone must never authorize deleting a physical file. A file is only
376 + // migrated when this plugin left evidence it wrote it.
377 + $tracked_names = [];
378 + $tracked_files = get_option('metasync_sitemap_files', []);
379 + if (is_array($tracked_files)) {
380 + foreach ($tracked_files as $entry) {
381 + $name = is_array($entry) && isset($entry['filename']) ? $entry['filename'] : $entry;
382 + if (is_string($name) && '' !== $name) {
383 + $tracked_names[] = $name;
384 + }
385 + }
386 + }
361 387 $virtual_index = get_option('metasync_sitemap_virtual_index', []);
388 + if (!is_array($virtual_index)) {
389 + $virtual_index = [];
390 + }
391 + $parked_names = array_keys($virtual_index);
392 + $generator_ran = !empty($tracked_names) && false !== get_option('metasync_sitemap_last_generated');
393 +
394 + $ours = [];
395 + $metasync_files_found = false;
396 + foreach ($candidates as $file) {
397 + $bn = basename($file);
398 +
399 + // A parked name proves MetaSync owned the file at an earlier point, but
400 + // an expired transient no longer proves that a file currently on disk
401 + // is ours. A different plugin may have recreated the same basename.
402 + if (in_array($bn, $parked_names, true)) {
403 + if (false !== get_transient('metasync_vsm_' . md5($bn))) {
404 + continue;
405 + }
406 + continue;
407 + }
408 +
409 + // Chunk, index, news, and video files are migratable only when their
410 + // exact basename is recorded by MetaSync's generator/configuration.
411 + if (in_array($bn, $tracked_names, true) && $generator_ran) {
412 + $ours[] = $file;
413 + $metasync_files_found = true;
414 + continue;
415 + }
416 +
417 + if ($bn === 'news-sitemap.xml' && null !== get_option('metasync_news_sitemap_settings', null)) {
418 + $ours[] = $file;
419 + $metasync_files_found = true;
420 + continue;
421 + }
422 + if ($bn === 'video-sitemap.xml' && null !== get_option('metasync_video_sitemap_settings', null)) {
423 + $ours[] = $file;
424 + $metasync_files_found = true;
425 + }
426 + }
427 +
428 + // Do not infer ownership of sitemap_index.xml from another sitemap: Yoast,
429 + // Rank Math, and other SEO plugins commonly use the same root filename.
430 + // It must be explicitly recorded in MetaSync's tracked file list.
431 + $ours = array_values(array_unique($ours));
432 +
433 + if (empty($ours)) {
434 + return;
435 + }
436 +
362 437 $migrated_files = [];
363 438
364 - foreach ($candidates as $file) {
439 + foreach ($ours as $file) {
365 440 $bn = basename($file);
366 441 $content = @file_get_contents($file);
367 442 if (false !== $content) {
368 443 $tkey = 'metasync_vsm_' . md5($bn);
@@ -373,9 +448,11 @@
373 448 // it with a fresh date_query when this expires.
374 449 $ttl = ('news-sitemap.xml' === $bn) ? (int) (DAY_IN_SECONDS / 4) : 30 * DAY_IN_SECONDS;
375 450 set_transient($tkey, $content, $ttl);
376 451 if (false !== get_transient($tkey)) {
377 - @unlink($file);
452 + // The parked transient doubles as a 30-day backup of the
453 + // content before the file is removed from disk.
454 + @wp_delete_file($file);
378 455 $virtual_index[$bn] = $tkey;
379 456 if ($bn !== 'sitemap_index.xml' && $bn !== 'news-sitemap.xml' && $bn !== 'video-sitemap.xml') {
380 457 $migrated_files[] = [
381 458 'filename' => $bn,
@@ -406,10 +483,17 @@
406 483
407 484 $current_version = get_option('metasync_version', '0.0.0');
408 485 $plugin_version = METASYNC_VERSION;
409 486
410 - // If versions don't match, run migration
411 - if (version_compare($current_version, $plugin_version, '<')) {
487 + // If versions don't match, run migration. Dev builds store the 9.9.9
488 + // placeholder version, which compares greater than every real release —
489 + // once stored, a plain "<" comparison never fires again and migrations
490 + // are silently skipped forever. Also enter when the stored version is the
491 + // placeholder but the running version is a real release, so the migration
492 + // runner's dev-build escape hatch becomes reachable and the stored version
493 + // is corrected to the real release.
494 + if (version_compare($current_version, $plugin_version, '<')
495 + || ('9.9.9' === $current_version && '9.9.9' !== $plugin_version)) {
412 496 // Import whitelabel settings only if the JSON file is new or changed
413 497 // (prevents overwriting admin UI changes on every version check)
414 498 Metasync_Activator::check_whitelabel_settings_update();
415 499