PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / json-endpoints / class.wpcom-json-api-site-settings-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 1 week ago class.wpcom-json-api-add-widget-endpoint.php 7 months ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 7 months ago class.wpcom-json-api-bulk-delete-post-endpoint.php 7 months ago class.wpcom-json-api-bulk-restore-post-endpoint.php 7 months ago class.wpcom-json-api-bulk-update-comments-endpoint.php 1 month ago class.wpcom-json-api-comment-endpoint.php 7 months ago class.wpcom-json-api-delete-media-endpoint.php 7 months ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 7 months ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 1 month ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-comment-counts-endpoint.php 7 months ago class.wpcom-json-api-get-comment-endpoint.php 7 months ago class.wpcom-json-api-get-comment-history-endpoint.php 7 months ago class.wpcom-json-api-get-comments-tree-endpoint.php 7 months ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 7 months ago class.wpcom-json-api-get-customcss.php 7 months ago class.wpcom-json-api-get-media-endpoint.php 7 months ago class.wpcom-json-api-get-media-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-media-v1-2-endpoint.php 7 months ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 1 week ago class.wpcom-json-api-get-post-endpoint.php 7 months ago class.wpcom-json-api-get-post-v1-1-endpoint.php 7 months ago class.wpcom-json-api-get-site-endpoint.php 1 week ago class.wpcom-json-api-get-site-v1-2-endpoint.php 1 week ago class.wpcom-json-api-get-taxonomies-endpoint.php 2 months ago class.wpcom-json-api-get-taxonomy-endpoint.php 7 months ago class.wpcom-json-api-get-term-endpoint.php 7 months ago class.wpcom-json-api-list-comments-endpoint.php 1 week ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 7 months ago class.wpcom-json-api-list-embeds-endpoint.php 7 months ago class.wpcom-json-api-list-media-endpoint.php 7 months ago class.wpcom-json-api-list-media-v1-1-endpoint.php 1 month ago class.wpcom-json-api-list-media-v1-2-endpoint.php 7 months ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 7 months ago class.wpcom-json-api-list-post-types-endpoint.php 7 months ago class.wpcom-json-api-list-posts-endpoint.php 1 week ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 1 week ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 1 week ago class.wpcom-json-api-list-roles-endpoint.php 7 months ago class.wpcom-json-api-list-shortcodes-endpoint.php 7 months ago class.wpcom-json-api-list-terms-endpoint.php 7 months ago class.wpcom-json-api-list-users-endpoint.php 1 week ago class.wpcom-json-api-menus-v1-1-endpoint.php 1 month ago class.wpcom-json-api-post-endpoint.php 7 months ago class.wpcom-json-api-post-v1-1-endpoint.php 2 months ago class.wpcom-json-api-render-embed-endpoint.php 7 months ago class.wpcom-json-api-render-embed-reversal-endpoint.php 7 months ago class.wpcom-json-api-render-endpoint.php 1 month ago class.wpcom-json-api-render-shortcode-endpoint.php 7 months ago class.wpcom-json-api-sharing-buttons-endpoint.php 1 month ago class.wpcom-json-api-site-settings-endpoint.php 1 week ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 7 months ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 7 months ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 1 week ago class.wpcom-json-api-site-user-endpoint.php 7 months ago class.wpcom-json-api-taxonomy-endpoint.php 7 months ago class.wpcom-json-api-update-comment-endpoint.php 5 months ago class.wpcom-json-api-update-customcss.php 7 months ago class.wpcom-json-api-update-media-endpoint.php 7 months ago class.wpcom-json-api-update-media-v1-1-endpoint.php 1 month ago class.wpcom-json-api-update-post-endpoint.php 1 month ago class.wpcom-json-api-update-post-v1-1-endpoint.php 1 month ago class.wpcom-json-api-update-post-v1-2-endpoint.php 1 month ago class.wpcom-json-api-update-site-homepage-endpoint.php 7 months ago class.wpcom-json-api-update-site-logo-endpoint.php 7 months ago class.wpcom-json-api-update-taxonomy-endpoint.php 6 months ago class.wpcom-json-api-update-term-endpoint.php 7 months ago class.wpcom-json-api-update-user-endpoint.php 7 months ago class.wpcom-json-api-upload-media-endpoint.php 7 months ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 7 months ago
class.wpcom-json-api-site-settings-endpoint.php
1512 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Manage settings via the WordPress.com REST API.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection_Shared_Functions;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit( 0 );
12 }
13
14 new WPCOM_JSON_API_Site_Settings_Endpoint(
15 array(
16 'description' => 'Get detailed settings information about a site.',
17 'group' => '__do_not_document',
18 'stat' => 'sites:X',
19 'max_version' => '1.1',
20 'new_version' => '1.2',
21 'method' => 'GET',
22 'path' => '/sites/%s/settings',
23 'path_labels' => array(
24 '$site' => '(int|string) Site ID or domain',
25 ),
26
27 'query_parameters' => array(
28 'context' => false,
29 ),
30
31 'response_format' => WPCOM_JSON_API_Site_Settings_Endpoint::$site_format,
32
33 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
34 )
35 );
36
37 new WPCOM_JSON_API_Site_Settings_Endpoint(
38 array(
39 'description' => 'Update settings for a site.',
40 'group' => '__do_not_document',
41 'stat' => 'sites:X',
42 'max_version' => '1.1',
43 'new_version' => '1.2',
44 'method' => 'POST',
45 'path' => '/sites/%s/settings',
46 'a_new_very_long_key' => 'blabla',
47 'path_labels' => array(
48 '$site' => '(int|string) Site ID or domain',
49 ),
50
51 'request_format' => array(
52 'migration_source_site_domain' => '(string) The source site URL, from the migration flow',
53 'in_site_migration_flow' => '(string) The migration flow the site is in',
54 'blogname' => '(string) Blog name',
55 'blogdescription' => '(string) Blog description',
56 'default_pingback_flag' => '(bool) Notify blogs linked from article?',
57 'default_ping_status' => '(bool) Allow link notifications from other blogs?',
58 'default_comment_status' => '(bool) Allow comments on new articles?',
59 'blog_public' => '(string) Site visibility; -1: private, 0: discourage search engines, 1: allow search engines',
60 'wpcom_data_sharing_opt_out' => '(bool) Did the site opt out of sharing public content with third parties and research partners?',
61 'jetpack_sync_non_public_post_stati' => '(bool) allow sync of post and pages with non-public posts stati',
62 'jetpack_relatedposts_enabled' => '(bool) Enable related posts?',
63 'jetpack_relatedposts_show_context' => '(bool) Show post\'s tags and category in related posts?',
64 'jetpack_relatedposts_show_date' => '(bool) Show date in related posts?',
65 'jetpack_relatedposts_show_headline' => '(bool) Show headline in related posts?',
66 'jetpack_relatedposts_show_thumbnails' => '(bool) Show thumbnails in related posts?',
67 'jetpack_protect_whitelist' => '(array) List of IP addresses to always allow',
68 'instant_search_enabled' => '(bool) Enable the new Jetpack Instant Search interface',
69 'jetpack_search_enabled' => '(bool) Enable Jetpack Search',
70 'jetpack_search_supported' => '(bool) Jetpack Search is supported',
71 'infinite_scroll' => '(bool) Support infinite scroll of posts?',
72 'default_category' => '(int) Default post category',
73 'default_post_format' => '(string) Default post format',
74 'require_name_email' => '(bool) Require comment authors to fill out name and email?',
75 'comment_registration' => '(bool) Require users to be registered and logged in to comment?',
76 'close_comments_for_old_posts' => '(bool) Automatically close comments on old posts?',
77 'close_comments_days_old' => '(int) Age at which to close comments',
78 'thread_comments' => '(bool) Enable threaded comments?',
79 'thread_comments_depth' => '(int) Depth to thread comments',
80 'page_comments' => '(bool) Break comments into pages?',
81 'comments_per_page' => '(int) Number of comments to display per page',
82 'default_comments_page' => '(string) newest|oldest Which page of comments to display first',
83 'comment_order' => '(string) asc|desc Order to display comments within page',
84 'comments_notify' => '(bool) Email me when someone comments?',
85 'moderation_notify' => '(bool) Email me when a comment is helf for moderation?',
86 'social_notifications_like' => '(bool) Email me when someone likes my post?',
87 'social_notifications_reblog' => '(bool) Email me when someone reblogs my post?',
88 'social_notifications_subscribe' => '(bool) Email me when someone subscribes to my blog?',
89 'comment_moderation' => '(bool) Moderate comments for manual approval?',
90 'comment_previously_approved' => '(bool) Moderate comments unless author has a previously-approved comment?',
91 'comment_max_links' => '(int) Moderate comments that contain X or more links',
92 'moderation_keys' => '(string) Words or phrases that trigger comment moderation, one per line',
93 'disallowed_keys' => '(string) Words or phrases that mark comment spam, one per line',
94 'lang_id' => '(int) ID for language blog is written in',
95 'wga' => '(array) Google Analytics Settings',
96 'disabled_likes' => '(bool) Are likes globally disabled (they can still be turned on per post)?',
97 'disabled_reblogs' => '(bool) Are reblogs disabled on posts?',
98 'jetpack_comment_likes_enabled' => '(bool) Are comment likes enabled for all comments?',
99 'sharing_button_style' => '(string) Style to use for sharing buttons (icon-text, icon, text, or official)',
100 'sharing_label' => '(string) Label to use for sharing buttons, e.g. "Share this:"',
101 'sharing_show' => '(string|array:string) Post type or array of types where sharing buttons are to be displayed',
102 'sharing_open_links' => '(string) Link target for sharing buttons (same or new)',
103 'twitter_via' => '(string) Twitter username to include in tweets when people share using the Twitter button',
104 'jetpack-twitter-cards-site-tag' => '(string) The Twitter username of the owner of the site\'s domain.',
105 'eventbrite_api_token' => '(int) The Keyring token ID for an Eventbrite token to associate with the site',
106 'timezone_string' => '(string) PHP-compatible timezone string like \'UTC-5\'',
107 'gmt_offset' => '(int) Site offset from UTC in hours',
108 'date_format' => '(string) PHP Date-compatible date format',
109 'time_format' => '(string) PHP Date-compatible time format',
110 'start_of_week' => '(int) Starting day of week (0 = Sunday, 6 = Saturday)',
111 'jetpack_testimonial' => '(bool) Whether testimonial custom post type is enabled for the site',
112 'jetpack_testimonial_posts_per_page' => '(int) Number of testimonials to show per page',
113 'jetpack_portfolio' => '(bool) Whether portfolio custom post type is enabled for the site',
114 'jetpack_portfolio_posts_per_page' => '(int) Number of portfolio projects to show per page',
115 Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => '(string) The seo meta description for the site.',
116 Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => '(array) SEO meta title formats. Allowed keys: front_page, posts, pages, groups, archives',
117 'verification_services_codes' => '(array) Website verification codes. Allowed keys: google, pinterest, bing, yandex, facebook',
118 'markdown_supported' => '(bool) Whether markdown is supported for this site',
119 'wpcom_publish_posts_with_markdown' => '(bool) Whether markdown is enabled for posts',
120 'wpcom_publish_comments_with_markdown' => '(bool) Whether markdown is enabled for comments',
121 'site_icon' => '(int) Media attachment ID to use as site icon. Set to zero or an otherwise empty value to clear',
122 'api_cache' => '(bool) Turn on/off the Jetpack JSON API cache',
123 'posts_per_page' => '(int) Number of posts to show on blog pages',
124 'posts_per_rss' => '(int) Number of posts to show in the RSS feed',
125 'rss_use_excerpt' => '(bool) Whether the RSS feed will use post excerpts',
126 'launchpad_screen' => '(string) Whether or not launchpad is presented and what size it will be',
127 'sm_enabled' => '(bool) Whether the newsletter subscribe modal is enabled',
128 'jetpack_subscribe_overlay_enabled' => '(bool) Whether the newsletter subscribe overlay is enabled',
129 'jetpack_subscribe_floating_button_enabled' => '(bool) Whether the newsletter floating subscribe button is enabled',
130 'jetpack_subscriptions_subscribe_post_end_enabled' => '(bool) Whether the Subscribe block at the end of each post placement is enabled',
131 'jetpack_subscriptions_login_navigation_enabled' => '(bool) Whether the Subscriber Login block navigation placement is enabled',
132 'jetpack_subscriptions_subscribe_navigation_enabled' => '(Bool) Whether the Subscribe block navigation placement is enabled',
133 'wpcom_ai_site_prompt' => '(string) User input in the AI site prompt',
134 'jetpack_waf_automatic_rules' => '(bool) Whether the WAF should enforce automatic firewall rules',
135 'jetpack_waf_ip_allow_list' => '(string) List of IP addresses to always allow',
136 'jetpack_waf_ip_allow_list_enabled' => '(bool) Whether the IP allow list is enabled',
137 'jetpack_waf_ip_block_list' => '(string) List of IP addresses the WAF should always block',
138 'jetpack_waf_ip_block_list_enabled' => '(bool) Whether the IP block list is enabled',
139 'jetpack_waf_share_data' => '(bool) Whether the WAF should share basic data with Jetpack',
140 'jetpack_waf_share_debug_data' => '(bool) Whether the WAF should share debug data with Jetpack',
141 'jetpack_waf_automatic_rules_last_updated_timestamp' => '(int) Timestamp of the last time the automatic rules were updated',
142 'mcp_abilities' => '(array) List of MCP Abilities',
143 ),
144
145 'response_format' => array(
146 'updated' => '(array)',
147 ),
148
149 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
150 )
151 );
152
153 /**
154 * Manage Site settings endpoint.
155 *
156 * @phan-constructor-used-for-side-effects
157 */
158 class WPCOM_JSON_API_Site_Settings_Endpoint extends WPCOM_JSON_API_Endpoint {
159
160 /**
161 * Site format.
162 *
163 * @var array
164 */
165 public static $site_format = array(
166 'ID' => '(int) Site ID',
167 'name' => '(string) Title of site',
168 'description' => '(string) Tagline or description of site',
169 'URL' => '(string) Full URL to the site',
170 'lang' => '(string) Primary language code of the site',
171 'locale_variant' => '(string) Locale variant code for the site, if set',
172 'settings' => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
173 );
174
175 /**
176 * Endpoint response
177 *
178 * GET /sites/%s/settings
179 * POST /sites/%s/settings
180 *
181 * @param string $path Path.
182 * @param int $blog_id Blog ID.
183 */
184 public function callback( $path = '', $blog_id = 0 ) {
185 $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
186 if ( is_wp_error( $blog_id ) ) {
187 return $blog_id;
188 }
189
190 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
191 // Source & include the infinite scroll compatibility files prior to loading theme functions.
192 add_filter( 'restapi_theme_action_copy_dirs', array( 'WPCOM_JSON_API_Site_Settings_Endpoint', 'wpcom_restapi_copy_theme_plugin_actions' ) );
193 $this->load_theme_functions();
194 }
195
196 if ( ! is_user_logged_in() ) {
197 return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
198 } elseif ( ! current_user_can( 'manage_options' ) ) {
199 return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
200 }
201
202 if ( 'GET' === $this->api->method ) {
203 /**
204 * Fires on each GET request to a specific endpoint.
205 *
206 * @module json-api
207 *
208 * @since 3.2.0
209 *
210 * @param string sites.
211 */
212 do_action( 'wpcom_json_api_objects', 'sites' );
213 return $this->get_settings_response();
214 } elseif ( 'POST' === $this->api->method ) {
215 return $this->update_settings();
216 } else {
217 return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
218 }
219 }
220
221 /**
222 * Includes additional theme-specific files to be included in REST API theme
223 * context loading action copying.
224 *
225 * @see WPCOM_JSON_API_Endpoint#load_theme_functions
226 * @see the_neverending_home_page_theme_support
227 *
228 * @param array $copy_dirs Array of files to be included in theme context.
229 */
230 public static function wpcom_restapi_copy_theme_plugin_actions( $copy_dirs ) {
231 $theme_name = get_stylesheet();
232 $default_file_name = WP_CONTENT_DIR . "/mu-plugins/infinity/themes/{$theme_name}.php";
233
234 /**
235 * Filter the path to the Infinite Scroll compatibility file.
236 *
237 * @module infinite-scroll
238 *
239 * @since 2.0.0
240 *
241 * @param string $str IS compatibility file path.
242 * @param string $theme_name Theme name.
243 */
244 $customization_file = apply_filters( 'infinite_scroll_customization_file', $default_file_name, $theme_name );
245
246 if ( is_readable( $customization_file ) ) {
247 require_once $customization_file;
248 $copy_dirs[] = $customization_file;
249 }
250
251 return $copy_dirs;
252 }
253
254 /**
255 * Determines whether jetpack_relatedposts is supported
256 *
257 * @return bool
258 */
259 public function jetpack_relatedposts_supported() {
260 $wpcom_related_posts_theme_blacklist = array(
261 'Expound',
262 'Traveler',
263 'Opti',
264 'Currents',
265 );
266 return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist, true ) );
267 }
268
269 /**
270 * Returns category details
271 *
272 * @param WP_Term $category Category object.
273 *
274 * @return array
275 */
276 public function get_category_details( $category ) {
277 return array(
278 'value' => $category->term_id,
279 'name' => $category->name,
280 );
281 }
282
283 /**
284 * Returns an option value as the result of the callable being applied to
285 * it if a value is set, otherwise null.
286 *
287 * @param string $option_name Option name.
288 * @param callable $cast_callable Callable to invoke on option value.
289 *
290 * @return int|null Numeric option value or null.
291 */
292 protected function get_cast_option_value_or_null( $option_name, $cast_callable ) {
293 $option_value = get_option( $option_name, null );
294 if ( $option_value === null ) {
295 return $option_value;
296 }
297
298 return call_user_func( $cast_callable, $option_value );
299 }
300
301 /**
302 * Collects the necessary information to return for a get settings response.
303 *
304 * @return array
305 */
306 public function get_settings_response() {
307 $response = array();
308
309 // Allow update in later versions.
310 /**
311 * Filter the structure of site settings to return.
312 *
313 * @module json-api
314 *
315 * @since 3.9.3
316 *
317 * @param array $site_format Data structure.
318 */
319 $response_format = apply_filters( 'site_settings_site_format', self::$site_format );
320
321 $blog_id = (int) $this->api->get_blog_id_for_output();
322 $site = $this->get_platform()->get_site( $blog_id );
323
324 foreach ( array_keys( $response_format ) as $key ) {
325
326 // refactoring to change lang parameter to locale in 1.2.
327 $lang_or_locale = $this->get_locale( $key );
328 if ( $lang_or_locale ) {
329 $response[ $key ] = $lang_or_locale;
330 continue;
331 }
332
333 switch ( $key ) {
334 case 'ID':
335 $response[ $key ] = $blog_id;
336 break;
337 case 'name':
338 $name = get_bloginfo( 'name' );
339 $response[ $key ] = is_string( $name ) ? htmlspecialchars_decode( $name, ENT_QUOTES ) : '';
340 break;
341 case 'description':
342 $description = get_bloginfo( 'description' );
343 $response[ $key ] = is_string( $description ) ? htmlspecialchars_decode( $description, ENT_QUOTES ) : '';
344 break;
345 case 'URL':
346 $response[ $key ] = (string) home_url();
347 break;
348 case 'locale_variant':
349 if ( function_exists( 'wpcom_l10n_get_blog_locale_variant' ) ) {
350 $blog_locale_variant = wpcom_l10n_get_blog_locale_variant();
351 if ( $blog_locale_variant ) {
352 $response[ $key ] = $blog_locale_variant;
353 }
354 }
355 break;
356 case 'settings':
357 $jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts', array() );
358 // If the option's enabled key is NOT SET, it is considered enabled by the plugin.
359 if ( ! isset( $jetpack_relatedposts_options['enabled'] ) ) {
360 $jetpack_relatedposts_options['enabled'] = true;
361 }
362
363 $jetpack_relatedposts_options['enabled'] =
364 $jetpack_relatedposts_options['enabled']
365 && $site->is_module_active( 'related-posts' );
366
367 $jetpack_search_supported = false;
368 if ( function_exists( 'wpcom_is_jetpack_search_supported' ) ) {
369 $jetpack_search_supported = wpcom_is_jetpack_search_supported( $blog_id );
370 }
371
372 $jetpack_search_active =
373 $jetpack_search_supported
374 && $site->is_module_active( 'search' );
375
376 // array_values() is necessary to ensure the array starts at index 0.
377 $post_categories = array_values(
378 array_map(
379 array( $this, 'get_category_details' ),
380 get_categories( array( 'hide_empty' => false ) )
381 )
382 );
383
384 // Make sure we are returning a consistent type
385 if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) {
386 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php';
387 }
388 $newsletter_category_ids = Jetpack_Newsletter_Category_Helper::get_category_ids();
389
390 $api_cache = $site->is_jetpack() ? (bool) get_option( 'jetpack_api_cache_enabled' ) : true;
391
392 // Get Sites MCP settings
393 $mcp_abilities = $this->get_site_mcp_abilities();
394
395 $response[ $key ] = array(
396 // also exists as "options".
397 'admin_url' => get_admin_url(),
398 'default_ping_status' => 'closed' !== get_option( 'default_ping_status' ),
399 'default_comment_status' => 'closed' !== get_option( 'default_comment_status' ),
400
401 // new stuff starts here.
402 'instant_search_enabled' => (bool) get_option( 'instant_search_enabled' ),
403 'blog_public' => (int) get_option( 'blog_public' ),
404 'wpcom_data_sharing_opt_out' => (bool) get_option( 'wpcom_data_sharing_opt_out' ),
405 'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
406 'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
407 'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options['enabled'],
408 'jetpack_relatedposts_show_context' => ! empty( $jetpack_relatedposts_options['show_context'] ),
409 'jetpack_relatedposts_show_date' => ! empty( $jetpack_relatedposts_options['show_date'] ),
410 'jetpack_relatedposts_show_headline' => ! empty( $jetpack_relatedposts_options['show_headline'] ),
411 'jetpack_relatedposts_show_thumbnails' => ! empty( $jetpack_relatedposts_options['show_thumbnails'] ),
412 'jetpack_search_enabled' => $jetpack_search_active,
413 'jetpack_search_supported' => (bool) $jetpack_search_supported,
414 'default_category' => (int) get_option( 'default_category' ),
415 'post_categories' => (array) $post_categories,
416 'default_post_format' => get_option( 'default_post_format' ),
417 'default_pingback_flag' => (bool) get_option( 'default_pingback_flag' ),
418 'require_name_email' => (bool) get_option( 'require_name_email' ),
419 'comment_registration' => (bool) get_option( 'comment_registration' ),
420 'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
421 'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
422 'thread_comments' => (bool) get_option( 'thread_comments' ),
423 'thread_comments_depth' => (int) get_option( 'thread_comments_depth' ),
424 'page_comments' => (bool) get_option( 'page_comments' ),
425 'comments_per_page' => (int) get_option( 'comments_per_page' ),
426 'default_comments_page' => get_option( 'default_comments_page' ),
427 'comment_order' => get_option( 'comment_order' ),
428 'comments_notify' => (bool) get_option( 'comments_notify' ),
429 'moderation_notify' => (bool) get_option( 'moderation_notify' ),
430 'social_notifications_like' => ( 'on' === get_option( 'social_notifications_like' ) ),
431 'social_notifications_reblog' => ( 'on' === get_option( 'social_notifications_reblog' ) ),
432 'social_notifications_subscribe' => ( 'on' === get_option( 'social_notifications_subscribe' ) ),
433 'comment_moderation' => (bool) get_option( 'comment_moderation' ),
434 'comment_whitelist' => (bool) get_option( 'comment_previously_approved' ),
435 'comment_previously_approved' => (bool) get_option( 'comment_previously_approved' ),
436 'comment_max_links' => (int) get_option( 'comment_max_links' ),
437 'moderation_keys' => get_option( 'moderation_keys' ),
438 'blacklist_keys' => get_option( 'disallowed_keys' ),
439 'disallowed_keys' => get_option( 'disallowed_keys' ),
440 'lang_id' => defined( 'IS_WPCOM' ) && IS_WPCOM
441 ? get_lang_id_by_code( wpcom_l10n_get_blog_locale_variant( $blog_id, true ) )
442 : get_option( 'lang_id' ),
443 'site_vertical_id' => (string) get_option( 'site_vertical_id' ),
444 'jetpack_cloudflare_analytics' => get_option( 'jetpack_cloudflare_analytics' ),
445 'disabled_likes' => (bool) get_option( 'disabled_likes' ),
446 'disabled_reblogs' => (bool) get_option( 'disabled_reblogs' ),
447 'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
448 'twitter_via' => (string) get_option( 'twitter_via' ),
449 'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
450 'eventbrite_api_token' => $this->get_cast_option_value_or_null( 'eventbrite_api_token', 'intval' ),
451 'gmt_offset' => get_option( 'gmt_offset' ),
452 'timezone_string' => get_option( 'timezone_string' ),
453 'date_format' => get_option( 'date_format' ),
454 'time_format' => get_option( 'time_format' ),
455 'start_of_week' => get_option( 'start_of_week' ),
456 'woocommerce_onboarding_profile' => (array) get_option( 'woocommerce_onboarding_profile', array() ),
457 'woocommerce_store_address' => (string) get_option( 'woocommerce_store_address' ),
458 'woocommerce_store_address_2' => (string) get_option( 'woocommerce_store_address_2' ),
459 'woocommerce_store_city' => (string) get_option( 'woocommerce_store_city' ),
460 'woocommerce_default_country' => (string) get_option( 'woocommerce_default_country' ),
461 'woocommerce_store_postcode' => (string) get_option( 'woocommerce_store_postcode' ),
462 'jetpack_testimonial' => (bool) get_option( 'jetpack_testimonial', '0' ),
463 'jetpack_testimonial_posts_per_page' => (int) get_option( 'jetpack_testimonial_posts_per_page', '10' ),
464 'jetpack_portfolio' => (bool) get_option( 'jetpack_portfolio', '0' ),
465 'jetpack_portfolio_posts_per_page' => (int) get_option( 'jetpack_portfolio_posts_per_page', '10' ),
466 'markdown_supported' => true,
467 'site_icon' => $this->get_cast_option_value_or_null( 'site_icon', 'intval' ),
468 Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => get_option( Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION, '' ),
469 Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => get_option( Jetpack_SEO_Titles::TITLE_FORMATS_OPTION, array() ),
470 'api_cache' => $api_cache,
471 'posts_per_page' => (int) get_option( 'posts_per_page' ),
472 'posts_per_rss' => (int) get_option( 'posts_per_rss' ),
473 'rss_use_excerpt' => (bool) get_option( 'rss_use_excerpt' ),
474 'launchpad_screen' => (string) get_option( 'launchpad_screen' ),
475 'wpcom_newsletter_send_default' => (bool) get_option( 'wpcom_newsletter_send_default', true ),
476 'wpcom_featured_image_in_email' => ( function () use ( $site ) {
477 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
478 $registered_date = method_exists( $site, 'get_registered_date' ) ? $site->get_registered_date() : '';
479 // Compare to May 2, 2025 (ISO 8601 format)
480 if ( $registered_date && $registered_date !== '0000-00-00T00:00:00+00:00' && strtotime( $registered_date ) >= strtotime( '2025-05-02T00:00:00+00:00' ) ) {
481 return (bool) get_option( 'wpcom_featured_image_in_email', true );
482 }
483 }
484 // For all other sites, use the saved value or default to false for legacy behavior.
485 return (bool) get_option( 'wpcom_featured_image_in_email', false );
486 } )(),
487 'jetpack_gravatar_in_email' => (bool) get_option( 'jetpack_gravatar_in_email', true ),
488 'jetpack_author_in_email' => (bool) get_option( 'jetpack_author_in_email', true ),
489 'jetpack_post_date_in_email' => (bool) get_option( 'jetpack_post_date_in_email', true ),
490 'wpcom_newsletter_categories' => $newsletter_category_ids,
491 'wpcom_newsletter_categories_enabled' => (bool) get_option( 'wpcom_newsletter_categories_enabled' ),
492 'sm_enabled' => (bool) get_option( 'sm_enabled' ),
493 'jetpack_subscribe_overlay_enabled' => (bool) get_option( 'jetpack_subscribe_overlay_enabled' ),
494 'jetpack_subscribe_floating_button_enabled' => (bool) get_option( 'jetpack_subscribe_floating_button_enabled' ),
495 'jetpack_subscriptions_subscribe_post_end_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_post_end_enabled' ),
496 'jetpack_subscriptions_login_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_login_navigation_enabled' ),
497 'jetpack_subscriptions_subscribe_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_navigation_enabled' ),
498 'wpcom_gifting_subscription' => (bool) get_option( 'wpcom_gifting_subscription', $this->get_wpcom_gifting_subscription_default() ),
499 'wpcom_reader_views_enabled' => (bool) get_option( 'wpcom_reader_views_enabled', true ),
500 'wpcom_subscription_emails_use_excerpt' => (bool) get_option( 'wpcom_subscription_emails_use_excerpt' ),
501 'jetpack_subscriptions_reply_to' => (string) $this->get_subscriptions_reply_to_option(),
502 'jetpack_subscriptions_from_name' => (string) get_option( 'jetpack_subscriptions_from_name' ),
503 'show_on_front' => (string) get_option( 'show_on_front' ),
504 'page_on_front' => (string) get_option( 'page_on_front' ),
505 'page_for_posts' => (string) get_option( 'page_for_posts' ),
506 'subscription_options' => (array) get_option( 'subscription_options' ),
507 'supports_free_tier_customization' => true,
508 'jetpack_verbum_subscription_modal' => (bool) get_option( 'jetpack_verbum_subscription_modal', true ),
509 'enable_verbum_commenting' => (bool) get_option( 'enable_verbum_commenting', true ),
510 'enable_blocks_comments' => (bool) get_option( 'enable_blocks_comments', true ),
511 'highlander_comment_form_prompt' => $this->get_highlander_comment_form_prompt_option(),
512 'jetpack_comment_form_color_scheme' => (string) get_option( 'jetpack_comment_form_color_scheme' ),
513 'in_site_migration_flow' => (string) get_option( 'in_site_migration_flow', '' ),
514 'migration_source_site_domain' => (string) get_option( 'migration_source_site_domain' ),
515 'jetpack_waf_automatic_rules' => (bool) get_option( 'jetpack_waf_automatic_rules' ),
516 'jetpack_waf_ip_allow_list' => (string) get_option( 'jetpack_waf_ip_allow_list' ),
517 'jetpack_waf_ip_allow_list_enabled' => (bool) get_option( 'jetpack_waf_ip_allow_list_enabled' ),
518 'jetpack_waf_ip_block_list' => (string) get_option( 'jetpack_waf_ip_block_list' ),
519 'jetpack_waf_ip_block_list_enabled' => (bool) get_option( 'jetpack_waf_ip_block_list_enabled' ),
520 'jetpack_waf_share_data' => (bool) get_option( 'jetpack_waf_share_data' ),
521 'jetpack_waf_share_debug_data' => (bool) get_option( 'jetpack_waf_share_debug_data' ),
522 'jetpack_waf_automatic_rules_last_updated_timestamp' => (int) get_option( 'jetpack_waf_automatic_rules_last_updated_timestamp' ),
523 'is_fully_managed_agency_site' => (bool) get_option( 'is_fully_managed_agency_site' ),
524 'wpcom_hide_action_bar' => (bool) get_option( 'wpcom_hide_action_bar' ),
525 'mcp_abilities' => $mcp_abilities,
526 );
527
528 require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
529 if ( class_exists( 'Jetpack_Memberships' ) ) {
530 $response[ $key ]['newsletter_has_active_plan'] = count( Jetpack_Memberships::get_all_newsletter_plan_ids( false ) ) > 0;
531 // Read-only/derived: the free tier's markdown description rendered to
532 // safe HTML, colocated with subscription_options so it's
533 // read-after-write consistent. Not part of the writable
534 // subscription_options bag (which would round-trip and persist it).
535 $response[ $key ]['free_tier_description_rendered'] = Jetpack_Memberships::render_tier_description_html(
536 ( (array) get_option( 'subscription_options' ) )['free_tier_description'] ?? ''
537 );
538 }
539
540 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
541 $response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_posting_enabled();
542 $response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_commenting_enabled();
543
544 // WPCOM-specific Infinite Scroll Settings.
545 if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
546 /**
547 * Clear the cached copy of widget info so it's pulled fresh from blog options.
548 * It was primed during the initial load under the __REST API site__'s context.
549 *
550 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
551 */
552 $GLOBALS['_wp_sidebars_widgets'] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
553
554 $infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
555 $response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && 'scroll' === $infinite_scroll_settings->type;
556 if ( $infinite_scroll_settings->footer_widgets || 'click' === $infinite_scroll_settings->requested_type ) {
557 // The blog has footer widgets -- infinite scroll is blocked.
558 $response[ $key ]['infinite_scroll_blocked'] = 'footer';
559 } else {
560 $response[ $key ]['infinite_scroll_blocked'] = false;
561 }
562 }
563 }
564
565 // allow future versions of this endpoint to support additional settings keys.
566 /**
567 * Filter the current site setting in the returned response.
568 *
569 * @module json-api
570 *
571 * @since 3.9.3
572 * @since 13.6 Added the API object parameter.
573 *
574 * @param mixed $response_item A single site setting.
575 * @param WPCOM_JSON_API_Site_Settings_Endpoint $this The API object.
576 */
577 $response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ], $this );
578
579 if ( class_exists( 'Sharing_Service' ) ) {
580 $ss = new Sharing_Service();
581 $sharing = $ss->get_global_options();
582 $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
583 $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
584 $response[ $key ]['sharing_show'] = (array) $sharing['show'];
585 $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
586 }
587
588 $response[ $key ]['jetpack_protect_whitelist'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
589
590 if ( ! current_user_can( 'edit_posts' ) ) {
591 unset( $response[ $key ] );
592 }
593 break;
594 }
595 }
596 return $response;
597 }
598
599 /**
600 * Get the default value for the wpcom_gifting_subscription option.
601 * The default value is the inverse of the plan's auto_renew setting.
602 *
603 * @return bool
604 */
605 protected function get_wpcom_gifting_subscription_default() {
606 if ( function_exists( 'wpcom_get_site_purchases' ) && function_exists( 'wpcom_purchase_has_feature' ) ) {
607 $purchases = wpcom_get_site_purchases();
608
609 foreach ( $purchases as $purchase ) {
610 if ( wpcom_purchase_has_feature( $purchase, \WPCOM_Features::SUBSCRIPTION_GIFTING ) ) {
611 /*
612 * We set default value as false when expiration date not match the following:
613 * - 54 days before the annual plan expiration.
614 * - 5 days before the monthly plan expiration.
615 * This is to match the gifting banner logic.
616 */
617 $days_of_warning = str_contains( $purchase->product_slug, 'monthly' ) ? 5 : 54;
618 $seconds_until_expiration = strtotime( $purchase->expiry_date ) - time();
619 if ( $seconds_until_expiration >= $days_of_warning * DAY_IN_SECONDS ) {
620 return false;
621 }
622
623 // We set default to the inverse of auto-renew.
624 if ( isset( $purchase->auto_renew ) ) {
625 return ! $purchase->auto_renew;
626 } elseif ( isset( $purchase->user_allows_auto_renew ) ) {
627 return ! $purchase->user_allows_auto_renew;
628 }
629 }
630 }
631 }
632 return false;
633 }
634
635 /**
636 * Get list of all site level MCP abilities.
637 *
638 * @return array
639 */
640 private function get_all_site_mcp_abilities(): array {
641 $all_abilities = array();
642 $ability_registry_file = WP_CONTENT_DIR . '/mu-plugins/wpcom-mcp/includes/AbilitiesRegistry/Registry/AbilityRegistry.php';
643 if ( file_exists( $ability_registry_file ) ) {
644 require_once $ability_registry_file;
645 // @phan-suppress-next-line PhanUndeclaredClassMethod
646 $abilities_resources = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_resources_for_server( 'site-level' );
647 // @phan-suppress-next-line PhanUndeclaredClassMethod
648 $abilities_tools = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_tools_for_server( 'site-level' );
649 // @phan-suppress-next-line PhanUndeclaredClassMethod
650 $abilities_prompts = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_prompts_for_server( 'site-level' );
651 $all_abilities = array_merge( $abilities_resources, $abilities_tools, $abilities_prompts );
652 }
653 return apply_filters( 'jetpack_site_mcp_abilities', $all_abilities );
654 }
655
656 /**
657 * Get ability meta from config.
658 *
659 * @param string $ability_name Ability name, i.e. wpcom-mcp/posts-search.
660 *
661 * @return array
662 */
663 private function get_mcp_abilities_metadata( string $ability_name ): array {
664 $ability_meta = array();
665 $ability_registry_file = WP_CONTENT_DIR . '/mu-plugins/wpcom-mcp/includes/AbilitiesRegistry/Registry/AbilityRegistry.php';
666 if ( file_exists( $ability_registry_file ) ) {
667 require_once $ability_registry_file;
668 // @phan-suppress-next-line PhanUndeclaredClassMethod
669 $ability_meta = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_metadata( $ability_name );
670 }
671 return apply_filters( 'jetpack_site_mcp_ability_meta', $ability_meta, $ability_name );
672 }
673
674 /**
675 * Get MCP abilities for the current site.
676 *
677 * @return array
678 */
679 public function get_site_mcp_abilities(): array {
680 $current_mcp_abilities = get_option( 'mcp_abilities', array() );
681 if ( ! is_array( $current_mcp_abilities ) ) {
682 $current_mcp_abilities = array();
683 }
684
685 $all_abilities = $this->get_all_site_mcp_abilities();
686 if ( empty( $all_abilities ) ) {
687 return array();
688 }
689
690 $computed_abilities = array();
691 foreach ( $all_abilities as $ability_name ) {
692 // Get base metadata first
693 $ability_meta = $this->get_mcp_abilities_metadata( $ability_name );
694 if ( ! empty( $ability_meta ) ) {
695 // Use stored value or fall back to metadata default
696 $enabled = $current_mcp_abilities[ $ability_name ] ?? $ability_meta['enabled'] ?? false;
697
698 $computed_abilities[ $ability_name ] = array(
699 'name' => $ability_name,
700 'title' => $ability_meta['title'] ?? '',
701 'description' => $ability_meta['description'] ?? '',
702 'category' => $ability_meta['category'] ?? '',
703 'type' => $ability_meta['type'] ?? '',
704 'enabled' => (bool) $enabled,
705 );
706 }
707 }
708 return $computed_abilities;
709 }
710
711 /**
712 * Sets the MCP abilities for the current site.
713 *
714 * @param mixed $value MCP abilities array.
715 *
716 * @return true|WP_Error
717 */
718 public function set_site_mcp_abilities( $value ) {
719 // Validate input format
720 if ( ! is_array( $value ) ) {
721 return new WP_Error( 'invalid_format', __( 'Site MCP abilities must be an array', 'jetpack' ) );
722 }
723
724 $all_abilities = $this->get_all_site_mcp_abilities();
725
726 // Filter ability names that don't exist
727 $value = array_filter(
728 $value,
729 function ( $ability_name ) use ( $all_abilities ) {
730 return in_array( $ability_name, $all_abilities, true );
731 },
732 ARRAY_FILTER_USE_KEY
733 );
734
735 // Validate each ability exists and value is boolean-like
736 foreach ( $value as $ability_name => $enabled ) {
737 if ( ! is_string( $ability_name ) || ( ! WPCOM_JSON_API::is_truthy( $enabled ) && ! WPCOM_JSON_API::is_falsy( $enabled ) ) ) {
738 $error_message = sprintf(
739 // Translators: %s is an MCP ability name
740 __( 'Invalid ability: %s', 'jetpack' ),
741 $ability_name
742 );
743 return new WP_Error( 'invalid_ability', $error_message );
744 }
745 }
746
747 update_option( 'mcp_abilities', $value );
748
749 return true;
750 }
751
752 /**
753 * Get locale.
754 *
755 * @param string $key Language.
756 */
757 protected function get_locale( $key ) {
758 if ( 'lang' === $key ) {
759 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
760 return (string) get_blog_lang_code();
761 } else {
762 return get_locale();
763 }
764 }
765
766 return false;
767 }
768
769 /**
770 * Updates site settings for authorized users
771 *
772 * @return array|WP_Error
773 */
774 public function update_settings() {
775 /*
776 * $this->input() retrieves posted arguments whitelisted and casted to the $request_format
777 * specs that get passed in when this class is instantiated
778 */
779 $input = $this->input();
780 $unfiltered_input = $this->input( false, false );
781 /**
782 * Filters the settings to be updated on the site.
783 *
784 * @module json-api
785 *
786 * @since 3.6.0
787 * @since 6.1.1 Added $unfiltered_input parameter.
788 *
789 * @param array $input Associative array of site settings to be updated.
790 * Cast and filtered based on documentation.
791 * @param array $unfiltered_input Associative array of site settings to be updated.
792 * Neither cast nor filtered. Contains raw input.
793 */
794 $input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
795
796 $blog_id = get_current_blog_id();
797
798 $jetpack_relatedposts_options = array();
799 $sharing_options = array();
800 $updated = array();
801
802 if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) {
803 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php';
804 }
805
806 foreach ( $input as $key => $value ) {
807
808 if ( ! is_array( $value ) ) {
809 $value = trim( $value );
810 }
811
812 // preserve the raw value before unslashing the value. The slashes need to be preserved for date and time formats.
813 $raw_value = $value;
814 $value = wp_unslash( $value );
815
816 switch ( $key ) {
817
818 case 'default_ping_status':
819 case 'default_comment_status':
820 // settings are stored as closed|open.
821 $coerce_value = ( $value ) ? 'open' : 'closed';
822 if ( update_option( $key, $coerce_value ) ) {
823 $updated[ $key ] = $value;
824 }
825 break;
826 case 'launchpad_screen':
827 if ( in_array( $value, array( 'full', 'off', 'minimized' ), true ) ) {
828 if ( update_option( $key, $value ) ) {
829 $updated[ $key ] = $value;
830 }
831 }
832 break;
833 case 'jetpack_protect_whitelist':
834 if ( class_exists( 'Brute_Force_Protection_Shared_Functions' ) ) {
835 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $value );
836 if ( is_wp_error( $result ) ) {
837 return $result;
838 }
839 $updated[ $key ] = Brute_Force_Protection_Shared_Functions::format_allow_list();
840 }
841 break;
842 case 'jetpack_sync_non_public_post_stati':
843 Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
844 break;
845 case 'jetpack_search_enabled':
846 if ( $value ) {
847 Jetpack::activate_module( $blog_id, 'search' );
848 } else {
849 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
850 Jetpack::deactivate_module( $blog_id, 'search' );
851 }
852 $updated[ $key ] = (bool) $value;
853 break;
854 case 'jetpack_relatedposts_enabled':
855 case 'jetpack_relatedposts_show_context':
856 case 'jetpack_relatedposts_show_date':
857 case 'jetpack_relatedposts_show_thumbnails':
858 case 'jetpack_relatedposts_show_headline':
859 if ( ! $this->jetpack_relatedposts_supported() ) {
860 break;
861 }
862 if ( 'jetpack_relatedposts_enabled' === $key ) {
863 if ( $value ) {
864 Jetpack::activate_module( $blog_id, 'related-posts' );
865 } else {
866 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
867 Jetpack::deactivate_module( $blog_id, 'related-posts' );
868 }
869 }
870 $just_the_key = substr( $key, 21 );
871 $jetpack_relatedposts_options[ $just_the_key ] = $value;
872 break;
873
874 case 'social_notifications_like':
875 case 'social_notifications_reblog':
876 case 'social_notifications_subscribe':
877 // settings are stored as on|off.
878 $coerce_value = ( $value ) ? 'on' : 'off';
879 if ( update_option( $key, $coerce_value ) ) {
880 $updated[ $key ] = $value;
881 }
882 break;
883
884 case 'cloudflare_analytics':
885 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
886 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
887 }
888
889 if ( update_option( $key, $value ) ) {
890 $updated[ $key ] = $value;
891 }
892 break;
893
894 case 'jetpack_testimonial':
895 case 'jetpack_portfolio':
896 case 'jetpack_comment_likes_enabled':
897 case 'wpcom_reader_views_enabled':
898 case 'jetpack_verbum_subscription_modal':
899 // settings are stored as 1|0.
900 $coerce_value = (int) $value;
901 if ( update_option( $key, $coerce_value ) ) {
902 $updated[ $key ] = (bool) $value;
903 }
904 break;
905
906 case 'jetpack_testimonial_posts_per_page':
907 case 'jetpack_portfolio_posts_per_page':
908 // settings are stored as numeric.
909 $coerce_value = (int) $value;
910 if ( update_option( $key, $coerce_value ) ) {
911 $updated[ $key ] = $coerce_value;
912 }
913 break;
914
915 // Sharing options.
916 case 'sharing_button_style':
917 case 'sharing_show':
918 case 'sharing_open_links':
919 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
920 break;
921 case 'sharing_label':
922 $sharing_options[ $key ] = $value;
923 break;
924
925 // Keyring token option.
926 case 'eventbrite_api_token':
927 // These options can only be updated for sites hosted on WordPress.com.
928 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
929 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
930 if ( delete_option( $key ) ) {
931 $updated[ $key ] = null;
932 }
933 } elseif ( update_option( $key, $value ) ) {
934 $updated[ $key ] = (int) $value;
935 }
936 }
937 break;
938
939 case 'api_cache':
940 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
941 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
942 $updated[ $key ] = false;
943 }
944 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
945 $updated[ $key ] = true;
946 }
947 break;
948
949 case 'timezone_string':
950 /*
951 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
952 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
953 */
954 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
955 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
956 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
957 $updated['gmt_offset'] = $gmt_offset;
958 }
959
960 $value = '';
961 }
962
963 /*
964 * Always set timezone_string either with the given value or with an
965 * empty string
966 */
967 if ( update_option( $key, $value ) ) {
968 $updated[ $key ] = $value;
969 }
970 break;
971
972 case 'subscription_options':
973 if ( ! is_array( $value ) ) {
974 break;
975 }
976
977 $allowed_keys = array( 'invitation', 'comment_follow', 'welcome', 'subscribe_modal_heading', 'free_tier_description', 'hide_free_tier' );
978 $filtered_value = array_filter(
979 $value,
980 function ( $key ) use ( $allowed_keys ) {
981 return in_array( $key, $allowed_keys, true );
982 },
983 ARRAY_FILTER_USE_KEY
984 );
985
986 if ( empty( $filtered_value ) ) {
987 break;
988 }
989
990 // `hide_free_tier` is a boolean flag, so pull it out before the HTML
991 // sanitization below (which expects strings). Parse it with is_truthy()
992 // so stringy booleans (e.g. "false", "0") are interpreted correctly
993 // rather than being treated as truthy by a plain `! empty()`.
994 $has_hide_free_tier = array_key_exists( 'hide_free_tier', $filtered_value );
995 $hide_free_tier = $has_hide_free_tier && WPCOM_JSON_API::is_truthy( $filtered_value['hide_free_tier'] );
996 unset( $filtered_value['hide_free_tier'] );
997
998 array_walk_recursive(
999 $filtered_value,
1000 function ( &$value ) {
1001 $value = wp_kses(
1002 $value,
1003 array(
1004 'a' => array(
1005 'href' => array(),
1006 ),
1007 )
1008 );
1009 }
1010 );
1011
1012 // Normalize whitespace-only `subscribe_modal_heading` input to empty so
1013 // the modal template's `empty()` fallback fires. PHP's `empty()` treats
1014 // `" "` as non-empty, which would otherwise render a blank heading.
1015 if ( isset( $filtered_value['subscribe_modal_heading'] ) ) {
1016 $filtered_value['subscribe_modal_heading'] = trim( $filtered_value['subscribe_modal_heading'] );
1017 }
1018
1019 // The free tier description is stored as plain markdown source, so strip
1020 // all HTML and cap its length to match the paid-tier description field.
1021 // WordPress core guarantees mb_substr() (polyfilled in wp-includes/compat.php
1022 // when the mbstring extension is unavailable), so it's safe to use directly.
1023 // A JSON payload could supply a non-scalar (array/object) for this field,
1024 // which would fatal in wp_kses()/mb_substr() on PHP 8+, so drop invalid values.
1025 if ( isset( $filtered_value['free_tier_description'] ) ) {
1026 if ( is_scalar( $filtered_value['free_tier_description'] ) ) {
1027 $filtered_value['free_tier_description'] = mb_substr( wp_kses( (string) $filtered_value['free_tier_description'], array() ), 0, 500 );
1028 } else {
1029 unset( $filtered_value['free_tier_description'] );
1030 }
1031 }
1032
1033 if ( $has_hide_free_tier ) {
1034 $filtered_value['hide_free_tier'] = $hide_free_tier;
1035 }
1036
1037 $old_subscription_options = get_option( 'subscription_options', array() );
1038 $new_subscription_options = array_merge( $old_subscription_options, $filtered_value );
1039
1040 if ( update_option( $key, $new_subscription_options ) ) {
1041 $updated[ $key ] = $filtered_value;
1042 }
1043 break;
1044
1045 case 'woocommerce_onboarding_profile':
1046 // Allow boolean values but sanitize_text_field everything else.
1047 $sanitized_value = (array) $value;
1048 array_walk_recursive(
1049 $sanitized_value,
1050 function ( &$value ) {
1051 if ( ! is_bool( $value ) ) {
1052 $value = sanitize_text_field( $value );
1053 }
1054 }
1055 );
1056 if ( update_option( $key, $sanitized_value ) ) {
1057 $updated[ $key ] = $sanitized_value;
1058 }
1059 break;
1060
1061 case 'woocommerce_store_address':
1062 case 'woocommerce_store_address_2':
1063 case 'woocommerce_store_city':
1064 case 'woocommerce_default_country':
1065 case 'woocommerce_store_postcode':
1066 $sanitized_value = sanitize_text_field( $value );
1067 if ( update_option( $key, $sanitized_value ) ) {
1068 $updated[ $key ] = $sanitized_value;
1069 }
1070 break;
1071
1072 case 'date_format':
1073 case 'time_format':
1074 // settings are stored as strings.
1075 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
1076 $sanitized_value = sanitize_text_field( $raw_value );
1077 if ( update_option( $key, $sanitized_value ) ) {
1078 $updated[ $key ] = $sanitized_value;
1079 }
1080 break;
1081
1082 case 'start_of_week':
1083 // setting is stored as int in 0-6 range (days of week).
1084 $coerce_value = (int) $value;
1085 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
1086 if ( update_option( $key, $limit_value ) ) {
1087 $updated[ $key ] = $limit_value;
1088 }
1089 break;
1090
1091 case 'site_icon':
1092 /*
1093 * settings are stored as deletable numeric (all empty
1094 * values as delete intent), validated as media image
1095 */
1096 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
1097 /**
1098 * Fallback mechanism to clear a third party site icon setting. Can be used
1099 * to unset the option when an API request instructs the site to remove the site icon.
1100 *
1101 * @module json-api
1102 *
1103 * @since 4.10
1104 */
1105 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
1106 $updated[ $key ] = null;
1107 }
1108 } elseif ( is_numeric( $value ) ) {
1109 $coerce_value = (int) $value;
1110 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
1111 $updated[ $key ] = $coerce_value;
1112 }
1113 }
1114 break;
1115
1116 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
1117 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
1118 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
1119 }
1120
1121 if ( ! is_string( $value ) ) {
1122 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
1123 }
1124
1125 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
1126
1127 if ( ! empty( $new_description ) ) {
1128 $updated[ $key ] = $new_description;
1129 }
1130 break;
1131
1132 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
1133 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
1134 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
1135 break;
1136 }
1137 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
1138 }
1139
1140 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
1141 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
1142 }
1143
1144 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
1145
1146 if ( ! empty( $new_title_formats ) ) {
1147 $updated[ $key ] = $new_title_formats;
1148 }
1149 break;
1150
1151 case 'verification_services_codes':
1152 $verification_codes = jetpack_verification_validate( $value );
1153
1154 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
1155 $updated[ $key ] = $verification_codes;
1156 }
1157 break;
1158
1159 case 'wpcom_publish_posts_with_markdown':
1160 case 'wpcom_publish_comments_with_markdown':
1161 $coerce_value = (bool) $value;
1162 if ( update_option( $key, $coerce_value ) ) {
1163 $updated[ $key ] = $coerce_value;
1164 }
1165 break;
1166
1167 case 'wpcom_gifting_subscription':
1168 $coerce_value = (bool) $value;
1169
1170 /*
1171 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
1172 * a serialized value. Knowing that we can check if the option already exists.
1173 */
1174 $gift_toggle = get_option( $key );
1175 if ( false === $gift_toggle ) {
1176 // update_option will not create a new option if the initial value is false. So use add_option.
1177 if ( add_option( $key, $coerce_value ) ) {
1178 $updated[ $key ] = $coerce_value;
1179 }
1180 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
1181 $updated[ $key ] = $coerce_value;
1182 }
1183 break;
1184
1185 case 'rss_use_excerpt':
1186 $sanitized_value = (int) (bool) $value;
1187 update_option( $key, $sanitized_value );
1188 $updated[ $key ] = $sanitized_value;
1189 break;
1190
1191 case 'wpcom_subscription_emails_use_excerpt':
1192 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
1193 $updated[ $key ] = (bool) $value;
1194 break;
1195
1196 case 'jetpack_subscriptions_reply_to':
1197 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1198 $to_set_value = Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value )
1199 ? (string) $value
1200 : Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1201
1202 if ( update_option( $key, $to_set_value ) ) {
1203 $updated[ $key ] = $to_set_value;
1204 }
1205 break;
1206
1207 case 'jetpack_subscriptions_from_name':
1208 $sanitized_value = sanitize_text_field( $value );
1209 if ( update_option( $key, $sanitized_value ) ) {
1210 $updated[ $key ] = $sanitized_value;
1211 }
1212 break;
1213
1214 case 'instant_search_enabled':
1215 update_option( 'instant_search_enabled', (bool) $value );
1216 $updated[ $key ] = (bool) $value;
1217 break;
1218
1219 case 'lang_id':
1220 /*
1221 * Due to the fact that locale variants are set in a locale_variant option,
1222 * changing locale from variant to primary
1223 * would look like the same lang_id is being saved and update_option would return false,
1224 * even though the correct options would be set by pre_update_option_lang_id,
1225 * so we should always return lang_id as updated.
1226 */
1227 update_option( 'lang_id', (int) $value );
1228 $updated[ $key ] = (int) $value;
1229 break;
1230
1231 case 'wpcom_newsletter_send_default':
1232 update_option( 'wpcom_newsletter_send_default', (int) (bool) $value );
1233 $updated[ $key ] = (int) (bool) $value;
1234 break;
1235
1236 case 'wpcom_featured_image_in_email':
1237 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1238 $updated[ $key ] = (int) (bool) $value;
1239 break;
1240
1241 case Jetpack_Newsletter_Category_Helper::NEWSLETTER_CATEGORIES_OPTION:
1242 $update_newsletter_categories = Jetpack_Newsletter_Category_Helper::save_category_ids( (array) $value );
1243 if ( $update_newsletter_categories ) {
1244 $updated[ $key ] = $update_newsletter_categories;
1245 }
1246
1247 break;
1248
1249 case 'wpcom_newsletter_categories_enabled':
1250 update_option( 'wpcom_newsletter_categories_enabled', (int) (bool) $value );
1251 $updated[ $key ] = (int) (bool) $value;
1252 break;
1253
1254 case 'sm_enabled':
1255 update_option( 'sm_enabled', (int) (bool) $value );
1256 $updated[ $key ] = (int) (bool) $value;
1257 break;
1258
1259 case 'jetpack_subscribe_overlay_enabled':
1260 update_option( 'jetpack_subscribe_overlay_enabled', (int) (bool) $value );
1261 $updated[ $key ] = (int) (bool) $value;
1262 break;
1263
1264 case 'jetpack_subscribe_floating_button_enabled':
1265 update_option( 'jetpack_subscribe_floating_button_enabled', (int) (bool) $value );
1266 $updated[ $key ] = (int) (bool) $value;
1267 break;
1268
1269 case 'jetpack_subscriptions_subscribe_post_end_enabled':
1270 update_option( 'jetpack_subscriptions_subscribe_post_end_enabled', (int) (bool) $value );
1271 $updated[ $key ] = (int) (bool) $value;
1272 break;
1273
1274 case 'jetpack_subscriptions_login_navigation_enabled':
1275 update_option( 'jetpack_subscriptions_login_navigation_enabled', (int) (bool) $value );
1276 $updated[ $key ] = (int) (bool) $value;
1277 break;
1278
1279 case 'jetpack_subscriptions_subscribe_navigation_enabled':
1280 update_option( 'jetpack_subscriptions_subscribe_navigation_enabled', (int) (bool) $value );
1281 $updated[ $key ] = (int) (bool) $value;
1282 break;
1283
1284 case 'show_on_front':
1285 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1286 $updated[ $key ] = $value;
1287 }
1288 break;
1289
1290 case 'page_on_front':
1291 case 'page_for_posts':
1292 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1293 if ( delete_option( $key ) ) {
1294 $updated[ $key ] = null;
1295 }
1296
1297 break;
1298 }
1299
1300 if ( ! $this->is_valid_page_id( $value ) ) {
1301 break;
1302 }
1303
1304 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1305 $related_option_value = get_option( $related_option_key );
1306 if ( $related_option_value === $value ) {
1307 // page_on_front and page_for_posts are not allowed to be the same
1308 break;
1309 }
1310
1311 if ( update_option( $key, $value ) ) {
1312 $updated[ $key ] = $value;
1313 }
1314
1315 break;
1316
1317 case 'in_site_migration_flow':
1318 if ( empty( $value ) ) {
1319 delete_option( 'in_site_migration_flow' );
1320 break;
1321 }
1322
1323 $migration_flow_whitelist = array(
1324 'site-migration',
1325 'migration-signup',
1326 );
1327
1328 if ( ! in_array( $value, $migration_flow_whitelist, true ) ) {
1329 break;
1330 }
1331
1332 update_option( 'in_site_migration_flow', $value );
1333 $updated[ $key ] = $value;
1334 break;
1335
1336 case 'migration_source_site_domain':
1337 // If we get an empty value, delete the option
1338 if ( empty( $value ) ) {
1339 delete_option( 'migration_source_site_domain' );
1340 break;
1341 }
1342
1343 // If we get a non-url value, don't update the option.
1344 if ( wp_http_validate_url( $value ) === false ) {
1345 break;
1346 }
1347
1348 update_option( 'migration_source_site_domain', $value );
1349 $updated[ $key ] = $value;
1350 break;
1351
1352 case 'is_fully_managed_agency_site':
1353 case 'wpcom_hide_action_bar':
1354 $coerce_value = (int) (bool) $value;
1355 if ( update_option( $key, $coerce_value ) ) {
1356 $updated[ $key ] = (bool) $coerce_value;
1357 }
1358 break;
1359
1360 case 'mcp_abilities':
1361 $result = $this->set_site_mcp_abilities( $value );
1362 if ( is_wp_error( $result ) ) {
1363 return $result;
1364 }
1365 $updated[ $key ] = $this->get_site_mcp_abilities();
1366 break;
1367
1368 default:
1369 // allow future versions of this endpoint to support additional settings keys.
1370 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1371 /**
1372 * Filter current site setting value to be updated.
1373 *
1374 * @module json-api
1375 *
1376 * @since 3.9.3
1377 * @since 13.6 Added the API object parameter.
1378 *
1379 * @param mixed $response_item A single site setting value.
1380 * @param WPCOM_JSON_API_Site_Settings_Endpoint The API object parameter.
1381 */
1382 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value, $this );
1383
1384 if ( is_wp_error( $value ) ) {
1385 return $value;
1386 }
1387
1388 if ( $value ) {
1389 $updated[ $key ] = $value;
1390 }
1391 break;
1392 }
1393 // no worries, we've already whitelisted and casted arguments above.
1394 if ( update_option( $key, $value ) ) {
1395 $updated[ $key ] = $value;
1396 }
1397 }
1398 }
1399
1400 if ( $jetpack_relatedposts_options !== array() ) {
1401 // track new jetpack_relatedposts options against old.
1402 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1403
1404 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1405 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1406 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1407 }
1408
1409 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1410 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1411 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1412 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1413 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1414
1415 if ( $has_initialized_option || $has_updated_option ) {
1416 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1417 }
1418 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1419 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1420 }
1421 }
1422 }
1423 }
1424
1425 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1426 $ss = new Sharing_Service();
1427
1428 /*
1429 * Merge current values with updated, since Sharing_Service expects
1430 * all values to be included when updating
1431 */
1432 $current_sharing_options = $ss->get_global_options();
1433 foreach ( $current_sharing_options as $key => $val ) {
1434 if ( ! isset( $sharing_options[ $key ] ) ) {
1435 $sharing_options[ $key ] = $val;
1436 }
1437 }
1438
1439 $updated_social_options = $ss->set_global_options( $sharing_options );
1440
1441 if ( isset( $input['sharing_button_style'] ) ) {
1442 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1443 }
1444 if ( isset( $input['sharing_label'] ) ) {
1445 // Sharing_Service won't report label as updated if set to default.
1446 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1447 }
1448 if ( isset( $input['sharing_show'] ) ) {
1449 $updated['sharing_show'] = (array) $updated_social_options['show'];
1450 }
1451 if ( isset( $input['sharing_open_links'] ) ) {
1452 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1453 }
1454 }
1455
1456 return array(
1457 'updated' => $updated,
1458 );
1459 }
1460
1461 /**
1462 * Get the string value of the jetpack_subscriptions_reply_to option.
1463 * When the option is not set, it will retun 'no-reply'.
1464 *
1465 * @return string
1466 */
1467 protected function get_subscriptions_reply_to_option() {
1468 $reply_to = get_option( 'jetpack_subscriptions_reply_to', null );
1469 if ( $reply_to === null ) {
1470 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1471 return Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1472 }
1473 return $reply_to;
1474 }
1475
1476 /**
1477 * Check if the given value is a valid page ID for the current site.
1478 *
1479 * @param mixed $value The value to check.
1480 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1481 */
1482 protected function is_valid_page_id( $value ) {
1483 $all_page_ids = get_all_page_ids();
1484
1485 $valid_page_id = false;
1486 foreach ( $all_page_ids as $page_id ) {
1487 if ( $page_id === (string) $value ) {
1488 $valid_page_id = true;
1489 break;
1490 }
1491 }
1492
1493 return $valid_page_id;
1494 }
1495
1496 /**
1497 * Get the value of the highlander_comment_form_prompt option.
1498 * When the option is not set, it will return the default value.
1499 *
1500 * @return string
1501 */
1502 protected function get_highlander_comment_form_prompt_option() {
1503 $highlander_comment_form_prompt_option = get_option( 'highlander_comment_form_prompt' );
1504
1505 if ( empty( $highlander_comment_form_prompt_option ) ) {
1506 return (string) __( 'Leave a comment', 'jetpack' );
1507 }
1508
1509 return (string) $highlander_comment_form_prompt_option;
1510 }
1511 }
1512