set_cache_id( self::generate_cache_id_from_url( $url ) ); $this->url = $url; $this->active_modules = $active_modules; $this->created = $created === null ? microtime( true ) : $created; $this->status = $status; $this->error = $error; $this->client = $client; $this->retry_count = 0; } /** * Generate the cache ID from the URL. * * @param string $url The URL to get the Speed Scores for. * * @return string */ public static function generate_cache_id_from_url( $url ) { return hash( self::CACHE_KEY_HASH_ALGO, $url ); } /** * Get the list of active performance modules while this request was created. * * @return string */ public function get_active_performance_modules() { // List of modules that affect the speed score. $performance_modules = array( 'cloud_css', 'critical_css', 'image_cdn', 'minify_css', 'minify_js', 'render_blocking_js', ); return array_intersect( $this->active_modules, $performance_modules ); } /** * Convert this object to a plain array for JSON serialization. */ #[\ReturnTypeWillChange] public function jsonSerialize() { return array( 'id' => $this->get_cache_id(), 'url' => $this->url, 'active_modules' => $this->get_active_performance_modules(), 'created' => $this->created, 'status' => $this->status, 'error' => $this->error, 'client' => $this->client, 'retry_count' => $this->retry_count, ); } /** * This is intended to be the reverse of JsonSerializable->jsonSerialize. * * @param mixed $data The data to turn into an object. * * @return Speed_Score_Request */ public static function jsonUnserialize( $data ) { $object = new Speed_Score_Request( $data['url'], $data['active_modules'], $data['created'], $data['status'], $data['error'], $data['client'] ); if ( ! empty( $data['id'] ) ) { $object->set_cache_id( $data['id'] ); } if ( ! empty( $data['retry_count'] ) ) { $object->retry_count = intval( $data['retry_count'] ); } return $object; } /** * Return the cache prefix. * * @return string */ protected static function cache_prefix() { return 'jetpack_boost_speed_scores_'; } /** * Send a Speed Scores request to the API. * * @return true|\WP_Error True on success, WP_Error on failure. */ public function execute() { $response = Boost_API::post( 'speed-scores', array( 'request_id' => $this->get_cache_id(), 'url' => Url::normalize( $this->url ), 'active_modules' => $this->active_modules, 'client' => $this->client, ) ); if ( is_wp_error( $response ) ) { $this->status = 'error'; $this->error = $response->get_error_message(); $this->store(); return $response; } return true; } /** * Is this request pending? */ public function is_pending() { return 'pending' === $this->status; } /** * Did the request fail? */ public function is_error() { return 'error' === $this->status; } /** * Did the request succeed? */ public function is_success() { return 'success' === $this->status; } /** * Poll for updates to this Speed Scores request. * * @return true|\WP_Error True on success, WP_Error on failure. */ public function poll_update() { $response = Boost_API::get( sprintf( 'speed-scores/%s', $this->get_cache_id() ) ); if ( is_wp_error( $response ) ) { // Special case: If the request is not found, restart it. if ( 'not_found' === $response->get_error_code() ) { return $this->restart(); } return $response; } switch ( $response['status'] ) { case 'pending': // The initial job probably failed, dispatch again if so. if ( $this->created <= strtotime( '-15 mins' ) ) { return $this->restart(); } break; case 'error': $this->status = 'error'; $this->error = $response['error']; $this->store(); break; case 'success': $this->status = 'success'; $this->store(); $this->record_history( $response ); break; default: return new \WP_Error( 'invalid_response', __( 'Invalid response from WPCOM API while polling for speed scores', 'jetpack-boost-speed-score' ), $response ); } return true; } /** * Restart this request; useful when WPCOM doesn't recognize the request or it times out. */ private function restart() { // Enforce a maximum number of restarts. if ( $this->retry_count > 2 ) { $this->status = 'error'; $this->error = 'Maximum number of retries exceeded'; $this->store(); return new \WP_Error( 'error', $this->error ); } $result = $this->execute(); if ( is_wp_error( $result ) ) { return $result; } $this->created = time(); ++$this->retry_count; $this->store(); return true; } /** * Save the speed score record to history. * * @param array $response Response from api. */ private function record_history( $response ) { $history = new Speed_Score_History( $this->url ); $last_history = $history->latest(); $last_scores = $last_history ? $last_history['scores'] : null; $last_theme = $last_history ? $last_history['theme'] : null; $current_theme = wp_get_theme()->get( 'Name' ); // Only change if there is a difference from last score or the theme changed. if ( $last_scores !== $response['scores'] || $current_theme !== $last_theme ) { $history->push( array( 'timestamp' => time(), 'scores' => $response['scores'], 'theme' => $current_theme, ) ); } } }