array( 2, 6 * HOUR_IN_SECONDS ), Classifier::SSL_ERROR => array( 2, 6 * HOUR_IN_SECONDS ), Classifier::SERVER_ERROR => array( 3, DAY_IN_SECONDS ), Classifier::TIMEOUT => array( 3, DAY_IN_SECONDS ), ); /** * Back-off after the 1st, 2nd and 3rd+ consecutive failure. */ private const BACKOFF = array( 6 * HOUR_IN_SECONDS, DAY_IN_SECONDS, 3 * DAY_IN_SECONDS ); /** * Whether a raw status is a failure. * * @param string $raw Raw status. */ public static function is_failure( string $raw ): bool { return isset( self::THRESHOLDS[ $raw ] ); } /** * Statuses shown as problems in the dashboard counts. * * @return string[] */ public static function problem_statuses(): array { return array_keys( self::THRESHOLDS ); } /** * Apply a new raw result to a link's previous state. * * @param array{status:string,fail_count:int,first_failure_at:int|null,last_result?:string|null} $previous Previous state (last_result = raw status of the previous check). * @param string $raw Raw classification. * @param int $now Current timestamp. * @param int $interval_days Re-check interval for healthy links. * @return array{status:string,fail_count:int,first_failure_at:int|null,next_check_at:int,last_result:string} */ public static function apply( array $previous, string $raw, int $now, int $interval_days ): array { if ( ! self::is_failure( $raw ) ) { return array( 'status' => $raw, 'fail_count' => 0, 'first_failure_at' => null, 'next_check_at' => $now + max( 1, $interval_days ) * DAY_IN_SECONDS, 'last_result' => $raw, ); } // Only consecutive failures of the same kind count: a timeout followed by one 404 is not "two 404s". $same_kind = isset( $previous['last_result'] ) && $previous['last_result'] === $raw; $was_failing = (int) $previous['fail_count'] > 0 && $same_kind; $fail_count = $was_failing ? (int) $previous['fail_count'] + 1 : 1; $first = $was_failing && null !== $previous['first_failure_at'] ? (int) $previous['first_failure_at'] : $now; list( $need_count, $need_span ) = self::THRESHOLDS[ $raw ]; $confirmed = $fail_count >= $need_count && ( $now - $first ) >= $need_span; $backoff = self::BACKOFF[ min( $fail_count, count( self::BACKOFF ) ) - 1 ]; return array( 'status' => $confirmed ? $raw : self::UNCONFIRMED, 'fail_count' => min( $fail_count, 65535 ), 'first_failure_at' => $first, 'next_check_at' => $now + $backoff, 'last_result' => $raw, ); } }