PluginProbe ʕ •ᴥ•ʔ
SiteSEO – SEO Simplified / 1.3.5
SiteSEO – SEO Simplified v1.3.5
trunk 1.1.5 1.1.6 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9
siteseo / main / instantindexing.php
siteseo / main Last commit date
metaboxes 5 months ago settings 5 months ago admin.php 5 months ago advanced.php 6 months ago ajax.php 5 months ago columns.php 9 months ago generatesitemap.php 5 months ago googleanalytics.php 9 months ago imageseo.php 9 months ago import.php 5 months ago install.php 9 months ago instantindexing.php 5 months ago primarycategory.php 9 months ago socialmetas.php 5 months ago tableofcontent.php 1 year ago titlesmetas.php 5 months ago
instantindexing.php
333 lines
1 <?php
2 /*
3 * SITESEO
4 * https://siteseo.io
5 * (c) SiteSEO Team
6 */
7
8 namespace SiteSEO;
9
10 if(!defined('ABSPATH')){
11 die('HACKING ATTEMPT!');
12 }
13
14 class InstantIndexing{
15
16 static function bing_txt_file(){
17 global $wp, $siteseo;
18
19 $request = home_url($wp->request);
20 if(empty($request)){
21 return;
22 }
23
24 $api_key = $siteseo->instant_settings['instant_indexing_bing_api_key'];
25 $api_url = trailingslashit(home_url()) . $api_key . '.txt';
26
27 if($request == $api_url){
28 header('X-Robots-Tag: noindex');
29 header('Content-Type: text/plain');
30 status_header(200);
31
32 echo esc_html($api_key);
33 die();
34 }
35 }
36
37 static function submit_urls_to_google($urls){
38 $access_token = self::get_google_auth_token();
39
40 if(empty($access_token)){
41 return;
42 }
43
44 $boundary = wp_rand();
45 $batch_body = '';
46 $options = get_option('siteseo_instant_indexing_option_name');
47 $type = !empty($options['instant_indexing_google_action']) ? $options['instant_indexing_google_action'] : 'URL_UPDATED';
48
49 switch($type){
50 case 'remove_urls':
51 $type = 'URL_DELETED';
52 break;
53
54 case 'update_urls':
55 $type = 'URL_UPDATED';
56 break;
57 }
58
59 // Creating a batch request
60 foreach($urls as $url){
61 $post_data = wp_json_encode(['url' => $url, 'type' => $type]);
62
63 $batch_body .= '--'.$boundary.PHP_EOL;
64 $batch_body .= 'Content-Type: application/http'.PHP_EOL;
65 $batch_body .= 'Content-Transfer-Encoding: binary'.PHP_EOL;
66 $batch_body .= 'Content-ID: <'.esc_url($url).'>'.PHP_EOL.PHP_EOL;
67 $batch_body .= 'POST /v3/urlNotifications:publish HTTP/1.1'.PHP_EOL;
68 $batch_body .= 'Content-Type: application/json' . PHP_EOL;
69 $batch_body .= 'accept: application/json'.PHP_EOL;
70 $batch_body .= 'content-length: '.strlen($post_data).PHP_EOL.PHP_EOL;
71 $batch_body .= $post_data.PHP_EOL;
72 }
73
74 $batch_body .= '--'.$boundary.'--';
75
76 $response = wp_remote_post('https://indexing.googleapis.com/batch', [
77 'body' => $batch_body,
78 'headers' => [
79 'Content-Type' => 'multipart/mixed; boundary='.$boundary,
80 'Authorization' => 'Bearer ' . $access_token
81 ],
82 'timeout' => 30
83 ]);
84
85 if(is_wp_error($response)){
86 $response = ['error' => $response->get_error_message()];
87 return;
88 }
89
90 $res_code = wp_remote_retrieve_response_code($response);
91
92 if($res_code > 299){
93 return $response;
94 }
95
96 $batch_response = self::parse_batch_response($response);
97
98 $response = [
99 'status_code' => wp_remote_retrieve_response_code($response),
100 'urls' => $batch_response,
101 'error' => is_wp_error($response) ? $response->get_error_message() : null
102 ];
103
104 return $response;
105 }
106
107 static function parse_batch_response(&$response){
108 $response_headers = wp_remote_retrieve_headers($response);
109 $response_headers = $response_headers->getAll();
110
111 $content_type = $response_headers['content-type'];
112
113 $urls = [];
114
115 $content_type = explode(';', $content_type);
116 $boundary = false;
117 foreach($content_type as $part){
118 $part = explode('=', $part, 2);
119 if (isset($part[0]) && 'boundary' == trim($part[0])) {
120 $boundary = $part[1];
121 }
122 }
123
124 $res_body = wp_remote_retrieve_body($response);
125 $res_body = str_replace('--'.$boundary.'--', '--'.$boundary, $res_body);
126 $batch_responses = explode('--'.$boundary, $res_body);
127 $batch_responses = array_filter($batch_responses);
128
129 foreach($batch_responses as $batch_response){
130 $batch_response = trim($batch_response);
131 if(empty($batch_response)){
132 continue;
133 }
134
135 $batch_response = explode("\r\n\r\n", $batch_response);
136
137 if(empty($batch_response[2])){
138 continue;
139 }
140
141 $batch_body = json_decode($batch_response[2], true);
142
143 if(empty($batch_body)){
144 continue;
145 }
146
147 if(!empty($batch_body['urlNotificationMetadata']) && !empty($batch_body['urlNotificationMetadata']['url'])){
148 $urls[] = sanitize_url($batch_body['urlNotificationMetadata']['url']);
149 }
150 }
151
152 return $urls;
153 }
154
155 static function get_google_auth_token(){
156 global $siteseo;
157
158 $endpoint = 'https://oauth2.googleapis.com/token';
159 $scope = 'https://www.googleapis.com/auth/indexing';
160
161 if(!function_exists('openssl_sign')){
162 return false;
163 }
164
165 $google_api_data = isset($siteseo->instant_settings['instant_indexing_google_api_key']) ? $siteseo->instant_settings['instant_indexing_google_api_key'] : '';
166
167 if(empty($google_api_data)){
168 return false;
169 }
170
171 $google_api_data = json_decode($google_api_data, true);
172 if(empty($google_api_data)){
173 return;
174 }
175
176 // Header
177 $headers = wp_json_encode(['alg' => 'RS256', 'typ' => 'JWT']);
178 $headers = base64_encode($headers);
179
180 // Claim Set
181 $now = time();
182 $claims = wp_json_encode([
183 'iss' => $google_api_data['client_email'],
184 'scope' => 'https://www.googleapis.com/auth/indexing',
185 'aud' => 'https://oauth2.googleapis.com/token',
186 'exp' => $now + 3600,
187 'iat' => $now
188 ]);
189
190 $claims = base64_encode($claims);
191
192 // Make sure base64 encoding is URL-safe
193 $headers = str_replace(['+', '/', '='], ['-', '_', ''], $headers);
194 $claim = str_replace(['+', '/', '='], ['-', '_', ''], $claims);
195
196 // Sign the JWT with the private key
197 $signature_input = "$headers.$claim";
198 openssl_sign($signature_input, $signature, $google_api_data['private_key'], OPENSSL_ALGO_SHA256);
199
200 $signature = base64_encode($signature);
201 $signature = str_replace(['+', '/', '='], ['-', '_', ''], $signature);
202
203 // JWT Token
204 $jwt_assertion = "$signature_input.$signature";
205
206 // OAuth2 Request
207 $post_data = http_build_query([
208 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
209 'assertion' => $jwt_assertion
210 ]);
211
212 $response = wp_remote_post($endpoint, [
213 'body' => $post_data,
214 'headers' => [
215 'Content-Type' => 'application/x-www-form-urlencoded',
216 ]
217 ]);
218
219 if(is_wp_error($response)){
220 return false;
221 }
222
223 $body = json_decode(wp_remote_retrieve_body($response), true);
224 return isset($body['access_token']) ? $body['access_token'] : false;
225
226 }
227
228 /**
229 * Sends request to index now api with list of urls and key and key location.
230 *
231 * Key location is just the URL to a file which has the same name as the Key
232 * as key.txt and the content in it is the key as well. This is a virtual file.
233 *
234 * @param array $urls List of URLs to submit.
235 * @param string $api_key bing key.
236 *
237 * @return array with 'status_code' and response 'body'
238 */
239 static function submit_urls_to_bing($urls, $api_key){
240 $host = wp_parse_url(home_url(), PHP_URL_HOST);
241 $key_location = trailingslashit(home_url()) . $api_key . '.txt';
242
243 $endpoint = 'https://api.indexnow.org/indexnow/';
244 $body = wp_json_encode([
245 'host' => $host,
246 'key' => $api_key,
247 'keyLocation' => $key_location,
248 'urlList' => $urls
249 ]);
250
251 $response = wp_remote_post($endpoint, [
252 'body' => $body,
253 'headers' => [
254 'Content-Type' => 'application/json',
255 'Accept' => 'application/json'
256 ],
257 'timeout' => 30
258 ]);
259
260 return [
261 'status_code' => wp_remote_retrieve_response_code($response),
262 'body' => wp_remote_retrieve_body($response)
263 ];
264 }
265
266 // Sends request to bing when the status of post changes
267 // We will only do it only if the post is published.
268 static function on_status_change($new_status, $old_status, $post){
269
270 if($new_status == $old_status){
271 return;
272 }
273
274 if($new_status != 'publish'){
275 return;
276 }
277
278 if(empty($post) || empty($post->post_type)){
279 return;
280 }
281
282 $post_type_object = get_post_type_object($post->post_type);
283
284 // Need to make sure we submit posts whos post type is public
285 if(empty($post_type_object) || empty($post_type_object->public)){
286 return;
287 }
288
289 $options = get_option('siteseo_instant_indexing_option_name', []);
290
291 $bing_api_key = !empty($options['instant_indexing_bing_api_key']) ? $options['instant_indexing_bing_api_key'] : '';
292 $google_api_key = !empty($options['instant_indexing_google_api_key']) ? $options['instant_indexing_google_api_key'] : '';
293 $url = get_permalink($post);
294
295 $url_list = [$url];
296 $res_google = null;
297 $res_bing = null;
298
299 if(!empty($bing_api_key)){
300 $res_bing = self::submit_urls_to_bing($url_list, $bing_api_key);
301 }
302
303 if(!empty($google_api_key)){
304 $res_google = self::submit_urls_to_google($url_list);
305 }
306
307 self::save_index_history($url_list, $res_google, $res_bing , 'auto');
308
309 }
310
311 static function save_index_history($urls, $google_response, $bing_response, $source){
312 global $siteseo;
313
314 $history = $siteseo->instant_settings;
315
316 if(!isset($history['indexing_history'])){
317 $history['indexing_history'] = [];
318 }
319
320 array_unshift($history['indexing_history'], [
321 'time' => time(),
322 'urls' => $urls,
323 'google_status_code' => !empty($google_response['status_code']) ? $google_response['status_code'] : null,
324 'bing_status_code' => !empty($bing_response['status_code']) ? $bing_response['status_code'] : null,
325 'source' => !empty($source) ? $source : 'manual', // 'manual' or 'auto'
326 ]);
327
328 $history['indexing_history'] = array_slice($history['indexing_history'], 0, 10);
329
330 update_option('siteseo_instant_indexing_option_name', $history);
331 }
332 }
333