%s %s

', esc_html( sprintf( /* translators: %s: minimum version, e.g. 0.6.0. */ __( 'AskMyPages needs updating to keep search and chat working (version %s or later).', 'askmypages' ), $minimum ) ), esc_url( admin_url( 'plugins.php' ) ), esc_html__( 'Go to Plugins', 'askmypages' ) ); } /** * Trades the single-use code for site credentials, proving this site holds * the pending private key. * * @param string $code Code from the portal redirect. * @return array|WP_Error Credentials. */ public static function exchange( $code ) { $signature = Keys::sign( Keys::CONNECT_PROOF . $code, true ); if ( false === $signature ) { return new WP_Error( 'askmypages_no_key', __( 'The connection key is missing. Start connecting again.', 'askmypages' ) ); } $body = wp_json_encode( array( 'code' => $code, 'publicKey' => get_option( Keys::OPT_PENDING_PUBLIC ), 'signature' => $signature, ) ); return self::send( 'POST', '/v1/plugin/connect/exchange', $body, array() ); } /** * Sends a signed request. * * @param string $method HTTP method. * @param string $path Path including any query string. * @param string $body Request body (JSON, or raw bytes for uploads), or empty. * @param int $timeout Seconds to wait. * @param string $content_type Body media type. * @return array|WP_Error Decoded response body. */ public static function signed( $method, $path, $body = '', $timeout = 15, $content_type = 'application/json' ) { $timestamp = (string) time(); $nonce = rtrim( strtr( base64_encode( random_bytes( 18 ) ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- random nonce. $message = implode( "\n", array( strtoupper( $method ), $path, $timestamp, $nonce, hash( 'sha256', $body ) ) ); $signature = Keys::sign( $message ); if ( false === $signature ) { return new WP_Error( 'askmypages_not_connected', __( 'This site is not connected.', 'askmypages' ) ); } $headers = array( 'Content-Type' => $content_type, 'X-HP-Key' => get_option( Keys::OPT_KEY_ID ), 'X-HP-Timestamp' => $timestamp, 'X-HP-Nonce' => $nonce, 'X-HP-Signature' => $signature, ); return self::send( $method, $path, $body, $headers, $timeout ); } /** * Performs the HTTP request. Certificate checks stay on. * * @param string $method HTTP method. * @param string $path Path. * @param string $body Body. * @param array $headers Extra headers. * @param int $timeout Seconds to wait. * @return array|WP_Error */ private static function send( $method, $path, $body, array $headers, $timeout = 15 ) { $response = wp_remote_request( rtrim( ASKMYPAGES_API_URL, '/' ) . $path, array( 'method' => $method, 'timeout' => $timeout, 'sslverify' => true, 'headers' => array_merge( array( 'Content-Type' => 'application/json', 'User-Agent' => 'AskMyPages/' . ASKMYPAGES_VERSION . '; ' . home_url(), 'X-HP-Plugin-Version' => ASKMYPAGES_VERSION, ), $headers ), 'body' => $body, ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'askmypages_unreachable', __( 'Could not reach the AskMyPages service. Your host may block outgoing requests.', 'askmypages' ) ); } $status = (int) wp_remote_retrieve_response_code( $response ); $decoded = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $status >= 200 && $status < 300 ) { if ( get_option( self::OPT_OUTDATED ) ) { delete_option( self::OPT_OUTDATED ); } return is_array( $decoded ) ? $decoded : array(); } $error = is_array( $decoded ) && isset( $decoded['error'] ) ? (string) $decoded['error'] : 'http_' . $status; if ( 'key_revoked' === $error && Keys::is_connected() ) { // Disconnected from the account portal or by an administrator: // drop the dead keys so this site stops saying "Connected". Connect::revoked_elsewhere(); } if ( 'plugin_outdated' === $error ) { update_option( self::OPT_OUTDATED, sanitize_text_field( (string) ( $decoded['minimum'] ?? '' ) ), false ); } return new WP_Error( 'askmypages_' . $error, self::message_for( $error ), array( 'status' => $status ) ); } /** * Plain-language message for an API error code. * * @param string $error Error code. * @return string */ private static function message_for( $error ) { switch ( $error ) { case 'invalid_or_expired': return __( 'That connection link expired or was already used. Click Connect again.', 'askmypages' ); case 'quota_exceeded': return __( 'This month\'s search limit is used up, so WordPress search is used until it resets.', 'askmypages' ); case 'plugin_outdated': return __( 'This version of AskMyPages is too old for the service. Update the plugin on the Plugins page.', 'askmypages' ); case 'key_revoked': return __( 'This site was disconnected from its Hook & Plug account (from the account portal or by an administrator). Connect again to resume.', 'askmypages' ); case 'invalid_signature': return __( 'The service did not accept this site\'s key. Check the server clock, or disconnect and connect again.', 'askmypages' ); default: return __( 'The AskMyPages service returned an error. Try again in a minute.', 'askmypages' ); } } }