PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.27.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.27.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 / core / class-plan-config.php

class-plan-config.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.27.0, at includes/core/class-plan-config.php

235 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plan Config Class
5 *
6 * Single source of truth for free vs. pro capability gating across ThinkRank.
7 * Every freemium gate (REST validation, render pipeline, scheduler, mailer, UI)
8 * reads from here. No feature code should check `defined('THINKRANK_PRO_VERSION')`
9 * or `apply_filters('thinkrank_is_pro_active', ...)` directly — call these methods
10 * instead so a single override flips behavior everywhere.
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.
15 *
16 * @package ThinkRank\Core
17 * @since 1.9.0
18 */
19
20 declare(strict_types=1);
21
22 namespace ThinkRank\Core;
23
24 if (!defined('ABSPATH')) {
25 exit;
26 }
27
28 /**
29 * Plan_Config — capability registry for free/pro feature gating.
30 *
31 * Usage:
32 * if (Plan_Config::can('custom_subject', 'email_report')) { ... }
33 * $caps = Plan_Config::email_report();
34 *
35 * @since 1.9.0
36 */
37 final class Plan_Config {
38
39 /**
40 * Whether the Pro plugin is active.
41 *
42 * Resolves through `thinkrank_is_pro_active` so the Pro plugin (or a
43 * staging override) can flip the answer. Defaults to checking the
44 * `THINKRANK_PRO_VERSION` constant the Pro plugin defines on load.
45 */
46 public static function is_pro(): bool {
47 return (bool) apply_filters(
48 'thinkrank_is_pro_active',
49 defined('THINKRANK_PRO_VERSION')
50 );
51 }
52
53 /**
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 * Check a single capability for a given feature.
167 *
168 * Currently only the `email_report` feature is registered. Adding more
169 * features means adding a switch case here that delegates to its own
170 * capability builder method.
171 *
172 * @param string $capability Capability key (e.g. 'custom_subject').
173 * @param string $feature Feature scope (default 'email_report').
174 */
175 public static function can(string $capability, string $feature = 'email_report'): bool {
176 $caps = self::capabilities_for($feature);
177 return ! empty($caps[$capability]);
178 }
179
180 /**
181 * Get the full capability map for a feature.
182 *
183 * @param string $feature Feature scope.
184 * @return array
185 */
186 public static function capabilities_for(string $feature): array {
187 switch ($feature) {
188 case 'email_report':
189 return self::email_report();
190 default:
191 return [];
192 }
193 }
194
195 /**
196 * Clamp a frequency value to one the current plan allows.
197 *
198 * Free plans always end up at 30. Pro plans accept 7, 15, or 30.
199 * Anything else falls back to the highest allowed value (most permissive
200 * default that still respects the cap).
201 *
202 * @param int $requested Requested frequency in days.
203 * @return int Clamped frequency.
204 */
205 public static function clamp_email_report_frequency(int $requested): int {
206 $allowed = self::email_report()['allowed_frequencies'];
207 if (in_array($requested, $allowed, true)) {
208 return $requested;
209 }
210 return (int) max($allowed);
211 }
212
213 /**
214 * Truncate a list of recipients to the plan-allowed maximum.
215 *
216 * Used at save and at render time. The save-time call gives the user
217 * feedback; the render-time call is a defense in depth so a downgrade
218 * never accidentally fans a report out to a list the user no longer
219 * has the plan for.
220 *
221 * @param string[] $recipients Recipient email addresses.
222 * @return string[] Truncated, de-duplicated recipients.
223 */
224 public static function clamp_email_report_recipients(array $recipients): array {
225 $caps = self::email_report();
226 $unique = array_values(array_unique(array_filter(array_map('trim', $recipients))));
227
228 if ('admin_email' === $caps['recipients_locked_to']) {
229 return [(string) get_option('admin_email')];
230 }
231
232 return array_slice($unique, 0, (int) $caps['max_recipients']);
233 }
234 }
235