PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.9.0 2.8.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 All 50 releases
thinkrank / includes / abilities / settings / class-update-ai-budget.php

class-update-ai-budget.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.9.0, at includes/abilities/settings/class-update-ai-budget.php

167 lines 4.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Update AI budget ability.
4 *
5 * @package ThinkRank\Abilities\Settings
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Settings;
11
12 use ThinkRank\Abilities\Ability_Base;
13 use ThinkRank\AI\Spend_Guard;
14 use ThinkRank\Core\Settings;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly.
18 }
19
20 /**
21 * Sets the site's AI spend ceiling, or stops outbound AI entirely.
22 *
23 * Returns what was stored rather than what was submitted, so a caller that
24 * sends a ceiling the store reduces is told the real value instead of a
25 * successful save of something the site does not apply.
26 */
27 class Update_Ai_Budget extends Ability_Base {
28
29 /**
30 * Constructor.
31 */
32 public function __construct() {
33 $this->id = 'thinkrank/update-ai-budget';
34 $this->label = __( 'Update ThinkRank AI Budget', 'thinkrank' );
35 $this->description = __( 'Change ThinkRank AI spend limits. Set `paused` to true to stop every outbound AI request immediately, including scheduled ones, without touching the API key. Set `daily_request_limit` to cap requests per day (0 removes the cap), or `max_requests_per_minute` for the per-minute throttle. Use get-ai-budget to read current usage.', 'thinkrank' );
36 }
37
38 /**
39 * {@inheritDoc}
40 *
41 * @return array<string, bool|float|string>
42 */
43 public function get_annotations() {
44 return [
45 'readonly' => false,
46 // Pausing AI loses nothing and is reversed by the same call with
47 // `paused: false`, so this is routine configuration, not a
48 // destructive act. Marking settings destructive makes MCP clients
49 // demand a human approval for every save (#675).
50 'destructive' => false,
51 'idempotent' => true,
52 'priority' => 2.0,
53 'openWorldHint' => false,
54 ];
55 }
56
57 /**
58 * {@inheritDoc}
59 *
60 * @return array<string, mixed>
61 */
62 public function get_input_schema() {
63 return [
64 'type' => 'object',
65 'additionalProperties' => false,
66 'properties' => [
67 'settings' => [
68 'type' => 'object',
69 'description' => __( 'AI budget settings to update.', 'thinkrank' ),
70 'properties' => [
71 'paused' => [
72 'type' => 'boolean',
73 'description' => __( 'True stops all outbound AI requests immediately. False resumes them.', 'thinkrank' ),
74 ],
75 'daily_request_limit' => [
76 'type' => 'integer',
77 'minimum' => 0,
78 'description' => __( 'Maximum AI requests per day. 0 means no daily ceiling.', 'thinkrank' ),
79 ],
80 'max_requests_per_minute' => [
81 'type' => 'integer',
82 'minimum' => 0,
83 'description' => __( 'Maximum AI requests per minute per user. 0 means no per-minute throttle.', 'thinkrank' ),
84 ],
85 'reset_usage' => [
86 'type' => 'boolean',
87 'description' => __( 'True clears today\'s request count. This does not refund anything at the AI provider; it only restarts this site\'s daily window.', 'thinkrank' ),
88 ],
89 ],
90 ],
91 ],
92 'required' => [ 'settings' ],
93 ];
94 }
95
96 /**
97 * {@inheritDoc}
98 *
99 * @return array<string, mixed>
100 */
101 public function get_output_schema() {
102 return [
103 'type' => 'object',
104 'properties' => [
105 'success' => [ 'type' => 'boolean' ],
106 'message' => [ 'type' => 'string' ],
107 'budget' => [ 'type' => 'object' ],
108 ],
109 ];
110 }
111
112 /**
113 * Execute ability.
114 *
115 * @param array<string, mixed> $input Ability input payload.
116 * @return array<string, mixed>|\WP_Error
117 */
118 public function execute( $input ) {
119 $settings = isset( $input['settings'] ) && is_array( $input['settings'] ) ? $input['settings'] : [];
120
121 if ( empty( $settings ) ) {
122 return new \WP_Error(
123 'thinkrank_missing_ai_budget_payload',
124 __( 'An AI budget settings payload is required.', 'thinkrank' ),
125 [ 'status' => 400 ]
126 );
127 }
128
129 $store = Settings::instance();
130 $found = false;
131
132 if ( array_key_exists( 'paused', $settings ) ) {
133 $store->set( 'ai_paused', (bool) $settings['paused'] );
134 $found = true;
135 }
136
137 if ( array_key_exists( 'daily_request_limit', $settings ) ) {
138 $store->set( 'ai_daily_request_limit', max( 0, (int) $settings['daily_request_limit'] ) );
139 $found = true;
140 }
141
142 if ( array_key_exists( 'max_requests_per_minute', $settings ) ) {
143 $store->set( 'max_requests_per_minute', max( 0, (int) $settings['max_requests_per_minute'] ) );
144 $found = true;
145 }
146
147 if ( ! empty( $settings['reset_usage'] ) ) {
148 Spend_Guard::reset_usage();
149 $found = true;
150 }
151
152 if ( ! $found ) {
153 return new \WP_Error(
154 'thinkrank_no_valid_ai_budget_keys',
155 __( 'No valid AI budget setting keys were provided.', 'thinkrank' ),
156 [ 'status' => 400 ]
157 );
158 }
159
160 return [
161 'success' => true,
162 'message' => __( 'AI budget settings updated.', 'thinkrank' ),
163 'budget' => Spend_Guard::status(),
164 ];
165 }
166 }
167