PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.32.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.32.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-image-seo-endpoint.php

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

261 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Image SEO API Endpoints Class
5 *
6 * REST API endpoints for image SEO management.
7 *
8 * @package ThinkRank
9 * @subpackage API
10 * @since 1.0.0
11 */
12
13 declare(strict_types=1);
14
15 namespace ThinkRank\API;
16
17 use ThinkRank\SEO\Image_SEO_Manager;
18 use ThinkRank\API\Traits\CSRF_Protection;
19 use WP_REST_Controller;
20 use WP_REST_Request;
21 use WP_REST_Response;
22 use WP_Error;
23
24 // Prevent direct access
25 if (!defined('ABSPATH')) {
26 exit;
27 }
28
29 /**
30 * Image SEO API Endpoints Class
31 *
32 * Provides REST API endpoints for image SEO settings management.
33 *
34 * @since 1.0.0
35 */
36 class Image_SEO_Endpoint extends WP_REST_Controller {
37 use CSRF_Protection;
38
39 /**
40 * Image SEO Manager instance
41 *
42 * @since 1.0.0
43 * @var Image_SEO_Manager
44 */
45 private Image_SEO_Manager $image_manager;
46
47 /**
48 * API namespace
49 *
50 * @since 1.0.0
51 * @var string
52 */
53 protected $namespace = 'thinkrank/v1';
54
55 /**
56 * API resource base
57 *
58 * @since 1.0.0
59 * @var string
60 */
61 protected $rest_base = 'image-seo';
62
63 /**
64 * Constructor
65 *
66 * @since 1.0.0
67 */
68 public function __construct() {
69 if (!class_exists('ThinkRank\\SEO\\Image_SEO_Manager')) {
70 require_once THINKRANK_PLUGIN_DIR . 'includes/seo/class-image-seo-manager.php';
71 }
72
73 $this->image_manager = new Image_SEO_Manager();
74 }
75
76 /**
77 * Register API routes
78 *
79 * @since 1.0.0
80 */
81 public function register_routes(): void {
82 register_rest_route(
83 $this->namespace,
84 '/' . $this->rest_base . '/settings',
85 [
86 [
87 'methods' => 'GET',
88 'callback' => [$this, 'get_settings'],
89 'permission_callback' => [$this, 'check_permissions']
90 ],
91 [
92 'methods' => 'POST',
93 'callback' => [$this, 'update_settings'],
94 'permission_callback' => [$this, 'check_permissions'],
95 'args' => $this->get_settings_args()
96 ]
97 ]
98 );
99
100 // Media Library alt-text coverage stats
101 register_rest_route(
102 $this->namespace,
103 '/' . $this->rest_base . '/media-alt/stats',
104 [
105 [
106 'methods' => 'GET',
107 'callback' => [$this, 'get_media_alt_stats'],
108 'permission_callback' => [$this, 'check_permissions']
109 ]
110 ]
111 );
112
113 // Bulk-fill alt text into the Media Library (batched)
114 register_rest_route(
115 $this->namespace,
116 '/' . $this->rest_base . '/media-alt/run',
117 [
118 [
119 'methods' => 'POST',
120 'callback' => [$this, 'run_media_alt_fill'],
121 'permission_callback' => [$this, 'check_permissions'],
122 'args' => [
123 'offset' => [
124 'type' => 'integer',
125 'required' => false,
126 'default' => 0,
127 'sanitize_callback' => 'absint'
128 ],
129 'limit' => [
130 'type' => 'integer',
131 'required' => false,
132 'default' => 50,
133 'sanitize_callback' => 'absint'
134 ],
135 'overwrite' => [
136 'type' => 'boolean',
137 'required' => false,
138 'default' => false,
139 'sanitize_callback' => 'rest_sanitize_boolean'
140 ]
141 ]
142 ]
143 ]
144 );
145 }
146
147 /**
148 * Check if user has required permissions
149 *
150 * @since 1.0.0
151 * @return bool True if authorized, false otherwise
152 */
153 public function check_permissions(): bool {
154 return \ThinkRank\Core\Capability_Manager::current_user_can('thinkrank_image_seo');
155 }
156
157 /**
158 * Get image SEO settings
159 *
160 * @since 1.0.0
161 * @param WP_REST_Request $request API request object
162 * @return WP_REST_Response|WP_Error API response
163 */
164 public function get_settings(WP_REST_Request $request): WP_REST_Response {
165 $settings = $this->image_manager->get_settings('site');
166 return new WP_REST_Response($settings, 200);
167 }
168
169 /**
170 * Update image SEO settings
171 *
172 * @since 1.0.0
173 * @param WP_REST_Request $request API request object
174 * @return WP_REST_Response|WP_Error API response
175 */
176 public function update_settings(WP_REST_Request $request): WP_REST_Response {
177 // Whitelist to known schema keys only, so framework params (_wpnonce,
178 // _locale, …) and any arbitrary extra fields are never persisted as settings.
179 $allowed_keys = array_keys($this->image_manager->get_settings_schema('site'));
180 $settings = array_intersect_key($request->get_params(), array_flip($allowed_keys));
181
182 $success = $this->image_manager->save_settings('site', 0, $settings);
183
184 if ($success) {
185 return new WP_REST_Response([
186 'success' => true,
187 'message' => __('Settings updated successfully', 'thinkrank')
188 ], 200);
189 }
190
191 return new WP_REST_Response([
192 'success' => false,
193 'message' => __('Failed to update settings', 'thinkrank')
194 ], 500);
195 }
196
197 /**
198 * Get Media Library alt-text coverage stats
199 *
200 * @since 1.19.1
201 * @param WP_REST_Request $request API request object
202 * @return WP_REST_Response API response
203 */
204 public function get_media_alt_stats(WP_REST_Request $request): WP_REST_Response {
205 return new WP_REST_Response($this->image_manager->get_media_alt_stats(), 200);
206 }
207
208 /**
209 * Run one batch of the Media Library alt-text bulk fill
210 *
211 * @since 1.19.1
212 * @param WP_REST_Request $request API request object
213 * @return WP_REST_Response API response
214 */
215 public function run_media_alt_fill(WP_REST_Request $request): WP_REST_Response {
216 $result = $this->image_manager->bulk_fill_missing_alt([
217 'offset' => (int) $request->get_param('offset'),
218 'limit' => (int) $request->get_param('limit'),
219 'overwrite' => (bool) $request->get_param('overwrite'),
220 ]);
221
222 return new WP_REST_Response($result, 200);
223 }
224
225 /**
226 * Get settings schema arguments
227 *
228 * @since 1.0.0
229 * @return array API arguments array
230 */
231 private function get_settings_args(): array {
232 $schema = $this->image_manager->get_settings_schema('site');
233 $args = [];
234
235 foreach ($schema as $key => $config) {
236 $args[$key] = [
237 'type' => $config['type'],
238 'required' => false,
239 'sanitize_callback' => $config['type'] === 'boolean' ? 'rest_sanitize_boolean' : 'sanitize_text_field'
240 ];
241
242 // Carry through any constraint the schema already declares. Copying
243 // only type/required/sanitize_callback silently dropped the
244 // alt_source enum, so the REST validator never enforced it.
245 //
246 // The enum needs a validate_callback to have any effect:
247 // WP_REST_Request::has_valid_params() skips an arg entirely unless
248 // one is set, so declaring the enum alone leaves it inert. Attach
249 // it only to args that actually carry a constraint — applying it to
250 // every arg would also start enforcing `type`, turning today's
251 // lenient boolean coercion into a hard 400.
252 if (isset($config['enum'])) {
253 $args[$key]['enum'] = $config['enum'];
254 $args[$key]['validate_callback'] = 'rest_validate_request_arg';
255 }
256 }
257
258 return $args;
259 }
260 }
261