PluginProbe
SEO Engine – Smart SEO with AI, Schema & Redirection for WordPress / 0.9.4
SEO Engine – Smart SEO with AI, Schema & Redirection for WordPress v0.9.4
0.9.4 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.2 0.8.1 0.7.9 0.8.0 0.7.7 0.7.8 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.5 All 88 releases
seo-engine / classes / modules / indexnow.php

indexnow.php in SEO Engine – Smart SEO with AI, Schema & Redirection for WordPress 0.9.4, at classes/modules/indexnow.php

105 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // IndexNow: instantly notifies Bing, Yandex, Naver, Seznam (and every IndexNow-compatible
4 // engine) when content is published or updated, so new pages don't sit waiting for a crawl.
5 // Google does not consume IndexNow; this complements the sitemap, it does not replace it.
6 // Spec: https://www.indexnow.org/documentation
7 class Meow_MWSEO_Modules_IndexNow
8 {
9 private $core = null;
10
11 public function __construct( $core ) {
12 $this->core = $core;
13 $this->init();
14 }
15
16 public function init() {
17 // Rides the Technical SEO module: one switch, fully automatic, on by default.
18 if ( !$this->core->get_option( 'technical_seo', true ) || !$this->core->get_option( 'indexnow', true ) ) {
19 return;
20 }
21 add_action( 'init', array( $this, 'serve_key_file' ), 0 );
22 add_action( 'transition_post_status', array( $this, 'on_transition' ), 10, 3 );
23 }
24
25 // The key is self-generated once; engines verify ownership by fetching /{key}.txt.
26 public function get_key() {
27 $key = $this->core->get_option( 'indexnow_key', '' );
28 if ( empty( $key ) ) {
29 $key = strtolower( wp_generate_password( 32, false, false ) );
30 $this->core->update_option( 'indexnow_key', $key );
31 }
32 return $key;
33 }
34
35 // Serve the verification file at the site root without touching rewrite rules
36 // (works on every host and on multi-domain installs, where each domain serves it).
37 public function serve_key_file() {
38 if ( empty( $_SERVER['REQUEST_URI'] ) ) return;
39 $path = trim( (string) parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ), '/' );
40 if ( substr( $path, -4 ) !== '.txt' || strlen( $path ) !== 36 ) return;
41 $key = $this->get_key();
42 if ( $path !== $key . '.txt' ) return;
43 header( 'Content-Type: text/plain; charset=utf-8' );
44 echo $key;
45 exit;
46 }
47
48 public function on_transition( $new_status, $old_status, $post ) {
49 // Fires for first publish AND for updates of already-published posts.
50 if ( $new_status !== 'publish' || !$post || wp_is_post_revision( $post ) ) return;
51
52 $public_types = get_post_types( array( 'public' => true ) );
53 unset( $public_types['attachment'] );
54 if ( !in_array( $post->post_type, $public_types, true ) ) return;
55
56 // No-index posts are excluded on purpose; don't ask engines to fetch them.
57 $excluded = array_map( 'intval', (array) $this->core->get_option( 'sitemap_excluded_post_ids', [] ) );
58 if ( in_array( (int) $post->ID, $excluded, true ) ) return;
59
60 $url = get_permalink( $post );
61 if ( !$url ) return;
62
63 // Throttle: a burst of saves on the same post pings once every 10 minutes.
64 $lock = 'mwseo_indexnow_' . $post->ID;
65 if ( get_transient( $lock ) ) return;
66 set_transient( $lock, 1, 10 * MINUTE_IN_SECONDS );
67
68 $this->ping( array( $url ) );
69 }
70
71 // Submit URLs to api.indexnow.org (shared endpoint: one ping reaches every engine).
72 // Multi-domain installs (Polylang) group by host, since key location must match the host.
73 public function ping( $urls ) {
74 $urls = array_values( array_filter( array_map( 'esc_url_raw', (array) $urls ) ) );
75 if ( empty( $urls ) ) return false;
76
77 $key = $this->get_key();
78 $by_host = array();
79 foreach ( $urls as $url ) {
80 $host = parse_url( $url, PHP_URL_HOST );
81 if ( !$host ) continue;
82 $by_host[ $host ][] = $url;
83 }
84
85 foreach ( $by_host as $host => $host_urls ) {
86 $scheme = parse_url( $host_urls[0], PHP_URL_SCHEME ) ?: 'https';
87 $body = array(
88 'host' => $host,
89 'key' => $key,
90 'keyLocation' => $scheme . '://' . $host . '/' . $key . '.txt',
91 'urlList' => array_slice( $host_urls, 0, 10000 ),
92 );
93 // Non-blocking: publishing must never wait on a third-party endpoint.
94 wp_remote_post( 'https://api.indexnow.org/indexnow', array(
95 'blocking' => false,
96 'timeout' => 3,
97 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
98 'body' => wp_json_encode( $body ),
99 ) );
100 $this->core->log( '📣 IndexNow pinged ' . count( $host_urls ) . ' URL(s) on ' . $host );
101 }
102 return true;
103 }
104 }
105