| @@ -80,9 +80,9 @@ | ||
| 80 | 80 | } else { |
| 81 | 81 | echo '<p style="color: var(--dashboard-text-secondary);">ℹ️ No cache plugins detected.</p>'; |
| 82 | 82 | } |
| 83 | 83 | } catch (Exception $e) { |
| 84 | - error_log('MetaSync Cache Status Error: ' . $e->getMessage()); | |
| 84 | + error_log('MetaSync Cache Status Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- unreachable in production | |
| 85 | 85 | echo '<p style="color: var(--dashboard-error);">⚠️ An error occurred while retrieving cache plugin status.</p>'; |
| 86 | 86 | } |
| 87 | 87 | } else { |
| 88 | 88 | echo '<p style="color: var(--dashboard-error);">⚠️ Cache Purge class not loaded.</p>'; |
| @@ -98,12 +98,12 @@ | ||
| 98 | 98 | <p class="description" style="margin-top: 10px; color: var(--dashboard-text-secondary);">This will clear cache from WP Rocket, LiteSpeed, W3 Total Cache, and all other detected cache plugins.</p> |
| 99 | 99 | </form> |
| 100 | 100 | |
| 101 | 101 | <?php |
| 102 | - if (isset($_GET['cache_cleared']) && $_GET['cache_cleared'] == '1') { | |
| 103 | - $cleared = isset($_GET['cleared']) ? intval($_GET['cleared']) : 0; | |
| 104 | - $failed = isset($_GET['failed']) ? intval($_GET['failed']) : 0; | |
| 105 | - $plugins = isset($_GET['plugins']) ? sanitize_text_field($_GET['plugins']) : ''; | |
| 102 | + if (isset($_GET['cache_cleared']) && $_GET['cache_cleared'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 103 | + $cleared = isset($_GET['cleared']) ? intval($_GET['cleared']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 104 | + $failed = isset($_GET['failed']) ? intval($_GET['failed']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 105 | + $plugins = isset($_GET['plugins']) ? sanitize_text_field(wp_unslash($_GET['plugins'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 106 | 106 | |
| 107 | 107 | if ($cleared > 0) { |
| 108 | 108 | echo '<div class="notice notice-success inline" style="margin-top: 15px;"><p>'; |
| 109 | 109 | echo '✅ <strong>Success!</strong> Cleared cache for ' . intval( $cleared ) . ' plugin(s)'; |
| @@ -123,10 +123,10 @@ | ||
| 123 | 123 | echo '</p></div>'; |
| 124 | 124 | } |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | - if (isset($_GET['cache_error']) && $_GET['cache_error'] == '1') { | |
| 128 | - $message = isset($_GET['message']) ? urldecode(sanitize_text_field($_GET['message'])) : ''; | |
| 127 | + if (isset($_GET['cache_error']) && $_GET['cache_error'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 128 | + $message = isset($_GET['message']) ? urldecode(sanitize_text_field(wp_unslash($_GET['message']))) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 129 | 129 | if (empty($message)) { |
| 130 | 130 | $message = 'An unknown error occurred while clearing cache. Please check error logs for details.'; |
| 131 | 131 | } |
| 132 | 132 | echo '<div class="notice notice-error inline" style="margin-top: 15px;"><p>'; |
| @@ -222,12 +222,12 @@ | ||
| 222 | 222 | </form> |
| 223 | 223 | |
| 224 | 224 | <?php |
| 225 | 225 | // Hosting cache result messages |
| 226 | - if (isset($_GET['hosting_cache_cleared']) && $_GET['hosting_cache_cleared'] == '1') { | |
| 227 | - $hc_cleared = isset($_GET['hc_cleared']) ? sanitize_text_field(urldecode($_GET['hc_cleared'])) : ''; | |
| 228 | - $hc_failed = isset($_GET['hc_failed']) ? sanitize_text_field(urldecode($_GET['hc_failed'])) : ''; | |
| 229 | - $hc_not_detected = isset($_GET['hc_not_detected']) ? sanitize_text_field(urldecode($_GET['hc_not_detected'])) : ''; | |
| 226 | + if (isset($_GET['hosting_cache_cleared']) && $_GET['hosting_cache_cleared'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 227 | + $hc_cleared = isset($_GET['hc_cleared']) ? sanitize_text_field(urldecode($_GET['hc_cleared'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 228 | + $hc_failed = isset($_GET['hc_failed']) ? sanitize_text_field(urldecode($_GET['hc_failed'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 229 | + $hc_not_detected = isset($_GET['hc_not_detected']) ? sanitize_text_field(urldecode($_GET['hc_not_detected'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 230 | 230 | |
| 231 | 231 | if ($hc_cleared) { |
| 232 | 232 | echo '<div class="notice notice-success inline" style="margin-top: 15px;"><p>'; |
| 233 | 233 | echo '✅ <strong>Success!</strong> Purged hosting cache on: ' . esc_html($hc_cleared); |
| @@ -295,11 +295,11 @@ | ||
| 295 | 295 | <span style="color: var(--dashboard-accent);"><?php echo esc_html($cache_count); ?> cached entries</span> |
| 296 | 296 | </div> |
| 297 | 297 | |
| 298 | 298 | <?php |
| 299 | - if (isset($_GET['otto_cache_cleared']) && $_GET['otto_cache_cleared'] == '1') { | |
| 300 | - $cleared_count = isset($_GET['count']) ? intval($_GET['count']) : 0; | |
| 301 | - $url = isset($_GET['url']) ? urldecode(sanitize_text_field($_GET['url'])) : ''; | |
| 299 | + if (isset($_GET['otto_cache_cleared']) && $_GET['otto_cache_cleared'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 300 | + $cleared_count = isset($_GET['count']) ? intval($_GET['count']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 301 | + $url = isset($_GET['url']) ? urldecode(sanitize_text_field(wp_unslash($_GET['url']))) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 302 | 302 | |
| 303 | 303 | echo '<div class="notice notice-success inline" style="margin-top: 15px;"><p>'; |
| 304 | 304 | if (!empty($url)) { |
| 305 | 305 | echo '✅ <strong>Success!</strong> Cleared cache for URL: <code>' . esc_html($url) . '</code> (' . intval( $cleared_count ) . ' entries)'; |
| @@ -308,10 +308,10 @@ | ||
| 308 | 308 | } |
| 309 | 309 | echo '</p></div>'; |
| 310 | 310 | } |
| 311 | 311 | |
| 312 | - if (isset($_GET['otto_cache_error']) && $_GET['otto_cache_error'] == '1') { | |
| 313 | - $message = isset($_GET['message']) ? urldecode(sanitize_text_field($_GET['message'])) : 'An unknown error occurred.'; | |
| 312 | + if (isset($_GET['otto_cache_error']) && $_GET['otto_cache_error'] == '1') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 313 | + $message = isset($_GET['message']) ? urldecode(sanitize_text_field(wp_unslash($_GET['message']))) : 'An unknown error occurred.'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only result flag for an escaped notice | |
| 314 | 314 | echo '<div class="notice notice-error inline" style="margin-top: 15px;"><p>'; |
| 315 | 315 | echo '❌ <strong>Error:</strong> ' . esc_html($message); |
| 316 | 316 | echo '</p></div>'; |
| 317 | 317 | } |
| @@ -350,9 +350,9 @@ | ||
| 350 | 350 | <td> |
| 351 | 351 | <input type="url" |
| 352 | 352 | id="otto_cache_url" |
| 353 | 353 | name="otto_cache_url" |
| 354 | - value="<?php echo isset($_GET['url']) ? esc_attr(urldecode(sanitize_text_field($_GET['url']))) : ''; ?>" | |
| 354 | + value="<?php echo isset($_GET['url']) ? esc_attr(urldecode(sanitize_text_field(wp_unslash($_GET['url'])))) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only URL prefill, the form submit carries a nonce ?>" | |
| 355 | 355 | class="regular-text" |
| 356 | 356 | placeholder="https://example.com/page/" |
| 357 | 357 | required /> |
| 358 | 358 | <p class="description" style="color: var(--dashboard-text-secondary);">Enter the full URL of the page whose cache you want to clear.</p> |
| @@ -398,9 +398,9 @@ | ||
| 398 | 398 | if (!empty($results['cleared'])) { |
| 399 | 399 | $redirect_url .= '&plugins=' . urlencode(implode(',', $results['cleared'])); |
| 400 | 400 | } |
| 401 | 401 | } catch (Exception $e) { |
| 402 | - error_log('MetaSync Cache Clear Error: ' . $e->getMessage()); | |
| 402 | + error_log('MetaSync Cache Clear Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 403 | 403 | $redirect_url .= '&cache_error=1&message=' . urlencode('An error occurred while clearing the cache. Please try again.'); |
| 404 | 404 | } |
| 405 | 405 | } else { |
| 406 | 406 | $redirect_url .= '&cache_error=1&message=' . urlencode('Cache Purge class not available'); |
| @@ -427,10 +427,19 @@ | ||
| 427 | 427 | if (class_exists('Metasync_Otto_Transient_Cache')) { |
| 428 | 428 | try { |
| 429 | 429 | $result = Metasync_Otto_Transient_Cache::clear_all_transients(); |
| 430 | 430 | $redirect_url .= '&otto_cache_cleared=1&count=' . $result['cleared_count']; |
| 431 | + | |
| 432 | + # Clearing the OTTO suggestions transient is not enough on its | |
| 433 | + # own. The OTTO-modified HTML (e.g. an injected hidden H1 from a module | |
| 434 | + # that has since been unapproved in the dashboard) is frozen in the host | |
| 435 | + # page cache and any edge CDN. Until those layers are purged, PHP never | |
| 436 | + # re-renders the page, so the stale injection keeps serving even though | |
| 437 | + # the suggestions cache is now empty. Purge the page caches too so the | |
| 438 | + # next request regenerates with the current (clean) OTTO state. | |
| 439 | + self::purge_page_caches_all(); | |
| 431 | 440 | } catch (Exception $e) { |
| 432 | - error_log('MetaSync OTTO Cache Clear Error: ' . $e->getMessage()); | |
| 441 | + error_log('MetaSync OTTO Cache Clear Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 433 | 442 | $redirect_url .= '&otto_cache_error=1&message=' . urlencode('An error occurred while clearing the OTTO cache. Please try again.'); |
| 434 | 443 | } |
| 435 | 444 | } else { |
| 436 | 445 | $redirect_url .= '&otto_cache_error=1&message=' . urlencode('Transient Cache class not found'); |
| @@ -472,13 +481,20 @@ | ||
| 472 | 481 | $result = Metasync_Otto_Transient_Cache::clear_url_transient($url); |
| 473 | 482 | |
| 474 | 483 | if ($result['success']) { |
| 475 | 484 | $redirect_url .= '&otto_cache_cleared=1&count=' . $result['cleared_count'] . '&url=' . urlencode($url); |
| 485 | + | |
| 486 | + # Also purge the host page cache and edge CDN for this URL so | |
| 487 | + # the frozen OTTO-modified HTML (e.g. a hidden injected H1 from a module | |
| 488 | + # unapproved in the dashboard) is regenerated. Clearing the suggestions | |
| 489 | + # transient alone leaves the stale HTML live in the page cache because | |
| 490 | + # PHP never re-renders the cached page. | |
| 491 | + self::purge_page_caches_for_url($url); | |
| 476 | 492 | } else { |
| 477 | 493 | $redirect_url .= '&otto_cache_error=1&message=' . urlencode($result['message']); |
| 478 | 494 | } |
| 479 | 495 | } catch (Exception $e) { |
| 480 | - error_log('MetaSync OTTO Cache Clear Error: ' . $e->getMessage()); | |
| 496 | + error_log('MetaSync OTTO Cache Clear Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 481 | 497 | $redirect_url .= '&otto_cache_error=1&message=' . urlencode('An error occurred while clearing the OTTO cache for this URL. Please try again.'); |
| 482 | 498 | } |
| 483 | 499 | } else { |
| 484 | 500 | $redirect_url .= '&otto_cache_error=1&message=' . urlencode('Transient Cache class not found'); |
| @@ -490,8 +506,68 @@ | ||
| 490 | 506 | exit; |
| 491 | 507 | } |
| 492 | 508 | |
| 493 | 509 | // ------------------------------------------------------------------ |
| 510 | + // Page-cache propagation | |
| 511 | + // ------------------------------------------------------------------ | |
| 512 | + | |
| 513 | + /** | |
| 514 | + * Purge the host page cache and edge CDN after clearing OTTO suggestions | |
| 515 | + * for a single URL. | |
| 516 | + * | |
| 517 | + * Clearing the OTTO suggestions transient only invalidates the data layer. | |
| 518 | + * The rendered, OTTO-modified HTML is still frozen in the host page cache | |
| 519 | + * (WP Rocket, Kinsta/WP Engine FastCGI, etc.) and any edge CDN, so PHP never | |
| 520 | + * re-runs and the stale injection keeps serving. Purging both page layers | |
| 521 | + * forces a clean regeneration on the next request. | |
| 522 | + * | |
| 523 | + * Unlike the crawl-notify webhook (which defers the same purge to a | |
| 524 | + * background job because it runs under a strict response-time budget), this | |
| 525 | + * is a deliberate, user-initiated admin action, so the purge runs inline — | |
| 526 | + * the admin expects to wait for the clear to complete. The purge is not | |
| 527 | + * followed by a warm: the goal here is to drop stale OTTO HTML, not to | |
| 528 | + * pre-populate fresh content, so a cold cache that repopulates on the next | |
| 529 | + * request is the desired outcome. | |
| 530 | + * | |
| 531 | + * @param string $url Absolute URL whose page caches should be purged. | |
| 532 | + * @return void | |
| 533 | + */ | |
| 534 | + public static function purge_page_caches_for_url($url) | |
| 535 | + { | |
| 536 | + if (class_exists('Metasync_Cache_Purge')) { | |
| 537 | + Metasync_Cache_Purge::purge_single_url($url); | |
| 538 | + } | |
| 539 | + if (class_exists('Metasync_Edge_Cache_Purge')) { | |
| 540 | + Metasync_Edge_Cache_Purge::purge(array($url)); | |
| 541 | + } | |
| 542 | + } | |
| 543 | + | |
| 544 | + /** | |
| 545 | + * Purge all host page caches and trigger edge CDN flush after clearing every | |
| 546 | + * OTTO suggestions transient. | |
| 547 | + * | |
| 548 | + * Edge coverage caveat: full-flush providers (Sucuri/Sevalla/Cloudways, etc.) | |
| 549 | + * ignore the URL list and clear everything, so the home-URL sentinel below is | |
| 550 | + * enough to fire them. Tag-based providers (Cloudflare/Fastly/Akamai), however, | |
| 551 | + * purge per-URL/per-tag — passing only the home URL purges just the homepage on | |
| 552 | + * those CDNs. The host page cache is still flushed site-wide by purge_all(), so | |
| 553 | + * the stale OTTO HTML is gone at the origin; on tag-based CDNs, non-home pages | |
| 554 | + * clear on their normal TTL. Clearing a specific page's CDN entry immediately is | |
| 555 | + * what the per-URL "Clear Cache for URL" action is for. | |
| 556 | + * | |
| 557 | + * @return void | |
| 558 | + */ | |
| 559 | + public static function purge_page_caches_all() | |
| 560 | + { | |
| 561 | + if (class_exists('Metasync_Cache_Purge')) { | |
| 562 | + Metasync_Cache_Purge::purge_all('otto_manual_clear'); | |
| 563 | + } | |
| 564 | + if (class_exists('Metasync_Edge_Cache_Purge')) { | |
| 565 | + Metasync_Edge_Cache_Purge::purge(array(home_url('/'))); | |
| 566 | + } | |
| 567 | + } | |
| 568 | + | |
| 569 | + // ------------------------------------------------------------------ | |
| 494 | 570 | // AJAX handlers – excluded URLs |
| 495 | 571 | // ------------------------------------------------------------------ |
| 496 | 572 | |
| 497 | 573 | /** |
| @@ -508,11 +584,11 @@ | ||
| 508 | 584 | wp_send_json_error(['message' => 'Invalid security token']); |
| 509 | 585 | return; |
| 510 | 586 | } |
| 511 | 587 | |
| 512 | - $url_pattern = isset($_POST['url_pattern']) ? sanitize_text_field($_POST['url_pattern']) : ''; | |
| 513 | - $pattern_type = isset($_POST['pattern_type']) ? sanitize_text_field($_POST['pattern_type']) : 'exact'; | |
| 514 | - $description = isset($_POST['description']) ? sanitize_textarea_field($_POST['description']) : ''; | |
| 588 | + $url_pattern = isset($_POST['url_pattern']) ? sanitize_text_field(wp_unslash($_POST['url_pattern'])) : ''; | |
| 589 | + $pattern_type = isset($_POST['pattern_type']) ? sanitize_text_field(wp_unslash($_POST['pattern_type'])) : 'exact'; | |
| 590 | + $description = isset($_POST['description']) ? sanitize_textarea_field(wp_unslash($_POST['description'])) : ''; | |
| 515 | 591 | |
| 516 | 592 | if (empty($url_pattern)) { |
| 517 | 593 | wp_send_json_error(['message' => 'URL pattern is required']); |
| 518 | 594 | return; |
| @@ -590,9 +666,9 @@ | ||
| 590 | 666 | $cache_purge->clear_url_cache($url_pattern); |
| 591 | 667 | } |
| 592 | 668 | wp_cache_flush(); |
| 593 | 669 | } catch (Exception $e) { |
| 594 | - error_log('MetaSync: Failed to clear cache for reactivated URL: ' . $e->getMessage()); | |
| 670 | + error_log('MetaSync: Failed to clear cache for reactivated URL: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 595 | 671 | } |
| 596 | 672 | } |
| 597 | 673 | |
| 598 | 674 | wp_send_json_success([ |
| @@ -612,9 +688,9 @@ | ||
| 612 | 688 | } |
| 613 | 689 | |
| 614 | 690 | wp_cache_flush(); |
| 615 | 691 | } catch (Exception $e) { |
| 616 | - error_log('MetaSync: Failed to clear cache for excluded URL: ' . $e->getMessage()); | |
| 692 | + error_log('MetaSync: Failed to clear cache for excluded URL: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 617 | 693 | } |
| 618 | 694 | } |
| 619 | 695 | |
| 620 | 696 | wp_send_json_success([ |
| @@ -682,9 +758,8 @@ | ||
| 682 | 758 | return; |
| 683 | 759 | } |
| 684 | 760 | |
| 685 | 761 | require_once plugin_dir_path(dirname(__FILE__)) . 'otto/class-metasync-otto-excluded-urls-database.php'; |
| 686 | - require_once plugin_dir_path(dirname(__FILE__)) . 'otto/otto_pixel.php'; | |
| 687 | 762 | |
| 688 | 763 | $db = new Metasync_Otto_Excluded_URLs_Database(); |
| 689 | 764 | $row = $db->get_record_by_id($id); |
| 690 | 765 | |
| @@ -692,13 +767,86 @@ | ||
| 692 | 767 | wp_send_json_error(['message' => 'Excluded URL not found']); |
| 693 | 768 | return; |
| 694 | 769 | } |
| 695 | 770 | |
| 771 | + // Only an exact pattern resolves to a single URL that can be probed; | |
| 772 | + // contain/start/end/regex rows match many URLs, so there is nothing to check. | |
| 773 | + if ('exact' !== $row->pattern_type) { | |
| 774 | + wp_send_json_error(['message' => 'Only exact URL exclusions can be rechecked']); | |
| 775 | + } | |
| 776 | + | |
| 696 | 777 | $url = trim($row->url_pattern); |
| 697 | - $available = metasync_otto_is_url_available($url); | |
| 698 | 778 | |
| 779 | + // The probe needs a full URL. Older rows may store a site-relative | |
| 780 | + // path, and a protocol-relative pattern only lacks the scheme; any | |
| 781 | + // other scheme-less pattern cannot be turned into one URL reliably, | |
| 782 | + // and guessing (host as path, other host as this site) would probe a | |
| 783 | + // URL nobody excluded. | |
| 784 | + if (null === wp_parse_url($url, PHP_URL_SCHEME)) { | |
| 785 | + if (0 === strpos($url, '//')) { | |
| 786 | + $url = wp_parse_url(home_url(), PHP_URL_SCHEME) . ':' . $url; | |
| 787 | + } elseif (0 === strpos($url, '/')) { | |
| 788 | + $url = home_url($url); | |
| 789 | + } else { | |
| 790 | + wp_send_json_success([ | |
| 791 | + 'available' => false, | |
| 792 | + 'checked' => false, | |
| 793 | + 'message' => 'Stored pattern is not a full URL; edit the entry to include the scheme.', | |
| 794 | + 'url' => $url, | |
| 795 | + ]); | |
| 796 | + } | |
| 797 | + } | |
| 798 | + | |
| 799 | + // Exclusions exist for this site's URLs, and letting the probe leave | |
| 800 | + // for arbitrary hosts would turn Recheck into a server-side request | |
| 801 | + // oracle, so the probe never targets another host. | |
| 802 | + $url_host = strtolower((string) wp_parse_url($url, PHP_URL_HOST)); | |
| 803 | + $home_host = strtolower((string) wp_parse_url(home_url(), PHP_URL_HOST)); | |
| 804 | + if ($url_host !== $home_host || '' === $url_host) { | |
| 805 | + wp_send_json_success([ | |
| 806 | + 'available' => false, | |
| 807 | + 'checked' => false, | |
| 808 | + 'message' => 'Only URLs on this site can be rechecked.', | |
| 809 | + 'url' => $url, | |
| 810 | + ]); | |
| 811 | + } | |
| 812 | + | |
| 813 | + // Probe the real URL rather than guessing from local data — a deleted | |
| 814 | + // post is invisible to local lookups but still 404s over HTTP. | |
| 815 | + $args = [ | |
| 816 | + 'timeout' => 10, | |
| 817 | + 'redirection' => 5, | |
| 818 | + // Same opt-out core uses for loopback requests to this site. | |
| 819 | + 'sslverify' => apply_filters('https_local_ssl_verify', true), | |
| 820 | + ]; | |
| 821 | + | |
| 822 | + $response = wp_remote_head($url, $args); | |
| 823 | + | |
| 824 | + // Some servers and edge layers refuse HEAD outright while the page | |
| 825 | + // still serves to GET requests, so retry once before reporting the | |
| 826 | + // refusal as a verdict. | |
| 827 | + if ( | |
| 828 | + !is_wp_error($response) | |
| 829 | + && 405 === (int) wp_remote_retrieve_response_code($response) | |
| 830 | + ) { | |
| 831 | + $response = wp_remote_get($url, $args); | |
| 832 | + } | |
| 833 | + | |
| 834 | + if (is_wp_error($response)) { | |
| 835 | + wp_send_json_success([ | |
| 836 | + 'available' => false, | |
| 837 | + 'checked' => false, | |
| 838 | + 'message' => $response->get_error_message(), | |
| 839 | + 'url' => $url, | |
| 840 | + ]); | |
| 841 | + } | |
| 842 | + | |
| 843 | + $code = (int) wp_remote_retrieve_response_code($response); | |
| 844 | + | |
| 699 | 845 | wp_send_json_success([ |
| 700 | - 'available' => $available, | |
| 846 | + 'available' => ($code >= 200 && $code < 400), | |
| 847 | + 'checked' => true, | |
| 848 | + 'status' => $code, | |
| 701 | 849 | 'url' => $url, |
| 702 | 850 | ]); |
| 703 | 851 | } |
| 704 | 852 | |