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
thinkrank / includes / abilities / settings / class-update-social-platforms-settings.php

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

209 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Update social platforms settings 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\Core\Settings;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Updates ThinkRank social platform verification settings.
21 *
22 * Mirrors `POST /thinkrank/v1/social-platforms/settings`: accepts any subset of
23 * the public platform IDs and sensitive verification codes, sanitizes them, and
24 * persists only the non-empty allowed keys via the Settings store (which
25 * encrypts the sensitive verification codes at rest). Returns the resulting
26 * settings with sensitive codes masked.
27 */
28 class Update_Social_Platforms_Settings extends Ability_Base {
29 /**
30 * Public platform IDs.
31 */
32 private const PUBLIC_KEYS = [
33 'facebook_app_id',
34 'facebook_admins',
35 'youtube_channel_id',
36 'whatsapp_business_id',
37 ];
38
39 /**
40 * Sensitive verification codes (encrypted at rest by the Settings store).
41 */
42 private const SENSITIVE_KEYS = [
43 'pinterest_site_verification',
44 'instagram_verification',
45 'tiktok_verification',
46 ];
47
48 /**
49 * Constructor.
50 */
51 public function __construct() {
52 $this->id = 'thinkrank/update-social-platforms-settings';
53 $this->label = __( 'Update ThinkRank Social Platform Settings', 'thinkrank' );
54 $this->description = __( 'Update ThinkRank social platform verification settings: public platform IDs (Facebook, YouTube, WhatsApp) and sensitive site-verification codes (Pinterest, Instagram, TikTok). Only non-empty provided keys are saved; sensitive codes are stored encrypted. Read the current values with get-social-platforms-settings first.', 'thinkrank' );
55 }
56
57 /**
58 * {@inheritDoc}
59 *
60 * @return array<string, bool|float|string>
61 */
62 public function get_annotations() {
63 return [
64 'readonly' => false,
65 // Settings writes are recoverable: the matching get-* ability reads
66 // the previous value, so nothing is lost that cannot be put back.
67 // `destructive` is reserved for calls that lose data or reach
68 // outside the site, and marking routine configuration with it made
69 // MCP clients demand a human approval for every save — which users
70 // reported as a permission bug, because the client's refusal reads
71 // as "No approval received" (#675).
72 'destructive' => false,
73 'idempotent' => true,
74 'priority' => 2.0,
75 'openWorldHint' => false,
76 ];
77 }
78
79 /**
80 * {@inheritDoc}
81 *
82 * @return array<string, mixed>
83 */
84 public function get_input_schema() {
85 return [
86 'type' => 'object',
87 'additionalProperties' => false,
88 'properties' => [
89 'settings' => [
90 'type' => 'object',
91 'description' => __( 'Social platform settings to update (any subset of the fields below).', 'thinkrank' ),
92 'additionalProperties' => false,
93 'properties' => [
94 'facebook_app_id' => [ 'type' => 'string' ],
95 'facebook_admins' => [ 'type' => 'string' ],
96 'youtube_channel_id' => [ 'type' => 'string' ],
97 'whatsapp_business_id' => [ 'type' => 'string' ],
98 'pinterest_site_verification' => [ 'type' => 'string' ],
99 'instagram_verification' => [ 'type' => 'string' ],
100 'tiktok_verification' => [ 'type' => 'string' ],
101 ],
102 ],
103 ],
104 'required' => [ 'settings' ],
105 ];
106 }
107
108 /**
109 * {@inheritDoc}
110 *
111 * @return array<string, mixed>
112 */
113 public function get_output_schema() {
114 return [
115 'type' => 'object',
116 'properties' => [
117 'success' => [ 'type' => 'boolean' ],
118 'message' => [ 'type' => 'string' ],
119 'settings' => [
120 'type' => 'object',
121 'description' => __( 'The resulting settings, with sensitive codes masked.', 'thinkrank' ),
122 'additionalProperties' => true,
123 ],
124 ],
125 ];
126 }
127
128 /**
129 * Execute ability.
130 *
131 * @param array<string, mixed> $input Ability input payload.
132 * @return array<string, mixed>|\WP_Error
133 */
134 public function execute( $input ) {
135 $settings = isset( $input['settings'] ) && is_array( $input['settings'] ) ? $input['settings'] : [];
136
137 if ( empty( $settings ) ) {
138 return new \WP_Error(
139 'thinkrank_missing_social_platforms_settings_payload',
140 __( 'A social platform settings payload is required.', 'thinkrank' ),
141 [ 'status' => 400 ]
142 );
143 }
144
145 $store = Settings::instance();
146 $allowed = array_merge( self::PUBLIC_KEYS, self::SENSITIVE_KEYS );
147 $found = false;
148
149 foreach ( $allowed as $key ) {
150 if ( ! array_key_exists( $key, $settings ) ) {
151 continue;
152 }
153
154 $value = sanitize_text_field( (string) $settings[ $key ] );
155
156 // Mirror the REST endpoint: only persist non-empty values so a
157 // masked/blank round-trip never wipes an existing code.
158 if ( '' === $value ) {
159 continue;
160 }
161
162 $store->set( $key, $value );
163 $found = true;
164 }
165
166 if ( ! $found ) {
167 return new \WP_Error(
168 'thinkrank_no_valid_social_platforms_setting_keys',
169 __( 'No valid, non-empty social platform setting keys were provided.', 'thinkrank' ),
170 [ 'status' => 400 ]
171 );
172 }
173
174 return [
175 'success' => true,
176 'message' => __( 'Social platform settings updated.', 'thinkrank' ),
177 'settings' => $this->read_masked( $store ),
178 ];
179 }
180
181 /**
182 * Read current settings back with sensitive codes masked.
183 *
184 * @param Settings $store Settings store.
185 * @return array<string, string>
186 */
187 private function read_masked( Settings $store ): array {
188 $out = [];
189
190 foreach ( self::PUBLIC_KEYS as $key ) {
191 $out[ $key ] = (string) $store->get( $key, '' );
192 }
193
194 foreach ( self::SENSITIVE_KEYS as $key ) {
195 $code = (string) $store->get( $key, '' );
196
197 if ( '' === $code ) {
198 $out[ $key ] = '';
199 } elseif ( strlen( $code ) > 8 ) {
200 $out[ $key ] = substr( $code, 0, 4 ) . 'XXXX';
201 } else {
202 $out[ $key ] = 'XXXX';
203 }
204 }
205
206 return $out;
207 }
208 }
209