PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.3.2
Jetpack – WP Security, Backup, Speed, & Growth v15.3.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 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 9 months ago class.wpcom-json-api-add-widget-endpoint.php 9 months ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 9 months ago class.wpcom-json-api-bulk-delete-post-endpoint.php 9 months ago class.wpcom-json-api-bulk-restore-post-endpoint.php 9 months ago class.wpcom-json-api-bulk-update-comments-endpoint.php 9 months ago class.wpcom-json-api-comment-endpoint.php 9 months ago class.wpcom-json-api-delete-media-endpoint.php 9 months ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 9 months ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 9 months ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 9 months ago class.wpcom-json-api-get-comment-counts-endpoint.php 9 months ago class.wpcom-json-api-get-comment-endpoint.php 9 months ago class.wpcom-json-api-get-comment-history-endpoint.php 9 months ago class.wpcom-json-api-get-comments-tree-endpoint.php 9 months ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 9 months ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 9 months ago class.wpcom-json-api-get-customcss.php 9 months ago class.wpcom-json-api-get-media-endpoint.php 9 months ago class.wpcom-json-api-get-media-v1-1-endpoint.php 9 months ago class.wpcom-json-api-get-media-v1-2-endpoint.php 9 months ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 9 months ago class.wpcom-json-api-get-post-endpoint.php 9 months ago class.wpcom-json-api-get-post-v1-1-endpoint.php 9 months ago class.wpcom-json-api-get-site-endpoint.php 9 months ago class.wpcom-json-api-get-site-v1-2-endpoint.php 9 months ago class.wpcom-json-api-get-taxonomies-endpoint.php 9 months ago class.wpcom-json-api-get-taxonomy-endpoint.php 9 months ago class.wpcom-json-api-get-term-endpoint.php 9 months ago class.wpcom-json-api-list-comments-endpoint.php 9 months ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 9 months ago class.wpcom-json-api-list-embeds-endpoint.php 9 months ago class.wpcom-json-api-list-media-endpoint.php 9 months ago class.wpcom-json-api-list-media-v1-1-endpoint.php 9 months ago class.wpcom-json-api-list-media-v1-2-endpoint.php 9 months ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 9 months ago class.wpcom-json-api-list-post-types-endpoint.php 9 months ago class.wpcom-json-api-list-posts-endpoint.php 9 months ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 9 months ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 9 months ago class.wpcom-json-api-list-roles-endpoint.php 9 months ago class.wpcom-json-api-list-shortcodes-endpoint.php 9 months ago class.wpcom-json-api-list-terms-endpoint.php 9 months ago class.wpcom-json-api-list-users-endpoint.php 9 months ago class.wpcom-json-api-menus-v1-1-endpoint.php 9 months ago class.wpcom-json-api-post-endpoint.php 9 months ago class.wpcom-json-api-post-v1-1-endpoint.php 9 months ago class.wpcom-json-api-render-embed-endpoint.php 9 months ago class.wpcom-json-api-render-embed-reversal-endpoint.php 9 months ago class.wpcom-json-api-render-endpoint.php 9 months ago class.wpcom-json-api-render-shortcode-endpoint.php 9 months ago class.wpcom-json-api-sharing-buttons-endpoint.php 9 months ago class.wpcom-json-api-site-settings-endpoint.php 9 months ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 9 months ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 9 months ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 9 months ago class.wpcom-json-api-site-user-endpoint.php 9 months ago class.wpcom-json-api-taxonomy-endpoint.php 9 months ago class.wpcom-json-api-update-comment-endpoint.php 9 months ago class.wpcom-json-api-update-customcss.php 9 months ago class.wpcom-json-api-update-media-endpoint.php 9 months ago class.wpcom-json-api-update-media-v1-1-endpoint.php 9 months ago class.wpcom-json-api-update-post-endpoint.php 9 months ago class.wpcom-json-api-update-post-v1-1-endpoint.php 9 months ago class.wpcom-json-api-update-post-v1-2-endpoint.php 9 months ago class.wpcom-json-api-update-site-homepage-endpoint.php 9 months ago class.wpcom-json-api-update-site-logo-endpoint.php 9 months ago class.wpcom-json-api-update-taxonomy-endpoint.php 9 months ago class.wpcom-json-api-update-term-endpoint.php 9 months ago class.wpcom-json-api-update-user-endpoint.php 9 months ago class.wpcom-json-api-upload-media-endpoint.php 9 months ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 9 months ago
class.wpcom-json-api-site-settings-endpoint.php
1465 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_featured_image_in_email' => ( function () use ( $site ) {
476 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
477 $registered_date = method_exists( $site, 'get_registered_date' ) ? $site->get_registered_date() : '';
478 // Compare to May 2, 2025 (ISO 8601 format)
479 if ( $registered_date && $registered_date !== '0000-00-00T00:00:00+00:00' && strtotime( $registered_date ) >= strtotime( '2025-05-02T00:00:00+00:00' ) ) {
480 return (bool) get_option( 'wpcom_featured_image_in_email', true );
481 }
482 }
483 // For all other sites, use the saved value or default to false for legacy behavior.
484 return (bool) get_option( 'wpcom_featured_image_in_email', false );
485 } )(),
486 'jetpack_gravatar_in_email' => (bool) get_option( 'jetpack_gravatar_in_email', true ),
487 'jetpack_author_in_email' => (bool) get_option( 'jetpack_author_in_email', true ),
488 'jetpack_post_date_in_email' => (bool) get_option( 'jetpack_post_date_in_email', true ),
489 'wpcom_newsletter_categories' => $newsletter_category_ids,
490 'wpcom_newsletter_categories_enabled' => (bool) get_option( 'wpcom_newsletter_categories_enabled' ),
491 'sm_enabled' => (bool) get_option( 'sm_enabled' ),
492 'jetpack_subscribe_overlay_enabled' => (bool) get_option( 'jetpack_subscribe_overlay_enabled' ),
493 'jetpack_subscribe_floating_button_enabled' => (bool) get_option( 'jetpack_subscribe_floating_button_enabled' ),
494 'jetpack_subscriptions_subscribe_post_end_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_post_end_enabled' ),
495 'jetpack_subscriptions_login_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_login_navigation_enabled' ),
496 'jetpack_subscriptions_subscribe_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_navigation_enabled' ),
497 'wpcom_gifting_subscription' => (bool) get_option( 'wpcom_gifting_subscription', $this->get_wpcom_gifting_subscription_default() ),
498 'wpcom_reader_views_enabled' => (bool) get_option( 'wpcom_reader_views_enabled', true ),
499 'wpcom_subscription_emails_use_excerpt' => (bool) get_option( 'wpcom_subscription_emails_use_excerpt' ),
500 'jetpack_subscriptions_reply_to' => (string) $this->get_subscriptions_reply_to_option(),
501 'jetpack_subscriptions_from_name' => (string) get_option( 'jetpack_subscriptions_from_name' ),
502 'show_on_front' => (string) get_option( 'show_on_front' ),
503 'page_on_front' => (string) get_option( 'page_on_front' ),
504 'page_for_posts' => (string) get_option( 'page_for_posts' ),
505 'subscription_options' => (array) get_option( 'subscription_options' ),
506 'jetpack_verbum_subscription_modal' => (bool) get_option( 'jetpack_verbum_subscription_modal', true ),
507 'enable_verbum_commenting' => (bool) get_option( 'enable_verbum_commenting', true ),
508 'enable_blocks_comments' => (bool) get_option( 'enable_blocks_comments', true ),
509 'highlander_comment_form_prompt' => $this->get_highlander_comment_form_prompt_option(),
510 'jetpack_comment_form_color_scheme' => (string) get_option( 'jetpack_comment_form_color_scheme' ),
511 'in_site_migration_flow' => (string) get_option( 'in_site_migration_flow', '' ),
512 'migration_source_site_domain' => (string) get_option( 'migration_source_site_domain' ),
513 'jetpack_waf_automatic_rules' => (bool) get_option( 'jetpack_waf_automatic_rules' ),
514 'jetpack_waf_ip_allow_list' => (string) get_option( 'jetpack_waf_ip_allow_list' ),
515 'jetpack_waf_ip_allow_list_enabled' => (bool) get_option( 'jetpack_waf_ip_allow_list_enabled' ),
516 'jetpack_waf_ip_block_list' => (string) get_option( 'jetpack_waf_ip_block_list' ),
517 'jetpack_waf_ip_block_list_enabled' => (bool) get_option( 'jetpack_waf_ip_block_list_enabled' ),
518 'jetpack_waf_share_data' => (bool) get_option( 'jetpack_waf_share_data' ),
519 'jetpack_waf_share_debug_data' => (bool) get_option( 'jetpack_waf_share_debug_data' ),
520 'jetpack_waf_automatic_rules_last_updated_timestamp' => (int) get_option( 'jetpack_waf_automatic_rules_last_updated_timestamp' ),
521 'is_fully_managed_agency_site' => (bool) get_option( 'is_fully_managed_agency_site' ),
522 'wpcom_hide_action_bar' => (bool) get_option( 'wpcom_hide_action_bar' ),
523 'mcp_abilities' => $mcp_abilities,
524 );
525
526 require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
527 if ( class_exists( 'Jetpack_Memberships' ) ) {
528 $response[ $key ]['newsletter_has_active_plan'] = count( Jetpack_Memberships::get_all_newsletter_plan_ids( false ) ) > 0;
529 }
530
531 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
532 $response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_posting_enabled();
533 $response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_commenting_enabled();
534
535 // WPCOM-specific Infinite Scroll Settings.
536 if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
537 /**
538 * Clear the cached copy of widget info so it's pulled fresh from blog options.
539 * It was primed during the initial load under the __REST API site__'s context.
540 *
541 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
542 */
543 $GLOBALS['_wp_sidebars_widgets'] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
544
545 $infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
546 $response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && 'scroll' === $infinite_scroll_settings->type;
547 if ( $infinite_scroll_settings->footer_widgets || 'click' === $infinite_scroll_settings->requested_type ) {
548 // The blog has footer widgets -- infinite scroll is blocked.
549 $response[ $key ]['infinite_scroll_blocked'] = 'footer';
550 } else {
551 $response[ $key ]['infinite_scroll_blocked'] = false;
552 }
553 }
554 }
555
556 // allow future versions of this endpoint to support additional settings keys.
557 /**
558 * Filter the current site setting in the returned response.
559 *
560 * @module json-api
561 *
562 * @since 3.9.3
563 * @since 13.6 Added the API object parameter.
564 *
565 * @param mixed $response_item A single site setting.
566 * @param WPCOM_JSON_API_Site_Settings_Endpoint $this The API object.
567 */
568 $response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ], $this );
569
570 if ( class_exists( 'Sharing_Service' ) ) {
571 $ss = new Sharing_Service();
572 $sharing = $ss->get_global_options();
573 $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
574 $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
575 $response[ $key ]['sharing_show'] = (array) $sharing['show'];
576 $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
577 }
578
579 $response[ $key ]['jetpack_protect_whitelist'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
580
581 if ( ! current_user_can( 'edit_posts' ) ) {
582 unset( $response[ $key ] );
583 }
584 break;
585 }
586 }
587 return $response;
588 }
589
590 /**
591 * Get the default value for the wpcom_gifting_subscription option.
592 * The default value is the inverse of the plan's auto_renew setting.
593 *
594 * @return bool
595 */
596 protected function get_wpcom_gifting_subscription_default() {
597 if ( function_exists( 'wpcom_get_site_purchases' ) && function_exists( 'wpcom_purchase_has_feature' ) ) {
598 $purchases = wpcom_get_site_purchases();
599
600 foreach ( $purchases as $purchase ) {
601 if ( wpcom_purchase_has_feature( $purchase, \WPCOM_Features::SUBSCRIPTION_GIFTING ) ) {
602 /*
603 * We set default value as false when expiration date not match the following:
604 * - 54 days before the annual plan expiration.
605 * - 5 days before the monthly plan expiration.
606 * This is to match the gifting banner logic.
607 */
608 $days_of_warning = str_contains( $purchase->product_slug, 'monthly' ) ? 5 : 54;
609 $seconds_until_expiration = strtotime( $purchase->expiry_date ) - time();
610 if ( $seconds_until_expiration >= $days_of_warning * DAY_IN_SECONDS ) {
611 return false;
612 }
613
614 // We set default to the inverse of auto-renew.
615 if ( isset( $purchase->auto_renew ) ) {
616 return ! $purchase->auto_renew;
617 } elseif ( isset( $purchase->user_allows_auto_renew ) ) {
618 return ! $purchase->user_allows_auto_renew;
619 }
620 }
621 }
622 }
623 return false;
624 }
625
626 /**
627 * Get list of all site level MCP abilities.
628 *
629 * @return array
630 */
631 private function get_all_site_mcp_abilities(): array {
632 $all_abilities = array();
633 $ability_registry_file = WP_CONTENT_DIR . '/mu-plugins/wpcom-mcp/includes/AbilitiesRegistry/Registry/AbilityRegistry.php';
634 if ( file_exists( $ability_registry_file ) ) {
635 require_once $ability_registry_file;
636 // @phan-suppress-next-line PhanUndeclaredClassMethod
637 $abilities_resources = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_resources_for_server( 'site-level' );
638 // @phan-suppress-next-line PhanUndeclaredClassMethod
639 $abilities_tools = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_tools_for_server( 'site-level' );
640 // @phan-suppress-next-line PhanUndeclaredClassMethod
641 $abilities_prompts = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_prompts_for_server( 'site-level' );
642 $all_abilities = array_merge( $abilities_resources, $abilities_tools, $abilities_prompts );
643 }
644 return apply_filters( 'jetpack_site_mcp_abilities', $all_abilities );
645 }
646
647 /**
648 * Get ability meta from config.
649 *
650 * @param string $ability_name Ability name, i.e. wpcom-mcp/posts-search.
651 *
652 * @return array
653 */
654 private function get_mcp_abilities_metadata( string $ability_name ): array {
655 $ability_meta = array();
656 $ability_registry_file = WP_CONTENT_DIR . '/mu-plugins/wpcom-mcp/includes/AbilitiesRegistry/Registry/AbilityRegistry.php';
657 if ( file_exists( $ability_registry_file ) ) {
658 require_once $ability_registry_file;
659 // @phan-suppress-next-line PhanUndeclaredClassMethod
660 $ability_meta = Automattic\WpcomMcp\AbilitiesRegistry\Registry\AbilityRegistry::get_metadata( $ability_name );
661 }
662 return apply_filters( 'jetpack_site_mcp_ability_meta', $ability_meta, $ability_name );
663 }
664
665 /**
666 * Get MCP abilities for the current site.
667 *
668 * @return array
669 */
670 public function get_site_mcp_abilities(): array {
671 $current_mcp_abilities = get_option( 'mcp_abilities', array() );
672 if ( ! is_array( $current_mcp_abilities ) ) {
673 $current_mcp_abilities = array();
674 }
675
676 $all_abilities = $this->get_all_site_mcp_abilities();
677 if ( empty( $all_abilities ) ) {
678 return array();
679 }
680
681 $computed_abilities = array();
682 foreach ( $all_abilities as $ability_name ) {
683 // Get base metadata first
684 $ability_meta = $this->get_mcp_abilities_metadata( $ability_name );
685 if ( ! empty( $ability_meta ) ) {
686 // Use stored value or fall back to metadata default
687 $enabled = $current_mcp_abilities[ $ability_name ] ?? $ability_meta['enabled'] ?? false;
688
689 $computed_abilities[ $ability_name ] = array(
690 'name' => $ability_name,
691 'title' => $ability_meta['title'] ?? '',
692 'description' => $ability_meta['description'] ?? '',
693 'category' => $ability_meta['category'] ?? '',
694 'type' => $ability_meta['type'] ?? '',
695 'enabled' => (bool) $enabled,
696 );
697 }
698 }
699 return $computed_abilities;
700 }
701
702 /**
703 * Sets the MCP abilities for the current site.
704 *
705 * @param mixed $value MCP abilities array.
706 *
707 * @return true|WP_Error
708 */
709 public function set_site_mcp_abilities( $value ) {
710 // Validate input format
711 if ( ! is_array( $value ) ) {
712 return new WP_Error( 'invalid_format', __( 'Site MCP abilities must be an array', 'jetpack' ) );
713 }
714
715 $all_abilities = $this->get_all_site_mcp_abilities();
716
717 // Filter ability names that don't exist
718 $value = array_filter(
719 $value,
720 function ( $ability_name ) use ( $all_abilities ) {
721 return in_array( $ability_name, $all_abilities, true );
722 },
723 ARRAY_FILTER_USE_KEY
724 );
725
726 // Validate each ability exists and value is boolean-like
727 foreach ( $value as $ability_name => $enabled ) {
728 if ( ! is_string( $ability_name ) || ( ! WPCOM_JSON_API::is_truthy( $enabled ) && ! WPCOM_JSON_API::is_falsy( $enabled ) ) ) {
729 $error_message = sprintf(
730 // Translators: %s is an MCP ability name
731 __( 'Invalid ability: %s', 'jetpack' ),
732 $ability_name
733 );
734 return new WP_Error( 'invalid_ability', $error_message );
735 }
736 }
737
738 update_option( 'mcp_abilities', $value );
739
740 return true;
741 }
742
743 /**
744 * Get locale.
745 *
746 * @param string $key Language.
747 */
748 protected function get_locale( $key ) {
749 if ( 'lang' === $key ) {
750 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
751 return (string) get_blog_lang_code();
752 } else {
753 return get_locale();
754 }
755 }
756
757 return false;
758 }
759
760 /**
761 * Updates site settings for authorized users
762 *
763 * @return array|WP_Error
764 */
765 public function update_settings() {
766 /*
767 * $this->input() retrieves posted arguments whitelisted and casted to the $request_format
768 * specs that get passed in when this class is instantiated
769 */
770 $input = $this->input();
771 $unfiltered_input = $this->input( false, false );
772 /**
773 * Filters the settings to be updated on the site.
774 *
775 * @module json-api
776 *
777 * @since 3.6.0
778 * @since 6.1.1 Added $unfiltered_input parameter.
779 *
780 * @param array $input Associative array of site settings to be updated.
781 * Cast and filtered based on documentation.
782 * @param array $unfiltered_input Associative array of site settings to be updated.
783 * Neither cast nor filtered. Contains raw input.
784 */
785 $input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
786
787 $blog_id = get_current_blog_id();
788
789 $jetpack_relatedposts_options = array();
790 $sharing_options = array();
791 $updated = array();
792
793 if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) {
794 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php';
795 }
796
797 foreach ( $input as $key => $value ) {
798
799 if ( ! is_array( $value ) ) {
800 $value = trim( $value );
801 }
802
803 // preserve the raw value before unslashing the value. The slashes need to be preserved for date and time formats.
804 $raw_value = $value;
805 $value = wp_unslash( $value );
806
807 switch ( $key ) {
808
809 case 'default_ping_status':
810 case 'default_comment_status':
811 // settings are stored as closed|open.
812 $coerce_value = ( $value ) ? 'open' : 'closed';
813 if ( update_option( $key, $coerce_value ) ) {
814 $updated[ $key ] = $value;
815 }
816 break;
817 case 'launchpad_screen':
818 if ( in_array( $value, array( 'full', 'off', 'minimized' ), true ) ) {
819 if ( update_option( $key, $value ) ) {
820 $updated[ $key ] = $value;
821 }
822 }
823 break;
824 case 'jetpack_protect_whitelist':
825 if ( class_exists( 'Brute_Force_Protection_Shared_Functions' ) ) {
826 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $value );
827 if ( is_wp_error( $result ) ) {
828 return $result;
829 }
830 $updated[ $key ] = Brute_Force_Protection_Shared_Functions::format_allow_list();
831 }
832 break;
833 case 'jetpack_sync_non_public_post_stati':
834 Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
835 break;
836 case 'jetpack_search_enabled':
837 if ( $value ) {
838 Jetpack::activate_module( $blog_id, 'search' );
839 } else {
840 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
841 Jetpack::deactivate_module( $blog_id, 'search' );
842 }
843 $updated[ $key ] = (bool) $value;
844 break;
845 case 'jetpack_relatedposts_enabled':
846 case 'jetpack_relatedposts_show_context':
847 case 'jetpack_relatedposts_show_date':
848 case 'jetpack_relatedposts_show_thumbnails':
849 case 'jetpack_relatedposts_show_headline':
850 if ( ! $this->jetpack_relatedposts_supported() ) {
851 break;
852 }
853 if ( 'jetpack_relatedposts_enabled' === $key ) {
854 if ( $value ) {
855 Jetpack::activate_module( $blog_id, 'related-posts' );
856 } else {
857 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
858 Jetpack::deactivate_module( $blog_id, 'related-posts' );
859 }
860 }
861 $just_the_key = substr( $key, 21 );
862 $jetpack_relatedposts_options[ $just_the_key ] = $value;
863 break;
864
865 case 'social_notifications_like':
866 case 'social_notifications_reblog':
867 case 'social_notifications_subscribe':
868 // settings are stored as on|off.
869 $coerce_value = ( $value ) ? 'on' : 'off';
870 if ( update_option( $key, $coerce_value ) ) {
871 $updated[ $key ] = $value;
872 }
873 break;
874
875 case 'cloudflare_analytics':
876 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
877 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
878 }
879
880 if ( update_option( $key, $value ) ) {
881 $updated[ $key ] = $value;
882 }
883 break;
884
885 case 'jetpack_testimonial':
886 case 'jetpack_portfolio':
887 case 'jetpack_comment_likes_enabled':
888 case 'wpcom_reader_views_enabled':
889 case 'jetpack_verbum_subscription_modal':
890 // settings are stored as 1|0.
891 $coerce_value = (int) $value;
892 if ( update_option( $key, $coerce_value ) ) {
893 $updated[ $key ] = (bool) $value;
894 }
895 break;
896
897 case 'jetpack_testimonial_posts_per_page':
898 case 'jetpack_portfolio_posts_per_page':
899 // settings are stored as numeric.
900 $coerce_value = (int) $value;
901 if ( update_option( $key, $coerce_value ) ) {
902 $updated[ $key ] = $coerce_value;
903 }
904 break;
905
906 // Sharing options.
907 case 'sharing_button_style':
908 case 'sharing_show':
909 case 'sharing_open_links':
910 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
911 break;
912 case 'sharing_label':
913 $sharing_options[ $key ] = $value;
914 break;
915
916 // Keyring token option.
917 case 'eventbrite_api_token':
918 // These options can only be updated for sites hosted on WordPress.com.
919 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
920 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
921 if ( delete_option( $key ) ) {
922 $updated[ $key ] = null;
923 }
924 } elseif ( update_option( $key, $value ) ) {
925 $updated[ $key ] = (int) $value;
926 }
927 }
928 break;
929
930 case 'api_cache':
931 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
932 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
933 $updated[ $key ] = false;
934 }
935 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
936 $updated[ $key ] = true;
937 }
938 break;
939
940 case 'timezone_string':
941 /*
942 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
943 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
944 */
945 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
946 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
947 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
948 $updated['gmt_offset'] = $gmt_offset;
949 }
950
951 $value = '';
952 }
953
954 /*
955 * Always set timezone_string either with the given value or with an
956 * empty string
957 */
958 if ( update_option( $key, $value ) ) {
959 $updated[ $key ] = $value;
960 }
961 break;
962
963 case 'subscription_options':
964 if ( ! is_array( $value ) ) {
965 break;
966 }
967
968 $allowed_keys = array( 'invitation', 'comment_follow', 'welcome' );
969 $filtered_value = array_filter(
970 $value,
971 function ( $key ) use ( $allowed_keys ) {
972 return in_array( $key, $allowed_keys, true );
973 },
974 ARRAY_FILTER_USE_KEY
975 );
976
977 if ( empty( $filtered_value ) ) {
978 break;
979 }
980
981 array_walk_recursive(
982 $filtered_value,
983 function ( &$value ) {
984 $value = wp_kses(
985 $value,
986 array(
987 'a' => array(
988 'href' => array(),
989 ),
990 )
991 );
992 }
993 );
994
995 $old_subscription_options = get_option( 'subscription_options', array() );
996 $new_subscription_options = array_merge( $old_subscription_options, $filtered_value );
997
998 if ( update_option( $key, $new_subscription_options ) ) {
999 $updated[ $key ] = $filtered_value;
1000 }
1001 break;
1002
1003 case 'woocommerce_onboarding_profile':
1004 // Allow boolean values but sanitize_text_field everything else.
1005 $sanitized_value = (array) $value;
1006 array_walk_recursive(
1007 $sanitized_value,
1008 function ( &$value ) {
1009 if ( ! is_bool( $value ) ) {
1010 $value = sanitize_text_field( $value );
1011 }
1012 }
1013 );
1014 if ( update_option( $key, $sanitized_value ) ) {
1015 $updated[ $key ] = $sanitized_value;
1016 }
1017 break;
1018
1019 case 'woocommerce_store_address':
1020 case 'woocommerce_store_address_2':
1021 case 'woocommerce_store_city':
1022 case 'woocommerce_default_country':
1023 case 'woocommerce_store_postcode':
1024 $sanitized_value = sanitize_text_field( $value );
1025 if ( update_option( $key, $sanitized_value ) ) {
1026 $updated[ $key ] = $sanitized_value;
1027 }
1028 break;
1029
1030 case 'date_format':
1031 case 'time_format':
1032 // settings are stored as strings.
1033 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
1034 $sanitized_value = sanitize_text_field( $raw_value );
1035 if ( update_option( $key, $sanitized_value ) ) {
1036 $updated[ $key ] = $sanitized_value;
1037 }
1038 break;
1039
1040 case 'start_of_week':
1041 // setting is stored as int in 0-6 range (days of week).
1042 $coerce_value = (int) $value;
1043 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
1044 if ( update_option( $key, $limit_value ) ) {
1045 $updated[ $key ] = $limit_value;
1046 }
1047 break;
1048
1049 case 'site_icon':
1050 /*
1051 * settings are stored as deletable numeric (all empty
1052 * values as delete intent), validated as media image
1053 */
1054 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
1055 /**
1056 * Fallback mechanism to clear a third party site icon setting. Can be used
1057 * to unset the option when an API request instructs the site to remove the site icon.
1058 *
1059 * @module json-api
1060 *
1061 * @since 4.10
1062 */
1063 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
1064 $updated[ $key ] = null;
1065 }
1066 } elseif ( is_numeric( $value ) ) {
1067 $coerce_value = (int) $value;
1068 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
1069 $updated[ $key ] = $coerce_value;
1070 }
1071 }
1072 break;
1073
1074 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
1075 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
1076 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
1077 }
1078
1079 if ( ! is_string( $value ) ) {
1080 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
1081 }
1082
1083 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
1084
1085 if ( ! empty( $new_description ) ) {
1086 $updated[ $key ] = $new_description;
1087 }
1088 break;
1089
1090 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
1091 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
1092 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
1093 break;
1094 }
1095 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
1096 }
1097
1098 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
1099 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
1100 }
1101
1102 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
1103
1104 if ( ! empty( $new_title_formats ) ) {
1105 $updated[ $key ] = $new_title_formats;
1106 }
1107 break;
1108
1109 case 'verification_services_codes':
1110 $verification_codes = jetpack_verification_validate( $value );
1111
1112 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
1113 $updated[ $key ] = $verification_codes;
1114 }
1115 break;
1116
1117 case 'wpcom_publish_posts_with_markdown':
1118 case 'wpcom_publish_comments_with_markdown':
1119 $coerce_value = (bool) $value;
1120 if ( update_option( $key, $coerce_value ) ) {
1121 $updated[ $key ] = $coerce_value;
1122 }
1123 break;
1124
1125 case 'wpcom_gifting_subscription':
1126 $coerce_value = (bool) $value;
1127
1128 /*
1129 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
1130 * a serialized value. Knowing that we can check if the option already exists.
1131 */
1132 $gift_toggle = get_option( $key );
1133 if ( false === $gift_toggle ) {
1134 // update_option will not create a new option if the initial value is false. So use add_option.
1135 if ( add_option( $key, $coerce_value ) ) {
1136 $updated[ $key ] = $coerce_value;
1137 }
1138 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
1139 $updated[ $key ] = $coerce_value;
1140 }
1141 break;
1142
1143 case 'rss_use_excerpt':
1144 $sanitized_value = (int) (bool) $value;
1145 update_option( $key, $sanitized_value );
1146 $updated[ $key ] = $sanitized_value;
1147 break;
1148
1149 case 'wpcom_subscription_emails_use_excerpt':
1150 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
1151 $updated[ $key ] = (bool) $value;
1152 break;
1153
1154 case 'jetpack_subscriptions_reply_to':
1155 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1156 $to_set_value = Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value )
1157 ? (string) $value
1158 : Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1159
1160 if ( update_option( $key, $to_set_value ) ) {
1161 $updated[ $key ] = $to_set_value;
1162 }
1163 break;
1164
1165 case 'jetpack_subscriptions_from_name':
1166 $sanitized_value = sanitize_text_field( $value );
1167 if ( update_option( $key, $sanitized_value ) ) {
1168 $updated[ $key ] = $sanitized_value;
1169 }
1170 break;
1171
1172 case 'instant_search_enabled':
1173 update_option( 'instant_search_enabled', (bool) $value );
1174 $updated[ $key ] = (bool) $value;
1175 break;
1176
1177 case 'lang_id':
1178 /*
1179 * Due to the fact that locale variants are set in a locale_variant option,
1180 * changing locale from variant to primary
1181 * would look like the same lang_id is being saved and update_option would return false,
1182 * even though the correct options would be set by pre_update_option_lang_id,
1183 * so we should always return lang_id as updated.
1184 */
1185 update_option( 'lang_id', (int) $value );
1186 $updated[ $key ] = (int) $value;
1187 break;
1188
1189 case 'wpcom_featured_image_in_email':
1190 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1191 $updated[ $key ] = (int) (bool) $value;
1192 break;
1193
1194 case Jetpack_Newsletter_Category_Helper::NEWSLETTER_CATEGORIES_OPTION:
1195 $update_newsletter_categories = Jetpack_Newsletter_Category_Helper::save_category_ids( (array) $value );
1196 if ( $update_newsletter_categories ) {
1197 $updated[ $key ] = $update_newsletter_categories;
1198 }
1199
1200 break;
1201
1202 case 'wpcom_newsletter_categories_enabled':
1203 update_option( 'wpcom_newsletter_categories_enabled', (int) (bool) $value );
1204 $updated[ $key ] = (int) (bool) $value;
1205 break;
1206
1207 case 'sm_enabled':
1208 update_option( 'sm_enabled', (int) (bool) $value );
1209 $updated[ $key ] = (int) (bool) $value;
1210 break;
1211
1212 case 'jetpack_subscribe_overlay_enabled':
1213 update_option( 'jetpack_subscribe_overlay_enabled', (int) (bool) $value );
1214 $updated[ $key ] = (int) (bool) $value;
1215 break;
1216
1217 case 'jetpack_subscribe_floating_button_enabled':
1218 update_option( 'jetpack_subscribe_floating_button_enabled', (int) (bool) $value );
1219 $updated[ $key ] = (int) (bool) $value;
1220 break;
1221
1222 case 'jetpack_subscriptions_subscribe_post_end_enabled':
1223 update_option( 'jetpack_subscriptions_subscribe_post_end_enabled', (int) (bool) $value );
1224 $updated[ $key ] = (int) (bool) $value;
1225 break;
1226
1227 case 'jetpack_subscriptions_login_navigation_enabled':
1228 update_option( 'jetpack_subscriptions_login_navigation_enabled', (int) (bool) $value );
1229 $updated[ $key ] = (int) (bool) $value;
1230 break;
1231
1232 case 'jetpack_subscriptions_subscribe_navigation_enabled':
1233 update_option( 'jetpack_subscriptions_subscribe_navigation_enabled', (int) (bool) $value );
1234 $updated[ $key ] = (int) (bool) $value;
1235 break;
1236
1237 case 'show_on_front':
1238 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1239 $updated[ $key ] = $value;
1240 }
1241 break;
1242
1243 case 'page_on_front':
1244 case 'page_for_posts':
1245 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1246 if ( delete_option( $key ) ) {
1247 $updated[ $key ] = null;
1248 }
1249
1250 break;
1251 }
1252
1253 if ( ! $this->is_valid_page_id( $value ) ) {
1254 break;
1255 }
1256
1257 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1258 $related_option_value = get_option( $related_option_key );
1259 if ( $related_option_value === $value ) {
1260 // page_on_front and page_for_posts are not allowed to be the same
1261 break;
1262 }
1263
1264 if ( update_option( $key, $value ) ) {
1265 $updated[ $key ] = $value;
1266 }
1267
1268 break;
1269
1270 case 'in_site_migration_flow':
1271 if ( empty( $value ) ) {
1272 delete_option( 'in_site_migration_flow' );
1273 break;
1274 }
1275
1276 $migration_flow_whitelist = array(
1277 'site-migration',
1278 'migration-signup',
1279 );
1280
1281 if ( ! in_array( $value, $migration_flow_whitelist, true ) ) {
1282 break;
1283 }
1284
1285 update_option( 'in_site_migration_flow', $value );
1286 $updated[ $key ] = $value;
1287 break;
1288
1289 case 'migration_source_site_domain':
1290 // If we get an empty value, delete the option
1291 if ( empty( $value ) ) {
1292 delete_option( 'migration_source_site_domain' );
1293 break;
1294 }
1295
1296 // If we get a non-url value, don't update the option.
1297 if ( wp_http_validate_url( $value ) === false ) {
1298 break;
1299 }
1300
1301 update_option( 'migration_source_site_domain', $value );
1302 $updated[ $key ] = $value;
1303 break;
1304
1305 case 'is_fully_managed_agency_site':
1306 case 'wpcom_hide_action_bar':
1307 $coerce_value = (int) (bool) $value;
1308 if ( update_option( $key, $coerce_value ) ) {
1309 $updated[ $key ] = (bool) $coerce_value;
1310 }
1311 break;
1312
1313 case 'mcp_abilities':
1314 $result = $this->set_site_mcp_abilities( $value );
1315 if ( is_wp_error( $result ) ) {
1316 return $result;
1317 }
1318 $updated[ $key ] = $this->get_site_mcp_abilities();
1319 break;
1320
1321 default:
1322 // allow future versions of this endpoint to support additional settings keys.
1323 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1324 /**
1325 * Filter current site setting value to be updated.
1326 *
1327 * @module json-api
1328 *
1329 * @since 3.9.3
1330 * @since 13.6 Added the API object parameter.
1331 *
1332 * @param mixed $response_item A single site setting value.
1333 * @param WPCOM_JSON_API_Site_Settings_Endpoint The API object parameter.
1334 */
1335 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value, $this );
1336
1337 if ( is_wp_error( $value ) ) {
1338 return $value;
1339 }
1340
1341 if ( $value ) {
1342 $updated[ $key ] = $value;
1343 }
1344 break;
1345 }
1346 // no worries, we've already whitelisted and casted arguments above.
1347 if ( update_option( $key, $value ) ) {
1348 $updated[ $key ] = $value;
1349 }
1350 }
1351 }
1352
1353 if ( $jetpack_relatedposts_options !== array() ) {
1354 // track new jetpack_relatedposts options against old.
1355 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1356
1357 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1358 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1359 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1360 }
1361
1362 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1363 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1364 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1365 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1366 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1367
1368 if ( $has_initialized_option || $has_updated_option ) {
1369 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1370 }
1371 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1372 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1373 }
1374 }
1375 }
1376 }
1377
1378 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1379 $ss = new Sharing_Service();
1380
1381 /*
1382 * Merge current values with updated, since Sharing_Service expects
1383 * all values to be included when updating
1384 */
1385 $current_sharing_options = $ss->get_global_options();
1386 foreach ( $current_sharing_options as $key => $val ) {
1387 if ( ! isset( $sharing_options[ $key ] ) ) {
1388 $sharing_options[ $key ] = $val;
1389 }
1390 }
1391
1392 $updated_social_options = $ss->set_global_options( $sharing_options );
1393
1394 if ( isset( $input['sharing_button_style'] ) ) {
1395 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1396 }
1397 if ( isset( $input['sharing_label'] ) ) {
1398 // Sharing_Service won't report label as updated if set to default.
1399 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1400 }
1401 if ( isset( $input['sharing_show'] ) ) {
1402 $updated['sharing_show'] = (array) $updated_social_options['show'];
1403 }
1404 if ( isset( $input['sharing_open_links'] ) ) {
1405 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1406 }
1407 }
1408
1409 return array(
1410 'updated' => $updated,
1411 );
1412 }
1413
1414 /**
1415 * Get the string value of the jetpack_subscriptions_reply_to option.
1416 * When the option is not set, it will retun 'no-reply'.
1417 *
1418 * @return string
1419 */
1420 protected function get_subscriptions_reply_to_option() {
1421 $reply_to = get_option( 'jetpack_subscriptions_reply_to', null );
1422 if ( $reply_to === null ) {
1423 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1424 return Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1425 }
1426 return $reply_to;
1427 }
1428
1429 /**
1430 * Check if the given value is a valid page ID for the current site.
1431 *
1432 * @param mixed $value The value to check.
1433 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1434 */
1435 protected function is_valid_page_id( $value ) {
1436 $all_page_ids = get_all_page_ids();
1437
1438 $valid_page_id = false;
1439 foreach ( $all_page_ids as $page_id ) {
1440 if ( $page_id === (string) $value ) {
1441 $valid_page_id = true;
1442 break;
1443 }
1444 }
1445
1446 return $valid_page_id;
1447 }
1448
1449 /**
1450 * Get the value of the highlander_comment_form_prompt option.
1451 * When the option is not set, it will return the default value.
1452 *
1453 * @return string
1454 */
1455 protected function get_highlander_comment_form_prompt_option() {
1456 $highlander_comment_form_prompt_option = get_option( 'highlander_comment_form_prompt' );
1457
1458 if ( empty( $highlander_comment_form_prompt_option ) ) {
1459 return (string) __( 'Leave a comment', 'jetpack' );
1460 }
1461
1462 return (string) $highlander_comment_form_prompt_option;
1463 }
1464 }
1465