PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
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 +179 -42 2.6.20 → 2.7.2 View file →
@@ -38,8 +38,36 @@
38 38
39 39 <div class="dashboard-card">
40 40 <?php $this->render_compatibility_sections(); ?>
41 41 </div>
42 +
43 + <?php
44 + # Plugin-compatibility toggles. Registered under the
45 + # <page_slug>_compatibility settings page only while the relevant
46 + # plugin is active, so the card self-hides when neither plugin is
47 + # present. Saves through options.php with a hidden active_tab so
48 + # the General-tab sanitizer blocks stay inert.
49 + $has_compat_fields = defined('WP_ROCKET_VERSION')
50 + || class_exists('WP_Rocket')
51 + || class_exists('SiteGround_Optimizer\Parser\Parser')
52 + || defined('SiteGround_Optimizer\VERSION')
53 + || class_exists('bookingpress_spam_protection');
54 + if ($has_compat_fields) :
55 + settings_errors();
56 + ?>
57 + <div class="dashboard-card" style="margin-top: 20px;">
58 + <h2 style="margin: 0 0 4px 0;">Plugin Compatibility Settings</h2>
59 + <p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;">Control how <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> interacts with detected plugins. Each setting appears only while its plugin is active.</p>
60 + <form method="post" action="options.php">
61 + <input type="hidden" name="active_tab" value="compatibility" />
62 + <?php
63 + settings_fields(Metasync_Admin::option_group);
64 + do_settings_sections(Metasync_Admin::$page_slug . '_compatibility');
65 + submit_button();
66 + ?>
67 + </form>
68 + </div>
69 + <?php endif; ?>
42 70
43 71 <!-- Section: Host Blocking Test -->
44 72 <div id="ms-comp-host-test" class="dashboard-card" style="margin-top: 20px;">
45 73 <details open>
@@ -58,8 +86,9 @@
58 86 <button type="button" id="test-both-requests" class="button button-primary">
59 87 <span class="dashicons dashicons-controls-repeat"></span> Test Both Requests
60 88 </button>
61 89 </div>
90 + <?php $this->render_last_automatic_check(); ?>
62 91 </div>
63 92 <div id="host-test-results" style="display: none; margin-top:16px;">
64 93 <h3 style="color: var(--dashboard-text-primary); margin-bottom: 10px;">Test Results</h3>
65 94 <div id="test-results-content"></div>
@@ -133,9 +162,9 @@
133 162 $.ajax({
134 163 url: ajaxUrl,
135 164 type: 'POST',
136 165 dataType: 'json',
137 - data: { action: action, nonce: '<?php echo wp_create_nonce("metasync_nonce"); ?>' },
166 + data: { action: action, nonce: '<?php echo esc_attr(wp_create_nonce("metasync_nonce")); ?>' },
138 167 timeout: 35000,
139 168 success: function(response, textStatus, xhr) {
140 169 try {
141 170 if (response && response.success && response.data) {
@@ -193,29 +222,33 @@
193 222 function displayResults(results) {
194 223 var html = '';
195 224
196 225 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';
226 + // Three states, not two: a request can also be
227 + // inconclusive when the checker service itself is
228 + // unavailable — that must never read as "blocked".
229 + var inconclusive = !!result.checker_unreachable;
230 + var statusClass = result.status === 'success' ? 'success' : (inconclusive ? 'warning' : 'error');
231 + var statusIcon = result.status === 'success' ? '✅' : (inconclusive ? '⚠️' : '❌');
232 + var blockedStatus = inconclusive ? 'INCONCLUSIVE' : (result.blocked ? 'BLOCKED' : 'ALLOWED');
233 + var blockedClass = inconclusive ? 'inconclusive' : (result.blocked ? 'blocked' : 'allowed');
201 234
202 235 html += '<div class="test-result-item ' + statusClass + '">';
203 236 html += '<div class="test-result-header">';
204 - html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
237 + html += '<h4>' + statusIcon + ' ' + escapeHtml(result.method) + ' Request - ' + blockedStatus + '</h4>';
205 238 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
206 239 html += '</div>';
207 240
208 241 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>';
242 + html += '<p><strong>Response Time:</strong> ' + escapeHtml(result.response_time) + '</p>';
243 + html += '<p><strong>Status:</strong> ' + escapeHtml(result.status) + '</p>';
211 244
212 245 if (result.status_code) {
213 - html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
246 + html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + escapeHtml(result.status_code) + '</span></p>';
214 247 }
215 248
216 249 if (result.error) {
217 - html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
250 + html += '<p><strong>Error:</strong> <span class="error-message">' + escapeHtml(result.error) + '</span></p>';
218 251 }
219 252
220 253 if (result.body) {
221 254 html += '<p><strong>Response Body:</strong></p>';
@@ -225,9 +258,9 @@
225 258 if (result.headers && Object.keys(result.headers).length > 0) {
226 259 html += '<p><strong>Response Headers:</strong></p>';
227 260 html += '<pre class="response-headers">';
228 261 Object.keys(result.headers).forEach(function(key) {
229 - html += key + ': ' + result.headers[key] + '\n';
262 + html += escapeHtml(key) + ': ' + escapeHtml(result.headers[key]) + '\n';
230 263 });
231 264 html += '</pre>';
232 265 }
233 266
@@ -240,9 +273,9 @@
240 273 html += '<p><strong>Parsed Response:</strong></p>';
241 274 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
242 275 }
243 276
244 - html += '<p><strong>Details:</strong> ' + result.details + '</p>';
277 + html += '<p><strong>Details:</strong> ' + escapeHtml(result.details) + '</p>';
245 278 html += '</div>';
246 279 html += '</div>';
247 280 });
248 281
@@ -257,9 +290,9 @@
257 290 '>': '&gt;',
258 291 '"': '&quot;',
259 292 "'": '&#039;'
260 293 };
261 - return text.replace(/[&<>"']/g, function(m) { return map[m]; });
294 + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
262 295 }
263 296
264 297 setTimeout(function() {
265 298 var btnBoth = $('#test-both-requests');
@@ -289,9 +322,13 @@
289 322
290 323 .test-result-item.error {
291 324 border-left: 4px solid #dc3545;
292 325 }
293 -
326 +
327 + .test-result-item.warning {
328 + border-left: 4px solid #dba617;
329 + }
330 +
294 331 .test-result-header {
295 332 background: var(--dashboard-bg);
296 333 padding: 15px 20px;
297 334 border-bottom: 1px solid #dee2e6;
@@ -322,9 +359,14 @@
322 359 .test-status.blocked {
323 360 background: rgba(248,113,113,0.15);
324 361 color: #f87171;
325 362 }
326 -
363 +
364 + .test-status.inconclusive {
365 + background: rgba(219,166,23,0.15);
366 + color: #dba617;
367 + }
368 +
327 369 .test-result-details {
328 370 padding: 20px;
329 371 }
330 372
@@ -493,8 +535,10 @@
493 535 color: #646970 !important;
494 536 opacity: 0.7;
495 537 }
496 538
539 + /* Equal width so badges of different lengths (Exact, Contains,
540 + etc.) render at the same size for a visually balanced column. */
497 541 .otto-pattern-type-badge {
498 542 display: inline-block;
499 543 padding: 3px 8px;
500 544 border-radius: 3px;
@@ -501,13 +545,19 @@
501 545 font-size: 11px;
502 546 font-weight: 600;
503 547 text-transform: uppercase;
504 548 letter-spacing: 0.5px;
549 + min-width: 90px;
550 + text-align: center;
551 + box-sizing: border-box;
505 552 }
506 553
554 + /* Badge text is 11px uppercase, i.e. normal-size text: the darker
555 + foreground steps keep every pair at or above the 4.5:1 AA bar
556 + on its own pastel background. */
507 557 .otto-pattern-exact {
508 558 background: #e3f2fd;
509 - color: #1976d2;
559 + color: #0d47a1;
510 560 }
511 561
512 562 .otto-pattern-contain {
513 563 background: #f3e5f5;
@@ -515,14 +565,14 @@
515 565 }
516 566
517 567 .otto-pattern-start {
518 568 background: #e8f5e9;
519 - color: #388e3c;
569 + color: #1b5e20;
520 570 }
521 571
522 572 .otto-pattern-end {
523 - background: var(--dashboard-card-bg)3e0;
524 - color: #f57c00;
573 + background: #fff3e0;
574 + color: #8f4400;
525 575 }
526 576
527 577 .otto-pattern-regex {
528 578 background: #fce4ec;
@@ -554,12 +604,35 @@
554 604 }
555 605
556 606 #otto-excluded-urls-table-container .wp-list-table td {
557 607 background: var(--dashboard-card-bg) !important;
558 - color: #2c3338 !important;
608 + color: var(--dashboard-text-primary) !important;
559 609 border-bottom: 1px solid #c3c4c7;
560 610 }
561 611
612 + /* Placeholder text (empty description, no recheck date) still has to
613 + clear AA against the card background in both themes. */
614 + #otto-excluded-urls-table-container .otto-muted {
615 + color: var(--dashboard-text-secondary) !important;
616 + }
617 +
618 + /* Loading / empty-state text shown in place of the table. */
619 + #otto-excluded-urls-table-container .otto-table-message {
620 + text-align: center;
621 + padding: 40px;
622 + color: var(--dashboard-text-secondary);
623 + }
624 +
625 + /* Error text replaces the table on a failed load. No single red
626 + clears AA on both card backgrounds, so each theme gets its own. */
627 + #otto-excluded-urls-table-container .otto-error-message {
628 + color: #f87171 !important;
629 + }
630 +
631 + [data-theme="light"] #otto-excluded-urls-table-container .otto-error-message {
632 + color: #b02a37 !important;
633 + }
634 +
562 635 #otto-excluded-urls-table-container .wp-list-table tbody tr {
563 636 background: var(--dashboard-card-bg);
564 637 }
565 638
@@ -576,11 +649,15 @@
576 649 border: 1px solid #dcdcde;
577 650 }
578 651
579 652 #otto-excluded-urls-table-container .otto-delete-url,
580 - #otto-excluded-urls-table-container .otto-recheck-url {
653 + #otto-excluded-urls-table-container .otto-recheck-url,
654 + #otto-excluded-urls-table-container .otto-recheck-disabled {
581 655 padding: 4px 10px;
582 656 font-size: 12px;
657 + box-sizing: border-box;
658 + height: 26px;
659 + line-height: 1;
583 660 }
584 661
585 662 #otto-excluded-urls-table-container .otto-delete-url {
586 663 background: #dc3545 !important;
@@ -629,8 +706,62 @@
629 706 <?php
630 707 $admin->render_layout_close();
631 708 }
632 709
710 + /**
711 + * Show the result of the last automatic host blocking check.
712 + *
713 + * Reads the stored result only — it never triggers a probe, so opening this page
714 + * costs nothing extra. The automatic check runs ~10 minutes after activation and
715 + * weekly after that; the button above re-runs the same probe on demand.
716 + */
717 + private function render_last_automatic_check(): void
718 + {
719 + if (!class_exists('Metasync_Host_Blocking_Check')) {
720 + return;
721 + }
722 +
723 + $checker = Metasync_Host_Blocking_Check::get_instance();
724 + $result = $checker->get_last_result();
725 +
726 + echo '<p style="margin: 16px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">';
727 +
728 + if ($result === null) {
729 + if (!$checker->site_is_publicly_reachable()) {
730 + 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');
731 + } else {
732 + echo esc_html__('The automatic check has not run yet. It runs about 10 minutes after activation, then weekly.', 'metasync');
733 + }
734 + echo '</p>';
735 + return;
736 + }
737 +
738 + $blocked = [];
739 + if ($result['get_blocked']) {
740 + $blocked[] = 'GET';
741 + }
742 + if ($result['post_blocked']) {
743 + $blocked[] = 'POST';
744 + }
745 +
746 + if (empty($blocked)) {
747 + printf(
748 + /* translators: %s: date/time of the last automatic check */
749 + esc_html__('Last automatic check: %s — no blocking detected.', 'metasync'),
750 + esc_html($result['checked_at'])
751 + );
752 + } else {
753 + printf(
754 + /* translators: 1: date/time of the last automatic check, 2: blocked HTTP methods */
755 + 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'),
756 + esc_html($result['checked_at']),
757 + esc_html(implode(' and ', $blocked))
758 + );
759 + }
760 +
761 + echo '</p>';
762 + }
763 +
633 764 public function render_compatibility_sections()
634 765 {
635 766 ?>
636 767 <div class="metasync-settings-accordion">
@@ -864,8 +995,9 @@
864 995 ],
865 996 'bricks' => [
866 997 'name' => 'Bricks Builder',
867 998 'plugin_files' => [ 'bricks/bricks.php' ],
999 + 'theme_name' => 'Bricks',
868 1000 'supported' => true,
869 1001 'version' => '1.11',
870 1002 ],
871 1003 ];
@@ -1254,39 +1386,44 @@
1254 1386 }
1255 1387
1256 1388 public function get_plugin_logo($plugin_key, $type)
1257 1389 {
1390 + // Compatibility icons are bundled locally; loading images from external
1391 + // servers (wp.org, vendor sites, CDNs) counts as offloaded content and
1392 + // is disallowed for wordpress.org releases.
1393 + $assets = plugins_url('assets/images/compat/', dirname(__DIR__) . '/metasync.php');
1394 +
1258 1395 $logos = [
1259 1396 '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',
1397 + 'elementor' => $assets . 'elementor.svg',
1398 + 'gutenberg' => $assets . 'gutenberg.svg',
1399 + 'divi' => $assets . 'divi.svg',
1400 + 'oxygen' => $assets . 'oxygen.svg',
1401 + 'bricks' => $assets . 'bricks.svg',
1265 1402 ],
1266 1403 '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',
1404 + 'astra' => $assets . 'astra.svg',
1405 + 'generatepress' => $assets . 'generatepress.svg',
1406 + 'oceanwp' => $assets . 'oceanwp.svg',
1407 + 'hello-elementor' => $assets . 'hello-elementor.svg',
1408 + 'kadence' => $assets . 'kadence.svg',
1272 1409 ],
1273 1410 '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',
1411 + 'yoast' => $assets . 'yoast.svg',
1412 + 'rankmath' => $assets . 'rankmath.svg',
1413 + 'aioseo' => $assets . 'aioseo.svg',
1277 1414 ],
1278 1415 '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',
1416 + 'wp-rocket' => $assets . 'wp-rocket.svg',
1417 + 'litespeed-cache' => $assets . 'litespeed-cache.svg',
1418 + 'w3-total-cache' => $assets . 'w3-total-cache.svg',
1419 + 'sg-cachepress' => $assets . 'sg-cachepress.svg',
1420 + 'hummingbird-performance' => $assets . 'hummingbird-performance.svg',
1421 + 'autoptimize' => $assets . 'autoptimize.svg',
1285 1422 ],
1286 1423 'cdn' => [
1287 - 'cloudflare' => 'https://ps.w.org/cloudflare/assets/icon-128x128.png',
1288 - 'fastly' => 'https://ps.w.org/purgely/assets/icon-128x128.png',
1424 + 'cloudflare' => $assets . 'cloudflare.svg',
1425 + 'fastly' => $assets . 'fastly.svg',
1289 1426 ],
1290 1427 ];
1291 1428
1292 1429 return isset($logos[$type][$plugin_key]) ? $logos[$type][$plugin_key] : '';