PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.26.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.26.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 / abilities / content / class-generate-content-brief.php

class-generate-content-brief.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.26.0, at includes/abilities/content/class-generate-content-brief.php

179 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Generate content brief ability.
4 *
5 * @package ThinkRank\Abilities\Content
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Content;
11
12 use ThinkRank\Abilities\Ability_Base;
13 use ThinkRank\AI\Content_Brief_Generator;
14 use ThinkRank\AI\Manager;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly.
18 }
19
20 /**
21 * Generates an AI SEO content brief for a set of target keywords.
22 *
23 * Mirrors `POST /thinkrank/v1/content-brief/generate`: given target keywords
24 * (and optional content type, audience, length, tone, competitor URLs, and
25 * extra context) it produces a structured brief — title/meta suggestions,
26 * outline, keyword analysis, schema and social recommendations, and more.
27 *
28 * Requires a configured AI provider API key. This calls an external AI service,
29 * consumes tokens, persists the brief, and is rate limited per user.
30 */
31 class Generate_Content_Brief extends Ability_Base {
32 /**
33 * Constructor.
34 */
35 public function __construct() {
36 $this->id = 'thinkrank/generate-content-brief';
37 $this->label = __( 'Generate Content Brief', 'thinkrank' );
38 $this->description = __( 'Generate an AI SEO content brief for the given target keywords: title and meta suggestions, an outline, keyword analysis, and schema/social recommendations. Requires a configured AI provider API key; calls an external AI service and consumes tokens.', 'thinkrank' );
39 }
40
41 /**
42 * {@inheritDoc}
43 *
44 * @return array<string, bool|float|string>
45 */
46 public function get_annotations() {
47 return [
48 'readonly' => false,
49 'destructive' => false,
50 'idempotent' => false,
51 'priority' => 2.0,
52 'openWorldHint' => true,
53 ];
54 }
55
56 /**
57 * {@inheritDoc}
58 *
59 * @return array<string, mixed>
60 */
61 public function get_input_schema() {
62 return [
63 'type' => 'object',
64 'additionalProperties' => false,
65 'properties' => [
66 'target_keywords' => [
67 'type' => 'array',
68 'description' => __( 'One or more target keywords for the brief.', 'thinkrank' ),
69 'minItems' => 1,
70 'items' => [
71 'type' => 'string',
72 'minLength' => 1,
73 ],
74 ],
75 'content_type' => [
76 'type' => 'string',
77 'description' => __( 'The type of content to brief.', 'thinkrank' ),
78 'enum' => [ 'blog_post', 'product_page', 'landing_page', 'tutorial' ],
79 'default' => 'blog_post',
80 ],
81 'target_audience' => [
82 'type' => 'string',
83 'description' => __( 'The intended audience.', 'thinkrank' ),
84 'enum' => [ 'beginners', 'professionals', 'general', 'experts' ],
85 'default' => 'general',
86 ],
87 'content_length' => [
88 'type' => 'string',
89 'description' => __( 'The target content length.', 'thinkrank' ),
90 'enum' => [ 'short', 'medium', 'long' ],
91 'default' => 'medium',
92 ],
93 'tone' => [
94 'type' => 'string',
95 'description' => __( 'The writing tone.', 'thinkrank' ),
96 'enum' => [ 'professional', 'casual', 'technical', 'friendly' ],
97 'default' => 'professional',
98 ],
99 'competitor_urls' => [
100 'type' => 'array',
101 'description' => __( 'Optional competitor URLs to analyze for content gaps.', 'thinkrank' ),
102 'default' => [],
103 'items' => [
104 'type' => 'string',
105 'format' => 'uri',
106 ],
107 ],
108 'additional_context' => [
109 'type' => 'string',
110 'description' => __( 'Optional additional context for the brief.', 'thinkrank' ),
111 'default' => '',
112 ],
113 ],
114 'required' => [ 'target_keywords' ],
115 ];
116 }
117
118 /**
119 * {@inheritDoc}
120 *
121 * @return array<string, mixed>
122 */
123 public function get_output_schema() {
124 return [
125 'type' => 'object',
126 'additionalProperties' => true,
127 'properties' => [],
128 ];
129 }
130
131 /**
132 * Execute ability.
133 *
134 * @param array<string, mixed> $input Ability input payload.
135 * @return array<string, mixed>|\WP_Error
136 */
137 public function execute( $input ) {
138 $keywords = isset( $input['target_keywords'] ) && is_array( $input['target_keywords'] )
139 ? array_values( array_filter( array_map( 'sanitize_text_field', $input['target_keywords'] ) ) )
140 : [];
141
142 if ( empty( $keywords ) ) {
143 return new \WP_Error(
144 'thinkrank_invalid_keywords',
145 __( 'At least one target keyword is required.', 'thinkrank' ),
146 [ 'status' => 400 ]
147 );
148 }
149
150 $competitor_urls = isset( $input['competitor_urls'] ) && is_array( $input['competitor_urls'] )
151 ? array_values( array_filter( array_map( 'esc_url_raw', $input['competitor_urls'] ) ) )
152 : [];
153
154 $params = [
155 'target_keywords' => $keywords,
156 'content_type' => isset( $input['content_type'] ) ? (string) $input['content_type'] : 'blog_post',
157 'target_audience' => isset( $input['target_audience'] ) ? (string) $input['target_audience'] : 'general',
158 'content_length' => isset( $input['content_length'] ) ? (string) $input['content_length'] : 'medium',
159 'tone' => isset( $input['tone'] ) ? (string) $input['tone'] : 'professional',
160 'competitor_urls' => $competitor_urls,
161 'additional_context' => isset( $input['additional_context'] ) ? sanitize_textarea_field( (string) $input['additional_context'] ) : '',
162 ];
163
164 try {
165 $ai_manager = new Manager();
166 $ai_client = $ai_manager->get_client();
167 $generator = new Content_Brief_Generator( null, $ai_client );
168
169 return $generator->generate_brief( $params );
170 } catch ( \Throwable $e ) {
171 return new \WP_Error(
172 'thinkrank_brief_generation_failed',
173 $e->getMessage(),
174 [ 'status' => 500 ]
175 );
176 }
177 }
178 }
179