Skip to content

Commit ef4e927

Browse files
committed
Initial code from my site.
1 parent 7fc0d45 commit ef4e927

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

inc/namespace.php

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,259 @@
1313
* Bootstrap the plugin.
1414
*/
1515
function bootstrap() {
16+
add_filter( 'query_vars', __NAMESPACE__ . '\\register_query_vars' );
17+
add_action( 'init', __NAMESPACE__ . '\\add_key_rewrite_rule' );
18+
add_action( 'parse_request', __NAMESPACE__ . '\\handle_key_file_request' );
19+
add_action( 'transition_post_status', __NAMESPACE__ . '\\maybe_ping_indexnow', 10, 3 );
20+
21+
/**
22+
* Filter to allow asynchronous pings.
23+
*
24+
* This can be used to defer the ping to IndexNow of content changes to run
25+
* asynchronously via a wp-cron job. This can be useful to prevent delays when
26+
* updating a post.
27+
*
28+
* By default, the plugin will ping IndexNow synchronously. It is recommended to
29+
* ping asynchronously only if you are using a proper cron system. Read this guide
30+
* as to how to set one up: https://peterwilson.cc/real-wordpress-cron-with-wp-cli/
31+
*
32+
* @since 1.0.0
33+
*
34+
* @param bool $notify_async Whether to notify IndexNow asynchronously.
35+
* Default is false, meaning synchronous pings.
36+
*/
37+
$notify_async = apply_filters( 'pwcc/index-now/notify-async', false );
38+
39+
add_action( 'pwcc/index-now/ping', __NAMESPACE__ . '\\async_ping_indexnow', 10, 1 );
40+
// For sites doing synchronous pings this would use the hook above instead.
41+
add_action( 'pwcc/index-now/async_ping', __NAMESPACE__ . '\\ping_indexnow', 10, 1 );
42+
}
43+
44+
/**
45+
* Register query vars.
46+
*
47+
* @param array $vars Array of query vars.
48+
* @return array Modified array of query vars.
49+
*/
50+
function register_query_vars( $vars ) {
51+
$vars[] = 'pwcc_indexnow_key';
52+
return $vars;
53+
}
54+
55+
/**
56+
* Add rewrite rule for the IndexNow key file.
57+
*/
58+
function add_key_rewrite_rule() {
59+
$key = get_indexnow_key();
60+
61+
add_rewrite_rule(
62+
'pwcc-indexnow-' . $key . '$',
63+
'index.php?pwcc_indexnow_key=' . $key,
64+
'top'
65+
);
66+
}
67+
68+
/**
69+
* Handle the IndexNow key file request.
70+
*
71+
* @param WP $wp WordPress instance.
72+
*/
73+
function handle_key_file_request( $wp ) {
74+
if ( empty( $wp->query_vars['pwcc_indexnow_key'] ) ) {
75+
return;
76+
}
77+
78+
$key = get_indexnow_key();
79+
if ( ! $key || $wp->query_vars['pwcc_indexnow_key'] !== $key ) {
80+
$error = 'Invalid key: ' . get_query_var( 'pwcc_indexnow_key' );
81+
wp_die( esc_html( $error ), 'IndexNow Key Error', array( 'response' => 403 ) );
82+
return;
83+
}
84+
85+
// Set the content type to text/plain.
86+
header( 'Content-Type: text/plain' );
87+
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + YEAR_IN_SECONDS ) . ' GMT' );
88+
header( 'Cache-Control: public, max-age=' . YEAR_IN_SECONDS );
89+
header( 'X-Robots-Tag: noindex', true );
90+
91+
// Output the key.
92+
echo esc_html( $key );
93+
exit;
94+
}
95+
96+
/**
97+
* Generate and store the IndexNow key if it doesn't exist.
98+
*
99+
* @return string Unique site key.
100+
*/
101+
function get_indexnow_key(): string {
102+
$key = get_option( 'pwcc_indexnow_key' );
103+
104+
if ( ! $key ) {
105+
// Generate a random key that meets IndexNow requirements.
106+
// Must be 8-128 hexadecimal characters (a-f, 0-9).
107+
$key = strtolower( wp_generate_password( 128, false, false ) );
108+
109+
update_option( 'pwcc_indexnow_key', $key );
110+
111+
// Flush the rewrite rules.
112+
flush_rewrite_rules();
113+
}
114+
115+
return $key;
116+
}
117+
118+
/**
119+
* Ping IndexNow when a post status changes.
120+
*
121+
* Runs on the `transition_post_status` action.
122+
*
123+
* @param string $new_status New post status.
124+
* @param string $old_status Old post status.
125+
* @param WP_Post $post Post object.
126+
*/
127+
function maybe_ping_indexnow( $new_status, $old_status, $post ): void {
128+
// Do not ping during an import.
129+
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
130+
return;
131+
}
132+
133+
/*
134+
* Skip if post type isn't viewable.
135+
*
136+
* The post type shouldn't change under normal circumstances,
137+
* so it's safe to assume that both the old and new post are
138+
* not viewable.
139+
*/
140+
if ( ! is_post_type_viewable( $post->post_type ) ) {
141+
return;
142+
}
143+
144+
/*
145+
* Skip if both old an new statuses are private.
146+
*
147+
* The page will have been a 404 before and after.
148+
*
149+
* For pages that are newly a 404, we still ping IndexNow
150+
* to encourage removal of the URL from search engines.
151+
*/
152+
if (
153+
! is_post_status_viewable( $new_status )
154+
&& ! is_post_status_viewable( $old_status )
155+
) {
156+
return;
157+
}
158+
159+
/*
160+
* Prevent double pings for block editor legacy meta boxes.
161+
*/
162+
if (
163+
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
164+
isset( $_GET['meta-box-loader'] )
165+
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, Universal.Operators.StrictComparisons.LooseEqual -- form input.
166+
&& '1' == $_GET['meta-box-loader']
167+
) {
168+
return;
169+
}
170+
171+
/**
172+
* Fire the action to ping IndexNow.
173+
*
174+
* @param WP_Post|int $post The post ID or object.
175+
*/
176+
do_action( 'pwcc/index-now/ping', $post );
177+
}
178+
179+
/**
180+
* Ping IndexNow with the post URL.
181+
*
182+
* @param WP_Post|int $post The post ID or object.
183+
*/
184+
function ping_indexnow( $post ) {
185+
186+
$key = get_indexnow_key();
187+
if ( ! $key ) {
188+
return;
189+
}
190+
191+
$url = get_permalink( $post );
192+
if ( ! $url ) {
193+
return;
194+
}
195+
196+
/**
197+
* Filters the URL or URLs to be submitted to IndexNow.
198+
*
199+
* @param array $url_list Array of URLs to submit.
200+
* Default is an array with the single URL of the post.
201+
*/
202+
$url_list = apply_filters( 'pwcc/index-now/url-list', array( $url ) );
203+
204+
/**
205+
* Filters the location of the IndexNow key file.
206+
*
207+
* @param string $key_location The URL where the key file is located.
208+
* @param array $url_list The list if URLs to be submitted.
209+
* @param WP_Post $post The post object.
210+
*/
211+
$key_location = apply_filters( 'pwcc/index-now/key-location', trailingslashit( home_url( 'pwcc-indexnow-' . $key ) ), $url_list, $post );
212+
213+
$data = array(
214+
'host' => wp_parse_url( $key_location, PHP_URL_HOST ),
215+
'key' => $key,
216+
'keyLocation' => $key_location,
217+
'urlList' => $url_list,
218+
);
219+
$request = array(
220+
'body' => wp_json_encode( $data, JSON_UNESCAPED_SLASHES ),
221+
'headers' => array(
222+
'Content-Type' => 'application/json; charset=utf-8',
223+
),
224+
);
225+
226+
if ( wp_get_environment_type() !== 'production' ) {
227+
// In development, log the request for debugging.
228+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
229+
error_log( 'IndexNow ping request: ' . print_r( $request, true ) );
230+
231+
// Do not send the request in development.
232+
return;
233+
}
234+
235+
// Ping IndexNow.
236+
$response = wp_remote_post(
237+
'https://api.indexnow.org/indexnow',
238+
$request
239+
);
240+
241+
// Log the response for debugging. As per https://www.indexnow.org/documentation#response, either 200 or 202 is acceptable.
242+
if ( is_wp_error( $response ) ) {
243+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
244+
error_log( 'IndexNow ping failed: ' . $response->get_error_message() . print_r( $request, true ) );
245+
return;
246+
}
247+
248+
$status = wp_remote_retrieve_response_code( $response );
249+
if ( ! in_array( $status, array( 200, 202 ), true ) ) {
250+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
251+
error_log( 'IndexNow ping failed: ' . $status . print_r( $request, true ) );
252+
}
253+
}
254+
255+
/**
256+
* Asynchronous ping to IndexNow.
257+
*
258+
* @param mixed $post The post ID or object to ping.
259+
*/
260+
function async_ping_indexnow( $post ) {
261+
$post = get_post( $post );
262+
if ( ! $post ) {
263+
return;
264+
}
265+
266+
$post_id = $post->ID;
267+
268+
if ( ! wp_next_scheduled( 'pwcc/index-now/async_ping', array( $post_id ) ) ) {
269+
wp_schedule_single_event( time() + 5, 'pwcc/index-now/async_ping', array( $post_id ) );
270+
}
16271
}

0 commit comments

Comments
 (0)