| 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 each feature's capability map. It never |
| 13 |
* needs to fork or monkey-patch this file. |
| 14 |
* |
| 15 |
* @package ThinkRank\Core |
| 16 |
* @since 1.9.0 |
| 17 |
*/ |
| 18 |
|
| 19 |
declare(strict_types=1); |
| 20 |
|
| 21 |
namespace ThinkRank\Core; |
| 22 |
|
| 23 |
if (!defined('ABSPATH')) { |
| 24 |
exit; |
| 25 |
} |
| 26 |
|
| 27 |
/** |
| 28 |
* Plan_Config — capability registry for free/pro feature gating. |
| 29 |
* |
| 30 |
* Usage: |
| 31 |
* if (Plan_Config::can('usage_policy', 'llms_txt')) { ... } |
| 32 |
* $caps = Plan_Config::llms_txt(); |
| 33 |
* |
| 34 |
* @since 1.9.0 |
| 35 |
*/ |
| 36 |
final class Plan_Config { |
| 37 |
|
| 38 |
/** |
| 39 |
* Whether the Pro plugin is active. |
| 40 |
* |
| 41 |
* Resolves through `thinkrank_is_pro_active` so the Pro plugin (or a |
| 42 |
* staging override) can flip the answer. Defaults to checking the |
| 43 |
* `THINKRANK_PRO_VERSION` constant the Pro plugin defines on load. |
| 44 |
*/ |
| 45 |
public static function is_pro(): bool { |
| 46 |
return (bool) apply_filters( |
| 47 |
'thinkrank_is_pro_active', |
| 48 |
defined('THINKRANK_PRO_VERSION') |
| 49 |
); |
| 50 |
} |
| 51 |
|
| 52 |
/** |
| 53 |
* Capability map for the llms.txt feature. |
| 54 |
* |
| 55 |
* Generating and editing llms.txt is FREE and stays free. What Pro adds is |
| 56 |
* the usage policy: the `Training:` / `Summarization:` / `Embedding:` / |
| 57 |
* `Require-Attribution:` directives that state what agents may do with the |
| 58 |
* content, rather than describing the content itself (#160 in Pro). |
| 59 |
* |
| 60 |
* Schema: |
| 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). |
| 64 |
* |
| 65 |
* @since 2.5.0 |
| 66 |
* |
| 67 |
* @return array Capability map. |
| 68 |
*/ |
| 69 |
public static function llms_txt(): array { |
| 70 |
$defaults = [ |
| 71 |
'usage_policy' => false, |
| 72 |
'full_document' => false, |
| 73 |
]; |
| 74 |
|
| 75 |
/** |
| 76 |
* Filter the llms.txt capability map. |
| 77 |
* |
| 78 |
* ThinkRank Pro sets `usage_policy` to true; nothing in the free |
| 79 |
* plugin ever does, so the directives simply never render without it. |
| 80 |
* |
| 81 |
* @since 2.5.0 |
| 82 |
* |
| 83 |
* @param array $defaults Capability map (see schema above). |
| 84 |
*/ |
| 85 |
$caps = apply_filters('thinkrank_llms_txt_capabilities', $defaults); |
| 86 |
|
| 87 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Capability map for the Site SEO Analyzer. |
| 92 |
* |
| 93 |
* Running the audit and seeing the score is FREE and stays free. What Pro |
| 94 |
* adds is memory: dated snapshots of each run, a score trend, and a |
| 95 |
* side-by-side comparison of two runs (#161 in Pro). Free keeps exactly |
| 96 |
* what it has today — the most recent run, cached for an hour. |
| 97 |
* |
| 98 |
* Schema: |
| 99 |
* history bool May persist and read audit snapshots. |
| 100 |
* history_runs int Snapshots retained per site (0 = unlimited). |
| 101 |
* |
| 102 |
* @since 2.5.0 |
| 103 |
* |
| 104 |
* @return array Capability map. |
| 105 |
*/ |
| 106 |
public static function seo_analyzer(): array { |
| 107 |
$defaults = [ |
| 108 |
'history' => false, |
| 109 |
'history_runs' => 0, |
| 110 |
]; |
| 111 |
|
| 112 |
/** |
| 113 |
* Filter the SEO Analyzer capability map. |
| 114 |
* |
| 115 |
* ThinkRank Pro sets `history` to true and declares how many runs it |
| 116 |
* retains. Nothing in the free plugin ever does, so no snapshot is |
| 117 |
* ever written without Pro. |
| 118 |
* |
| 119 |
* @since 2.5.0 |
| 120 |
* |
| 121 |
* @param array $defaults Capability map (see schema above). |
| 122 |
*/ |
| 123 |
$caps = apply_filters('thinkrank_seo_analyzer_capabilities', $defaults); |
| 124 |
|
| 125 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 126 |
} |
| 127 |
|
| 128 |
/** |
| 129 |
* Capability map for Focus Pages. |
| 130 |
* |
| 131 |
* Focus Pages is Pro: it is bulk (many pages in one run), historical, and |
| 132 |
* depends on Pro-only rank data (thinkrank-pro#164). Free has no entry |
| 133 |
* point in v1 — the per-post SEO score panel already answers the single-page |
| 134 |
* on-page question. |
| 135 |
* |
| 136 |
* Schema: |
| 137 |
* enabled bool May curate and diagnose focus pages. |
| 138 |
* max_pages int Pages that may be curated (0 = none). |
| 139 |
* |
| 140 |
* @since 2.5.0 |
| 141 |
* |
| 142 |
* @return array Capability map. |
| 143 |
*/ |
| 144 |
public static function focus_pages(): array { |
| 145 |
$defaults = [ |
| 146 |
'enabled' => false, |
| 147 |
'max_pages' => 0, |
| 148 |
]; |
| 149 |
|
| 150 |
/** |
| 151 |
* Filter the Focus Pages capability map. |
| 152 |
* |
| 153 |
* ThinkRank Pro enables the feature and declares its own page cap, |
| 154 |
* which bounds the external API cost of a refresh. |
| 155 |
* |
| 156 |
* @since 2.5.0 |
| 157 |
* |
| 158 |
* @param array $defaults Capability map (see schema above). |
| 159 |
*/ |
| 160 |
$caps = apply_filters('thinkrank_focus_pages_capabilities', $defaults); |
| 161 |
|
| 162 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* Capability map for sitewide duplicate snippet detection. |
| 167 |
* |
| 168 |
* The check itself is Free: finding out that two of your pages carry one |
| 169 |
* title is a read, it runs when the user asks for it, and it is exactly the |
| 170 |
* kind of thing that should work on a first install (#564). What Pro adds |
| 171 |
* is the unattended half — a recheck that runs on a schedule and tells you |
| 172 |
* when a new duplicate appears, rather than when you next open the screen. |
| 173 |
* |
| 174 |
* Nothing in the free plugin schedules anything, so `scheduled` only ever |
| 175 |
* reports what Pro has arranged; the report reads it to say whether it is |
| 176 |
* being watched or only answered on demand. |
| 177 |
* |
| 178 |
* Schema: |
| 179 |
* scheduled bool Duplicates are rechecked without the user asking. |
| 180 |
* |
| 181 |
* @since 2.10.0 |
| 182 |
* |
| 183 |
* @return array Capability map. |
| 184 |
*/ |
| 185 |
public static function duplicate_snippets(): array { |
| 186 |
$defaults = [ |
| 187 |
'scheduled' => false, |
| 188 |
]; |
| 189 |
|
| 190 |
/** |
| 191 |
* Filter the duplicate snippets capability map. |
| 192 |
* |
| 193 |
* @since 2.10.0 |
| 194 |
* |
| 195 |
* @param array $defaults Capability map (see schema above). |
| 196 |
*/ |
| 197 |
$caps = apply_filters('thinkrank_duplicate_snippets_capabilities', $defaults); |
| 198 |
|
| 199 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 200 |
} |
| 201 |
|
| 202 |
/** |
| 203 |
* Capability map for the thin content report. |
| 204 |
* |
| 205 |
* Finding out which of your pages are thin is a read, it runs when the user |
| 206 |
* asks for it, and it is exactly what should work on a first install, so |
| 207 |
* the report is Free (#565). What Pro adds is the unattended half: an alert |
| 208 |
* when content that was fine becomes thin, or when something thin is |
| 209 |
* published, which is the decay framing Content Refresh Radar already uses. |
| 210 |
* |
| 211 |
* Nothing in the free plugin schedules anything, so `scheduled_alerts` only |
| 212 |
* ever reports what Pro has arranged; the report reads it to say whether it |
| 213 |
* is being watched or only answered on demand. |
| 214 |
* |
| 215 |
* Schema: |
| 216 |
* scheduled_alerts bool Newly-thin content is reported without being asked. |
| 217 |
* |
| 218 |
* @since 2.10.0 |
| 219 |
* |
| 220 |
* @return array Capability map. |
| 221 |
*/ |
| 222 |
public static function thin_content(): array { |
| 223 |
$defaults = [ |
| 224 |
'scheduled_alerts' => false, |
| 225 |
]; |
| 226 |
|
| 227 |
/** |
| 228 |
* Filter the thin content capability map. |
| 229 |
* |
| 230 |
* @since 2.10.0 |
| 231 |
* |
| 232 |
* @param array $defaults Capability map (see schema above). |
| 233 |
*/ |
| 234 |
$caps = apply_filters('thinkrank_thin_content_capabilities', $defaults); |
| 235 |
|
| 236 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 237 |
} |
| 238 |
|
| 239 |
/** |
| 240 |
* Check a single capability for a given feature. |
| 241 |
* |
| 242 |
* Adding a feature means adding a switch case to capabilities_for() that |
| 243 |
* delegates to its own capability builder method. |
| 244 |
* |
| 245 |
* @param string $capability Capability key (e.g. 'usage_policy'). |
| 246 |
* @param string $feature Feature scope (e.g. 'llms_txt'). |
| 247 |
*/ |
| 248 |
public static function can(string $capability, string $feature): bool { |
| 249 |
$caps = self::capabilities_for($feature); |
| 250 |
return ! empty($caps[$capability]); |
| 251 |
} |
| 252 |
|
| 253 |
/** |
| 254 |
* Get the full capability map for a feature. |
| 255 |
* |
| 256 |
* @param string $feature Feature scope. |
| 257 |
* @return array |
| 258 |
*/ |
| 259 |
public static function capabilities_for(string $feature): array { |
| 260 |
switch ($feature) { |
| 261 |
case 'llms_txt': |
| 262 |
return self::llms_txt(); |
| 263 |
case 'seo_analyzer': |
| 264 |
return self::seo_analyzer(); |
| 265 |
case 'focus_pages': |
| 266 |
return self::focus_pages(); |
| 267 |
case 'duplicate_snippets': |
| 268 |
return self::duplicate_snippets(); |
| 269 |
case 'thin_content': |
| 270 |
return self::thin_content(); |
| 271 |
default: |
| 272 |
return []; |
| 273 |
} |
| 274 |
} |
| 275 |
} |
| 276 |
|