| 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 |
* |
| 63 |
* @since 2.5.0 |
| 64 |
* |
| 65 |
* @return array Capability map. |
| 66 |
*/ |
| 67 |
public static function llms_txt(): array { |
| 68 |
$defaults = [ |
| 69 |
'usage_policy' => false, |
| 70 |
]; |
| 71 |
|
| 72 |
/** |
| 73 |
* Filter the llms.txt capability map. |
| 74 |
* |
| 75 |
* ThinkRank Pro sets `usage_policy` to true; nothing in the free |
| 76 |
* plugin ever does, so the directives simply never render without it. |
| 77 |
* |
| 78 |
* @since 2.5.0 |
| 79 |
* |
| 80 |
* @param array $defaults Capability map (see schema above). |
| 81 |
*/ |
| 82 |
$caps = apply_filters('thinkrank_llms_txt_capabilities', $defaults); |
| 83 |
|
| 84 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* Capability map for the Site SEO Analyzer. |
| 89 |
* |
| 90 |
* Running the audit and seeing the score is FREE and stays free. What Pro |
| 91 |
* adds is memory: dated snapshots of each run, a score trend, and a |
| 92 |
* side-by-side comparison of two runs (#161 in Pro). Free keeps exactly |
| 93 |
* what it has today — the most recent run, cached for an hour. |
| 94 |
* |
| 95 |
* Schema: |
| 96 |
* history bool May persist and read audit snapshots. |
| 97 |
* history_runs int Snapshots retained per site (0 = unlimited). |
| 98 |
* |
| 99 |
* @since 2.5.0 |
| 100 |
* |
| 101 |
* @return array Capability map. |
| 102 |
*/ |
| 103 |
public static function seo_analyzer(): array { |
| 104 |
$defaults = [ |
| 105 |
'history' => false, |
| 106 |
'history_runs' => 0, |
| 107 |
]; |
| 108 |
|
| 109 |
/** |
| 110 |
* Filter the SEO Analyzer capability map. |
| 111 |
* |
| 112 |
* ThinkRank Pro sets `history` to true and declares how many runs it |
| 113 |
* retains. Nothing in the free plugin ever does, so no snapshot is |
| 114 |
* ever written without Pro. |
| 115 |
* |
| 116 |
* @since 2.5.0 |
| 117 |
* |
| 118 |
* @param array $defaults Capability map (see schema above). |
| 119 |
*/ |
| 120 |
$caps = apply_filters('thinkrank_seo_analyzer_capabilities', $defaults); |
| 121 |
|
| 122 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Capability map for Focus Pages. |
| 127 |
* |
| 128 |
* Focus Pages is Pro: it is bulk (many pages in one run), historical, and |
| 129 |
* depends on Pro-only rank data (thinkrank-pro#164). Free has no entry |
| 130 |
* point in v1 — the per-post SEO score panel already answers the single-page |
| 131 |
* on-page question. |
| 132 |
* |
| 133 |
* Schema: |
| 134 |
* enabled bool May curate and diagnose focus pages. |
| 135 |
* max_pages int Pages that may be curated (0 = none). |
| 136 |
* |
| 137 |
* @since 2.5.0 |
| 138 |
* |
| 139 |
* @return array Capability map. |
| 140 |
*/ |
| 141 |
public static function focus_pages(): array { |
| 142 |
$defaults = [ |
| 143 |
'enabled' => false, |
| 144 |
'max_pages' => 0, |
| 145 |
]; |
| 146 |
|
| 147 |
/** |
| 148 |
* Filter the Focus Pages capability map. |
| 149 |
* |
| 150 |
* ThinkRank Pro enables the feature and declares its own page cap, |
| 151 |
* which bounds the external API cost of a refresh. |
| 152 |
* |
| 153 |
* @since 2.5.0 |
| 154 |
* |
| 155 |
* @param array $defaults Capability map (see schema above). |
| 156 |
*/ |
| 157 |
$caps = apply_filters('thinkrank_focus_pages_capabilities', $defaults); |
| 158 |
|
| 159 |
return array_merge($defaults, is_array($caps) ? $caps : []); |
| 160 |
} |
| 161 |
|
| 162 |
/** |
| 163 |
* Check a single capability for a given feature. |
| 164 |
* |
| 165 |
* Adding a feature means adding a switch case to capabilities_for() that |
| 166 |
* delegates to its own capability builder method. |
| 167 |
* |
| 168 |
* @param string $capability Capability key (e.g. 'usage_policy'). |
| 169 |
* @param string $feature Feature scope (e.g. 'llms_txt'). |
| 170 |
*/ |
| 171 |
public static function can(string $capability, string $feature): bool { |
| 172 |
$caps = self::capabilities_for($feature); |
| 173 |
return ! empty($caps[$capability]); |
| 174 |
} |
| 175 |
|
| 176 |
/** |
| 177 |
* Get the full capability map for a feature. |
| 178 |
* |
| 179 |
* @param string $feature Feature scope. |
| 180 |
* @return array |
| 181 |
*/ |
| 182 |
public static function capabilities_for(string $feature): array { |
| 183 |
switch ($feature) { |
| 184 |
case 'llms_txt': |
| 185 |
return self::llms_txt(); |
| 186 |
case 'seo_analyzer': |
| 187 |
return self::seo_analyzer(); |
| 188 |
case 'focus_pages': |
| 189 |
return self::focus_pages(); |
| 190 |
default: |
| 191 |
return []; |
| 192 |
} |
| 193 |
} |
| 194 |
} |
| 195 |
|