PluginProbe
AL Pack / trunk
AL Pack vtrunk
1.3.13 trunk 1.0.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.11 1.3.12
alpack / includes / indexing.php

indexing.php in AL Pack trunk, at includes/indexing.php

288 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 class PressLearn_IndexNow {
7
8 private static $instance = null;
9 private $api_endpoint = 'https://searchadvisor.naver.com/indexnow';
10 private $api_key = '';
11
12 public static function get_instance() {
13 if (null === self::$instance) {
14 self::$instance = new self();
15 }
16 return self::$instance;
17 }
18
19 private function __construct() {
20 add_action('init', array($this, 'init'));
21 add_action('save_post', array($this, 'auto_index_post'), 10, 3);
22 add_action('presslearn_index_post_delayed', array($this, 'index_post_delayed'), 10, 4);
23 add_action('wp_ajax_presslearn_manual_index', array($this, 'manual_index_ajax'));
24
25 add_action('wp_ajax_presslearn_bulk_index', array($this, 'bulk_index_ajax'));
26 }
27
28 public function init() {
29 $this->api_key = get_option('presslearn_indexnow_api_key', '');
30 $this->create_indexing_table();
31 }
32
33 private function create_indexing_table() {
34 global $wpdb;
35 $table_name = $wpdb->prefix . 'presslearn_indexing_logs';
36
37 $charset_collate = $wpdb->get_charset_collate();
38
39 $sql = "CREATE TABLE IF NOT EXISTS $table_name (
40 id bigint(20) NOT NULL AUTO_INCREMENT,
41 post_id bigint(20) NOT NULL,
42 post_url text NOT NULL,
43 post_title text NOT NULL,
44 request_type varchar(50) NOT NULL DEFAULT 'auto',
45 response_code int(11) DEFAULT NULL,
46 response_message text DEFAULT NULL,
47 indexed_at datetime DEFAULT CURRENT_TIMESTAMP,
48 PRIMARY KEY (id),
49 KEY post_id (post_id),
50 KEY indexed_at (indexed_at)
51 ) $charset_collate;";
52
53 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
54 dbDelta($sql);
55 }
56
57 public function auto_index_post($post_id, $post, $update) {
58 if (get_option('presslearn_auto_index_enabled', 'no') !== 'yes') {
59 return;
60 }
61
62 if (get_option('presslearn_auto_indexing_enabled', 'yes') !== 'yes') {
63 return;
64 }
65
66 if ($post->post_status !== 'publish') {
67 return;
68 }
69
70 if (!in_array($post->post_type, array('post', 'page'))) {
71 return;
72 }
73
74 if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
75 return;
76 }
77
78 $post_url = get_permalink($post_id);
79 if (!$post_url) {
80 return;
81 }
82
83 wp_schedule_single_event(time() + 10, 'presslearn_index_post_delayed', array($post_id, $post_url, $post->post_title, 'auto'));
84 }
85
86 public function index_post_delayed($post_id, $post_url, $post_title, $request_type) {
87 $this->submit_to_indexnow($post_id, $post_url, $post_title, $request_type);
88 }
89
90 public function submit_to_indexnow($post_id, $post_url, $post_title, $request_type = 'manual') {
91 if (empty($this->api_key)) {
92 $this->log_indexing_result($post_id, $post_url, $post_title, $request_type, 400, 'API 키가 설정되지 않았습니다.');
93 return false;
94 }
95
96 $body = array(
97 'host' => parse_url(home_url(), PHP_URL_HOST),
98 'key' => $this->api_key,
99 'urlList' => array($post_url)
100 );
101
102 $response = wp_remote_post($this->api_endpoint, array(
103 'headers' => array(
104 'Content-Type' => 'application/json; charset=utf-8',
105 'User-Agent' => 'WordPress/' . get_bloginfo('version') . ' PressLearn IndexNow'
106 ),
107 'body' => wp_json_encode($body),
108 'timeout' => 30,
109 'method' => 'POST'
110 ));
111
112 if (is_wp_error($response)) {
113 $error_message = $response->get_error_message();
114 $this->log_indexing_result($post_id, $post_url, $post_title, $request_type, 0, $error_message);
115 return false;
116 }
117
118 $response_code = wp_remote_retrieve_response_code($response);
119 $response_body = wp_remote_retrieve_body($response);
120
121 $success = in_array($response_code, array(200, 202));
122
123 $message = $success ? '인덱싱 요청 성공' : '인덱싱 요청 실패: ' . $response_body;
124
125 $this->log_indexing_result($post_id, $post_url, $post_title, $request_type, $response_code, $message);
126
127 return $success;
128 }
129
130 private function log_indexing_result($post_id, $post_url, $post_title, $request_type, $response_code, $response_message) {
131 global $wpdb;
132 $table_name = $wpdb->prefix . 'presslearn_indexing_logs';
133
134 $wpdb->insert(
135 $table_name,
136 array(
137 'post_id' => $post_id,
138 'post_url' => $post_url,
139 'post_title' => $post_title,
140 'request_type' => $request_type,
141 'response_code' => $response_code,
142 'response_message' => $response_message,
143 'indexed_at' => current_time('mysql')
144 ),
145 array(
146 '%d', '%s', '%s', '%s', '%d', '%s', '%s'
147 )
148 );
149 }
150
151 public function manual_index_ajax() {
152 check_ajax_referer('presslearn_indexing_nonce', 'nonce');
153
154 if (!current_user_can('manage_options')) {
155 wp_send_json_error(array('message' => '권한이 없습니다.'));
156 return;
157 }
158
159 $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
160
161 if (!$post_id) {
162 wp_send_json_error(array('message' => '유효하지 않은 게시글 ID�
163 니다.'));
164 return;
165 }
166
167 $post = get_post($post_id);
168 if (!$post || $post->post_status !== 'publish') {
169 wp_send_json_error(array('message' => '발행된 게시글을 찾을 없습니다.'));
170 return;
171 }
172
173 $post_url = get_permalink($post_id);
174 if (!$post_url) {
175 wp_send_json_error(array('message' => '게시글 URL을 가져올 없습니다.'));
176 return;
177 }
178
179 $result = $this->submit_to_indexnow($post_id, $post_url, $post->post_title, 'manual');
180
181 if ($result) {
182 wp_send_json_success(array('message' => '인덱싱 요청이 성공적으로 전송되었습니다.'));
183 } else {
184 wp_send_json_error(array('message' => '인덱싱 요청 전송에 실패했습니다.'));
185 }
186 }
187
188 public function bulk_index_ajax() {
189 check_ajax_referer('presslearn_indexing_nonce', 'nonce');
190
191 if (!current_user_can('manage_options')) {
192 wp_send_json_error(array('message' => '권한이 없습니다.'));
193 return;
194 }
195
196 $post_ids = isset($_POST['post_ids']) ? array_map('intval', $_POST['post_ids']) : array();
197
198 if (empty($post_ids)) {
199 wp_send_json_error(array('message' => '선택된 게시글이 없습니다.'));
200 return;
201 }
202
203 $success_count = 0;
204 $total_count = count($post_ids);
205
206 if ($total_count > 50) {
207 wp_send_json_error(array('message' => ' 번에 최대 50개까지만 인덱싱할 있습니다.'));
208 return;
209 }
210
211 foreach ($post_ids as $post_id) {
212 $post = get_post($post_id);
213 if (!$post || $post->post_status !== 'publish') {
214 continue;
215 }
216
217 $post_url = get_permalink($post_id);
218 if (!$post_url) {
219 continue;
220 }
221
222 if ($this->submit_to_indexnow($post_id, $post_url, $post->post_title, 'bulk')) {
223 $success_count++;
224 }
225 }
226
227 wp_send_json_success(array(
228 'message' => "총 {$total_count}개 중 {$success_count}개의 게시글이 성공적으로 인덱싱 요청되었습니다.",
229 'success_count' => $success_count,
230 'total_count' => $total_count
231 ));
232 }
233
234
235
236
237
238 public function validate_api_key($api_key) {
239 if (empty($api_key) || !is_string($api_key)) {
240 return false;
241 }
242
243 if (strlen($api_key) < 10 || strlen($api_key) > 64) {
244 return false;
245 }
246
247 if (!preg_match('/^[a-zA-Z0-9\-]+$/', $api_key)) {
248 return false;
249 }
250
251 return true;
252 }
253
254 public function create_api_key_file($api_key) {
255 if (!$this->validate_api_key($api_key)) {
256 return false;
257 }
258
259 if (!function_exists('WP_Filesystem')) {
260 require_once ABSPATH . 'wp-admin/includes/file.php';
261 }
262
263 WP_Filesystem();
264 global $wp_filesystem;
265
266 if (!$wp_filesystem) {
267 return false;
268 }
269
270 $key_file_path = ABSPATH . $api_key . '.txt';
271
272 $filename = basename($key_file_path);
273 if (!preg_match('/^[a-zA-Z0-9\-]+\.txt$/', $filename) || strlen($filename) > 68) {
274 return false;
275 }
276
277 $result = $wp_filesystem->put_contents($key_file_path, $api_key, FS_CHMOD_FILE);
278
279 return $result;
280 }
281 }
282
283 function presslearn_indexnow() {
284 return PressLearn_IndexNow::get_instance();
285 }
286
287 presslearn_indexnow();
288