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/core/class-plan-config.php +14 -238 2.5.02.7.0 View file →
@@ -8,11 +8,10 @@
8 8 * reads from here. No feature code should check `defined('THINKRANK_PRO_VERSION')`
9 9 * or `apply_filters('thinkrank_is_pro_active', ...)` directly — call these methods
10 10 * instead so a single override flips behavior everywhere.
11 11 *
12 - * The Pro plugin attaches by filtering `thinkrank_email_report_capabilities`
13 - * (or the analogous filter for other features). It never needs to fork or
14 - * monkey-patch this file.
12 + * The Pro plugin attaches by filtering each feature's capability map. It never
13 + * needs to fork or monkey-patch this file.
15 14 *
16 15 * @package ThinkRank\Core
17 16 * @since 1.9.0
18 17 */
@@ -28,10 +27,10 @@
28 27 /**
29 28 * Plan_Config — capability registry for free/pro feature gating.
30 29 *
31 30 * Usage:
32 - * if (Plan_Config::can('custom_subject', 'email_report')) { ... }
33 - * $caps = Plan_Config::email_report();
31 + * if (Plan_Config::can('usage_policy', 'llms_txt')) { ... }
32 + * $caps = Plan_Config::llms_txt();
34 33 *
35 34 * @since 1.9.0
36 35 */
37 36 final class Plan_Config {
@@ -50,187 +49,8 @@
50 49 );
51 50 }
52 51
53 52 /**
54 - * Capability map for the Email Reporting feature.
55 - *
56 - * Returns a flat array of capability_key => bool|int|array describing
57 - * what the current plan can do. The Pro plugin filters this to enable
58 - * its capabilities.
59 - *
60 - * Schema (keep in sync with src/admin/config/email-report-plan.js):
61 - * - allowed_frequencies int[] Days between sends user may pick.
62 - * - max_recipients int Max addresses on the recipients field.
63 - * - recipients_locked_to string|null 'admin_email' = pre-filled & read-only.
64 - * - custom_subject bool May edit subject template.
65 - * - custom_logo bool May upload a header logo.
66 - * - logo_link bool May set a click-through URL on the logo.
67 - * - header_background bool May set a custom header background CSS.
68 - * - link_to_full_report bool May toggle the dashboard CTA at the foot.
69 - * - intro_text bool May set a custom intro paragraph.
70 - * - sections_configurable bool May enable/disable individual sections.
71 - * - footer_text bool May set a custom footer paragraph.
72 - * - additional_css bool May inject additional CSS into the email.
73 - * - ai_highlights bool May render the AI Highlights section.
74 - */
75 - public static function email_report(): array {
76 - $defaults = [
77 - 'allowed_frequencies' => [30],
78 - 'max_recipients' => 1,
79 - 'recipients_locked_to' => 'admin_email',
80 - 'custom_subject' => false,
81 - 'custom_logo' => false,
82 - 'logo_link' => false,
83 - 'header_background' => false,
84 - 'link_to_full_report' => false,
85 - 'intro_text' => false,
86 - 'sections_configurable' => false,
87 - 'footer_text' => false,
88 - 'additional_css' => false,
89 - 'ai_highlights' => false,
90 - ];
91 -
92 - if (self::is_pro()) {
93 - $defaults = array_merge($defaults, [
94 - 'allowed_frequencies' => [7, 15, 30],
95 - 'max_recipients' => 50,
96 - 'recipients_locked_to' => null,
97 - 'custom_subject' => true,
98 - 'custom_logo' => true,
99 - 'logo_link' => true,
100 - 'header_background' => true,
101 - 'link_to_full_report' => true,
102 - 'intro_text' => true,
103 - 'sections_configurable' => true,
104 - 'footer_text' => true,
105 - 'additional_css' => true,
106 - // ai_highlights stays false by default — it's a separate sub-feature
107 - // the Pro plugin opts in to once the AI summary integration ships.
108 - ]);
109 - }
110 -
111 - /**
112 - * Filter the Email Reporting capability map.
113 - *
114 - * The Pro plugin uses this filter (and only this filter) to enable
115 - * pro capabilities. Returning a partial array is fine; missing keys
116 - * fall back to the values above.
117 - *
118 - * @since 1.9.0
119 - *
120 - * @param array $defaults Capability map (see schema above).
121 - */
122 - $caps = apply_filters('thinkrank_email_report_capabilities', $defaults);
123 -
124 - // Defensive merge: never let a filter drop required keys.
125 - return array_merge($defaults, is_array($caps) ? $caps : []);
126 - }
127 -
128 - /**
129 - * Capability map for the Focus Keywords feature.
130 - *
131 - * Free allows up to 5 focus keywords; ThinkRank Pro lifts the cap. The 6th
132 - * and subsequent keywords are stored but only become usable (analyzed,
133 - * output, editable) once Pro raises the limit.
134 - *
135 - * Localized to the metabox as `thinkrankMetabox.focusKeywords`.
136 - * Schema:
137 - * - max_keywords int Usable focus keywords. 0 = unlimited (Pro).
138 - *
139 - * @since 2.0.0
140 - *
141 - * @return array Capability map.
142 - */
143 - public static function focus_keywords(): array {
144 - // Free default. ThinkRank Pro lifts the cap by filtering the map below
145 - // (Pro owns its own limit rather than the free plugin hard-coding it).
146 - $defaults = [
147 - 'max_keywords' => 5,
148 - ];
149 -
150 - /**
151 - * Filter the Focus Keywords capability map.
152 - *
153 - * The Pro plugin uses this filter to lift the free cap — set
154 - * `max_keywords` to 0 for unlimited, or a finite number.
155 - *
156 - * @since 2.0.0
157 - *
158 - * @param array $defaults Capability map (see schema above).
159 - */
160 - $caps = apply_filters('thinkrank_focus_keywords_capabilities', $defaults);
161 -
162 - return array_merge($defaults, is_array($caps) ? $caps : []);
163 - }
164 -
165 - /**
166 - * Capability map for the AI Insights trio.
167 - *
168 - * Deliberately NOT gated on "needs an AI key" — the user pays their
169 - * provider either way, so that is not what separates free from Pro. The
170 - * split is acquisition vs. recurring depth:
171 - *
172 - * - AI Traffic analytics stays FREE and ungated. It costs nothing to run
173 - * (referrer classification, no AI call) and is the feature that shows
174 - * value on day one. No capability key exists for it on purpose.
175 - * - Brand Visibility is FREEMIUM: free runs a couple of queries by hand
176 - * and keeps a short history; Pro lifts the query cap, keeps full
177 - * history, and unlocks scheduled (unattended) checks.
178 - * - Auto AI metadata is PRO: unattended automation is the clearest Pro
179 - * trait in the lineup.
180 - *
181 - * Unlike email_report, this map has no JS mirror in src/admin/config/ on
182 - * purpose: the admin UI reads the resolved values off the AI Insights REST
183 - * responses (`plan`, `is_pro`, `available`) rather than re-declaring them
184 - * client-side, so there is nothing here that can drift out of sync.
185 - *
186 - * Schema:
187 - * brand_max_queries int Saved brand queries allowed (0 = unlimited).
188 - * brand_history_limit int History rows returned (0 = unlimited).
189 - * brand_scheduled bool Unattended scheduled brand checks.
190 - * auto_ai_meta bool Auto-generate metadata on first publish.
191 - *
192 - * @since 1.28.0
193 - *
194 - * @return array Capability map.
195 - */
196 - public static function ai_visibility(): array {
197 - $defaults = [
198 - 'brand_max_queries' => 2,
199 - 'brand_history_limit' => 10,
200 - 'brand_scheduled' => false,
201 - 'auto_ai_meta' => false,
202 -
203 - // Brand Visibility v2. Free keeps a usable "quick check" — a
204 - // couple of questions on one platform, single sample — which is
205 - // enough to see the feature work and understand what Pro measures.
206 - // Everything that turns a probe into a MEASUREMENT (sampling,
207 - // competitors, multi-platform, trends) is Pro.
208 - 'brand_wizard' => false,
209 - 'brand_competitors' => 0, // max competitors; 0 = none
210 - 'brand_max_platforms' => 1,
211 - 'brand_max_samples' => 1,
212 - 'brand_sentiment' => false,
213 - 'brand_history_runs' => 1, // runs kept for the trend chart
214 - ];
215 -
216 - /**
217 - * Filter the AI Insights capability map.
218 - *
219 - * ThinkRank Pro sets `brand_max_queries` to 0 (unlimited, bounded
220 - * only by what the run request itself asks for), enables
221 - * `brand_scheduled` and `auto_ai_meta`, and lifts the history limit.
222 - *
223 - * @since 1.28.0
224 - *
225 - * @param array $defaults Capability map (see schema above).
226 - */
227 - $caps = apply_filters('thinkrank_ai_visibility_capabilities', $defaults);
228 -
229 - return array_merge($defaults, is_array($caps) ? $caps : []);
230 - }
231 -
232 - /**
233 53 * Capability map for the llms.txt feature.
234 54 *
235 55 * Generating and editing llms.txt is FREE and stays free. What Pro adds is
236 56 * the usage policy: the `Training:` / `Summarization:` / `Embedding:` /
@@ -237,9 +57,11 @@
237 57 * `Require-Attribution:` directives that state what agents may do with the
238 58 * content, rather than describing the content itself (#160 in Pro).
239 59 *
240 60 * Schema:
241 - * usage_policy bool May publish usage-policy directives in llms.txt.
61 + * usage_policy bool May publish usage-policy directives in llms.txt.
62 + * full_document bool May publish llms-full.txt, the full-content
63 + * companion document (thinkrank-pro#171).
242 64 *
243 65 * @since 2.5.0
244 66 *
245 67 * @return array Capability map.
@@ -245,9 +67,10 @@
245 67 * @return array Capability map.
246 68 */
247 69 public static function llms_txt(): array {
248 70 $defaults = [
249 - 'usage_policy' => false,
71 + 'usage_policy' => false,
72 + 'full_document' => false,
250 73 ];
251 74
252 75 /**
253 76 * Filter the llms.txt capability map.
@@ -341,16 +164,15 @@
341 164
342 165 /**
343 166 * Check a single capability for a given feature.
344 167 *
345 - * Currently only the `email_report` feature is registered. Adding more
346 - * features means adding a switch case here that delegates to its own
347 - * capability builder method.
168 + * Adding a feature means adding a switch case to capabilities_for() that
169 + * delegates to its own capability builder method.
348 170 *
349 - * @param string $capability Capability key (e.g. 'custom_subject').
350 - * @param string $feature Feature scope (default 'email_report').
171 + * @param string $capability Capability key (e.g. 'usage_policy').
172 + * @param string $feature Feature scope (e.g. 'llms_txt').
351 173 */
352 - public static function can(string $capability, string $feature = 'email_report'): bool {
174 + public static function can(string $capability, string $feature): bool {
353 175 $caps = self::capabilities_for($feature);
354 176 return ! empty($caps[$capability]);
355 177 }
356 178
@@ -361,14 +183,8 @@
361 183 * @return array
362 184 */
363 185 public static function capabilities_for(string $feature): array {
364 186 switch ($feature) {
365 - case 'email_report':
366 - return self::email_report();
367 - case 'focus_keywords':
368 - return self::focus_keywords();
369 - case 'ai_visibility':
370 - return self::ai_visibility();
371 187 case 'llms_txt':
372 188 return self::llms_txt();
373 189 case 'seo_analyzer':
374 190 return self::seo_analyzer();
@@ -376,46 +192,6 @@
376 192 return self::focus_pages();
377 193 default:
378 194 return [];
379 195 }
380 - }
381 -
382 - /**
383 - * Clamp a frequency value to one the current plan allows.
384 - *
385 - * Free plans always end up at 30. Pro plans accept 7, 15, or 30.
386 - * Anything else falls back to the highest allowed value (most permissive
387 - * default that still respects the cap).
388 - *
389 - * @param int $requested Requested frequency in days.
390 - * @return int Clamped frequency.
391 - */
392 - public static function clamp_email_report_frequency(int $requested): int {
393 - $allowed = self::email_report()['allowed_frequencies'];
394 - if (in_array($requested, $allowed, true)) {
395 - return $requested;
396 - }
397 - return (int) max($allowed);
398 - }
399 -
400 - /**
401 - * Truncate a list of recipients to the plan-allowed maximum.
402 - *
403 - * Used at save and at render time. The save-time call gives the user
404 - * feedback; the render-time call is a defense in depth so a downgrade
405 - * never accidentally fans a report out to a list the user no longer
406 - * has the plan for.
407 - *
408 - * @param string[] $recipients Recipient email addresses.
409 - * @return string[] Truncated, de-duplicated recipients.
410 - */
411 - public static function clamp_email_report_recipients(array $recipients): array {
412 - $caps = self::email_report();
413 - $unique = array_values(array_unique(array_filter(array_map('trim', $recipients))));
414 -
415 - if ('admin_email' === $caps['recipients_locked_to']) {
416 - return [(string) get_option('admin_email')];
417 - }
418 -
419 - return array_slice($unique, 0, (int) $caps['max_recipients']);
420 196 }
421 197 }