← All changes
|
includes/class-metasync-compatibility-checker.php
+159
-346
2.6.6
→
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> |
| @@ -133,9 +134,9 @@ | ||
| 133 | 134 | $.ajax({ |
| 134 | 135 | url: ajaxUrl, |
| 135 | 136 | type: 'POST', |
| 136 | 137 | dataType: 'json', |
| 137 | - data: { action: action }, | |
| 138 | + data: { action: action, nonce: '<?php echo wp_create_nonce("metasync_nonce"); ?>' }, | |
| 138 | 139 | timeout: 35000, |
| 139 | 140 | success: function(response, textStatus, xhr) { |
| 140 | 141 | try { |
| 141 | 142 | if (response && response.success && response.data) { |
| @@ -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 | '>': '>', |
| 258 | 263 | '"': '"', |
| 259 | 264 | "'": ''' |
| 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 | |
| @@ -460,302 +474,8 @@ | ||
| 460 | 474 | </div> |
| 461 | 475 | </details> |
| 462 | 476 | </div> |
| 463 | 477 | |
| 464 | - <!-- OTTO Excluded URLs JavaScript --> | |
| 465 | - <script> | |
| 466 | - (function() { | |
| 467 | - if (typeof jQuery === 'undefined') { | |
| 468 | - setTimeout(arguments.callee, 100); | |
| 469 | - return; | |
| 470 | - } | |
| 471 | - | |
| 472 | - jQuery(document).ready(function($) { | |
| 473 | - var currentPage = 1; | |
| 474 | - var perPage = 10; | |
| 475 | - var ajaxUrl = '<?php echo esc_js(admin_url('admin-ajax.php')); ?>'; | |
| 476 | - var nonce = '<?php echo wp_create_nonce('metasync_otto_excluded_urls'); ?>'; | |
| 477 | - | |
| 478 | - loadExcludedURLs(); | |
| 479 | - | |
| 480 | - $('#otto-add-excluded-url-btn').on('click', function(e) { | |
| 481 | - e.preventDefault(); | |
| 482 | - addExcludedURL(); | |
| 483 | - }); | |
| 484 | - | |
| 485 | - $('#otto-url-pattern').on('keypress', function(e) { | |
| 486 | - if (e.which === 13) { | |
| 487 | - e.preventDefault(); | |
| 488 | - addExcludedURL(); | |
| 489 | - } | |
| 490 | - }); | |
| 491 | - | |
| 492 | - function addExcludedURL() { | |
| 493 | - var $button = $('#otto-add-excluded-url-btn'); | |
| 494 | - var $status = $('#otto-add-status'); | |
| 495 | - var urlPattern = $('#otto-url-pattern').val().trim(); | |
| 496 | - var patternType = $('#otto-pattern-type').val(); | |
| 497 | - var description = $('#otto-description').val().trim(); | |
| 498 | - | |
| 499 | - if (!urlPattern) { | |
| 500 | - showStatus('error', 'URL pattern is required'); | |
| 501 | - return; | |
| 502 | - } | |
| 503 | - | |
| 504 | - $button.prop('disabled', true).text('Adding...'); | |
| 505 | - $status.hide(); | |
| 506 | - | |
| 507 | - $.ajax({ | |
| 508 | - url: ajaxUrl, | |
| 509 | - type: 'POST', | |
| 510 | - data: { | |
| 511 | - action: 'metasync_otto_add_excluded_url', | |
| 512 | - nonce: nonce, | |
| 513 | - url_pattern: urlPattern, | |
| 514 | - pattern_type: patternType, | |
| 515 | - description: description | |
| 516 | - }, | |
| 517 | - success: function(response) { | |
| 518 | - if (response.success) { | |
| 519 | - showStatus('success', response.data.message || 'URL excluded successfully'); | |
| 520 | - $('#otto-url-pattern').val(''); | |
| 521 | - $('#otto-description').val(''); | |
| 522 | - $('#otto-pattern-type').val('exact'); | |
| 523 | - loadExcludedURLs(); | |
| 524 | - } else { | |
| 525 | - showStatus('error', response.data.message || 'Failed to add excluded URL'); | |
| 526 | - } | |
| 527 | - }, | |
| 528 | - error: function() { | |
| 529 | - showStatus('error', 'An error occurred. Please try again.'); | |
| 530 | - }, | |
| 531 | - complete: function() { | |
| 532 | - $button.prop('disabled', false).text('➕ Add Excluded URL'); | |
| 533 | - } | |
| 534 | - }); | |
| 535 | - } | |
| 536 | - | |
| 537 | - function loadExcludedURLs(page) { | |
| 538 | - page = page || currentPage; | |
| 539 | - var $container = $('#otto-excluded-urls-table-container'); | |
| 540 | - | |
| 541 | - $container.html('<div style="text-align: center; padding: 40px; color: var(--dashboard-text-secondary);"><p>Loading...</p></div>'); | |
| 542 | - | |
| 543 | - $.ajax({ | |
| 544 | - url: ajaxUrl, | |
| 545 | - type: 'POST', | |
| 546 | - data: { | |
| 547 | - action: 'metasync_otto_get_excluded_urls', | |
| 548 | - nonce: nonce, | |
| 549 | - page: page, | |
| 550 | - per_page: perPage | |
| 551 | - }, | |
| 552 | - success: function(response) { | |
| 553 | - if (response.success) { | |
| 554 | - currentPage = page; | |
| 555 | - renderExcludedURLsTable(response.data.records, response.data.pagination); | |
| 556 | - } else { | |
| 557 | - $container.html('<div style="text-align: center; padding: 40px; color: #dc3545;"><p>Error loading excluded URLs</p></div>'); | |
| 558 | - } | |
| 559 | - }, | |
| 560 | - error: function() { | |
| 561 | - $container.html('<div style="text-align: center; padding: 40px; color: #dc3545;"><p>Failed to load excluded URLs</p></div>'); | |
| 562 | - } | |
| 563 | - }); | |
| 564 | - } | |
| 565 | - | |
| 566 | - function renderExcludedURLsTable(records, pagination) { | |
| 567 | - var $container = $('#otto-excluded-urls-table-container'); | |
| 568 | - | |
| 569 | - if (!records || records.length === 0) { | |
| 570 | - $container.html('<div style="text-align: center; padding: 40px; color: var(--dashboard-text-secondary);"><p>No excluded URLs found. Add one above to get started.</p></div>'); | |
| 571 | - return; | |
| 572 | - } | |
| 573 | - | |
| 574 | - var html = '<table class="wp-list-table widefat fixed striped" style="margin-top: 10px;">'; | |
| 575 | - html += '<thead><tr>'; | |
| 576 | - html += '<th style="width: 30%;">URL Pattern</th>'; | |
| 577 | - html += '<th style="width: 12%;">Match Type</th>'; | |
| 578 | - html += '<th style="width: 25%;">Description</th>'; | |
| 579 | - html += '<th style="width: 13%;">Recheck After</th>'; | |
| 580 | - html += '<th style="width: 20%;">Actions</th>'; | |
| 581 | - html += '</tr></thead><tbody>'; | |
| 582 | - | |
| 583 | - records.forEach(function(record) { | |
| 584 | - html += '<tr>'; | |
| 585 | - html += '<td><code>' + escapeHtml(record.url_pattern) + '</code></td>'; | |
| 586 | - html += '<td><span class="otto-pattern-type-badge otto-pattern-' + record.pattern_type + '">' + formatPatternType(record.pattern_type) + '</span></td>'; | |
| 587 | - html += '<td>' + (record.description ? escapeHtml(record.description) : '<span style="color: #999;">—</span>') + '</td>'; | |
| 588 | - html += '<td>' + formatRecheckAfter(record) + '</td>'; | |
| 589 | - html += '<td><span class="otto-actions">'; | |
| 590 | - html += '<button type="button" class="button button-small otto-recheck-url" data-id="' + record.id + '" style="margin-right: 5px;">Recheck</button>'; | |
| 591 | - html += '<button type="button" class="button button-small otto-delete-url" data-id="' + record.id + '" style="color: #dc3545;">Delete</button>'; | |
| 592 | - html += '</span></td>'; | |
| 593 | - html += '</tr>'; | |
| 594 | - }); | |
| 595 | - | |
| 596 | - html += '</tbody></table>'; | |
| 597 | - | |
| 598 | - if (pagination.total_pages > 1) { | |
| 599 | - html += '<div class="tablenav" style="margin-top: 15px; display: flex; justify-content: space-between; align-items: center;">'; | |
| 600 | - html += '<div class="tablenav-pages">'; | |
| 601 | - html += '<span class="displaying-num">' + pagination.total_count + ' items</span>'; | |
| 602 | - | |
| 603 | - html += '<span class="pagination-links">'; | |
| 604 | - | |
| 605 | - if (pagination.current_page > 1) { | |
| 606 | - html += '<a class="button otto-page-nav" data-page="1" href="#">«</a> '; | |
| 607 | - html += '<a class="button otto-page-nav" data-page="' + (pagination.current_page - 1) + '" href="#">‹</a> '; | |
| 608 | - } else { | |
| 609 | - html += '<span class="button disabled">«</span> '; | |
| 610 | - html += '<span class="button disabled">‹</span> '; | |
| 611 | - } | |
| 612 | - | |
| 613 | - html += '<span class="paging-input">'; | |
| 614 | - html += '<span class="tablenav-paging-text">' + pagination.current_page + ' of <span class="total-pages">' + pagination.total_pages + '</span></span>'; | |
| 615 | - html += '</span> '; | |
| 616 | - | |
| 617 | - if (pagination.current_page < pagination.total_pages) { | |
| 618 | - html += '<a class="button otto-page-nav" data-page="' + (pagination.current_page + 1) + '" href="#">›</a> '; | |
| 619 | - html += '<a class="button otto-page-nav" data-page="' + pagination.total_pages + '" href="#">»</a>'; | |
| 620 | - } else { | |
| 621 | - html += '<span class="button disabled">›</span> '; | |
| 622 | - html += '<span class="button disabled">»</span>'; | |
| 623 | - } | |
| 624 | - | |
| 625 | - html += '</span></div></div>'; | |
| 626 | - } | |
| 627 | - | |
| 628 | - $container.html(html); | |
| 629 | - | |
| 630 | - $('.otto-delete-url').on('click', function(e) { | |
| 631 | - e.preventDefault(); | |
| 632 | - var id = $(this).data('id'); | |
| 633 | - deleteExcludedURL(id); | |
| 634 | - }); | |
| 635 | - | |
| 636 | - $('.otto-recheck-url').on('click', function(e) { | |
| 637 | - e.preventDefault(); | |
| 638 | - var $btn = $(this); | |
| 639 | - var id = $btn.data('id'); | |
| 640 | - recheckExcludedURL(id, $btn); | |
| 641 | - }); | |
| 642 | - | |
| 643 | - $('.otto-page-nav').on('click', function(e) { | |
| 644 | - e.preventDefault(); | |
| 645 | - var page = $(this).data('page'); | |
| 646 | - loadExcludedURLs(page); | |
| 647 | - }); | |
| 648 | - } | |
| 649 | - | |
| 650 | - function recheckExcludedURL(id, $btn) { | |
| 651 | - var originalText = $btn.text(); | |
| 652 | - $btn.prop('disabled', true).text('Checking...'); | |
| 653 | - | |
| 654 | - $.ajax({ | |
| 655 | - url: ajaxUrl, | |
| 656 | - type: 'POST', | |
| 657 | - data: { | |
| 658 | - action: 'metasync_otto_recheck_excluded_url', | |
| 659 | - nonce: nonce, | |
| 660 | - id: id | |
| 661 | - }, | |
| 662 | - success: function(response) { | |
| 663 | - $btn.prop('disabled', false).text(originalText); | |
| 664 | - if (response.success) { | |
| 665 | - if (response.data.available) { | |
| 666 | - if (confirm('This URL is now available. Do you want to remove it from the excluded list?')) { | |
| 667 | - deleteExcludedURL(id); | |
| 668 | - } | |
| 669 | - } else { | |
| 670 | - alert('This URL is still returning 404.'); | |
| 671 | - } | |
| 672 | - } else { | |
| 673 | - alert('Error: ' + (response.data.message || 'Recheck failed')); | |
| 674 | - } | |
| 675 | - }, | |
| 676 | - error: function(xhr, status, err) { | |
| 677 | - $btn.prop('disabled', false).text(originalText); | |
| 678 | - alert('An error occurred while rechecking. Please try again.'); | |
| 679 | - } | |
| 680 | - }); | |
| 681 | - } | |
| 682 | - | |
| 683 | - function deleteExcludedURL(id) { | |
| 684 | - if (!confirm('Are you sure you want to delete this excluded URL?')) { | |
| 685 | - return; | |
| 686 | - } | |
| 687 | - | |
| 688 | - $.ajax({ | |
| 689 | - url: ajaxUrl, | |
| 690 | - type: 'POST', | |
| 691 | - data: { | |
| 692 | - action: 'metasync_otto_delete_excluded_url', | |
| 693 | - nonce: nonce, | |
| 694 | - id: id | |
| 695 | - }, | |
| 696 | - success: function(response) { | |
| 697 | - if (response.success) { | |
| 698 | - loadExcludedURLs(); | |
| 699 | - } else { | |
| 700 | - alert('Error: ' + (response.data.message || 'Failed to delete')); | |
| 701 | - } | |
| 702 | - }, | |
| 703 | - error: function() { | |
| 704 | - alert('An error occurred. Please try again.'); | |
| 705 | - } | |
| 706 | - }); | |
| 707 | - } | |
| 708 | - | |
| 709 | - function showStatus(type, message) { | |
| 710 | - var $status = $('#otto-add-status'); | |
| 711 | - $status.removeClass('otto-status-success otto-status-error'); | |
| 712 | - $status.addClass('otto-status-' + type); | |
| 713 | - $status.text(message).fadeIn(); | |
| 714 | - | |
| 715 | - setTimeout(function() { | |
| 716 | - $status.fadeOut(); | |
| 717 | - }, 5000); | |
| 718 | - } | |
| 719 | - | |
| 720 | - function formatPatternType(type) { | |
| 721 | - var types = { | |
| 722 | - 'exact': 'Exact', | |
| 723 | - 'contain': 'Contains', | |
| 724 | - 'start': 'Starts With', | |
| 725 | - 'end': 'Ends With', | |
| 726 | - 'regex': 'Regex' | |
| 727 | - }; | |
| 728 | - return types[type] || type; | |
| 729 | - } | |
| 730 | - | |
| 731 | - function formatRecheckAfter(record) { | |
| 732 | - if (!record.auto_excluded || !record.recheck_after) { | |
| 733 | - return '<span style="color: #999;">NA</span>'; | |
| 734 | - } | |
| 735 | - var d = new Date(record.recheck_after.replace(' ', 'T')); | |
| 736 | - if (isNaN(d.getTime())) { | |
| 737 | - return escapeHtml(record.recheck_after); | |
| 738 | - } | |
| 739 | - var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; | |
| 740 | - return months[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear(); | |
| 741 | - } | |
| 742 | - | |
| 743 | - function escapeHtml(text) { | |
| 744 | - if (!text) return ''; | |
| 745 | - var map = { | |
| 746 | - '&': '&', | |
| 747 | - '<': '<', | |
| 748 | - '>': '>', | |
| 749 | - '"': '"', | |
| 750 | - "'": ''' | |
| 751 | - }; | |
| 752 | - return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); | |
| 753 | - } | |
| 754 | - }); | |
| 755 | - })(); | |
| 756 | - </script> | |
| 757 | - | |
| 758 | 478 | <!-- OTTO Excluded URLs CSS --> |
| 759 | 479 | <style> |
| 760 | 480 | /* Fix input and select field text colors */ |
| 761 | 481 | #otto-excluded-url-form input[type="text"], |
| @@ -797,11 +517,14 @@ | ||
| 797 | 517 | text-transform: uppercase; |
| 798 | 518 | letter-spacing: 0.5px; |
| 799 | 519 | } |
| 800 | 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. */ | |
| 801 | 524 | .otto-pattern-exact { |
| 802 | 525 | background: #e3f2fd; |
| 803 | - color: #1976d2; | |
| 526 | + color: #0d47a1; | |
| 804 | 527 | } |
| 805 | 528 | |
| 806 | 529 | .otto-pattern-contain { |
| 807 | 530 | background: #f3e5f5; |
| @@ -809,14 +532,14 @@ | ||
| 809 | 532 | } |
| 810 | 533 | |
| 811 | 534 | .otto-pattern-start { |
| 812 | 535 | background: #e8f5e9; |
| 813 | - color: #388e3c; | |
| 536 | + color: #1b5e20; | |
| 814 | 537 | } |
| 815 | 538 | |
| 816 | 539 | .otto-pattern-end { |
| 817 | - background: var(--dashboard-card-bg)3e0; | |
| 818 | - color: #f57c00; | |
| 540 | + background: #fff3e0; | |
| 541 | + color: #8f4400; | |
| 819 | 542 | } |
| 820 | 543 | |
| 821 | 544 | .otto-pattern-regex { |
| 822 | 545 | background: #fce4ec; |
| @@ -848,12 +571,35 @@ | ||
| 848 | 571 | } |
| 849 | 572 | |
| 850 | 573 | #otto-excluded-urls-table-container .wp-list-table td { |
| 851 | 574 | background: var(--dashboard-card-bg) !important; |
| 852 | - color: #2c3338 !important; | |
| 575 | + color: var(--dashboard-text-primary) !important; | |
| 853 | 576 | border-bottom: 1px solid #c3c4c7; |
| 854 | 577 | } |
| 855 | 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 | + | |
| 856 | 602 | #otto-excluded-urls-table-container .wp-list-table tbody tr { |
| 857 | 603 | background: var(--dashboard-card-bg); |
| 858 | 604 | } |
| 859 | 605 | |
| @@ -919,13 +665,66 @@ | ||
| 919 | 665 | .tablenav-paging-text { |
| 920 | 666 | color: #2c3338 !important; |
| 921 | 667 | } |
| 922 | 668 | </style> |
| 923 | - </div> | |
| 924 | 669 | <?php |
| 925 | 670 | $admin->render_layout_close(); |
| 926 | 671 | } |
| 927 | 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 | + | |
| 928 | 727 | public function render_compatibility_sections() |
| 929 | 728 | { |
| 930 | 729 | ?> |
| 931 | 730 | <div class="metasync-settings-accordion"> |
| @@ -1159,8 +958,9 @@ | ||
| 1159 | 958 | ], |
| 1160 | 959 | 'bricks' => [ |
| 1161 | 960 | 'name' => 'Bricks Builder', |
| 1162 | 961 | 'plugin_files' => [ 'bricks/bricks.php' ], |
| 962 | + 'theme_name' => 'Bricks', | |
| 1163 | 963 | 'supported' => true, |
| 1164 | 964 | 'version' => '1.11', |
| 1165 | 965 | ], |
| 1166 | 966 | ]; |
| @@ -1168,10 +968,12 @@ | ||
| 1168 | 968 | $result = []; |
| 1169 | 969 | |
| 1170 | 970 | foreach ($builders as $key => $builder) { |
| 1171 | 971 | $is_core = isset($builder['is_core']) && $builder['is_core']; |
| 1172 | - $theme_name = isset($builder['theme_name']) ? $builder['theme_name'] : null; | |
| 1173 | - $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); | |
| 1174 | 976 | |
| 1175 | 977 | if ($builder['supported']) { |
| 1176 | 978 | $status = 'supported'; |
| 1177 | 979 | $status_text = 'Supported'; |
| @@ -1297,9 +1099,11 @@ | ||
| 1297 | 1099 | ]; |
| 1298 | 1100 | |
| 1299 | 1101 | $result = []; |
| 1300 | 1102 | foreach ( $themes as $key => $theme ) { |
| 1301 | - $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 ); | |
| 1302 | 1106 | $result[] = [ |
| 1303 | 1107 | 'name' => $theme['name'], |
| 1304 | 1108 | 'version' => $theme['version'], |
| 1305 | 1109 | 'status' => 'supported', |
| @@ -1472,12 +1276,14 @@ | ||
| 1472 | 1276 | * Get detailed plugin status (installed and/or active). |
| 1473 | 1277 | * |
| 1474 | 1278 | * @param array $plugin_files Array of plugin file paths to check |
| 1475 | 1279 | * @param bool $is_core Whether this is a WordPress core feature |
| 1476 | - * @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. | |
| 1477 | 1283 | * @return array ['is_installed' => bool, 'is_active' => bool, 'active_version' => string|null] |
| 1478 | 1284 | */ |
| 1479 | - 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) | |
| 1480 | 1286 | { |
| 1481 | 1287 | if ($is_core) { |
| 1482 | 1288 | return [ |
| 1483 | 1289 | 'is_installed' => true, |
| @@ -1485,14 +1291,16 @@ | ||
| 1485 | 1291 | 'active_version' => 'core' |
| 1486 | 1292 | ]; |
| 1487 | 1293 | } |
| 1488 | 1294 | |
| 1489 | - if ($theme_name) { | |
| 1295 | + if ($theme_slug) { | |
| 1490 | 1296 | $current_theme = wp_get_theme(); |
| 1491 | - $is_theme_active = (strtolower($current_theme->get('Name')) === strtolower($theme_name) || | |
| 1492 | - 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)); | |
| 1493 | 1299 | |
| 1494 | - $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(); | |
| 1495 | 1303 | |
| 1496 | 1304 | if ($theme_exists) { |
| 1497 | 1305 | return [ |
| 1498 | 1306 | 'is_installed' => true, |
| @@ -1541,39 +1349,44 @@ | ||
| 1541 | 1349 | } |
| 1542 | 1350 | |
| 1543 | 1351 | public function get_plugin_logo($plugin_key, $type) |
| 1544 | 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 | + | |
| 1545 | 1358 | $logos = [ |
| 1546 | 1359 | 'page_builder' => [ |
| 1547 | - 'elementor' => 'https://ps.w.org/elementor/assets/icon-256x256.gif', | |
| 1548 | - 'gutenberg' => 'https://i0.wp.com/wordpress.org/files/2023/02/wmark.png', | |
| 1549 | - 'divi' => 'https://www.elegantthemes.com/images/logo.svg', | |
| 1550 | - 'oxygen' => 'https://oxygenbuilder.com/wp-content/uploads/2021/09/oxygen-logo-icon.png', | |
| 1551 | - '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', | |
| 1552 | 1365 | ], |
| 1553 | 1366 | 'theme' => [ |
| 1554 | - 'astra' => 'https://ps.w.org/astra/assets/icon-128x128.png', | |
| 1555 | - 'generatepress' => 'https://ps.w.org/generatepress/assets/icon-128x128.png', | |
| 1556 | - 'oceanwp' => 'https://ps.w.org/ocean-extra/assets/icon-128x128.png', | |
| 1557 | - 'hello-elementor' => 'https://ps.w.org/hello-elementor/assets/icon-128x128.png', | |
| 1558 | - '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', | |
| 1559 | 1372 | ], |
| 1560 | 1373 | 'seo' => [ |
| 1561 | - 'yoast' => 'https://ps.w.org/wordpress-seo/assets/icon-128x128.gif', | |
| 1562 | - 'rankmath' => 'https://ps.w.org/seo-by-rank-math/assets/icon-128x128.png', | |
| 1563 | - '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', | |
| 1564 | 1377 | ], |
| 1565 | 1378 | 'cache' => [ |
| 1566 | - 'wp-rocket' => 'https://ps.w.org/rocket-lazy-load/assets/icon-128x128.png', | |
| 1567 | - 'litespeed-cache' => 'https://ps.w.org/litespeed-cache/assets/icon-128x128.png', | |
| 1568 | - 'w3-total-cache' => 'https://ps.w.org/w3-total-cache/assets/icon-128x128.png', | |
| 1569 | - 'sg-cachepress' => 'https://ps.w.org/sg-cachepress/assets/icon-128x128.png', | |
| 1570 | - 'hummingbird-performance' => 'https://ps.w.org/hummingbird-performance/assets/icon-128x128.png', | |
| 1571 | - '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', | |
| 1572 | 1385 | ], |
| 1573 | 1386 | 'cdn' => [ |
| 1574 | - 'cloudflare' => 'https://ps.w.org/cloudflare/assets/icon-128x128.png', | |
| 1575 | - 'fastly' => 'https://ps.w.org/purgely/assets/icon-128x128.png', | |
| 1387 | + 'cloudflare' => $assets . 'cloudflare.svg', | |
| 1388 | + 'fastly' => $assets . 'fastly.svg', | |
| 1576 | 1389 | ], |
| 1577 | 1390 | ]; |
| 1578 | 1391 | |
| 1579 | 1392 | return isset($logos[$type][$plugin_key]) ? $logos[$type][$plugin_key] : ''; |