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.
476 lines
13 KiB
PHP
476 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Instant Indexing module.
|
|
*
|
|
* @since 1.0.56
|
|
* @package RankMath
|
|
* @author Rank Math <support@rankmath.com>
|
|
*/
|
|
|
|
namespace RankMath\Instant_Indexing;
|
|
|
|
use RankMath\KB;
|
|
use RankMath\Helper;
|
|
use RankMath\Module\Base;
|
|
use RankMath\Traits\Hooker;
|
|
use RankMath\Traits\Ajax;
|
|
use RankMath\Admin\Options;
|
|
use RankMath\Helpers\Param;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* Instant_Indexing class.
|
|
*/
|
|
class Instant_Indexing extends Base {
|
|
|
|
use Hooker, Ajax;
|
|
|
|
/**
|
|
* API Object.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $api;
|
|
|
|
/**
|
|
* Keep log of submitted objects to avoid double submissions.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $submitted = [];
|
|
|
|
/**
|
|
* Store previous post status that we can check agains in save_post.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $previous_post_status = [];
|
|
|
|
/**
|
|
* Store original permalinks for when they get trashed.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $previous_post_permalinks = [];
|
|
|
|
/**
|
|
* Restrict to one request every X seconds to a given URL.
|
|
*/
|
|
const THROTTLE_LIMIT = 5;
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct() {
|
|
parent::__construct();
|
|
|
|
$this->action( 'admin_enqueue_scripts', 'enqueue', 20 );
|
|
|
|
if ( ! $this->is_configured() ) {
|
|
Api::get()->reset_key();
|
|
}
|
|
|
|
$post_types = $this->get_auto_submit_post_types();
|
|
if ( ! empty( $post_types ) ) {
|
|
$this->filter( 'wp_insert_post_data', 'before_save_post', 10, 4 );
|
|
}
|
|
|
|
foreach ( $post_types as $post_type ) {
|
|
$this->action( 'save_post_' . $post_type, 'save_post', 10, 3 );
|
|
$this->filter( "bulk_actions-edit-{$post_type}", 'post_bulk_actions', 11 );
|
|
$this->filter( "handle_bulk_actions-edit-{$post_type}", 'handle_post_bulk_actions', 10, 3 );
|
|
}
|
|
|
|
$this->filter( 'post_row_actions', 'post_row_actions', 10, 2 );
|
|
$this->filter( 'page_row_actions', 'post_row_actions', 10, 2 );
|
|
$this->filter( 'admin_init', 'handle_post_row_actions' );
|
|
|
|
$this->action( 'wp', 'serve_api_key' );
|
|
$this->action( 'rest_api_init', 'init_rest_api' );
|
|
}
|
|
|
|
/**
|
|
* Load the REST API endpoints.
|
|
*/
|
|
public function init_rest_api() {
|
|
$rest = new Rest();
|
|
$rest->register_routes();
|
|
}
|
|
|
|
/**
|
|
* Add bulk actions for applicable posts, pages, CPTs.
|
|
*
|
|
* @param array $actions Actions.
|
|
* @return array New actions.
|
|
*/
|
|
public function post_bulk_actions( $actions ) {
|
|
$actions['rank_math_indexnow'] = esc_html__( 'Instant Indexing: Submit Pages', 'rank-math' );
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Action links for the post listing screens.
|
|
*
|
|
* @param array $actions Action links.
|
|
* @param object $post Current post object.
|
|
* @return array
|
|
*/
|
|
public function post_row_actions( $actions, $post ) {
|
|
if ( ! Helper::has_cap( 'general' ) ) {
|
|
return $actions;
|
|
}
|
|
|
|
if ( 'publish' !== $post->post_status ) {
|
|
return $actions;
|
|
}
|
|
|
|
$post_types = $this->get_auto_submit_post_types();
|
|
if ( ! in_array( $post->post_type, $post_types, true ) ) {
|
|
return $actions;
|
|
}
|
|
|
|
$link = wp_nonce_url(
|
|
add_query_arg(
|
|
[
|
|
'action' => 'rank_math_instant_index_post',
|
|
'index_post_id' => $post->ID,
|
|
'method' => 'bing_submit',
|
|
]
|
|
),
|
|
'rank_math_instant_index_post'
|
|
);
|
|
|
|
$actions['indexnow_submit'] = '<a href="' . esc_url( $link ) . '" class="rm-instant-indexing-action rm-indexnow-submit">' . __( 'Instant Indexing: Submit Page', 'rank-math' ) . '</a>';
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Handle post row action link actions.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function handle_post_row_actions() {
|
|
if ( 'rank_math_instant_index_post' !== Param::get( 'action' ) ) {
|
|
return;
|
|
}
|
|
|
|
$post_id = absint( Param::get( 'index_post_id' ) );
|
|
if ( ! $post_id ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! wp_verify_nonce( Param::get( '_wpnonce' ), 'rank_math_instant_index_post' ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! Helper::has_cap( 'general' ) ) {
|
|
return;
|
|
}
|
|
|
|
$this->api_submit( get_permalink( $post_id ), true );
|
|
|
|
Helper::redirect( remove_query_arg( [ 'action', 'index_post_id', 'method', '_wpnonce' ] ) );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Handle bulk actions for applicable posts, pages, CPTs.
|
|
*
|
|
* @param string $redirect Redirect URL.
|
|
* @param string $doaction Performed action.
|
|
* @param array $object_ids Post IDs.
|
|
*
|
|
* @return string New redirect URL.
|
|
*/
|
|
public function handle_post_bulk_actions( $redirect, $doaction, $object_ids ) {
|
|
if ( 'rank_math_indexnow' !== $doaction || empty( $object_ids ) ) {
|
|
return $redirect;
|
|
}
|
|
|
|
if ( ! Helper::has_cap( 'general' ) ) {
|
|
return $redirect;
|
|
}
|
|
|
|
$urls = [];
|
|
foreach ( $object_ids as $object_id ) {
|
|
$urls[] = get_permalink( $object_id );
|
|
}
|
|
|
|
$this->api_submit( $urls, true );
|
|
|
|
return $redirect;
|
|
}
|
|
|
|
/**
|
|
* Register admin page.
|
|
*/
|
|
public function register_admin_page() {
|
|
$tabs = [
|
|
'url-submission' => [
|
|
'icon' => 'rm-icon rm-icon-instant-indexing',
|
|
'title' => esc_html__( 'Submit URLs', 'rank-math' ),
|
|
'desc' => esc_html__( 'Send URLs directly to the IndexNow API.', 'rank-math' ) . ' <a href="' . KB::get( 'instant-indexing', 'Indexing Submit URLs' ) . '" target="_blank">' . esc_html__( 'Learn more', 'rank-math' ) . '</a>',
|
|
'classes' => 'rank-math-advanced-option',
|
|
'file' => dirname( __FILE__ ) . '/views/console.php',
|
|
],
|
|
'settings' => [
|
|
'icon' => 'rm-icon rm-icon-settings',
|
|
'title' => esc_html__( 'Settings', 'rank-math' ),
|
|
/* translators: Link to kb article */
|
|
'desc' => sprintf( esc_html__( 'Instant Indexing module settings. %s.', 'rank-math' ), '<a href="' . KB::get( 'instant-indexing', 'Indexing Settings' ) . '" target="_blank">' . esc_html__( 'Learn more', 'rank-math' ) . '</a>' ),
|
|
'file' => dirname( __FILE__ ) . '/views/options.php',
|
|
],
|
|
'history' => [
|
|
'icon' => 'rm-icon rm-icon-htaccess',
|
|
'title' => esc_html__( 'History', 'rank-math' ),
|
|
'desc' => esc_html__( 'The last 100 IndexNow API requests.', 'rank-math' ),
|
|
'classes' => 'rank-math-advanced-option',
|
|
'file' => dirname( __FILE__ ) . '/views/history.php',
|
|
],
|
|
];
|
|
|
|
if ( 'easy' === Helper::get_settings( 'general.setup_mode', 'advanced' ) ) {
|
|
// Move ['settings'] to the top.
|
|
$tabs = [ 'settings' => $tabs['settings'] ] + $tabs;
|
|
}
|
|
|
|
/**
|
|
* Allow developers to add new sections in the IndexNow settings.
|
|
*
|
|
* @param array $tabs
|
|
*/
|
|
$tabs = $this->do_filter( 'settings/instant_indexing', $tabs );
|
|
|
|
new Options(
|
|
[
|
|
'key' => 'rank-math-options-instant-indexing',
|
|
'title' => esc_html__( 'Instant Indexing', 'rank-math' ),
|
|
'menu_title' => esc_html__( 'Instant Indexing', 'rank-math' ),
|
|
'capability' => 'rank_math_general',
|
|
'tabs' => $tabs,
|
|
'position' => 100,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Store previous post status & permalink before saving the post.
|
|
*
|
|
* @param array $data Post data.
|
|
* @param array $postarr Raw post data.
|
|
* @param array $unsanitized_postarr Unsanitized post data.
|
|
* @param bool $update Whether this is an existing post being updated or not.
|
|
*/
|
|
public function before_save_post( $data, $postarr, $unsanitized_postarr, $update = false ) {
|
|
if ( ! $update ) {
|
|
return $data;
|
|
}
|
|
|
|
$this->previous_post_status[ $postarr['ID'] ] = get_post_status( $postarr['ID'] );
|
|
$this->previous_post_permalinks[ $postarr['ID'] ] = str_replace( '__trashed', '', get_permalink( $postarr['ID'] ) );
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* When a post from a watched post type is published or updated, submit its URL
|
|
* to the API and add notice about it.
|
|
*
|
|
* @param int $post_id Post ID.
|
|
* @param object $post Post object.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function save_post( $post_id, $post ) {
|
|
// Check if already submitted.
|
|
if ( in_array( $post_id, $this->submitted, true ) ) {
|
|
return;
|
|
}
|
|
|
|
// Check if post status changed to publish or trash.
|
|
if ( ! in_array( $post->post_status, [ 'publish', 'trash' ], true ) ) {
|
|
return;
|
|
}
|
|
|
|
// If new status is trash, check if previous status was publish.
|
|
if ( 'trash' === $post->post_status && 'publish' !== $this->previous_post_status[ $post_id ] ) {
|
|
return;
|
|
}
|
|
|
|
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! Helper::is_post_indexable( $post_id ) ) {
|
|
return;
|
|
}
|
|
|
|
// Check if it's a hidden product.
|
|
if ( 'product' === $post->post_type && Helper::is_woocommerce_active() ) {
|
|
$product = wc_get_product( $post_id );
|
|
if ( $product && ! $product->is_visible() ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
$url = get_permalink( $post );
|
|
if ( 'trash' === $post->post_status ) {
|
|
$url = $this->previous_post_permalinks[ $post_id ];
|
|
}
|
|
|
|
/**
|
|
* Filter the URL to be submitted to IndexNow.
|
|
* Returning false will prevent the URL from being submitted.
|
|
*
|
|
* @param string $url URL to be submitted.
|
|
* @param WP_POST $post Post object.
|
|
*/
|
|
$send_url = $this->do_filter( 'instant_indexing/publish_url', $url, $post );
|
|
|
|
// Early exit if filter is set to false.
|
|
if ( ! $send_url ) {
|
|
return;
|
|
}
|
|
|
|
$this->api_submit( $send_url, false );
|
|
$this->submitted[] = $post_id;
|
|
}
|
|
|
|
/**
|
|
* Is module configured.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
private function is_configured() {
|
|
return (bool) Helper::get_settings( 'instant_indexing.indexnow_api_key' );
|
|
}
|
|
|
|
/**
|
|
* Enqueue CSS & JS.
|
|
*
|
|
* @param string $hook Page hook name.
|
|
* @return void
|
|
*/
|
|
public function enqueue( $hook ) {
|
|
if ( 'rank-math_page_rank-math-options-instant-indexing' !== $hook && 'rank-math_page_instant-indexing' !== $hook ) {
|
|
return;
|
|
}
|
|
|
|
$uri = untrailingslashit( plugin_dir_url( __FILE__ ) );
|
|
wp_enqueue_script( 'rank-math-instant-indexing', $uri . '/assets/js/instant-indexing.js', [ 'jquery' ], rank_math()->version, true );
|
|
Helper::add_json(
|
|
'indexNow',
|
|
[
|
|
'restUrl' => rest_url( \RankMath\Rest\Rest_Helper::BASE . '/in' ),
|
|
'refreshHistoryInterval' => 30000,
|
|
'i18n' => [
|
|
'submitError' => esc_html__( 'An error occurred while submitting the URL.', 'rank-math' ),
|
|
'clearHistoryError' => esc_html__( 'Error: could not clear history.', 'rank-math' ),
|
|
'getHistoryError' => esc_html__( 'Error: could not get history.', 'rank-math' ),
|
|
'noHistory' => esc_html__( 'No submissions yet.', 'rank-math' ),
|
|
],
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Serve API key for search engines.
|
|
*/
|
|
public function serve_api_key() {
|
|
global $wp;
|
|
|
|
$api = Api::get();
|
|
$key = $api->get_key();
|
|
$key_location = $api->get_key_location( 'serve_api_key' );
|
|
$current_url = home_url( $wp->request );
|
|
|
|
if ( isset( $current_url ) && $key_location === $current_url ) {
|
|
header( 'Content-Type: text/plain' );
|
|
header( 'X-Robots-Tag: noindex' );
|
|
status_header( 200 );
|
|
echo esc_html( $key );
|
|
|
|
exit();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submit URL to IndexNow API.
|
|
*
|
|
* @param string $url URL to be submitted.
|
|
* @param bool $is_manual_submission Whether the URL is submitted manually by the user.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function api_submit( $url, $is_manual_submission ) {
|
|
$api = Api::get();
|
|
|
|
/**
|
|
* Filter the URL to be submitted to IndexNow.
|
|
* Returning false will prevent the URL from being submitted.
|
|
*
|
|
* @param bool $is_manual_submission Whether the URL is submitted manually by the user.
|
|
*/
|
|
$url = $this->do_filter( 'instant_indexing/submit_url', $url, $is_manual_submission );
|
|
if ( ! $url ) {
|
|
return false;
|
|
}
|
|
|
|
$api_logs = $api->get_log();
|
|
if ( ! $is_manual_submission && ! empty( $api_logs ) ) {
|
|
$logs = array_values( array_reverse( $api_logs ) );
|
|
if ( ! empty( $logs[0] ) && $logs[0]['url'] === $url && time() - $logs[0]['time'] < self::THROTTLE_LIMIT ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$submitted = $api->submit( $url, $is_manual_submission );
|
|
|
|
if ( ! $is_manual_submission ) {
|
|
return $submitted;
|
|
}
|
|
|
|
$count = is_array( $url ) ? count( $url ) : 1;
|
|
$this->add_submit_message_notice( $submitted, $count );
|
|
|
|
return $submitted;
|
|
}
|
|
|
|
/**
|
|
* Add notice after submitting one or more URLs.
|
|
*
|
|
* @param bool $success Whether the submission was successful.
|
|
* @param int $count Number of submitted URLs.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function add_submit_message_notice( $success, $count ) {
|
|
$notification_type = 'error';
|
|
$notification_message = __( 'Error submitting page to IndexNow.', 'rank-math' );
|
|
|
|
if ( $success ) {
|
|
$notification_type = 'success';
|
|
$notification_message = sprintf(
|
|
/* translators: %s: Number of pages submitted. */
|
|
_n( '%s page submitted to IndexNow.', '%s pages submitted to IndexNow.', $count, 'rank-math' ),
|
|
$count
|
|
);
|
|
}
|
|
|
|
Helper::add_notification( $notification_message, [ 'type' => $notification_type ] );
|
|
}
|
|
|
|
/**
|
|
* Get post types where auto-submit is enabled.
|
|
*
|
|
* @return array
|
|
*/
|
|
private function get_auto_submit_post_types() {
|
|
$post_types = Helper::get_settings( 'instant_indexing.bing_post_types', [] );
|
|
return $post_types;
|
|
}
|
|
|
|
}
|