'LCP', 'interaction_to_next_paint' => 'INP', 'cumulative_layout_shift' => 'CLS', 'experimental_time_to_first_byte' => 'TTFB', ); public function __construct( private SampleRepository $samples ) {} public function pull_all(): void { $api_key = (string) Options::get( 'api_key' ); if ( '' === $api_key ) { return; } $today = gmdate( 'Y-m-d' ); foreach ( Options::tracked_urls() as $url ) { // One CrUX sample per URL per day, whichever level it came from. if ( $this->samples->has_crux_sample_for_day( $url, 'LCP', $today ) ) { continue; } $result = $this->fetch( $url, $api_key ); $source = SampleRepository::SOURCE_CRUX_URL; if ( null === $result ) { // URL-level record too thin: fall back to origin-level data. // That describes every page on the site, not this URL, so it is // stored under its own source and never averaged with the other. $result = $this->fetch( $url, $api_key, true ); $source = SampleRepository::SOURCE_CRUX_ORIGIN; } if ( null === $result ) { continue; } $now = Util::now(); foreach ( $result as $metric => $p75 ) { $this->samples->insert( $url, $metric, $p75, $source, $now ); } } } /** * @return array|null Metric => p75, or null when no record. */ private function fetch( string $url, string $api_key, bool $origin_level = false ): ?array { $cache_key = 'aftercare_crux_' . md5( ( $origin_level ? 'o:' : 'u:' ) . $url ); $cached = get_transient( $cache_key ); if ( is_array( $cached ) ) { return empty( $cached ) ? null : $cached; } $body = array( 'metrics' => array_keys( self::METRIC_MAP ) ); if ( $origin_level ) { $parts = wp_parse_url( $url ); $body['origin'] = ( $parts['scheme'] ?? 'https' ) . '://' . ( $parts['host'] ?? '' ); } else { $body['url'] = $url; } $response = wp_remote_post( add_query_arg( 'key', rawurlencode( $api_key ), self::ENDPOINT ), array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { return null; } $code = wp_remote_retrieve_response_code( $response ); if ( 404 === $code ) { // No data for this URL — cache the miss for a day to avoid re-hitting. set_transient( $cache_key, array(), 12 * HOUR_IN_SECONDS ); return null; } if ( 200 !== $code ) { return null; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $data ) || empty( $data['record']['metrics'] ) ) { return null; } $out = array(); foreach ( self::METRIC_MAP as $api_key_name => $metric ) { $p75 = $data['record']['metrics'][ $api_key_name ]['percentiles']['p75'] ?? null; if ( null !== $p75 ) { $out[ $metric ] = (float) $p75; // CLS arrives as a string like "0.05". } } if ( empty( $out ) ) { return null; } set_transient( $cache_key, $out, 12 * HOUR_IN_SECONDS ); return $out; } }