PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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
← All changes | includes/api/class-image-seo-endpoint.php +59 -1 1.29.02.7.0 View file →
@@ -36,8 +36,26 @@
36 36 class Image_SEO_Endpoint extends WP_REST_Controller {
37 37 use CSRF_Protection;
38 38
39 39 /**
40 + * Sanitizer per schema type, for settings args built from the schema.
41 + *
42 + * Only scalar types appear here. An array or object type deliberately gets
43 + * no `sanitize_callback` — core falls back to `rest_parse_request_arg`,
44 + * which sanitizes against the declared schema, where a string sanitizer
45 + * would flatten the value to "Array" or "".
46 + *
47 + * @since 2.0.1
48 + * @var array<string, string>
49 + */
50 + private const SANITIZERS = [
51 + 'boolean' => 'rest_sanitize_boolean',
52 + 'string' => 'sanitize_text_field',
53 + 'integer' => 'absint',
54 + 'number' => 'floatval',
55 + ];
56 +
57 + /**
40 58 * Image SEO Manager instance
41 59 *
42 60 * @since 1.0.0
43 61 * @var Image_SEO_Manager
@@ -129,8 +147,13 @@
129 147 'limit' => [
130 148 'type' => 'integer',
131 149 'required' => false,
132 150 'default' => 50,
151 + // Bounded: ?limit=100000 walked the whole media
152 + // library synchronously, and with alt_source=ai
153 + // that is one AI call per image (#394).
154 + 'minimum' => 1,
155 + 'maximum' => 500,
133 156 'sanitize_callback' => 'absint'
134 157 ],
135 158 'overwrite' => [
136 159 'type' => 'boolean',
@@ -235,10 +258,45 @@
235 258 foreach ($schema as $key => $config) {
236 259 $args[$key] = [
237 260 'type' => $config['type'],
238 261 'required' => false,
239 - 'sanitize_callback' => $config['type'] === 'boolean' ? 'rest_sanitize_boolean' : 'sanitize_text_field'
240 262 ];
263 +
264 + // Pick the sanitizer from the declared type. `sanitize_text_field`
265 + // for everything non-boolean was a trap for the first array- or
266 + // object-typed setting added to the schema: it casts an array to
267 + // the string "Array" (PHP notice) or an empty string, so the value
268 + // would arrive at the handler destroyed rather than rejected.
269 + // A type with no scalar sanitizer gets none — core then falls back
270 + // to `rest_parse_request_arg`, which sanitizes against this very
271 + // schema instead of flattening it.
272 + $sanitizer = self::SANITIZERS[$config['type']] ?? null;
273 +
274 + if (null !== $sanitizer) {
275 + $args[$key]['sanitize_callback'] = $sanitizer;
276 + }
277 +
278 + // A structural type is unusable to core without its shape.
279 + foreach (['items', 'properties', 'additionalProperties'] as $keyword) {
280 + if (isset($config[$keyword])) {
281 + $args[$key][$keyword] = $config[$keyword];
282 + }
283 + }
284 +
285 + // Carry through any constraint the schema already declares. Copying
286 + // only type/required/sanitize_callback silently dropped the
287 + // alt_source enum, so the REST validator never enforced it.
288 + //
289 + // The enum needs a validate_callback to have any effect:
290 + // WP_REST_Request::has_valid_params() skips an arg entirely unless
291 + // one is set, so declaring the enum alone leaves it inert. Attach
292 + // it only to args that actually carry a constraint — applying it to
293 + // every arg would also start enforcing `type`, turning today's
294 + // lenient boolean coercion into a hard 400.
295 + if (isset($config['enum'])) {
296 + $args[$key]['enum'] = $config['enum'];
297 + $args[$key]['validate_callback'] = 'rest_validate_request_arg';
298 + }
241 299 }
242 300
243 301 return $args;
244 302 }