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