PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.0.2
Jetpack – WP Security, Backup, Speed, & Growth v12.0.2
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 3 years ago class.wpcom-json-api-add-widget-endpoint.php 3 years ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 5 years ago class.wpcom-json-api-bulk-delete-post-endpoint.php 4 years ago class.wpcom-json-api-bulk-restore-post-endpoint.php 4 years ago class.wpcom-json-api-bulk-update-comments-endpoint.php 4 years ago class.wpcom-json-api-comment-endpoint.php 4 years ago class.wpcom-json-api-delete-media-endpoint.php 4 years ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 3 years ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 5 years ago class.wpcom-json-api-get-comment-counts-endpoint.php 4 years ago class.wpcom-json-api-get-comment-endpoint.php 4 years ago class.wpcom-json-api-get-comment-history-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 4 years ago class.wpcom-json-api-get-customcss.php 4 years ago class.wpcom-json-api-get-media-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-2-endpoint.php 3 years ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-post-endpoint.php 4 years ago class.wpcom-json-api-get-post-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-site-endpoint.php 3 years ago class.wpcom-json-api-get-site-v1-2-endpoint.php 3 years ago class.wpcom-json-api-get-taxonomies-endpoint.php 4 years ago class.wpcom-json-api-get-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-get-term-endpoint.php 4 years ago class.wpcom-json-api-list-comments-endpoint.php 3 years ago class.wpcom-json-api-list-dropdown-pages-endpoint.php 3 years ago class.wpcom-json-api-list-embeds-endpoint.php 4 years ago class.wpcom-json-api-list-media-endpoint.php 4 years ago class.wpcom-json-api-list-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-list-media-v1-2-endpoint.php 3 years ago class.wpcom-json-api-list-post-type-taxonomies-endpoint.php 4 years ago class.wpcom-json-api-list-post-types-endpoint.php 3 years ago class.wpcom-json-api-list-posts-endpoint.php 3 years ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 3 years ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 3 years ago class.wpcom-json-api-list-roles-endpoint.php 4 years ago class.wpcom-json-api-list-shortcodes-endpoint.php 4 years ago class.wpcom-json-api-list-terms-endpoint.php 4 years ago class.wpcom-json-api-list-users-endpoint.php 3 years ago class.wpcom-json-api-menus-v1-1-endpoint.php 4 years ago class.wpcom-json-api-post-endpoint.php 3 years ago class.wpcom-json-api-post-v1-1-endpoint.php 3 years ago class.wpcom-json-api-render-embed-endpoint.php 4 years ago class.wpcom-json-api-render-embed-reversal-endpoint.php 4 years ago class.wpcom-json-api-render-endpoint.php 3 years ago class.wpcom-json-api-render-shortcode-endpoint.php 4 years ago class.wpcom-json-api-sharing-buttons-endpoint.php 3 years ago class.wpcom-json-api-site-settings-endpoint.php 3 years ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 3 years ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 3 years ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 3 years ago class.wpcom-json-api-site-user-endpoint.php 4 years ago class.wpcom-json-api-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-update-comment-endpoint.php 3 years ago class.wpcom-json-api-update-customcss.php 4 years ago class.wpcom-json-api-update-media-endpoint.php 4 years ago class.wpcom-json-api-update-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-update-post-endpoint.php 3 years ago class.wpcom-json-api-update-post-v1-1-endpoint.php 3 years ago class.wpcom-json-api-update-post-v1-2-endpoint.php 3 years ago class.wpcom-json-api-update-site-homepage-endpoint.php 4 years ago class.wpcom-json-api-update-site-logo-endpoint.php 4 years ago class.wpcom-json-api-update-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-update-term-endpoint.php 4 years ago class.wpcom-json-api-update-user-endpoint.php 4 years ago class.wpcom-json-api-upload-media-endpoint.php 4 years ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 3 years ago
class.wpcom-json-api-site-settings-endpoint.php
1161 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 new WPCOM_JSON_API_Site_Settings_Endpoint(
11 array(
12 'description' => 'Get detailed settings information about a site.',
13 'group' => '__do_not_document',
14 'stat' => 'sites:X',
15 'max_version' => '1.1',
16 'new_version' => '1.2',
17 'method' => 'GET',
18 'path' => '/sites/%s/settings',
19 'path_labels' => array(
20 '$site' => '(int|string) Site ID or domain',
21 ),
22
23 'query_parameters' => array(
24 'context' => false,
25 ),
26
27 'response_format' => WPCOM_JSON_API_Site_Settings_Endpoint::$site_format,
28
29 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
30 )
31 );
32
33 new WPCOM_JSON_API_Site_Settings_Endpoint(
34 array(
35 'description' => 'Update settings for a site.',
36 'group' => '__do_not_document',
37 'stat' => 'sites:X',
38 'max_version' => '1.1',
39 'new_version' => '1.2',
40 'method' => 'POST',
41 'path' => '/sites/%s/settings',
42 'a_new_very_long_key' => 'blabla',
43 'path_labels' => array(
44 '$site' => '(int|string) Site ID or domain',
45 ),
46
47 'request_format' => array(
48 'blogname' => '(string) Blog name',
49 'blogdescription' => '(string) Blog description',
50 'default_pingback_flag' => '(bool) Notify blogs linked from article?',
51 'default_ping_status' => '(bool) Allow link notifications from other blogs?',
52 'default_comment_status' => '(bool) Allow comments on new articles?',
53 'blog_public' => '(string) Site visibility; -1: private, 0: discourage search engines, 1: allow search engines',
54 'jetpack_sync_non_public_post_stati' => '(bool) allow sync of post and pages with non-public posts stati',
55 'jetpack_relatedposts_enabled' => '(bool) Enable related posts?',
56 'jetpack_relatedposts_show_context' => '(bool) Show post\'s tags and category in related posts?',
57 'jetpack_relatedposts_show_date' => '(bool) Show date in related posts?',
58 'jetpack_relatedposts_show_headline' => '(bool) Show headline in related posts?',
59 'jetpack_relatedposts_show_thumbnails' => '(bool) Show thumbnails in related posts?',
60 'jetpack_protect_whitelist' => '(array) List of IP addresses to always allow',
61 'instant_search_enabled' => '(bool) Enable the new Jetpack Instant Search interface',
62 'jetpack_search_enabled' => '(bool) Enable Jetpack Search',
63 'jetpack_search_supported' => '(bool) Jetpack Search is supported',
64 'infinite_scroll' => '(bool) Support infinite scroll of posts?',
65 'default_category' => '(int) Default post category',
66 'default_post_format' => '(string) Default post format',
67 'require_name_email' => '(bool) Require comment authors to fill out name and email?',
68 'comment_registration' => '(bool) Require users to be registered and logged in to comment?',
69 'close_comments_for_old_posts' => '(bool) Automatically close comments on old posts?',
70 'close_comments_days_old' => '(int) Age at which to close comments',
71 'thread_comments' => '(bool) Enable threaded comments?',
72 'thread_comments_depth' => '(int) Depth to thread comments',
73 'page_comments' => '(bool) Break comments into pages?',
74 'comments_per_page' => '(int) Number of comments to display per page',
75 'default_comments_page' => '(string) newest|oldest Which page of comments to display first',
76 'comment_order' => '(string) asc|desc Order to display comments within page',
77 'comments_notify' => '(bool) Email me when someone comments?',
78 'moderation_notify' => '(bool) Email me when a comment is helf for moderation?',
79 'social_notifications_like' => '(bool) Email me when someone likes my post?',
80 'social_notifications_reblog' => '(bool) Email me when someone reblogs my post?',
81 'social_notifications_subscribe' => '(bool) Email me when someone follows my blog?',
82 'comment_moderation' => '(bool) Moderate comments for manual approval?',
83 'comment_previously_approved' => '(bool) Moderate comments unless author has a previously-approved comment?',
84 'comment_max_links' => '(int) Moderate comments that contain X or more links',
85 'moderation_keys' => '(string) Words or phrases that trigger comment moderation, one per line',
86 'disallowed_keys' => '(string) Words or phrases that mark comment spam, one per line',
87 'lang_id' => '(int) ID for language blog is written in',
88 'wga' => '(array) Google Analytics Settings',
89 'disabled_likes' => '(bool) Are likes globally disabled (they can still be turned on per post)?',
90 'disabled_reblogs' => '(bool) Are reblogs disabled on posts?',
91 'jetpack_comment_likes_enabled' => '(bool) Are comment likes enabled for all comments?',
92 'sharing_button_style' => '(string) Style to use for sharing buttons (icon-text, icon, text, or official)',
93 'sharing_label' => '(string) Label to use for sharing buttons, e.g. "Share this:"',
94 'sharing_show' => '(string|array:string) Post type or array of types where sharing buttons are to be displayed',
95 'sharing_open_links' => '(string) Link target for sharing buttons (same or new)',
96 'twitter_via' => '(string) Twitter username to include in tweets when people share using the Twitter button',
97 'jetpack-twitter-cards-site-tag' => '(string) The Twitter username of the owner of the site\'s domain.',
98 'eventbrite_api_token' => '(int) The Keyring token ID for an Eventbrite token to associate with the site',
99 'timezone_string' => '(string) PHP-compatible timezone string like \'UTC-5\'',
100 'gmt_offset' => '(int) Site offset from UTC in hours',
101 'date_format' => '(string) PHP Date-compatible date format',
102 'time_format' => '(string) PHP Date-compatible time format',
103 'start_of_week' => '(int) Starting day of week (0 = Sunday, 6 = Saturday)',
104 'jetpack_testimonial' => '(bool) Whether testimonial custom post type is enabled for the site',
105 'jetpack_testimonial_posts_per_page' => '(int) Number of testimonials to show per page',
106 'jetpack_portfolio' => '(bool) Whether portfolio custom post type is enabled for the site',
107 'jetpack_portfolio_posts_per_page' => '(int) Number of portfolio projects to show per page',
108 Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => '(string) The seo meta description for the site.',
109 Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => '(array) SEO meta title formats. Allowed keys: front_page, posts, pages, groups, archives',
110 'verification_services_codes' => '(array) Website verification codes. Allowed keys: google, pinterest, bing, yandex, facebook',
111 'markdown_supported' => '(bool) Whether markdown is supported for this site',
112 'wpcom_publish_posts_with_markdown' => '(bool) Whether markdown is enabled for posts',
113 'wpcom_publish_comments_with_markdown' => '(bool) Whether markdown is enabled for comments',
114 'amp_is_enabled' => '(bool) Whether AMP is enabled for this site',
115 'site_icon' => '(int) Media attachment ID to use as site icon. Set to zero or an otherwise empty value to clear',
116 'api_cache' => '(bool) Turn on/off the Jetpack JSON API cache',
117 'posts_per_page' => '(int) Number of posts to show on blog pages',
118 'posts_per_rss' => '(int) Number of posts to show in the RSS feed',
119 'rss_use_excerpt' => '(bool) Whether the RSS feed will use post excerpts',
120 'launchpad_screen' => '(string) Whether or not launchpad is presented and what size it will be',
121 ),
122
123 'response_format' => array(
124 'updated' => '(array)',
125 ),
126
127 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
128 )
129 );
130
131 /**
132 * Manage Site settings endpoint.
133 */
134 class WPCOM_JSON_API_Site_Settings_Endpoint extends WPCOM_JSON_API_Endpoint {
135
136 /**
137 * Site format.
138 *
139 * @var array
140 */
141 public static $site_format = array(
142 'ID' => '(int) Site ID',
143 'name' => '(string) Title of site',
144 'description' => '(string) Tagline or description of site',
145 'URL' => '(string) Full URL to the site',
146 'lang' => '(string) Primary language code of the site',
147 'locale_variant' => '(string) Locale variant code for the site, if set',
148 'settings' => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
149 );
150
151 /**
152 * Endpoint response
153 *
154 * GET /sites/%s/settings
155 * POST /sites/%s/settings
156 *
157 * @param string $path Path.
158 * @param int $blog_id Blog ID.
159 */
160 public function callback( $path = '', $blog_id = 0 ) {
161 $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
162 if ( is_wp_error( $blog_id ) ) {
163 return $blog_id;
164 }
165
166 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
167 // Source & include the infinite scroll compatibility files prior to loading theme functions.
168 add_filter( 'restapi_theme_action_copy_dirs', array( 'WPCOM_JSON_API_Site_Settings_Endpoint', 'wpcom_restapi_copy_theme_plugin_actions' ) );
169 $this->load_theme_functions();
170 }
171
172 if ( ! is_user_logged_in() ) {
173 return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
174 } elseif ( ! current_user_can( 'manage_options' ) ) {
175 return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
176 }
177
178 if ( 'GET' === $this->api->method ) {
179 /**
180 * Fires on each GET request to a specific endpoint.
181 *
182 * @module json-api
183 *
184 * @since 3.2.0
185 *
186 * @param string sites.
187 */
188 do_action( 'wpcom_json_api_objects', 'sites' );
189 return $this->get_settings_response();
190 } elseif ( 'POST' === $this->api->method ) {
191 return $this->update_settings();
192 } else {
193 return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
194 }
195 }
196
197 /**
198 * Includes additional theme-specific files to be included in REST API theme
199 * context loading action copying.
200 *
201 * @see WPCOM_JSON_API_Endpoint#load_theme_functions
202 * @see the_neverending_home_page_theme_support
203 *
204 * @param array $copy_dirs Array of files to be included in theme context.
205 */
206 public function wpcom_restapi_copy_theme_plugin_actions( $copy_dirs ) {
207 $theme_name = get_stylesheet();
208 $default_file_name = WP_CONTENT_DIR . "/mu-plugins/infinity/themes/{$theme_name}.php";
209
210 /**
211 * Filter the path to the Infinite Scroll compatibility file.
212 *
213 * @module infinite-scroll
214 *
215 * @since 2.0.0
216 *
217 * @param string $str IS compatibility file path.
218 * @param string $theme_name Theme name.
219 */
220 $customization_file = apply_filters( 'infinite_scroll_customization_file', $default_file_name, $theme_name );
221
222 if ( is_readable( $customization_file ) ) {
223 require_once $customization_file;
224 $copy_dirs[] = $customization_file;
225 }
226
227 return $copy_dirs;
228 }
229
230 /**
231 * Determines whether jetpack_relatedposts is supported
232 *
233 * @return bool
234 */
235 public function jetpack_relatedposts_supported() {
236 $wpcom_related_posts_theme_blacklist = array(
237 'Expound',
238 'Traveler',
239 'Opti',
240 'Currents',
241 );
242 return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist, true ) );
243 }
244
245 /**
246 * Returns category details
247 *
248 * @param WP_Term $category Category object.
249 *
250 * @return array
251 */
252 public function get_category_details( $category ) {
253 return array(
254 'value' => $category->term_id,
255 'name' => $category->name,
256 );
257 }
258
259 /**
260 * Returns an option value as the result of the callable being applied to
261 * it if a value is set, otherwise null.
262 *
263 * @param string $option_name Option name.
264 * @param callable $cast_callable Callable to invoke on option value.
265 *
266 * @return int|null Numeric option value or null.
267 */
268 protected function get_cast_option_value_or_null( $option_name, $cast_callable ) {
269 $option_value = get_option( $option_name, null );
270 if ( $option_value === null ) {
271 return $option_value;
272 }
273
274 return call_user_func( $cast_callable, $option_value );
275 }
276
277 /**
278 * Collects the necessary information to return for a get settings response.
279 *
280 * @return array
281 */
282 public function get_settings_response() {
283 $response = array();
284
285 // Allow update in later versions.
286 /**
287 * Filter the structure of site settings to return.
288 *
289 * @module json-api
290 *
291 * @since 3.9.3
292 *
293 * @param array $site_format Data structure.
294 */
295 $response_format = apply_filters( 'site_settings_site_format', self::$site_format );
296
297 $blog_id = (int) $this->api->get_blog_id_for_output();
298 $site = $this->get_platform()->get_site( $blog_id );
299
300 foreach ( array_keys( $response_format ) as $key ) {
301
302 // refactoring to change lang parameter to locale in 1.2.
303 $lang_or_locale = $this->get_locale( $key );
304 if ( $lang_or_locale ) {
305 $response[ $key ] = $lang_or_locale;
306 continue;
307 }
308
309 switch ( $key ) {
310 case 'ID':
311 $response[ $key ] = $blog_id;
312 break;
313 case 'name':
314 $response[ $key ] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
315 break;
316 case 'description':
317 $response[ $key ] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
318 break;
319 case 'URL':
320 $response[ $key ] = (string) home_url();
321 break;
322 case 'locale_variant':
323 if ( function_exists( 'wpcom_l10n_get_blog_locale_variant' ) ) {
324 $blog_locale_variant = wpcom_l10n_get_blog_locale_variant();
325 if ( $blog_locale_variant ) {
326 $response[ $key ] = $blog_locale_variant;
327 }
328 }
329 break;
330 case 'settings':
331 $jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts', array() );
332 // If the option's enabled key is NOT SET, it is considered enabled by the plugin.
333 if ( ! isset( $jetpack_relatedposts_options['enabled'] ) ) {
334 $jetpack_relatedposts_options['enabled'] = true;
335 }
336
337 $jetpack_relatedposts_options['enabled'] =
338 $jetpack_relatedposts_options['enabled']
339 && $site->is_module_active( 'related-posts' );
340
341 $jetpack_search_supported = false;
342 if ( function_exists( 'wpcom_is_jetpack_search_supported' ) ) {
343 $jetpack_search_supported = wpcom_is_jetpack_search_supported( $blog_id );
344 }
345
346 $jetpack_search_active =
347 $jetpack_search_supported
348 && $site->is_module_active( 'search' );
349
350 // array_values() is necessary to ensure the array starts at index 0.
351 $post_categories = array_values(
352 array_map(
353 array( $this, 'get_category_details' ),
354 get_categories( array( 'hide_empty' => false ) )
355 )
356 );
357
358 $api_cache = $site->is_jetpack() ? (bool) get_option( 'jetpack_api_cache_enabled' ) : true;
359
360 $response[ $key ] = array(
361 // also exists as "options".
362 'admin_url' => get_admin_url(),
363 'default_ping_status' => 'closed' !== get_option( 'default_ping_status' ),
364 'default_comment_status' => 'closed' !== get_option( 'default_comment_status' ),
365
366 // new stuff starts here.
367 'instant_search_enabled' => (bool) get_option( 'instant_search_enabled' ),
368 'blog_public' => (int) get_option( 'blog_public' ),
369 'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
370 'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
371 'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options['enabled'],
372 'jetpack_relatedposts_show_context' => ! empty( $jetpack_relatedposts_options['show_context'] ),
373 'jetpack_relatedposts_show_date' => ! empty( $jetpack_relatedposts_options['show_date'] ),
374 'jetpack_relatedposts_show_headline' => ! empty( $jetpack_relatedposts_options['show_headline'] ),
375 'jetpack_relatedposts_show_thumbnails' => ! empty( $jetpack_relatedposts_options['show_thumbnails'] ),
376 'jetpack_search_enabled' => (bool) $jetpack_search_active,
377 'jetpack_search_supported' => (bool) $jetpack_search_supported,
378 'default_category' => (int) get_option( 'default_category' ),
379 'post_categories' => (array) $post_categories,
380 'default_post_format' => get_option( 'default_post_format' ),
381 'default_pingback_flag' => (bool) get_option( 'default_pingback_flag' ),
382 'require_name_email' => (bool) get_option( 'require_name_email' ),
383 'comment_registration' => (bool) get_option( 'comment_registration' ),
384 'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
385 'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
386 'thread_comments' => (bool) get_option( 'thread_comments' ),
387 'thread_comments_depth' => (int) get_option( 'thread_comments_depth' ),
388 'page_comments' => (bool) get_option( 'page_comments' ),
389 'comments_per_page' => (int) get_option( 'comments_per_page' ),
390 'default_comments_page' => get_option( 'default_comments_page' ),
391 'comment_order' => get_option( 'comment_order' ),
392 'comments_notify' => (bool) get_option( 'comments_notify' ),
393 'moderation_notify' => (bool) get_option( 'moderation_notify' ),
394 'social_notifications_like' => ( 'on' === get_option( 'social_notifications_like' ) ),
395 'social_notifications_reblog' => ( 'on' === get_option( 'social_notifications_reblog' ) ),
396 'social_notifications_subscribe' => ( 'on' === get_option( 'social_notifications_subscribe' ) ),
397 'comment_moderation' => (bool) get_option( 'comment_moderation' ),
398 'comment_whitelist' => (bool) get_option( 'comment_previously_approved' ),
399 'comment_previously_approved' => (bool) get_option( 'comment_previously_approved' ),
400 'comment_max_links' => (int) get_option( 'comment_max_links' ),
401 'moderation_keys' => get_option( 'moderation_keys' ),
402 'blacklist_keys' => get_option( 'disallowed_keys' ),
403 'disallowed_keys' => get_option( 'disallowed_keys' ),
404 'lang_id' => defined( 'IS_WPCOM' ) && IS_WPCOM
405 ? get_lang_id_by_code( wpcom_l10n_get_blog_locale_variant( $blog_id, true ) )
406 : get_option( 'lang_id' ),
407 'site_vertical_id' => (string) get_option( 'site_vertical_id' ),
408 'wga' => $this->get_google_analytics(),
409 'jetpack_cloudflare_analytics' => get_option( 'jetpack_cloudflare_analytics' ),
410 'disabled_likes' => (bool) get_option( 'disabled_likes' ),
411 'disabled_reblogs' => (bool) get_option( 'disabled_reblogs' ),
412 'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
413 'twitter_via' => (string) get_option( 'twitter_via' ),
414 'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
415 'eventbrite_api_token' => $this->get_cast_option_value_or_null( 'eventbrite_api_token', 'intval' ),
416 'gmt_offset' => get_option( 'gmt_offset' ),
417 'timezone_string' => get_option( 'timezone_string' ),
418 'date_format' => get_option( 'date_format' ),
419 'time_format' => get_option( 'time_format' ),
420 'start_of_week' => get_option( 'start_of_week' ),
421 'woocommerce_onboarding_profile' => (array) get_option( 'woocommerce_onboarding_profile', array() ),
422 'woocommerce_store_address' => (string) get_option( 'woocommerce_store_address' ),
423 'woocommerce_store_address_2' => (string) get_option( 'woocommerce_store_address_2' ),
424 'woocommerce_store_city' => (string) get_option( 'woocommerce_store_city' ),
425 'woocommerce_default_country' => (string) get_option( 'woocommerce_default_country' ),
426 'woocommerce_store_postcode' => (string) get_option( 'woocommerce_store_postcode' ),
427 'jetpack_testimonial' => (bool) get_option( 'jetpack_testimonial', '0' ),
428 'jetpack_testimonial_posts_per_page' => (int) get_option( 'jetpack_testimonial_posts_per_page', '10' ),
429 'jetpack_portfolio' => (bool) get_option( 'jetpack_portfolio', '0' ),
430 'jetpack_portfolio_posts_per_page' => (int) get_option( 'jetpack_portfolio_posts_per_page', '10' ),
431 'markdown_supported' => true,
432 'site_icon' => $this->get_cast_option_value_or_null( 'site_icon', 'intval' ),
433 Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => get_option( Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION, '' ),
434 Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => get_option( Jetpack_SEO_Titles::TITLE_FORMATS_OPTION, array() ),
435 'amp_is_supported' => (bool) function_exists( 'wpcom_is_amp_supported' ) && wpcom_is_amp_supported( $blog_id ),
436 'amp_is_enabled' => (bool) function_exists( 'wpcom_is_amp_enabled' ) && wpcom_is_amp_enabled( $blog_id ),
437 'amp_is_deprecated' => (bool) function_exists( 'wpcom_is_amp_deprecated' ) && wpcom_is_amp_deprecated( $blog_id ),
438 'api_cache' => $api_cache,
439 'posts_per_page' => (int) get_option( 'posts_per_page' ),
440 'posts_per_rss' => (int) get_option( 'posts_per_rss' ),
441 'rss_use_excerpt' => (bool) get_option( 'rss_use_excerpt' ),
442 'launchpad_screen' => (string) get_option( 'launchpad_screen' ),
443 'wpcom_featured_image_in_email' => (bool) get_option( 'wpcom_featured_image_in_email' ),
444 'wpcom_gifting_subscription' => (bool) get_option( 'wpcom_gifting_subscription', $this->get_wpcom_gifting_subscription_default() ),
445 'wpcom_subscription_emails_use_excerpt' => $this->get_wpcom_subscription_emails_use_excerpt_option(),
446 'show_on_front' => (string) get_option( 'show_on_front' ),
447 'page_on_front' => (string) get_option( 'page_on_front' ),
448 'page_for_posts' => (string) get_option( 'page_for_posts' ),
449 'subscription_options' => (array) get_option( 'subscription_options' ),
450 );
451
452 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
453 $response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::is_posting_enabled();
454 $response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::is_commenting_enabled();
455
456 // WPCOM-specific Infinite Scroll Settings.
457 if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
458 /**
459 * Clear the cached copy of widget info so it's pulled fresh from blog options.
460 * It was primed during the initial load under the __REST API site__'s context.
461 *
462 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
463 */
464 $GLOBALS['_wp_sidebars_widgets'] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
465
466 $infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
467 $response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && 'scroll' === $infinite_scroll_settings->type;
468 if ( $infinite_scroll_settings->footer_widgets || 'click' === $infinite_scroll_settings->requested_type ) {
469 // The blog has footer widgets -- infinite scroll is blocked.
470 $response[ $key ]['infinite_scroll_blocked'] = 'footer';
471 } else {
472 $response[ $key ]['infinite_scroll_blocked'] = false;
473 }
474 }
475 }
476
477 // allow future versions of this endpoint to support additional settings keys.
478 /**
479 * Filter the current site setting in the returned response.
480 *
481 * @module json-api
482 *
483 * @since 3.9.3
484 *
485 * @param mixed $response_item A single site setting.
486 */
487 $response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ] );
488
489 if ( class_exists( 'Sharing_Service' ) ) {
490 $ss = new Sharing_Service();
491 $sharing = $ss->get_global_options();
492 $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
493 $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
494 $response[ $key ]['sharing_show'] = (array) $sharing['show'];
495 $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
496 }
497
498 $response[ $key ]['jetpack_protect_whitelist'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
499
500 if ( ! current_user_can( 'edit_posts' ) ) {
501 unset( $response[ $key ] );
502 }
503 break;
504 }
505 }
506 return $response;
507 }
508
509 /**
510 * Get the default value for the wpcom_gifting_subscription option.
511 * The default value is the inverse of the plan's auto_renew setting.
512 *
513 * @return bool
514 */
515 protected function get_wpcom_gifting_subscription_default() {
516 if ( function_exists( 'wpcom_get_site_purchases' ) && function_exists( 'wpcom_purchase_has_feature' ) ) {
517 $purchases = wpcom_get_site_purchases();
518
519 foreach ( $purchases as $purchase ) {
520 if ( wpcom_purchase_has_feature( $purchase, \WPCOM_Features::SUBSCRIPTION_GIFTING ) ) {
521 /*
522 * We set default value as false when expiration date not match the following:
523 * - 54 days before the annual plan expiration.
524 * - 5 days before the monthly plan expiration.
525 * This is to match the gifting banner logic.
526 */
527 $days_of_warning = false !== strpos( $purchase->product_slug, 'monthly' ) ? 5 : 54;
528 $seconds_until_expiration = strtotime( $purchase->expiry_date ) - time();
529 if ( $seconds_until_expiration >= $days_of_warning * DAY_IN_SECONDS ) {
530 return false;
531 }
532
533 // We set default to the inverse of auto-renew.
534 if ( isset( $purchase->auto_renew ) ) {
535 return ! $purchase->auto_renew;
536 } elseif ( isset( $purchase->user_allows_auto_renew ) ) {
537 return ! $purchase->user_allows_auto_renew;
538 }
539 }
540 }
541 }
542 return false;
543 }
544
545 /**
546 * Get locale.
547 *
548 * @param string $key Language.
549 */
550 protected function get_locale( $key ) {
551 if ( 'lang' === $key ) {
552 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
553 return (string) get_blog_lang_code();
554 } else {
555 return get_locale();
556 }
557 }
558
559 return false;
560 }
561
562 /**
563 * Get GA tracking code.
564 */
565 protected function get_google_analytics() {
566 $option_name = defined( 'IS_WPCOM' ) && IS_WPCOM ? 'wga' : 'jetpack_wga';
567 return get_option( $option_name );
568 }
569
570 /**
571 * Updates site settings for authorized users
572 *
573 * @return array
574 */
575 public function update_settings() {
576 /*
577 * $this->input() retrieves posted arguments whitelisted and casted to the $request_format
578 * specs that get passed in when this class is instantiated
579 */
580 $input = $this->input();
581 $unfiltered_input = $this->input( false, false );
582 /**
583 * Filters the settings to be updated on the site.
584 *
585 * @module json-api
586 *
587 * @since 3.6.0
588 * @since 6.1.1 Added $unfiltered_input parameter.
589 *
590 * @param array $input Associative array of site settings to be updated.
591 * Cast and filtered based on documentation.
592 * @param array $unfiltered_input Associative array of site settings to be updated.
593 * Neither cast nor filtered. Contains raw input.
594 */
595 $input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
596
597 $blog_id = get_current_blog_id();
598
599 $jetpack_relatedposts_options = array();
600 $sharing_options = array();
601 $updated = array();
602
603 foreach ( $input as $key => $value ) {
604
605 if ( ! is_array( $value ) ) {
606 $value = trim( $value );
607 }
608
609 // preserve the raw value before unslashing the value. The slashes need to be preserved for date and time formats.
610 $raw_value = $value;
611 $value = wp_unslash( $value );
612
613 switch ( $key ) {
614
615 case 'default_ping_status':
616 case 'default_comment_status':
617 // settings are stored as closed|open.
618 $coerce_value = ( $value ) ? 'open' : 'closed';
619 if ( update_option( $key, $coerce_value ) ) {
620 $updated[ $key ] = $value;
621 }
622 break;
623 case 'launchpad_screen':
624 if ( in_array( $value, array( 'full', 'off', 'minimized' ), true ) ) {
625 if ( update_option( $key, $value ) ) {
626 $updated[ $key ] = $value;
627 }
628 }
629 break;
630 case 'jetpack_protect_whitelist':
631 if ( class_exists( 'Brute_Force_Protection_Shared_Functions' ) ) {
632 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $value );
633 if ( is_wp_error( $result ) ) {
634 return $result;
635 }
636 $updated[ $key ] = Brute_Force_Protection_Shared_Functions::format_allow_list();
637 }
638 break;
639 case 'jetpack_sync_non_public_post_stati':
640 Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
641 break;
642 case 'jetpack_search_enabled':
643 if ( $value ) {
644 Jetpack::activate_module( $blog_id, 'search' );
645 } else {
646 Jetpack::deactivate_module( $blog_id, 'search' );
647 }
648 $updated[ $key ] = (bool) $value;
649 break;
650 case 'jetpack_relatedposts_enabled':
651 case 'jetpack_relatedposts_show_context':
652 case 'jetpack_relatedposts_show_date':
653 case 'jetpack_relatedposts_show_thumbnails':
654 case 'jetpack_relatedposts_show_headline':
655 if ( ! $this->jetpack_relatedposts_supported() ) {
656 break;
657 }
658 if ( 'jetpack_relatedposts_enabled' === $key ) {
659 if ( $value ) {
660 Jetpack::activate_module( $blog_id, 'related-posts' );
661 } else {
662 Jetpack::deactivate_module( $blog_id, 'related-posts' );
663 }
664 }
665 $just_the_key = substr( $key, 21 );
666 $jetpack_relatedposts_options[ $just_the_key ] = $value;
667 break;
668
669 case 'social_notifications_like':
670 case 'social_notifications_reblog':
671 case 'social_notifications_subscribe':
672 // settings are stored as on|off.
673 $coerce_value = ( $value ) ? 'on' : 'off';
674 if ( update_option( $key, $coerce_value ) ) {
675 $updated[ $key ] = $value;
676 }
677 break;
678 case 'wga':
679 case 'jetpack_wga':
680 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^(UA-\d+-\d+)|(G-[A-Z0-9]+)$/i', $value['code'] ) ) {
681 return new WP_Error( 'invalid_code', 'Invalid UA ID' );
682 }
683
684 $is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
685 $option_name = $is_wpcom ? 'wga' : 'jetpack_wga';
686
687 $wga = get_option( $option_name, array() );
688 $wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics.
689
690 /**
691 * Allow newer versions of this endpoint to filter in additional fields for Google Analytics
692 *
693 * @since 5.4.0
694 *
695 * @param array $wga Associative array of existing Google Analytics settings.
696 * @param array $value Associative array of new Google Analytics settings passed to the endpoint.
697 */
698 $wga = apply_filters( 'site_settings_update_wga', $wga, $value );
699
700 if ( update_option( $option_name, $wga ) ) {
701 $updated[ $key ] = $value;
702 }
703
704 $enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
705
706 /** This action is documented in modules/widgets/social-media-icons.php */
707 do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
708
709 if ( $is_wpcom ) {
710 $business_plugins = WPCOM_Business_Plugins::instance();
711 $business_plugins->activate_plugin( 'wp-google-analytics' );
712 }
713 break;
714
715 case 'cloudflare_analytics':
716 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
717 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
718 }
719
720 if ( update_option( $key, $value ) ) {
721 $updated[ $key ] = $value;
722 }
723 break;
724
725 case 'jetpack_testimonial':
726 case 'jetpack_portfolio':
727 case 'jetpack_comment_likes_enabled':
728 // settings are stored as 1|0.
729 $coerce_value = (int) $value;
730 if ( update_option( $key, $coerce_value ) ) {
731 $updated[ $key ] = (bool) $value;
732 }
733 break;
734
735 case 'jetpack_testimonial_posts_per_page':
736 case 'jetpack_portfolio_posts_per_page':
737 // settings are stored as numeric.
738 $coerce_value = (int) $value;
739 if ( update_option( $key, $coerce_value ) ) {
740 $updated[ $key ] = $coerce_value;
741 }
742 break;
743
744 // Sharing options.
745 case 'sharing_button_style':
746 case 'sharing_show':
747 case 'sharing_open_links':
748 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
749 break;
750 case 'sharing_label':
751 $sharing_options[ $key ] = $value;
752 break;
753
754 // Keyring token option.
755 case 'eventbrite_api_token':
756 // These options can only be updated for sites hosted on WordPress.com.
757 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
758 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
759 if ( delete_option( $key ) ) {
760 $updated[ $key ] = null;
761 }
762 } elseif ( update_option( $key, $value ) ) {
763 $updated[ $key ] = (int) $value;
764 }
765 }
766 break;
767
768 case 'api_cache':
769 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
770 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
771 $updated[ $key ] = false;
772 }
773 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
774 $updated[ $key ] = true;
775 }
776 break;
777
778 case 'timezone_string':
779 /*
780 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
781 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
782 */
783 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
784 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
785 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
786 $updated['gmt_offset'] = $gmt_offset;
787 }
788
789 $value = '';
790 }
791
792 /*
793 * Always set timezone_string either with the given value or with an
794 * empty string
795 */
796 if ( update_option( $key, $value ) ) {
797 $updated[ $key ] = $value;
798 }
799 break;
800
801 case 'subscription_options':
802 $sanitized_value = (array) $value;
803 array_walk_recursive(
804 $sanitized_value,
805 function ( &$value ) {
806 $value = wp_kses(
807 $value,
808 array(
809 'a' => array(
810 'href' => array(),
811 ),
812 )
813 );
814 }
815 );
816
817 $has_correct_length = count( $sanitized_value ) === 2;
818 $required_keys_exist = array_key_exists( 'invitation', $sanitized_value )
819 && array_key_exists( 'comment_follow', $sanitized_value );
820 $is_valid = $has_correct_length && $required_keys_exist;
821
822 if ( $is_valid && update_option( $key, $sanitized_value ) ) {
823 $updated[ $key ] = $sanitized_value;
824 }
825 break;
826
827 case 'woocommerce_onboarding_profile':
828 // Allow boolean values but sanitize_text_field everything else.
829 $sanitized_value = (array) $value;
830 array_walk_recursive(
831 $sanitized_value,
832 function ( &$value ) {
833 if ( ! is_bool( $value ) ) {
834 $value = sanitize_text_field( $value );
835 }
836 }
837 );
838 if ( update_option( $key, $sanitized_value ) ) {
839 $updated[ $key ] = $sanitized_value;
840 }
841 break;
842
843 case 'woocommerce_store_address':
844 case 'woocommerce_store_address_2':
845 case 'woocommerce_store_city':
846 case 'woocommerce_default_country':
847 case 'woocommerce_store_postcode':
848 $sanitized_value = sanitize_text_field( $value );
849 if ( update_option( $key, $sanitized_value ) ) {
850 $updated[ $key ] = $sanitized_value;
851 }
852 break;
853
854 case 'date_format':
855 case 'time_format':
856 // settings are stored as strings.
857 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
858 $sanitized_value = sanitize_text_field( $raw_value );
859 if ( update_option( $key, $sanitized_value ) ) {
860 $updated[ $key ] = $sanitized_value;
861 }
862 break;
863
864 case 'start_of_week':
865 // setting is stored as int in 0-6 range (days of week).
866 $coerce_value = (int) $value;
867 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
868 if ( update_option( $key, $limit_value ) ) {
869 $updated[ $key ] = $limit_value;
870 }
871 break;
872
873 case 'site_icon':
874 /*
875 * settings are stored as deletable numeric (all empty
876 * values as delete intent), validated as media image
877 */
878 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
879 /**
880 * Fallback mechanism to clear a third party site icon setting. Can be used
881 * to unset the option when an API request instructs the site to remove the site icon.
882 *
883 * @module json-api
884 *
885 * @since 4.10
886 */
887 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
888 $updated[ $key ] = null;
889 }
890 } elseif ( is_numeric( $value ) ) {
891 $coerce_value = (int) $value;
892 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
893 $updated[ $key ] = $coerce_value;
894 }
895 }
896 break;
897
898 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
899 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
900 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
901 }
902
903 if ( ! is_string( $value ) ) {
904 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
905 }
906
907 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
908
909 if ( ! empty( $new_description ) ) {
910 $updated[ $key ] = $new_description;
911 }
912 break;
913
914 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
915 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
916 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
917 break;
918 }
919 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
920 }
921
922 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
923 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
924 }
925
926 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
927
928 if ( ! empty( $new_title_formats ) ) {
929 $updated[ $key ] = $new_title_formats;
930 }
931 break;
932
933 case 'verification_services_codes':
934 $verification_codes = jetpack_verification_validate( $value );
935
936 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
937 $updated[ $key ] = $verification_codes;
938 }
939 break;
940
941 case 'wpcom_publish_posts_with_markdown':
942 case 'wpcom_publish_comments_with_markdown':
943 $coerce_value = (bool) $value;
944 if ( update_option( $key, $coerce_value ) ) {
945 $updated[ $key ] = $coerce_value;
946 }
947 break;
948
949 case 'wpcom_gifting_subscription':
950 $coerce_value = (bool) $value;
951
952 /*
953 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
954 * a serialized value. Knowing that we can check if the option already exists.
955 */
956 $gift_toggle = get_option( $key );
957 if ( false === $gift_toggle ) {
958 // update_option will not create a new option if the initial value is false. So use add_option.
959 if ( add_option( $key, $coerce_value ) ) {
960 $updated[ $key ] = $coerce_value;
961 }
962 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
963 $updated[ $key ] = $coerce_value;
964 }
965 break;
966
967 case 'amp_is_enabled':
968 if ( function_exists( 'wpcom_update_amp_enabled' ) ) {
969 $saved = wpcom_update_amp_enabled( $blog_id, $value );
970 if ( $saved ) {
971 $updated[ $key ] = (bool) $value;
972 }
973 }
974 break;
975
976 case 'rss_use_excerpt':
977 update_option( 'rss_use_excerpt', (int) (bool) $value );
978 break;
979
980 case 'wpcom_subscription_emails_use_excerpt':
981 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
982 $updated[ $key ] = (bool) $value;
983 break;
984
985 case 'instant_search_enabled':
986 update_option( 'instant_search_enabled', (bool) $value );
987 $updated[ $key ] = (bool) $value;
988 break;
989
990 case 'lang_id':
991 /*
992 * Due to the fact that locale variants are set in a locale_variant option,
993 * changing locale from variant to primary
994 * would look like the same lang_id is being saved and update_option would return false,
995 * even though the correct options would be set by pre_update_option_lang_id,
996 * so we should always return lang_id as updated.
997 */
998 update_option( 'lang_id', (int) $value );
999 $updated[ $key ] = (int) $value;
1000 break;
1001
1002 case 'wpcom_featured_image_in_email':
1003 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1004 $updated[ $key ] = (int) (bool) $value;
1005 break;
1006
1007 case 'show_on_front':
1008 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1009 $updated[ $key ] = $value;
1010 }
1011 break;
1012
1013 case 'page_on_front':
1014 case 'page_for_posts':
1015 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1016 if ( delete_option( $key ) ) {
1017 $updated[ $key ] = null;
1018 }
1019
1020 break;
1021 }
1022
1023 if ( ! $this->is_valid_page_id( $value ) ) {
1024 break;
1025 }
1026
1027 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1028 $related_option_value = get_option( $related_option_key );
1029 if ( $related_option_value === $value ) {
1030 // page_on_front and page_for_posts are not allowed to be the same
1031 break;
1032 }
1033
1034 if ( update_option( $key, $value ) ) {
1035 $updated[ $key ] = $value;
1036 }
1037
1038 break;
1039
1040 default:
1041 // allow future versions of this endpoint to support additional settings keys.
1042 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1043 /**
1044 * Filter current site setting value to be updated.
1045 *
1046 * @module json-api
1047 *
1048 * @since 3.9.3
1049 *
1050 * @param mixed $response_item A single site setting value.
1051 */
1052 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value );
1053 $updated[ $key ] = $value;
1054 break;
1055 }
1056 // no worries, we've already whitelisted and casted arguments above.
1057 if ( update_option( $key, $value ) ) {
1058 $updated[ $key ] = $value;
1059 }
1060 }
1061 }
1062
1063 if ( count( $jetpack_relatedposts_options ) ) {
1064 // track new jetpack_relatedposts options against old.
1065 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1066
1067 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1068 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1069 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1070 }
1071
1072 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1073 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1074 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1075 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1076 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1077
1078 if ( $has_initialized_option || $has_updated_option ) {
1079 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1080 }
1081 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1082 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1083 }
1084 }
1085 }
1086 }
1087
1088 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1089 $ss = new Sharing_Service();
1090
1091 /*
1092 * Merge current values with updated, since Sharing_Service expects
1093 * all values to be included when updating
1094 */
1095 $current_sharing_options = $ss->get_global_options();
1096 foreach ( $current_sharing_options as $key => $val ) {
1097 if ( ! isset( $sharing_options[ $key ] ) ) {
1098 $sharing_options[ $key ] = $val;
1099 }
1100 }
1101
1102 $updated_social_options = $ss->set_global_options( $sharing_options );
1103
1104 if ( isset( $input['sharing_button_style'] ) ) {
1105 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1106 }
1107 if ( isset( $input['sharing_label'] ) ) {
1108 // Sharing_Service won't report label as updated if set to default.
1109 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1110 }
1111 if ( isset( $input['sharing_show'] ) ) {
1112 $updated['sharing_show'] = (array) $updated_social_options['show'];
1113 }
1114 if ( isset( $input['sharing_open_links'] ) ) {
1115 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1116 }
1117 }
1118
1119 return array(
1120 'updated' => $updated,
1121 );
1122 }
1123
1124 /**
1125 * Get the value of the wpcom_subscription_emails_use_excerpt option.
1126 * When the option is not set, it will return the value of the rss_use_excerpt option.
1127 *
1128 * @return bool
1129 */
1130 protected function get_wpcom_subscription_emails_use_excerpt_option() {
1131 $wpcom_subscription_emails_use_excerpt = get_option( 'wpcom_subscription_emails_use_excerpt', null );
1132
1133 if ( $wpcom_subscription_emails_use_excerpt === null ) {
1134 $rss_use_excerpt = get_option( 'rss_use_excerpt', null );
1135 $wpcom_subscription_emails_use_excerpt = $rss_use_excerpt === null ? false : $rss_use_excerpt;
1136 }
1137
1138 return (bool) $wpcom_subscription_emails_use_excerpt;
1139 }
1140
1141 /**
1142 * Check if the given value is a valid page ID for the current site.
1143 *
1144 * @param mixed $value The value to check.
1145 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1146 */
1147 protected function is_valid_page_id( $value ) {
1148 $all_page_ids = get_all_page_ids();
1149
1150 $valid_page_id = false;
1151 foreach ( $all_page_ids as $page_id ) {
1152 if ( $page_id === (string) $value ) {
1153 $valid_page_id = true;
1154 break;
1155 }
1156 }
1157
1158 return $valid_page_id;
1159 }
1160 }
1161