| 1 |
<?php |
| 2 |
/** |
| 3 |
* Jetpack Newsletter Abilities Registration |
| 4 |
* |
| 5 |
* Registers Jetpack Newsletter (subscriptions) abilities with the WordPress |
| 6 |
* Abilities API. |
| 7 |
* |
| 8 |
* @package automattic/jetpack |
| 9 |
*/ |
| 10 |
|
| 11 |
namespace Automattic\Jetpack\Plugin\Abilities; |
| 12 |
|
| 13 |
use Automattic\Jetpack\Connection\Client; |
| 14 |
use Automattic\Jetpack\Modules\Subscriptions\Settings as Subscriptions_Settings; |
| 15 |
use Automattic\Jetpack\WP_Abilities\Registrar; |
| 16 |
use Jetpack; |
| 17 |
use Jetpack_Options; |
| 18 |
|
| 19 |
// The Subscriptions module doesn't load its Settings helpers eagerly. Pull it |
| 20 |
// in here so `Subscriptions_Settings::$default_reply_to` and |
| 21 |
// `is_valid_reply_to()` are resolvable when the abilities run. |
| 22 |
require_once __DIR__ . '/../class-settings.php'; |
| 23 |
|
| 24 |
/** |
| 25 |
* Registers Jetpack Newsletter abilities with the WordPress Abilities API. |
| 26 |
* |
| 27 |
* Exposes a consolidated read of the site's newsletter (subscriptions) settings |
| 28 |
* and a partial-update writer so AI agents can configure the Newsletter module |
| 29 |
* through the standard `wp-abilities/v1` REST surface. |
| 30 |
*/ |
| 31 |
class Newsletter_Abilities extends Registrar { |
| 32 |
|
| 33 |
// Field type tags used in `settings_map()`. Constants (not strings) so a |
| 34 |
// typo in a `case` label fails fast instead of silently falling through. |
| 35 |
private const TYPE_BOOL = 'bool'; |
| 36 |
private const TYPE_ON_OFF = 'on_off'; |
| 37 |
private const TYPE_ENUM = 'enum'; |
| 38 |
private const TYPE_STRING = 'string'; |
| 39 |
|
| 40 |
/** |
| 41 |
* Allowed values for the `reply_to` setting. Mirror of |
| 42 |
* `Subscriptions_Settings::is_valid_reply_to()` — kept here so it can be |
| 43 |
* referenced from the JSON Schema enum without loading the Settings class |
| 44 |
* at file-parse time. |
| 45 |
*/ |
| 46 |
private const REPLY_TO_VALUES = array( 'comment', 'author', 'no-reply' ); |
| 47 |
|
| 48 |
/** |
| 49 |
* Returns the abilities category, definition, or registered abilities. |
| 50 |
* |
| 51 |
* @inheritDoc |
| 52 |
*/ |
| 53 |
public static function get_category_slug(): string { |
| 54 |
return 'jetpack-newsletter'; |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* Returns the abilities category, definition, or registered abilities. |
| 59 |
* |
| 60 |
* @inheritDoc |
| 61 |
*/ |
| 62 |
public static function get_category_definition(): array { |
| 63 |
return array( |
| 64 |
// "Jetpack" and "Newsletter" are product names and should not be translated. |
| 65 |
'label' => 'Jetpack Newsletter', |
| 66 |
'description' => __( 'Abilities for reading and updating Jetpack Newsletter settings.', 'jetpack' ), |
| 67 |
); |
| 68 |
} |
| 69 |
|
| 70 |
/** |
| 71 |
* Returns the abilities category, definition, or registered abilities. |
| 72 |
* |
| 73 |
* @inheritDoc |
| 74 |
*/ |
| 75 |
public static function get_abilities(): array { |
| 76 |
$settings_object_schema = array( |
| 77 |
'type' => 'object', |
| 78 |
'additionalProperties' => false, |
| 79 |
'properties' => array( |
| 80 |
'subscribe_post_end_enabled' => array( |
| 81 |
'type' => 'boolean', |
| 82 |
'description' => __( 'Show a "subscribe to blog" checkbox at the end of every post. Default true.', 'jetpack' ), |
| 83 |
), |
| 84 |
'subscribe_comments_enabled' => array( |
| 85 |
'type' => 'boolean', |
| 86 |
'description' => __( 'Show a "notify me of new comments" checkbox in the comment form. Default true.', 'jetpack' ), |
| 87 |
), |
| 88 |
'notify_admin_on_subscribe' => array( |
| 89 |
'type' => 'boolean', |
| 90 |
'description' => __( 'Email the site admin whenever a new subscriber signs up. Default true.', 'jetpack' ), |
| 91 |
), |
| 92 |
'reply_to' => array( |
| 93 |
'type' => 'string', |
| 94 |
'enum' => self::REPLY_TO_VALUES, |
| 95 |
'description' => __( 'Reply-to address for newsletter emails. "comment" routes to the post comment author, "author" to the post author, "no-reply" disables replies. Default "comment".', 'jetpack' ), |
| 96 |
), |
| 97 |
'from_name' => array( |
| 98 |
'type' => 'string', |
| 99 |
'description' => __( 'Sender name shown on newsletter emails. Empty string falls back to the site name.', 'jetpack' ), |
| 100 |
'maxLength' => 200, |
| 101 |
), |
| 102 |
), |
| 103 |
); |
| 104 |
|
| 105 |
return array( |
| 106 |
'jetpack-newsletter/get-settings' => array( |
| 107 |
'label' => __( 'Get Newsletter settings', 'jetpack' ), |
| 108 |
'description' => __( |
| 109 |
'Return the current Jetpack Newsletter settings as a flat object. Always returns the same five fields: subscribe_post_end_enabled (bool), subscribe_comments_enabled (bool), notify_admin_on_subscribe (bool), reply_to ("comment"|"author"|"no-reply"), and from_name (string). Read-only and idempotent. To change any value, call jetpack-newsletter/update-settings.', |
| 110 |
'jetpack' |
| 111 |
), |
| 112 |
'input_schema' => array( |
| 113 |
'type' => 'object', |
| 114 |
'default' => array(), |
| 115 |
'properties' => array(), |
| 116 |
'additionalProperties' => false, |
| 117 |
), |
| 118 |
'output_schema' => $settings_object_schema, |
| 119 |
'execute_callback' => array( __CLASS__, 'get_settings' ), |
| 120 |
'permission_callback' => array( __CLASS__, 'can_view_settings' ), |
| 121 |
'meta' => array( |
| 122 |
'annotations' => array( |
| 123 |
'readonly' => true, |
| 124 |
'destructive' => false, |
| 125 |
'idempotent' => true, |
| 126 |
), |
| 127 |
'show_in_rest' => true, |
| 128 |
'mcp' => array( |
| 129 |
'public' => true, |
| 130 |
'type' => 'tool', // default is already "tool", but can be explicit. |
| 131 |
), |
| 132 |
), |
| 133 |
), |
| 134 |
|
| 135 |
'jetpack-newsletter/update-settings' => array( |
| 136 |
'label' => __( 'Update Newsletter settings', 'jetpack' ), |
| 137 |
'description' => __( |
| 138 |
'Update one or more Jetpack Newsletter settings. Any subset of the five fields may be supplied; omitted fields are left untouched. Idempotent — fields whose desired value already matches the current value are not rewritten. Returns { settings: <full current state after the update>, changed: <array of field names that actually transitioned> }. An empty input or input matching the current state returns changed = [].', |
| 139 |
'jetpack' |
| 140 |
), |
| 141 |
'input_schema' => $settings_object_schema, |
| 142 |
'output_schema' => array( |
| 143 |
'type' => 'object', |
| 144 |
'properties' => array( |
| 145 |
'settings' => $settings_object_schema, |
| 146 |
'changed' => array( |
| 147 |
'type' => 'array', |
| 148 |
'items' => array( 'type' => 'string' ), |
| 149 |
'description' => __( 'Names of the fields that actually changed during this call. Empty when the call was a no-op.', 'jetpack' ), |
| 150 |
), |
| 151 |
), |
| 152 |
), |
| 153 |
'execute_callback' => array( __CLASS__, 'update_settings' ), |
| 154 |
'permission_callback' => array( __CLASS__, 'can_manage_settings' ), |
| 155 |
'meta' => array( |
| 156 |
'annotations' => array( |
| 157 |
'readonly' => false, |
| 158 |
'destructive' => false, |
| 159 |
'idempotent' => true, |
| 160 |
), |
| 161 |
'show_in_rest' => true, |
| 162 |
'mcp' => array( |
| 163 |
'public' => true, |
| 164 |
'type' => 'tool', // default is already "tool", but can be explicit. |
| 165 |
), |
| 166 |
), |
| 167 |
), |
| 168 |
|
| 169 |
'jetpack-newsletter/get-subscriber-stats' => array( |
| 170 |
'label' => __( 'Get Newsletter subscriber stats', 'jetpack' ), |
| 171 |
'description' => __( |
| 172 |
'Return aggregate subscriber counts for the site. Always returns { all: int, email: int, paid: int }: all is the total subscriber count (email + WordPress.com followers); email is the subset that receives email; paid is the subset on a paid newsletter plan. Numbers are fetched from WordPress.com and cached locally for one hour, so transient network errors yield a stale-but-non-zero response when one is available. Requires an active Jetpack connection — sites without one return jetpack_newsletter_not_connected.', |
| 173 |
'jetpack' |
| 174 |
), |
| 175 |
'input_schema' => array( |
| 176 |
'type' => 'object', |
| 177 |
'default' => array(), |
| 178 |
'properties' => array(), |
| 179 |
'additionalProperties' => false, |
| 180 |
), |
| 181 |
'output_schema' => array( |
| 182 |
'type' => 'object', |
| 183 |
'properties' => array( |
| 184 |
'all' => array( 'type' => 'integer' ), |
| 185 |
'email' => array( 'type' => 'integer' ), |
| 186 |
'paid' => array( 'type' => 'integer' ), |
| 187 |
), |
| 188 |
), |
| 189 |
'execute_callback' => array( __CLASS__, 'get_subscriber_stats' ), |
| 190 |
'permission_callback' => array( __CLASS__, 'can_view_settings' ), |
| 191 |
'meta' => array( |
| 192 |
'annotations' => array( |
| 193 |
'readonly' => true, |
| 194 |
'destructive' => false, |
| 195 |
'idempotent' => true, |
| 196 |
), |
| 197 |
'show_in_rest' => true, |
| 198 |
'mcp' => array( |
| 199 |
'public' => true, |
| 200 |
'type' => 'tool', // default is already "tool", but can be explicit. |
| 201 |
), |
| 202 |
), |
| 203 |
), |
| 204 |
); |
| 205 |
} |
| 206 |
|
| 207 |
/** |
| 208 |
* Permission check for read abilities. Newsletter settings live on the WP |
| 209 |
* Newsletter settings screen, which is itself gated on `manage_options`. |
| 210 |
*/ |
| 211 |
public static function can_view_settings(): bool { |
| 212 |
return current_user_can( 'manage_options' ); |
| 213 |
} |
| 214 |
|
| 215 |
/** |
| 216 |
* Permission check for write abilities. Mirrors the gating on the |
| 217 |
* Newsletter settings screen. |
| 218 |
*/ |
| 219 |
public static function can_manage_settings(): bool { |
| 220 |
return current_user_can( 'manage_options' ); |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Execute: return the current newsletter settings. |
| 225 |
* |
| 226 |
* @param array|null $input Unused — input schema accepts no parameters. |
| 227 |
* @return array |
| 228 |
*/ |
| 229 |
public static function get_settings( $input = null ): array { |
| 230 |
unset( $input ); |
| 231 |
return self::current_settings(); |
| 232 |
} |
| 233 |
|
| 234 |
/** |
| 235 |
* Transient key for the wpcom subscriber-stats response. |
| 236 |
*/ |
| 237 |
private const SUBSCRIBER_STATS_CACHE_KEY = 'jetpack_newsletter_subscriber_stats'; |
| 238 |
|
| 239 |
/** |
| 240 |
* Transient TTL for subscriber-stats responses, in seconds. |
| 241 |
* |
| 242 |
* Matches the existing legacy widget pattern of an hour-long cache so |
| 243 |
* agents calling this ability repeatedly don't fan out to wpcom. |
| 244 |
*/ |
| 245 |
private const SUBSCRIBER_STATS_CACHE_TTL = HOUR_IN_SECONDS; |
| 246 |
|
| 247 |
/** |
| 248 |
* Execute: fetch (and cache) aggregate subscriber counts from WordPress.com. |
| 249 |
* |
| 250 |
* @param array|null $input Unused — input schema accepts no parameters. |
| 251 |
* @return array|\WP_Error |
| 252 |
*/ |
| 253 |
public static function get_subscriber_stats( $input = null ) { |
| 254 |
unset( $input ); |
| 255 |
|
| 256 |
$cached = get_transient( self::SUBSCRIBER_STATS_CACHE_KEY ); |
| 257 |
if ( is_array( $cached ) ) { |
| 258 |
return $cached; |
| 259 |
} |
| 260 |
|
| 261 |
if ( ! class_exists( 'Jetpack' ) || ! Jetpack::is_connection_ready() ) { |
| 262 |
return new \WP_Error( |
| 263 |
'jetpack_newsletter_not_connected', |
| 264 |
__( 'Subscriber stats are only available on Jetpack-connected sites. Connect Jetpack and retry.', 'jetpack' ) |
| 265 |
); |
| 266 |
} |
| 267 |
|
| 268 |
$site_id = (int) Jetpack_Options::get_option( 'id' ); |
| 269 |
if ( $site_id <= 0 ) { |
| 270 |
return new \WP_Error( |
| 271 |
'jetpack_newsletter_not_connected', |
| 272 |
__( 'No Jetpack site ID is registered. Connect Jetpack and retry.', 'jetpack' ) |
| 273 |
); |
| 274 |
} |
| 275 |
|
| 276 |
$response = Client::wpcom_json_api_request_as_blog( |
| 277 |
sprintf( '/sites/%d/subscribers/stats', $site_id ), |
| 278 |
'2', |
| 279 |
array(), |
| 280 |
null, |
| 281 |
'wpcom' |
| 282 |
); |
| 283 |
|
| 284 |
if ( is_wp_error( $response ) ) { |
| 285 |
return new \WP_Error( |
| 286 |
'jetpack_newsletter_subscriber_stats_unavailable', |
| 287 |
$response->get_error_message() |
| 288 |
); |
| 289 |
} |
| 290 |
|
| 291 |
if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { |
| 292 |
return new \WP_Error( |
| 293 |
'jetpack_newsletter_subscriber_stats_unavailable', |
| 294 |
__( 'WordPress.com did not return subscriber stats. Retry shortly.', 'jetpack' ) |
| 295 |
); |
| 296 |
} |
| 297 |
|
| 298 |
$body = json_decode( wp_remote_retrieve_body( $response ), true ); |
| 299 |
$counts = is_array( $body ) && isset( $body['counts'] ) && is_array( $body['counts'] ) |
| 300 |
? $body['counts'] |
| 301 |
: array(); |
| 302 |
|
| 303 |
$stats = array( |
| 304 |
'all' => isset( $counts['all_subscribers'] ) ? (int) $counts['all_subscribers'] : 0, |
| 305 |
'email' => isset( $counts['email_subscribers'] ) ? (int) $counts['email_subscribers'] : 0, |
| 306 |
'paid' => isset( $counts['paid_subscribers'] ) ? (int) $counts['paid_subscribers'] : 0, |
| 307 |
); |
| 308 |
|
| 309 |
set_transient( self::SUBSCRIBER_STATS_CACHE_KEY, $stats, self::SUBSCRIBER_STATS_CACHE_TTL ); |
| 310 |
|
| 311 |
return $stats; |
| 312 |
} |
| 313 |
|
| 314 |
/** |
| 315 |
* Execute: idempotent partial update of newsletter settings. |
| 316 |
* |
| 317 |
* Validates every supplied field before writing anything, so a malformed |
| 318 |
* field cannot leave the option set in a partially-updated state. |
| 319 |
* |
| 320 |
* @param array|null $input Input matching the ability's input_schema. |
| 321 |
* @return array|\WP_Error |
| 322 |
*/ |
| 323 |
public static function update_settings( $input = null ) { |
| 324 |
$input = is_array( $input ) ? $input : array(); |
| 325 |
$map = self::settings_map(); |
| 326 |
|
| 327 |
// Validate + normalize every supplied field up-front. Any failure |
| 328 |
// short-circuits the call with no writes, so a bad field can't leave |
| 329 |
// earlier fields in a partially-updated state. |
| 330 |
$normalized = array(); |
| 331 |
foreach ( $map as $field => $config ) { |
| 332 |
if ( ! array_key_exists( $field, $input ) ) { |
| 333 |
continue; |
| 334 |
} |
| 335 |
|
| 336 |
$result = self::normalize_input_value( $field, $config, $input[ $field ] ); |
| 337 |
if ( $result instanceof \WP_Error ) { |
| 338 |
return $result; |
| 339 |
} |
| 340 |
$normalized[ $field ] = $result; |
| 341 |
} |
| 342 |
|
| 343 |
// Read every field's current value once. This pass also feeds the |
| 344 |
// post-update response, avoiding a second `get_option` sweep. |
| 345 |
$current_storage = array(); |
| 346 |
foreach ( $map as $field => $config ) { |
| 347 |
$current_storage[ $field ] = self::read_option( $config ); |
| 348 |
} |
| 349 |
|
| 350 |
$changed = array(); |
| 351 |
foreach ( $normalized as $field => $desired ) { |
| 352 |
// String-cast on both sides because every field's storage form is |
| 353 |
// scalar (`0`/`1` for BOOL, `'on'`/`'off'` for ON_OFF, plain strings |
| 354 |
// for ENUM/STRING). New field types added later must keep that |
| 355 |
// invariant or this comparison will misfire. |
| 356 |
if ( (string) $desired === (string) $current_storage[ $field ] ) { |
| 357 |
continue; |
| 358 |
} |
| 359 |
update_option( $map[ $field ]['option'], $desired ); |
| 360 |
$current_storage[ $field ] = $desired; |
| 361 |
$changed[] = $field; |
| 362 |
} |
| 363 |
|
| 364 |
$settings = array(); |
| 365 |
foreach ( $map as $field => $config ) { |
| 366 |
$settings[ $field ] = self::cast_to_response( $config, $current_storage[ $field ] ); |
| 367 |
} |
| 368 |
|
| 369 |
return array( |
| 370 |
'settings' => $settings, |
| 371 |
'changed' => $changed, |
| 372 |
); |
| 373 |
} |
| 374 |
|
| 375 |
/** |
| 376 |
* Map of public ability field name → backing option config. |
| 377 |
* |
| 378 |
* Storage shape (option key, type tag, default, enum). The agent-facing |
| 379 |
* descriptions and JSON Schema live in `get_abilities()`; this map drives |
| 380 |
* the storage-side validation, normalization, and casting. |
| 381 |
* |
| 382 |
* Kept as a method (not a class constant) so the description strings |
| 383 |
* referenced from `cast_to_response()` and `normalize_input_value()` can |
| 384 |
* resolve through `__()` at call time rather than file load time. |
| 385 |
*/ |
| 386 |
private static function settings_map(): array { |
| 387 |
return array( |
| 388 |
'subscribe_post_end_enabled' => array( |
| 389 |
'option' => 'stb_enabled', |
| 390 |
'type' => self::TYPE_BOOL, |
| 391 |
'default' => 1, |
| 392 |
), |
| 393 |
'subscribe_comments_enabled' => array( |
| 394 |
'option' => 'stc_enabled', |
| 395 |
'type' => self::TYPE_BOOL, |
| 396 |
'default' => 1, |
| 397 |
), |
| 398 |
'notify_admin_on_subscribe' => array( |
| 399 |
'option' => 'social_notifications_subscribe', |
| 400 |
'type' => self::TYPE_ON_OFF, |
| 401 |
'default' => 'on', |
| 402 |
), |
| 403 |
'reply_to' => array( |
| 404 |
'option' => 'jetpack_subscriptions_reply_to', |
| 405 |
'type' => self::TYPE_ENUM, |
| 406 |
'default' => Subscriptions_Settings::$default_reply_to, |
| 407 |
'enum' => self::REPLY_TO_VALUES, |
| 408 |
), |
| 409 |
'from_name' => array( |
| 410 |
'option' => 'jetpack_subscriptions_from_name', |
| 411 |
'type' => self::TYPE_STRING, |
| 412 |
'default' => '', |
| 413 |
'max_length' => 200, |
| 414 |
), |
| 415 |
); |
| 416 |
} |
| 417 |
|
| 418 |
/** |
| 419 |
* Read all settings as the public response shape. |
| 420 |
*/ |
| 421 |
private static function current_settings(): array { |
| 422 |
$out = array(); |
| 423 |
foreach ( self::settings_map() as $field => $config ) { |
| 424 |
$out[ $field ] = self::cast_to_response( $config, self::read_option( $config ) ); |
| 425 |
} |
| 426 |
return $out; |
| 427 |
} |
| 428 |
|
| 429 |
/** |
| 430 |
* Read the raw option for a field config, falling back to its default. |
| 431 |
* |
| 432 |
* @param array $config Field config from `settings_map()`. |
| 433 |
* @return mixed |
| 434 |
*/ |
| 435 |
private static function read_option( array $config ) { |
| 436 |
return get_option( $config['option'], $config['default'] ); |
| 437 |
} |
| 438 |
|
| 439 |
/** |
| 440 |
* Validate + normalize a single input value to the storage form. |
| 441 |
* |
| 442 |
* @param string $field Public field name (used in error messages). |
| 443 |
* @param array $config Field config from `settings_map()`. |
| 444 |
* @param mixed $value Raw input value. |
| 445 |
* @return mixed|\WP_Error Storage-form value, or WP_Error when invalid. |
| 446 |
*/ |
| 447 |
private static function normalize_input_value( string $field, array $config, $value ) { |
| 448 |
switch ( $config['type'] ) { |
| 449 |
case self::TYPE_BOOL: |
| 450 |
if ( ! is_bool( $value ) ) { |
| 451 |
return self::invalid_field( $field, __( 'expected a boolean (true or false).', 'jetpack' ) ); |
| 452 |
} |
| 453 |
return $value ? 1 : 0; |
| 454 |
|
| 455 |
case self::TYPE_ON_OFF: |
| 456 |
if ( ! is_bool( $value ) ) { |
| 457 |
return self::invalid_field( $field, __( 'expected a boolean (true or false).', 'jetpack' ) ); |
| 458 |
} |
| 459 |
return $value ? 'on' : 'off'; |
| 460 |
|
| 461 |
case self::TYPE_ENUM: |
| 462 |
// reply_to is the only enum today and shares its allowed-values |
| 463 |
// list with `Subscriptions_Settings::is_valid_reply_to()`. Defer |
| 464 |
// to that validator so the two surfaces can't drift. |
| 465 |
$valid = 'reply_to' === $field |
| 466 |
? Subscriptions_Settings::is_valid_reply_to( $value ) |
| 467 |
: ( is_string( $value ) && in_array( $value, $config['enum'], true ) ); |
| 468 |
if ( ! $valid ) { |
| 469 |
return self::invalid_field( |
| 470 |
$field, |
| 471 |
sprintf( |
| 472 |
/* translators: %s: comma-separated list of allowed values. */ |
| 473 |
__( 'allowed values are %s.', 'jetpack' ), |
| 474 |
implode( ', ', $config['enum'] ) |
| 475 |
) |
| 476 |
); |
| 477 |
} |
| 478 |
return $value; |
| 479 |
|
| 480 |
case self::TYPE_STRING: |
| 481 |
if ( ! is_string( $value ) ) { |
| 482 |
return self::invalid_field( $field, __( 'expected a string.', 'jetpack' ) ); |
| 483 |
} |
| 484 |
$sanitized = sanitize_text_field( $value ); |
| 485 |
if ( isset( $config['max_length'] ) && mb_strlen( $sanitized ) > (int) $config['max_length'] ) { |
| 486 |
return self::invalid_field( |
| 487 |
$field, |
| 488 |
sprintf( |
| 489 |
/* translators: %d: maximum number of characters. */ |
| 490 |
__( 'must be %d characters or fewer.', 'jetpack' ), |
| 491 |
(int) $config['max_length'] |
| 492 |
) |
| 493 |
); |
| 494 |
} |
| 495 |
return $sanitized; |
| 496 |
} |
| 497 |
|
| 498 |
return self::invalid_field( $field, __( 'unsupported field type.', 'jetpack' ) ); |
| 499 |
} |
| 500 |
|
| 501 |
/** |
| 502 |
* Cast a stored option value to the public response shape. |
| 503 |
* |
| 504 |
* @param array $config Field config from `settings_map()`. |
| 505 |
* @param mixed $value Raw stored value. |
| 506 |
* @return mixed |
| 507 |
*/ |
| 508 |
private static function cast_to_response( array $config, $value ) { |
| 509 |
switch ( $config['type'] ) { |
| 510 |
case self::TYPE_BOOL: |
| 511 |
return 1 === (int) $value; |
| 512 |
case self::TYPE_ON_OFF: |
| 513 |
return 'on' === (string) $value; |
| 514 |
case self::TYPE_ENUM: |
| 515 |
$value = (string) $value; |
| 516 |
return in_array( $value, $config['enum'], true ) ? $value : (string) $config['default']; |
| 517 |
case self::TYPE_STRING: |
| 518 |
return (string) $value; |
| 519 |
} |
| 520 |
return $value; |
| 521 |
} |
| 522 |
|
| 523 |
/** |
| 524 |
* Build a `jetpack_newsletter_invalid_<field>` WP_Error with a message |
| 525 |
* that names the field and tells the agent how to fix the input. |
| 526 |
* |
| 527 |
* @param string $field Public field name; appears in the error code and message. |
| 528 |
* @param string $reason Translated explanation of the expected value. |
| 529 |
* @return \WP_Error |
| 530 |
*/ |
| 531 |
private static function invalid_field( string $field, string $reason ): \WP_Error { |
| 532 |
return new \WP_Error( |
| 533 |
'jetpack_newsletter_invalid_' . $field, |
| 534 |
sprintf( |
| 535 |
/* translators: 1: field name, 2: explanation of the expected value. */ |
| 536 |
__( 'Invalid value for "%1$s": %2$s', 'jetpack' ), |
| 537 |
$field, |
| 538 |
$reason |
| 539 |
) |
| 540 |
); |
| 541 |
} |
| 542 |
} |
| 543 |
|