PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.1.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.1.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / api / class-seo-score-endpoint.php

class-seo-score-endpoint.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.1.0, at includes/api/class-seo-score-endpoint.php

340 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * SEO Score REST API Endpoint
4 *
5 * Handles REST API endpoints for SEO score calculation and history
6 *
7 * @package ThinkRank\API
8 * @since 1.0.0
9 */
10
11 declare(strict_types=1);
12
13 namespace ThinkRank\API;
14
15 use ThinkRank\AI\SEOScoreCalculator;
16 use ThinkRank\Core\Database;
17 use WP_REST_Request;
18 use WP_REST_Response;
19 use WP_Error;
20
21 // Prevent direct access
22 if (!defined('ABSPATH')) {
23 exit;
24 }
25
26 /**
27 * SEO Score Endpoint Class
28 *
29 * Provides REST API endpoints for:
30 * - /wp-json/thinkrank/v1/seo-score/calculate
31 * - /wp-json/thinkrank/v1/seo-score/history
32 *
33 * @since 1.0.0
34 */
35 class SEOScoreEndpoint {
36
37 /**
38 * SEO Score Calculator instance
39 *
40 * @var SEOScoreCalculator
41 */
42 private SEOScoreCalculator $calculator;
43
44 /**
45 * Constructor
46 *
47 * @param SEOScoreCalculator $calculator SEO Score Calculator instance
48 */
49 public function __construct(SEOScoreCalculator $calculator) {
50 $this->calculator = $calculator;
51 }
52
53 /**
54 * Initialize the endpoint
55 *
56 * @return void
57 */
58 public function init(): void {
59 add_action('rest_api_init', [$this, 'register_routes']);
60 }
61
62 /**
63 * Register REST API routes
64 *
65 * @return void
66 */
67 public function register_routes(): void {
68 // Calculate SEO score endpoint
69 register_rest_route('thinkrank/v1', '/seo-score/calculate', [
70 'methods' => 'POST',
71 'callback' => [$this, 'calculate_score'],
72 'permission_callback' => [$this, 'check_permissions'],
73 'args' => [
74 'post_id' => [
75 'required' => true,
76 'type' => 'integer',
77 'validate_callback' => [$this, 'validate_post_id'],
78 ],
79 'target_keyword' => [
80 'required' => false,
81 'type' => 'string',
82 'sanitize_callback' => 'sanitize_text_field',
83 ],
84 'save_score' => [
85 'required' => false,
86 'type' => 'boolean',
87 'default' => true,
88 ],
89 'live_content' => [
90 'required' => false,
91 'type' => 'string',
92 'sanitize_callback' => 'wp_kses_post',
93 ],
94 ],
95 ]);
96
97 // Get existing SEO score endpoint
98 register_rest_route('thinkrank/v1', '/seo-score/get', [
99 'methods' => 'GET',
100 'callback' => [$this, 'get_existing_score'],
101 'permission_callback' => [$this, 'check_permissions'],
102 'args' => [
103 'post_id' => [
104 'required' => true,
105 'type' => 'integer',
106 'validate_callback' => [$this, 'validate_post_id'],
107 ],
108 ],
109 ]);
110
111 // Get score history endpoint
112 register_rest_route('thinkrank/v1', '/seo-score/history', [
113 'methods' => 'GET',
114 'callback' => [$this, 'get_score_history'],
115 'permission_callback' => [$this, 'check_permissions'],
116 'args' => [
117 'post_id' => [
118 'required' => true,
119 'type' => 'integer',
120 'validate_callback' => [$this, 'validate_post_id'],
121 ],
122 'limit' => [
123 'required' => false,
124 'type' => 'integer',
125 'default' => 10,
126 'minimum' => 1,
127 'maximum' => 50,
128 ],
129 ],
130 ]);
131
132 // Get latest score endpoint
133 register_rest_route('thinkrank/v1', '/seo-score/latest', [
134 'methods' => 'GET',
135 'callback' => [$this, 'get_latest_score'],
136 'permission_callback' => [$this, 'check_permissions'],
137 'args' => [
138 'post_id' => [
139 'required' => true,
140 'type' => 'integer',
141 'validate_callback' => [$this, 'validate_post_id'],
142 ],
143 ],
144 ]);
145 }
146
147 /**
148 * Calculate SEO score for a post
149 *
150 * @param WP_REST_Request $request Request object
151 * @return WP_REST_Response|WP_Error Response object
152 */
153 public function calculate_score(WP_REST_Request $request) {
154 try {
155 $post_id = $request->get_param('post_id');
156 $target_keyword = $request->get_param('target_keyword') ?? '';
157 $save_score = $request->get_param('save_score') ?? true;
158 $live_content = $request->get_param('live_content') ?? '';
159
160 // Analyze content - use live content if provided, otherwise saved content
161 if (!empty($live_content)) {
162 $content_data = $this->calculator->analyze_live_content($live_content, $post_id);
163 } else {
164 $content_data = $this->calculator->analyze_post_content($post_id);
165 }
166
167 if (empty($content_data)) {
168 return new WP_Error(
169 'post_not_found',
170 'Post not found or has no content',
171 ['status' => 404]
172 );
173 }
174
175 // Get post metadata
176 $metadata = [
177 'title' => get_post_meta($post_id, '_thinkrank_seo_title', true) ?: $content_data['title'],
178 'description' => get_post_meta($post_id, '_thinkrank_meta_description', true) ?: '',
179 ];
180
181 // Calculate score
182 $score_data = $this->calculator->calculate_score(
183 $content_data,
184 $metadata,
185 ['target_keyword' => $target_keyword]
186 );
187
188 // Save score if requested
189 if ($save_score) {
190 $user_id = get_current_user_id();
191 $score_id = $this->calculator->save_score($post_id, $user_id, $score_data);
192
193 if ($score_id) {
194 $score_data['score_id'] = $score_id;
195 }
196 }
197
198 return new WP_REST_Response([
199 'success' => true,
200 'data' => $score_data,
201 ], 200);
202
203 } catch (\Exception $e) {
204 return new WP_Error(
205 'calculation_failed',
206 'Failed to calculate SEO score: ' . $e->getMessage(),
207 ['status' => 500]
208 );
209 }
210 }
211
212 /**
213 * Get existing SEO score for a post
214 *
215 * @param WP_REST_Request $request Request object
216 * @return WP_REST_Response|WP_Error Response object
217 */
218 public function get_existing_score(WP_REST_Request $request) {
219 try {
220 $post_id = $request->get_param('post_id');
221
222 // Get existing score data from database
223 $existing_data = $this->calculator->get_existing_score_data($post_id);
224
225 if ($existing_data) {
226 return new WP_REST_Response([
227 'success' => true,
228 'data' => $existing_data,
229 'message' => __('Existing SEO score retrieved successfully', 'thinkrank')
230 ], 200);
231 } else {
232 return new WP_REST_Response([
233 'success' => false,
234 'data' => null,
235 'message' => __('No existing SEO analysis found', 'thinkrank')
236 ], 200); // 200 because it's not an error, just no data
237 }
238
239 } catch (\Exception $e) {
240 return new WP_Error('get_score_error', $e->getMessage(), ['status' => 500]);
241 }
242 }
243
244 /**
245 * Get score history for a post
246 *
247 * @param WP_REST_Request $request Request object
248 * @return WP_REST_Response|WP_Error Response object
249 */
250 public function get_score_history(WP_REST_Request $request) {
251 try {
252 $post_id = $request->get_param('post_id');
253 $limit = $request->get_param('limit') ?? 10;
254
255 $history = $this->calculator->get_score_history($post_id, $limit);
256
257 return new WP_REST_Response([
258 'success' => true,
259 'data' => $history,
260 ], 200);
261
262 } catch (\Exception $e) {
263 return new WP_Error(
264 'history_failed',
265 'Failed to retrieve score history: ' . $e->getMessage(),
266 ['status' => 500]
267 );
268 }
269 }
270
271 /**
272 * Get latest score for a post
273 *
274 * @param WP_REST_Request $request Request object
275 * @return WP_REST_Response|WP_Error Response object
276 */
277 public function get_latest_score(WP_REST_Request $request) {
278 try {
279 $post_id = $request->get_param('post_id');
280
281 $latest_score = $this->calculator->get_latest_score($post_id);
282
283 return new WP_REST_Response([
284 'success' => true,
285 'data' => $latest_score,
286 ], 200);
287
288 } catch (\Exception $e) {
289 return new WP_Error(
290 'latest_failed',
291 'Failed to retrieve latest score: ' . $e->getMessage(),
292 ['status' => 500]
293 );
294 }
295 }
296
297 /**
298 * Check permissions for API access
299 *
300 * @param WP_REST_Request $request Request object
301 * @return bool True if user has permission
302 */
303 public function check_permissions(WP_REST_Request $request): bool {
304 // Check if user is logged in
305 if (!is_user_logged_in()) {
306 return false;
307 }
308
309 // Check if user can edit posts
310 if (!current_user_can('edit_posts')) {
311 return false;
312 }
313
314 // For specific post operations, check if user can edit the specific post
315 $post_id = $request->get_param('post_id');
316 if ($post_id && !current_user_can('edit_post', $post_id)) {
317 return false;
318 }
319
320 return true;
321 }
322
323 /**
324 * Validate post ID parameter
325 *
326 * @param mixed $value Parameter value
327 * @param WP_REST_Request $request Request object
328 * @param string $param Parameter name
329 * @return bool True if valid
330 */
331 public function validate_post_id($value, WP_REST_Request $request, string $param): bool {
332 if (!is_numeric($value) || $value <= 0) {
333 return false;
334 }
335
336 $post = get_post((int) $value);
337 return $post !== null;
338 }
339 }
340