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 | includes/class-metasync-compatibility-checker.php +158 -50 2.6.9 → 2.7.1 View file →
@@ -55,11 +55,12 @@
55 55 <h3 style="margin-top: 0; color: var(--dashboard-text-primary);">Test Configuration</h3>
56 56 <p style="margin-bottom: 16px; color: var(--dashboard-text-secondary);">Test connectivity by running both GET and POST requests. Results appear below.</p>
57 57 <div style="display: flex; gap: 10px; flex-wrap: wrap;">
58 58 <button type="button" id="test-both-requests" class="button button-primary">
59 - <span class="dashicons dashicons-controls-repeat" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Test Both Requests
59 + <span class="dashicons dashicons-controls-repeat"></span> Test Both Requests
60 60 </button>
61 61 </div>
62 + <?php $this->render_last_automatic_check(); ?>
62 63 </div>
63 64 <div id="host-test-results" style="display: none; margin-top:16px;">
64 65 <h3 style="color: var(--dashboard-text-primary); margin-bottom: 10px;">Test Results</h3>
65 66 <div id="test-results-content"></div>
@@ -193,29 +194,33 @@
193 194 function displayResults(results) {
194 195 var html = '';
195 196
196 197 results.forEach(function(result) {
197 - var statusClass = result.status === 'success' ? 'success' : 'error';
198 - var statusIcon = result.status === 'success' ? '✅' : '❌';
199 - var blockedStatus = result.blocked ? 'BLOCKED' : 'ALLOWED';
200 - var blockedClass = result.blocked ? 'blocked' : 'allowed';
198 + // Three states, not two: a request can also be
199 + // inconclusive when the checker service itself is
200 + // unavailable — that must never read as "blocked".
201 + var inconclusive = !!result.checker_unreachable;
202 + var statusClass = result.status === 'success' ? 'success' : (inconclusive ? 'warning' : 'error');
203 + var statusIcon = result.status === 'success' ? '✅' : (inconclusive ? '⚠️' : '❌');
204 + var blockedStatus = inconclusive ? 'INCONCLUSIVE' : (result.blocked ? 'BLOCKED' : 'ALLOWED');
205 + var blockedClass = inconclusive ? 'inconclusive' : (result.blocked ? 'blocked' : 'allowed');
201 206
202 207 html += '<div class="test-result-item ' + statusClass + '">';
203 208 html += '<div class="test-result-header">';
204 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
209 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
205 210 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
206 211 html += '</div>';
207 212
208 213 html += '<div class="test-result-details">';
209 - html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
210 - html += '<p><strong>Status:</strong> ' + result.status + '</p>';
214 + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>';
215 + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>';
211 216
212 217 if (result.status_code) {
213 - html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
218 + html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + escapeHtml(result.status_code) + '</span></p>';
214 219 }
215 220
216 221 if (result.error) {
217 - html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
222 + html += '<p><strong>Error:</strong> <span class="error-message">' + escapeHtml(result.error) + '</span></p>';
218 223 }
219 224
220 225 if (result.body) {
221 226 html += '<p><strong>Response Body:</strong></p>';
@@ -225,9 +230,9 @@
225 230 if (result.headers && Object.keys(result.headers).length > 0) {
226 231 html += '<p><strong>Response Headers:</strong></p>';
227 232 html += '<pre class="response-headers">';
228 233 Object.keys(result.headers).forEach(function(key) {
229 - html += key + ': ' + result.headers[key] + '\n';
234 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
230 235 });
231 236 html += '</pre>';
232 237 }
233 238
@@ -240,9 +245,9 @@
240 245 html += '<p><strong>Parsed Response:</strong></p>';
241 246 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
242 247 }
243 248
244 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
249 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
245 250 html += '</div>';
246 251 html += '</div>';
247 252 });
248 253
@@ -257,9 +262,9 @@
257 262 '>': '&gt;',
258 263 '"': '&quot;',
259 264 "'": '&#039;'
260 265 };
261 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
266 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
262 267 }
263 268
264 269 setTimeout(function() {
265 270 var btnBoth = $('#test-both-requests');
@@ -289,9 +294,13 @@
289 294
290 295 .test-result-item.error {
291 296 border-left: 4px solid #dc3545;
292 297 }
293 -
298 +
299 + .test-result-item.warning {
300 + border-left: 4px solid #dba617;
301 + }
302 +
294 303 .test-result-header {
295 304 background: var(--dashboard-bg);
296 305 padding: 15px 20px;
297 306 border-bottom: 1px solid #dee2e6;
@@ -322,9 +331,14 @@
322 331 .test-status.blocked {
323 332 background: rgba(248,113,113,0.15);
324 333 color: #f87171;
325 334 }
326 -
335 +
336 + .test-status.inconclusive {
337 + background: rgba(219,166,23,0.15);
338 + color: #dba617;
339 + }
340 +
327 341 .test-result-details {
328 342 padding: 20px;
329 343 }
330 344
@@ -503,11 +517,14 @@
503 517 text-transform: uppercase;
504 518 letter-spacing: 0.5px;
505 519 }
506 520
521 + /* Badge text is 11px uppercase, i.e. normal-size text: the darker
522 + foreground steps keep every pair at or above the 4.5:1 AA bar
523 + on its own pastel background. */
507 524 .otto-pattern-exact {
508 525 background: #e3f2fd;
509 - color: #1976d2;
526 + color: #0d47a1;
510 527 }
511 528
512 529 .otto-pattern-contain {
513 530 background: #f3e5f5;
@@ -515,14 +532,14 @@
515 532 }
516 533
517 534 .otto-pattern-start {
518 535 background: #e8f5e9;
519 - color: #388e3c;
536 + color: #1b5e20;
520 537 }
521 538
522 539 .otto-pattern-end {
523 - background: var(--dashboard-card-bg)3e0;
524 - color: #f57c00;
540 + background: #fff3e0;
541 + color: #8f4400;
525 542 }
526 543
527 544 .otto-pattern-regex {
528 545 background: #fce4ec;
@@ -554,12 +571,35 @@
554 571 }
555 572
556 573 #otto-excluded-urls-table-container .wp-list-table td {
557 574 background: var(--dashboard-card-bg) !important;
558 - color: #2c3338 !important;
575 + color: var(--dashboard-text-primary) !important;
559 576 border-bottom: 1px solid #c3c4c7;
560 577 }
561 578
579 + /* Placeholder text (empty description, no recheck date) still has to
580 + clear AA against the card background in both themes. */
581 + #otto-excluded-urls-table-container .otto-muted {
582 + color: var(--dashboard-text-secondary) !important;
583 + }
584 +
585 + /* Loading / empty-state text shown in place of the table. */
586 + #otto-excluded-urls-table-container .otto-table-message {
587 + text-align: center;
588 + padding: 40px;
589 + color: var(--dashboard-text-secondary);
590 + }
591 +
592 + /* Error text replaces the table on a failed load. No single red
593 + clears AA on both card backgrounds, so each theme gets its own. */
594 + #otto-excluded-urls-table-container .otto-error-message {
595 + color: #f87171 !important;
596 + }
597 +
598 + [data-theme="light"] #otto-excluded-urls-table-container .otto-error-message {
599 + color: #b02a37 !important;
600 + }
601 +
562 602 #otto-excluded-urls-table-container .wp-list-table tbody tr {
563 603 background: var(--dashboard-card-bg);
564 604 }
565 605
@@ -629,8 +669,62 @@
629 669 <?php
630 670 $admin->render_layout_close();
631 671 }
632 672
673 + /**
674 + * Show the result of the last automatic host blocking check.
675 + *
676 + * Reads the stored result only — it never triggers a probe, so opening this page
677 + * costs nothing extra. The automatic check runs ~10 minutes after activation and
678 + * weekly after that; the button above re-runs the same probe on demand.
679 + */
680 + private function render_last_automatic_check(): void
681 + {
682 + if (!class_exists('Metasync_Host_Blocking_Check')) {
683 + return;
684 + }
685 +
686 + $checker = Metasync_Host_Blocking_Check::get_instance();
687 + $result = $checker->get_last_result();
688 +
689 + echo '<p style="margin: 16px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">';
690 +
691 + if ($result === null) {
692 + if (!$checker->site_is_publicly_reachable()) {
693 + echo esc_html__('Automatic checks are skipped on local and private-network sites, because our services cannot call this site back. Use the button above to test manually.', 'metasync');
694 + } else {
695 + echo esc_html__('The automatic check has not run yet. It runs about 10 minutes after activation, then weekly.', 'metasync');
696 + }
697 + echo '</p>';
698 + return;
699 + }
700 +
701 + $blocked = [];
702 + if ($result['get_blocked']) {
703 + $blocked[] = 'GET';
704 + }
705 + if ($result['post_blocked']) {
706 + $blocked[] = 'POST';
707 + }
708 +
709 + if (empty($blocked)) {
710 + printf(
711 + /* translators: %s: date/time of the last automatic check */
712 + esc_html__('Last automatic check: %s — no blocking detected.', 'metasync'),
713 + esc_html($result['checked_at'])
714 + );
715 + } else {
716 + printf(
717 + /* translators: 1: date/time of the last automatic check, 2: blocked HTTP methods */
718 + esc_html__('Last automatic check: %1$s — %2$s requests were blocked. Re-run the test above after changing any firewall or security settings.', 'metasync'),
719 + esc_html($result['checked_at']),
720 + esc_html(implode(' and ', $blocked))
721 + );
722 + }
723 +
724 + echo '</p>';
725 + }
726 +
633 727 public function render_compatibility_sections()
634 728 {
635 729 ?>
636 730 <div class="metasync-settings-accordion">
@@ -864,8 +958,9 @@
864 958 ],
865 959 'bricks' => [
866 960 'name' => 'Bricks Builder',
867 961 'plugin_files' => [ 'bricks/bricks.php' ],
962 + 'theme_name' => 'Bricks',
868 963 'supported' => true,
869 964 'version' => '1.11',
870 965 ],
871 966 ];
@@ -873,10 +968,12 @@
873 968 $result = [];
874 969
875 970 foreach ($builders as $key => $builder) {
876 971 $is_core = isset($builder['is_core']) && $builder['is_core'];
877 - $theme_name = isset($builder['theme_name']) ? $builder['theme_name'] : null;
878 - $plugin_status = $this->get_plugin_status($builder['plugin_files'], $is_core, $theme_name);
972 + // The array key is the theme directory slug; pass it (not the display name)
973 + // so wp_get_theme() can resolve the theme on case-sensitive filesystems.
974 + $theme_slug = isset($builder['theme_name']) ? $key : null;
975 + $plugin_status = $this->get_plugin_status($builder['plugin_files'], $is_core, $theme_slug);
879 976
880 977 if ($builder['supported']) {
881 978 $status = 'supported';
882 979 $status_text = 'Supported';
@@ -1002,9 +1099,11 @@
1002 1099 ];
1003 1100
1004 1101 $result = [];
1005 1102 foreach ( $themes as $key => $theme ) {
1006 - $status = $this->get_plugin_status( $theme['plugin_files'], false, $theme['theme_name'] );
1103 + // The array key is the theme directory slug; pass it (not the display name)
1104 + // so wp_get_theme() can resolve the theme on case-sensitive filesystems.
1105 + $status = $this->get_plugin_status( $theme['plugin_files'], false, $key );
1007 1106 $result[] = [
1008 1107 'name' => $theme['name'],
1009 1108 'version' => $theme['version'],
1010 1109 'status' => 'supported',
@@ -1177,12 +1276,14 @@
1177 1276 * Get detailed plugin status (installed and/or active).
1178 1277 *
1179 1278 * @param array $plugin_files Array of plugin file paths to check
1180 1279 * @param bool $is_core Whether this is a WordPress core feature
1181 - * @param string $theme_name Optional theme name to check
1280 + * @param string $theme_slug Optional theme directory slug to check (e.g. 'hello-elementor',
1281 + * 'astra'). Must be the directory slug, not the display name,
1282 + * because wp_get_theme() resolves themes by directory slug.
1182 1283 * @return array ['is_installed' => bool, 'is_active' => bool, 'active_version' => string|null]
1183 1284 */
1184 - public function get_plugin_status($plugin_files, $is_core = false, $theme_name = null)
1285 + public function get_plugin_status($plugin_files, $is_core = false, $theme_slug = null)
1185 1286 {
1186 1287 if ($is_core) {
1187 1288 return [
1188 1289 'is_installed' => true,
@@ -1190,14 +1291,16 @@
1190 1291 'active_version' => 'core'
1191 1292 ];
1192 1293 }
1193 1294
1194 - if ($theme_name) {
1295 + if ($theme_slug) {
1195 1296 $current_theme = wp_get_theme();
1196 - $is_theme_active = (strtolower($current_theme->get('Name')) === strtolower($theme_name) ||
1197 - strtolower($current_theme->get_stylesheet()) === strtolower($theme_name));
1297 + $is_theme_active = (strtolower($current_theme->get('Name')) === strtolower($theme_slug) ||
1298 + strtolower($current_theme->get_stylesheet()) === strtolower($theme_slug));
1198 1299
1199 - $theme_exists = wp_get_theme($theme_name)->exists();
1300 + // An active theme is by definition installed. Fall back to a directory
1301 + // lookup for themes that are installed but not currently active.
1302 + $theme_exists = $is_theme_active || wp_get_theme($theme_slug)->exists();
1200 1303
1201 1304 if ($theme_exists) {
1202 1305 return [
1203 1306 'is_installed' => true,
@@ -1246,39 +1349,44 @@
1246 1349 }
1247 1350
1248 1351 public function get_plugin_logo($plugin_key, $type)
1249 1352 {
1353 + // Compatibility icons are bundled locally; loading images from external
1354 + // servers (wp.org, vendor sites, CDNs) counts as offloaded content and
1355 + // is disallowed for wordpress.org releases.
1356 + $assets = plugins_url('assets/images/compat/', dirname(__DIR__) . '/metasync.php');
1357 +
1250 1358 $logos = [
1251 1359 'page_builder' => [
1252 - 'elementor' => 'https://ps.w.org/elementor/assets/icon-256x256.gif',
1253 - 'gutenberg' => 'https://i0.wp.com/wordpress.org/files/2023/02/wmark.png',
1254 - 'divi' => 'https://www.elegantthemes.com/images/logo.svg',
1255 - 'oxygen' => 'https://oxygenbuilder.com/wp-content/uploads/2021/09/oxygen-logo-icon.png',
1256 - 'bricks' => 'https://bricksbuilder.io/wp-content/uploads/2021/05/bricks-icon.svg',
1360 + 'elementor' => $assets . 'elementor.svg',
1361 + 'gutenberg' => $assets . 'gutenberg.svg',
1362 + 'divi' => $assets . 'divi.svg',
1363 + 'oxygen' => $assets . 'oxygen.svg',
1364 + 'bricks' => $assets . 'bricks.svg',
1257 1365 ],
1258 1366 'theme' => [
1259 - 'astra' => 'https://ps.w.org/astra/assets/icon-128x128.png',
1260 - 'generatepress' => 'https://ps.w.org/generatepress/assets/icon-128x128.png',
1261 - 'oceanwp' => 'https://ps.w.org/ocean-extra/assets/icon-128x128.png',
1262 - 'hello-elementor' => 'https://ps.w.org/hello-elementor/assets/icon-128x128.png',
1263 - 'kadence' => 'https://ps.w.org/kadence-blocks/assets/icon-128x128.png',
1367 + 'astra' => $assets . 'astra.svg',
1368 + 'generatepress' => $assets . 'generatepress.svg',
1369 + 'oceanwp' => $assets . 'oceanwp.svg',
1370 + 'hello-elementor' => $assets . 'hello-elementor.svg',
1371 + 'kadence' => $assets . 'kadence.svg',
1264 1372 ],
1265 1373 'seo' => [
1266 - 'yoast' => 'https://ps.w.org/wordpress-seo/assets/icon-128x128.gif',
1267 - 'rankmath' => 'https://ps.w.org/seo-by-rank-math/assets/icon-128x128.png',
1268 - 'aioseo' => 'https://ps.w.org/all-in-one-seo-pack/assets/icon-128x128.png',
1374 + 'yoast' => $assets . 'yoast.svg',
1375 + 'rankmath' => $assets . 'rankmath.svg',
1376 + 'aioseo' => $assets . 'aioseo.svg',
1269 1377 ],
1270 1378 'cache' => [
1271 - 'wp-rocket' => 'https://ps.w.org/rocket-lazy-load/assets/icon-128x128.png',
1272 - 'litespeed-cache' => 'https://ps.w.org/litespeed-cache/assets/icon-128x128.png',
1273 - 'w3-total-cache' => 'https://ps.w.org/w3-total-cache/assets/icon-128x128.png',
1274 - 'sg-cachepress' => 'https://ps.w.org/sg-cachepress/assets/icon-128x128.png',
1275 - 'hummingbird-performance' => 'https://ps.w.org/hummingbird-performance/assets/icon-128x128.png',
1276 - 'autoptimize' => 'https://ps.w.org/autoptimize/assets/icon-128x128.png',
1379 + 'wp-rocket' => $assets . 'wp-rocket.svg',
1380 + 'litespeed-cache' => $assets . 'litespeed-cache.svg',
1381 + 'w3-total-cache' => $assets . 'w3-total-cache.svg',
1382 + 'sg-cachepress' => $assets . 'sg-cachepress.svg',
1383 + 'hummingbird-performance' => $assets . 'hummingbird-performance.svg',
1384 + 'autoptimize' => $assets . 'autoptimize.svg',
1277 1385 ],
1278 1386 'cdn' => [
1279 - 'cloudflare' => 'https://ps.w.org/cloudflare/assets/icon-128x128.png',
1280 - 'fastly' => 'https://ps.w.org/purgely/assets/icon-128x128.png',
1387 + 'cloudflare' => $assets . 'cloudflare.svg',
1388 + 'fastly' => $assets . 'fastly.svg',
1281 1389 ],
1282 1390 ];
1283 1391
1284 1392 return isset($logos[$type][$plugin_key]) ? $logos[$type][$plugin_key] : '';