get_error_message() ) ); } /** * Latest scores for this site (no key needed): the newest finished report, or null. * * @return array|null|WP_Error */ public static function latest() { $res = wp_remote_get( self::api_url() . '/api/v1/domains/' . rawurlencode( self::host() ), array( 'timeout' => 8, 'headers' => array( 'accept' => 'application/json' ), ) ); if ( is_wp_error( $res ) ) { return self::unreachable( $res ); } if ( 200 !== (int) wp_remote_retrieve_response_code( $res ) ) { return new WP_Error( 'getreport_http', __( 'getReport did not answer. Try again in a minute.', 'getreport' ) ); } $body = json_decode( wp_remote_retrieve_body( $res ), true ); if ( ! is_array( $body ) || empty( $body['reports'] ) ) { return null; } foreach ( $body['reports'] as $r ) { if ( ! empty( $r['scores'] ) && in_array( $r['status'] ?? '', array( 'done', 'partial' ), true ) ) { return $r; } } return null; } /** * Starts a fresh check of a URL with the saved API key. Returns the report id. * * @param string $url Page to check. * @return string|WP_Error */ public static function start_check( $url ) { $key = GetReport_Plugin::api_key(); if ( '' === $key ) { return new WP_Error( 'getreport_no_key', __( 'Add an API key to run checks from WordPress.', 'getreport' ) ); } $res = wp_remote_post( self::api_url() . '/api/v1/reports', array( 'timeout' => 15, 'headers' => array( 'authorization' => 'Bearer ' . $key, 'content-type' => 'application/json', 'accept' => 'application/json', ), 'body' => wp_json_encode( array( 'url' => $url, 'force' => true, ) ), ) ); if ( is_wp_error( $res ) ) { return self::unreachable( $res ); } $code = (int) wp_remote_retrieve_response_code( $res ); $body = json_decode( wp_remote_retrieve_body( $res ), true ); if ( $code >= 400 || empty( $body['id'] ) ) { $reason = is_array( $body ) ? ( $body['detail'] ?? $body['title'] ?? '' ) : ''; /* translators: %s: the reason getReport gave. */ return new WP_Error( 'getreport_refused', sprintf( __( 'getReport could not start the check: %s', 'getreport' ), $reason ) ); } return (string) $body['id']; } /** * A report by id (public). Returns the decoded view, or WP_Error. * * @param string $id Report id. * @return array|WP_Error */ public static function report( $id ) { if ( ! preg_match( '/^[0-9A-Za-z]{10}$/', (string) $id ) ) { return new WP_Error( 'getreport_id', 'Bad report id' ); } $res = wp_remote_get( self::api_url() . '/api/v1/reports/' . $id, array( 'timeout' => 8, 'headers' => array( 'accept' => 'application/json' ), ) ); if ( is_wp_error( $res ) ) { return self::unreachable( $res ); } $body = json_decode( wp_remote_retrieve_body( $res ), true ); return is_array( $body ) ? $body : new WP_Error( 'getreport_http', __( 'getReport did not answer. Try again in a minute.', 'getreport' ) ); } /** * Step 1 of the automatic key (ADR 0055): getReport's verification token for this domain, * which the plugin then publishes (GetReport_Plugin::serve_verify_file, the home page meta tag). * * @return string|WP_Error */ public static function verify_token() { $res = wp_remote_post( self::api_url() . '/api/v1/domains/' . rawurlencode( self::host() ) . '/verify', array( 'timeout' => 15, 'headers' => array( 'accept' => 'application/json', 'content-type' => 'application/json', ), 'body' => '{}', ) ); if ( is_wp_error( $res ) ) { return self::unreachable( $res ); } $body = json_decode( wp_remote_retrieve_body( $res ), true ); if ( 200 !== (int) wp_remote_retrieve_response_code( $res ) || empty( $body['token'] ) || ! preg_match( '/^[A-Za-z0-9_-]{22}$/', (string) $body['token'] ) ) { return self::refused( $body ); } return (string) $body['token']; } /** * Step 2: getReport looks for the published token and, when it finds it, answers with an API * key that may check this site only (20 new reports a day). getReport fetches this site while * this request waits, so the timeout is generous. * * @return array{key:string, dailyLimit:int}|WP_Error */ public static function plugin_key() { $res = wp_remote_post( self::api_url() . '/api/v1/domains/' . rawurlencode( self::host() ) . '/plugin-key', array( 'timeout' => 45, 'headers' => array( 'accept' => 'application/json', 'content-type' => 'application/json', ), 'body' => '{}', ) ); if ( is_wp_error( $res ) ) { return self::unreachable( $res ); } $body = json_decode( wp_remote_retrieve_body( $res ), true ); if ( 200 !== (int) wp_remote_retrieve_response_code( $res ) || empty( $body['key'] ) || ! preg_match( '/^gr_[A-Za-z0-9_-]{8,200}$/', (string) $body['key'] ) ) { return self::refused( $body ); } return array( 'key' => (string) $body['key'], 'dailyLimit' => (int) ( $body['dailyLimit'] ?? 0 ), ); } /** * The refusal in getReport's own words (title and detail of the problem answer), as an error. * * @param mixed $body Decoded body. */ private static function refused( $body ) { $text = is_array( $body ) ? trim( ( $body['title'] ?? '' ) . '. ' . ( $body['detail'] ?? '' ), ' .' ) : ''; return new WP_Error( 'getreport_refused', '' !== $text ? $text . '.' : __( 'getReport did not answer. Try again in a minute.', 'getreport' ) ); } /** * The report page on getreport.app. * * @param string $id Report id. */ public static function report_link( $id ) { return self::site_url() . '/r/' . rawurlencode( $id ); } /** * Runs a check in the admin's own browser (no key: getReport's own form and bot check). * * @param string $url Page to check. * @param bool $fresh Skip the 12-hour cache. */ public static function check_link( $url, $fresh = false ) { return add_query_arg( array_filter( array( 'url' => rawurlencode( $url ), 'force' => $fresh ? '1' : null, ) ), self::site_url() . '/check' ); } }