| @@ -312,9 +312,9 @@ | ||
| 312 | 312 | * @param string $message Message to log. |
| 313 | 313 | */ |
| 314 | 314 | protected function log($message) |
| 315 | 315 | { |
| 316 | - error_log('[MetaSync HOST_BLOCKING] ' . $message); | |
| 316 | + error_log('[MetaSync HOST_BLOCKING] ' . $message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 317 | 317 | } |
| 318 | 318 | |
| 319 | 319 | /** |
| 320 | 320 | * Turn a wp_remote_* return value into a blocked / not-blocked / inconclusive verdict. |
| @@ -499,8 +499,10 @@ | ||
| 499 | 499 | |
| 500 | 500 | $get = $this->run_check('GET'); |
| 501 | 501 | $post = $this->run_check('POST'); |
| 502 | 502 | |
| 503 | + $previous = $this->get_last_result(); | |
| 504 | + | |
| 503 | 505 | $result = [ |
| 504 | 506 | 'get_blocked' => !empty($get['blocked']), |
| 505 | 507 | 'post_blocked' => !empty($post['blocked']), |
| 506 | 508 | 'checked_at' => current_time('mysql'), |
| @@ -507,8 +509,10 @@ | ||
| 507 | 509 | ]; |
| 508 | 510 | |
| 509 | 511 | update_option(self::RESULT_OPTION, $result, true); |
| 510 | 512 | |
| 513 | + $this->track_result($result, $previous, !empty($get['checker_unreachable']) || !empty($post['checker_unreachable'])); | |
| 514 | + | |
| 511 | 515 | if (!empty($get['checker_unreachable']) || !empty($post['checker_unreachable'])) { |
| 512 | 516 | $this->log(sprintf( |
| 513 | 517 | 'Inconclusive automatic check (checker unavailable) — no warning shown. GET: %s | POST: %s', |
| 514 | 518 | $get['details'], |
| @@ -529,8 +533,44 @@ | ||
| 529 | 533 | return $result; |
| 530 | 534 | } |
| 531 | 535 | |
| 532 | 536 | /** |
| 537 | + * Consent-gated analytics for the automatic check: sent while the host is | |
| 538 | + * blocking us, and once when a block clears. A clean run that stays clean | |
| 539 | + * sends nothing; an inconclusive run never reports a block as cleared. | |
| 540 | + * | |
| 541 | + * @param array $result Result just stored. | |
| 542 | + * @param array|null $previous Result before this run, null on first run. | |
| 543 | + * @param bool $inconclusive Whether the checker itself was unreachable. | |
| 544 | + */ | |
| 545 | + private function track_result(array $result, $previous, $inconclusive) | |
| 546 | + { | |
| 547 | + $blocked = $result['get_blocked'] || $result['post_blocked']; | |
| 548 | + $was_blocked = is_array($previous) && (!empty($previous['get_blocked']) || !empty($previous['post_blocked'])); | |
| 549 | + | |
| 550 | + if (!$blocked && (!$was_blocked || $inconclusive)) { | |
| 551 | + return; | |
| 552 | + } | |
| 553 | + | |
| 554 | + if ($previous === null) { | |
| 555 | + $trigger = 'first_check'; | |
| 556 | + } elseif ($blocked !== $was_blocked | |
| 557 | + || $result['get_blocked'] !== !empty($previous['get_blocked']) | |
| 558 | + || $result['post_blocked'] !== !empty($previous['post_blocked'])) { | |
| 559 | + $trigger = 'state_change'; | |
| 560 | + } else { | |
| 561 | + $trigger = 'recheck'; | |
| 562 | + } | |
| 563 | + | |
| 564 | + Metasync_PostHog::feature('host_blocking_detected', [ | |
| 565 | + 'get_blocked' => $result['get_blocked'], | |
| 566 | + 'post_blocked' => $result['post_blocked'], | |
| 567 | + 'trigger' => $trigger, | |
| 568 | + 'inconclusive' => (bool) $inconclusive, | |
| 569 | + ]); | |
| 570 | + } | |
| 571 | + | |
| 572 | + /** | |
| 533 | 573 | * Whether the inbound leg of the check can possibly succeed. |
| 534 | 574 | * |
| 535 | 575 | * On localhost, *.local/*.test, or a private-range IP the checker cannot call back, |
| 536 | 576 | * so every automatic run would report blocking. The manual button stays available on |
| @@ -633,13 +673,13 @@ | ||
| 633 | 673 | * @return bool |
| 634 | 674 | */ |
| 635 | 675 | public function is_plugin_admin_page() |
| 636 | 676 | { |
| 637 | - if (empty($_GET['page'])) { | |
| 677 | + if (empty($_GET['page'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check for notice placement | |
| 638 | 678 | return false; |
| 639 | 679 | } |
| 640 | 680 | |
| 641 | - $page = sanitize_text_field(wp_unslash($_GET['page'])); | |
| 681 | + $page = sanitize_text_field(wp_unslash($_GET['page'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check for notice placement | |
| 642 | 682 | $slug = class_exists('Metasync_Admin') && !empty(Metasync_Admin::$page_slug) |
| 643 | 683 | ? Metasync_Admin::$page_slug |
| 644 | 684 | : 'searchatlas'; |
| 645 | 685 | |
| @@ -709,8 +749,9 @@ | ||
| 709 | 749 | <?php if ($compatibility_url !== '') : ?> |
| 710 | 750 | <a href="<?php echo esc_url($compatibility_url); ?>"><?php esc_html_e('View details and re-run the connectivity test', 'metasync'); ?></a> |
| 711 | 751 | · |
| 712 | 752 | <?php endif; ?> |
| 753 | + <?php /* translators: %s: date and time of the last check. */ ?> | |
| 713 | 754 | <?php echo esc_html(sprintf(__('Last checked: %s', 'metasync'), $result['checked_at'])); ?> |
| 714 | 755 | </p> |
| 715 | 756 | </div> |
| 716 | 757 | </div> |