PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.10.0 2.9.0 2.8.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 All 51 releases
← All changes | includes/api/class-content-brief-endpoint.php +37 -19 1.30.0 → 2.10.0 View file →
@@ -208,9 +208,9 @@
208 208 *
209 209 * @param WP_REST_Request $request Request object
210 210 * @return WP_REST_Response|WP_Error Response object
211 211 */
212 - public function generate_brief(WP_REST_Request $request): WP_REST_Response|WP_Error {
212 + public function generate_brief(WP_REST_Request $request) {
213 213 try {
214 214 // Persistent per-user throttle on this paid AI-backed route (the other
215 215 // AI endpoints do the same) to prevent an edit_posts user looping it.
216 216 if (!$this->check_ai_rate_limit()) {
@@ -253,9 +253,9 @@
253 253 *
254 254 * @param WP_REST_Request $request Request object
255 255 * @return WP_REST_Response|WP_Error Response object
256 256 */
257 - public function get_briefs(WP_REST_Request $request): WP_REST_Response|WP_Error {
257 + public function get_briefs(WP_REST_Request $request) {
258 258 try {
259 259 $limit = $request->get_param('limit');
260 260 $offset = $request->get_param('offset');
261 261
@@ -281,9 +281,9 @@
281 281 *
282 282 * @param WP_REST_Request $request Request object
283 283 * @return WP_REST_Response|WP_Error Response object
284 284 */
285 - public function delete_brief(WP_REST_Request $request): WP_REST_Response|WP_Error {
285 + public function delete_brief(WP_REST_Request $request) {
286 286 try {
287 287 $brief_id = $request->get_param('id');
288 288 $success = $this->get_storage_generator()->delete_brief($brief_id);
289 289
@@ -314,9 +314,9 @@
314 314 *
315 315 * @param WP_REST_Request $request Request object
316 316 * @return WP_REST_Response|WP_Error Response object
317 317 */
318 - public function export_brief(WP_REST_Request $request): WP_REST_Response|WP_Error {
318 + public function export_brief(WP_REST_Request $request) {
319 319 try {
320 320 $brief_id = (int) $request->get_param('id');
321 321 $format = $request->get_param('format');
322 322
@@ -356,28 +356,46 @@
356 356 * @param string $format Export format
357 357 * @return string Formatted content
358 358 */
359 359 private function format_brief_for_export(array $brief, string $format): string {
360 - $brief_data = $brief['brief_data'];
361 -
362 - $content = "Content Brief: " . $brief['title'] . "\n\n";
363 - $content .= "Target Keywords: " . implode(', ', $brief['target_keywords']) . "\n";
364 - $content .= "Content Type: " . $brief['content_type'] . "\n\n";
365 -
366 - if (!empty($brief_data['outline'])) {
360 + // Every read here is a field of json_decode() output, so nothing about
361 + // its shape is guaranteed. implode() on null and str_repeat() on a
362 + // negative count are a TypeError and a ValueError respectively, and
363 + // neither is an \Exception — so the catch around this call never
364 + // matched and an export of a malformed brief was a fatal (#394).
365 + $brief_data = is_array($brief['brief_data'] ?? null) ? $brief['brief_data'] : [];
366 + $keywords = is_array($brief['target_keywords'] ?? null) ? $brief['target_keywords'] : [];
367 +
368 + $content = "Content Brief: " . (string) ($brief['title'] ?? '') . "\n\n";
369 + $content .= "Target Keywords: " . implode(', ', array_map('strval', $keywords)) . "\n";
370 + $content .= "Content Type: " . (string) ($brief['content_type'] ?? '') . "\n\n";
371 +
372 + if (!empty($brief_data['outline']) && is_array($brief_data['outline'])) {
367 373 $content .= "Content Outline:\n";
374 +
368 375 foreach ($brief_data['outline'] as $item) {
369 - $indent = str_repeat(' ', $item['level'] - 1);
370 - $content .= $indent . "H{$item['level']}: " . $item['heading'];
371 - if ($item['word_count'] > 0) {
372 - $content .= " ({$item['word_count']} words)";
376 + if (!is_array($item)) {
377 + continue;
373 378 }
379 +
380 + // Clamped: a level of 0 or a missing one made the repeat count
381 + // negative.
382 + $level = max(1, min(6, (int) ($item['level'] ?? 1)));
383 + $word_count = (int) ($item['word_count'] ?? 0);
384 + $indent = str_repeat(' ', $level - 1);
385 +
386 + $content .= $indent . "H{$level}: " . (string) ($item['heading'] ?? '');
387 +
388 + if ($word_count > 0) {
389 + $content .= " ({$word_count} words)";
390 + }
391 +
374 392 $content .= "\n";
375 393 }
376 394 }
377 -
378 - $content .= "\nGenerated on: " . $brief['created_at'];
379 -
395 +
396 + $content .= "\nGenerated on: " . (string) ($brief['created_at'] ?? '');
397 +
380 398 return $content;
381 399 }
382 400
383 401 /**
@@ -385,9 +403,9 @@
385 403 *
386 404 * @param array $keywords Keywords to validate
387 405 * @return bool|WP_Error Validation result
388 406 */
389 - public function validate_keywords($keywords): bool|WP_Error {
407 + public function validate_keywords($keywords) {
390 408 // A custom validate_callback replaces WP's array type-coercion, so the
391 409 // raw param arrives here as-is; reject non-arrays instead of letting a
392 410 // strict array type hint throw an uncaught TypeError during dispatch.
393 411 if (!is_array($keywords)) {