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 +140 -40 2.6.21 → 2.7.1 View file →
@@ -58,8 +58,9 @@
58 58 <button type="button" id="test-both-requests" class="button button-primary">
59 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 ];
@@ -1254,39 +1349,44 @@
1254 1349 }
1255 1350
1256 1351 public function get_plugin_logo($plugin_key, $type)
1257 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 +
1258 1358 $logos = [
1259 1359 'page_builder' => [
1260 - 'elementor' => 'https://ps.w.org/elementor/assets/icon-256x256.gif',
1261 - 'gutenberg' => 'https://i0.wp.com/wordpress.org/files/2023/02/wmark.png',
1262 - 'divi' => 'https://www.elegantthemes.com/images/logo.svg',
1263 - 'oxygen' => 'https://oxygenbuilder.com/wp-content/uploads/2021/09/oxygen-logo-icon.png',
1264 - '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',
1265 1365 ],
1266 1366 'theme' => [
1267 - 'astra' => 'https://ps.w.org/astra/assets/icon-128x128.png',
1268 - 'generatepress' => 'https://ps.w.org/generatepress/assets/icon-128x128.png',
1269 - 'oceanwp' => 'https://ps.w.org/ocean-extra/assets/icon-128x128.png',
1270 - 'hello-elementor' => 'https://ps.w.org/hello-elementor/assets/icon-128x128.png',
1271 - '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',
1272 1372 ],
1273 1373 'seo' => [
1274 - 'yoast' => 'https://ps.w.org/wordpress-seo/assets/icon-128x128.gif',
1275 - 'rankmath' => 'https://ps.w.org/seo-by-rank-math/assets/icon-128x128.png',
1276 - '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',
1277 1377 ],
1278 1378 'cache' => [
1279 - 'wp-rocket' => 'https://ps.w.org/rocket-lazy-load/assets/icon-128x128.png',
1280 - 'litespeed-cache' => 'https://ps.w.org/litespeed-cache/assets/icon-128x128.png',
1281 - 'w3-total-cache' => 'https://ps.w.org/w3-total-cache/assets/icon-128x128.png',
1282 - 'sg-cachepress' => 'https://ps.w.org/sg-cachepress/assets/icon-128x128.png',
1283 - 'hummingbird-performance' => 'https://ps.w.org/hummingbird-performance/assets/icon-128x128.png',
1284 - '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',
1285 1385 ],
1286 1386 'cdn' => [
1287 - 'cloudflare' => 'https://ps.w.org/cloudflare/assets/icon-128x128.png',
1288 - 'fastly' => 'https://ps.w.org/purgely/assets/icon-128x128.png',
1387 + 'cloudflare' => $assets . 'cloudflare.svg',
1388 + 'fastly' => $assets . 'fastly.svg',
1289 1389 ],
1290 1390 ];
1291 1391
1292 1392 return isset($logos[$type][$plugin_key]) ? $logos[$type][$plugin_key] : '';