You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
7.7 KiB
PHP

<?php
/**
* IndexNow API
*
* @since 1.0.56
* @package RankMath
* @author Rank Math <support@rankmath.com>
*/
namespace RankMath\Instant_Indexing;
use RankMath\Helper;
use RankMath\Traits\Hooker;
defined( 'ABSPATH' ) || exit;
/**
* API class.
*
* @codeCoverageIgnore
*/
class Api {
use Hooker;
/**
* IndexNow API URL.
*
* @var string
*/
private $api_url = 'https://api.indexnow.org/indexnow/';
/**
* IndexNow API key.
*
* @var string
*/
protected $api_key = '';
/**
* Was the last request successful.
*
* @var bool
*/
protected $is_success = false;
/**
* Last error.
*
* @var string
*/
protected $last_error = '';
/**
* Last response.
*
* @var array
*/
protected $last_response = '';
/**
* Last response header code.
*
* @var int
*/
protected $last_code = 0;
/**
* Next submission is a manual submission.
*
* @var bool
*/
public $is_manual = true;
/**
* User agent used for the API requests.
*
* @var string
*/
protected $user_agent = '';
/**
* User agent used for the API requests.
*
* @var string
*/
protected $version = '';
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Api
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Api ) ) {
$instance = new Api();
$instance->user_agent = 'RankMath/' . md5( esc_url( home_url( '/' ) ) );
$instance->version = rank_math()->version;
}
return $instance;
}
/**
* Make request to the IndexNow API.
*
* @param array $urls URLs to submit.
* @param bool $manual Whether the request is manual or not.
*
* @return bool
*/
public function submit( $urls, $manual = null ) {
$this->reset();
if ( ! is_null( $manual ) ) {
$this->is_manual = (bool) $manual;
}
$data = $this->get_payload( $urls );
$response = wp_remote_post(
'https://api.indexnow.org/indexnow/',
[
'body' => $data,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => $this->user_agent,
'X-Source-Info' => 'https://rankmath.com/' . $this->version . '/' . ( $this->is_manual ? '1' : '' ),
],
]
);
if ( is_wp_error( $response ) ) {
$this->last_error = 'WP_Error: ' . $response->get_error_message();
$this->log( (array) $urls, 0, $this->last_error );
return false;
}
$this->last_code = wp_remote_retrieve_response_code( $response );
$this->last_response = wp_remote_retrieve_body( $response );
if ( in_array( $this->last_code, [ 200, 202, 204 ], true ) ) {
$this->is_success = true;
$this->log( (array) $urls, $this->last_code, 'OK' );
return true;
}
$message = wp_remote_retrieve_response_message( $response );
$this->set_error_message( $message );
$this->log( (array) $urls, $this->last_code, $this->last_error );
return false;
}
/**
* Get the last error message.
*
* @return string
*/
public function get_error() {
return $this->last_error;
}
/**
* Get the last response code.
*
* @return int
*/
public function get_response_code() {
return $this->last_code;
}
/**
* Get the last response.
*
* @return string
*/
public function get_response() {
return $this->last_response;
}
/**
* Get the host parameter value to send to the API.
*
* @return string
*/
public function get_host() {
$host = wp_parse_url( home_url(), PHP_URL_HOST );
if ( empty( $host ) ) {
$host = 'localhost';
}
/**
* Filter the host parameter value to send to the API.
*
* @param string $host Host.
*/
return $this->do_filter( 'instant_indexing/indexnow_host', $host );
}
/**
* Get the API key.
*
* @return string
*/
public function get_key() {
if ( ! empty( $this->api_key ) ) {
return $this->api_key;
}
$api_key = Helper::get_settings( 'instant_indexing.indexnow_api_key' );
/**
* Filter the API key.
*
* @param string $api_key API key.
*/
$this->api_key = $this->do_filter( 'instant_indexing/indexnow_key', $api_key );
return $this->api_key;
}
/**
* Alias for get_key().
*/
public function get_api_key() {
return $this->get_key();
}
/**
* Get the API key location.
*
* @param string $context Key context.
* @return string
*/
public function get_key_location( $context = '' ) {
/**
* Filter the API key location.
*
* @param string $location Location.
*/
return $this->do_filter( 'instant_indexing/indexnow_key_location', trailingslashit( home_url() ) . $this->get_key() . '.txt', $context );
}
/**
* Log the request.
*
* @param array $urls URLs to submit.
* @param int $status Response code.
* @param string $message Response message.
*/
public function log( $urls, $status, $message = '' ) {
$log = get_option( 'rank_math_indexnow_log', [] );
$url = $this->get_loggable_url( $urls );
if ( ! $url ) {
return;
}
$log[] = [
'url' => $url,
'status' => (int) $status,
'manual_submission' => (bool) $this->is_manual,
'message' => $message,
'time' => time(),
];
// Only keep the last 100 entries.
$log = array_slice( $log, -100 );
update_option( 'rank_math_indexnow_log', $log, false );
}
/**
* Get the loggable URL from an array of URLs.
* If multiple URLs are submitted, return the first one and [+12]
*
* @param array $urls URLs to submit.
*
* @return string
*/
public function get_loggable_url( $urls ) {
$urls = array_values( (array) $urls );
$count_urls = count( $urls );
if ( ! $count_urls ) {
return '';
}
$url = $urls[0];
if ( $count_urls > 1 ) {
$url .= ' [+' . ( $count_urls - 1 ) . ']';
}
return $url;
}
/**
* Get the log.
*
* @return array
*/
public function get_log() {
return get_option( 'rank_math_indexnow_log', [] );
}
/**
* Clear the log.
*/
public function clear_log() {
delete_option( 'rank_math_indexnow_log' );
}
/**
* Reset object properties.
*/
private function reset() {
$this->last_error = '';
$this->last_code = 0;
$this->last_response = '';
$this->is_success = false;
}
/**
* Get the additional data to send to the API.
*
* @param array $urls URLs to submit.
*
* @return array
*/
private function get_payload( $urls ) {
return wp_json_encode(
[
'host' => $this->get_host(),
'key' => $this->get_key(),
'keyLocation' => $this->get_key_location( 'request_payload' ),
'urlList' => (array) $urls,
]
);
}
/**
* Get the error message from the response message.
*
* @param string $message Response message.
*/
private function set_error_message( $message ) {
if ( ! empty( $message ) ) {
$this->last_error = $message;
return;
}
$message = __( 'Unknown error.', 'rank-math' );
$message_map = [
400 => __( 'Invalid request.', 'rank-math' ),
403 => __( 'Invalid API key.', 'rank-math' ),
422 => __( 'Invalid URL.', 'rank-math' ),
429 => __( 'Too many requests.', 'rank-math' ),
500 => __( 'Internal server error.', 'rank-math' ),
];
if ( isset( $message_map[ $this->last_code ] ) ) {
$message = $message_map[ $this->last_code ];
}
$this->last_error = $message;
}
/**
* Generate and save a new API key.
*/
public function reset_key() {
$settings = Helper::get_settings( 'instant_indexing', [] );
$settings['indexnow_api_key'] = $this->generate_api_key();
$this->api_key = $settings['indexnow_api_key'];
update_option( 'rank-math-options-instant-indexing', $settings );
}
/**
* Generate new random API key.
*/
private function generate_api_key() {
$api_key = wp_generate_uuid4();
$api_key = preg_replace( '[-]', '', $api_key );
return $api_key;
}
}