PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.3.2
Jetpack – WP Security, Backup, Speed, & Growth v12.3.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / json-endpoints / class.wpcom-json-api-site-settings-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 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 3 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 3 years ago class.wpcom-json-api-list-shortcodes-endpoint.php 4 years ago class.wpcom-json-api-list-terms-endpoint.php 3 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 3 years ago class.wpcom-json-api-render-embed-reversal-endpoint.php 3 years ago class.wpcom-json-api-render-endpoint.php 3 years ago class.wpcom-json-api-render-shortcode-endpoint.php 3 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 3 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 3 years ago class.wpcom-json-api-upload-media-endpoint.php 3 years ago class.wpcom-json-api-upload-media-v1-1-endpoint.php 3 years ago
class.wpcom-json-api-site-settings-endpoint.php
1177 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_reader_views_enabled' => (bool) get_option( 'wpcom_reader_views_enabled', true ),
446 'wpcom_subscription_emails_use_excerpt' => $this->get_wpcom_subscription_emails_use_excerpt_option(),
447 'show_on_front' => (string) get_option( 'show_on_front' ),
448 'page_on_front' => (string) get_option( 'page_on_front' ),
449 'page_for_posts' => (string) get_option( 'page_for_posts' ),
450 'subscription_options' => (array) get_option( 'subscription_options' ),
451 );
452
453 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
454 $response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::is_posting_enabled();
455 $response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::is_commenting_enabled();
456
457 // WPCOM-specific Infinite Scroll Settings.
458 if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
459 /**
460 * Clear the cached copy of widget info so it's pulled fresh from blog options.
461 * It was primed during the initial load under the __REST API site__'s context.
462 *
463 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
464 */
465 $GLOBALS['_wp_sidebars_widgets'] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
466
467 $infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
468 $response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && 'scroll' === $infinite_scroll_settings->type;
469 if ( $infinite_scroll_settings->footer_widgets || 'click' === $infinite_scroll_settings->requested_type ) {
470 // The blog has footer widgets -- infinite scroll is blocked.
471 $response[ $key ]['infinite_scroll_blocked'] = 'footer';
472 } else {
473 $response[ $key ]['infinite_scroll_blocked'] = false;
474 }
475 }
476 }
477
478 // allow future versions of this endpoint to support additional settings keys.
479 /**
480 * Filter the current site setting in the returned response.
481 *
482 * @module json-api
483 *
484 * @since 3.9.3
485 *
486 * @param mixed $response_item A single site setting.
487 */
488 $response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ] );
489
490 if ( class_exists( 'Sharing_Service' ) ) {
491 $ss = new Sharing_Service();
492 $sharing = $ss->get_global_options();
493 $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
494 $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
495 $response[ $key ]['sharing_show'] = (array) $sharing['show'];
496 $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
497 }
498
499 $response[ $key ]['jetpack_protect_whitelist'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
500
501 if ( ! current_user_can( 'edit_posts' ) ) {
502 unset( $response[ $key ] );
503 }
504 break;
505 }
506 }
507 return $response;
508 }
509
510 /**
511 * Get the default value for the wpcom_gifting_subscription option.
512 * The default value is the inverse of the plan's auto_renew setting.
513 *
514 * @return bool
515 */
516 protected function get_wpcom_gifting_subscription_default() {
517 if ( function_exists( 'wpcom_get_site_purchases' ) && function_exists( 'wpcom_purchase_has_feature' ) ) {
518 $purchases = wpcom_get_site_purchases();
519
520 foreach ( $purchases as $purchase ) {
521 if ( wpcom_purchase_has_feature( $purchase, \WPCOM_Features::SUBSCRIPTION_GIFTING ) ) {
522 /*
523 * We set default value as false when expiration date not match the following:
524 * - 54 days before the annual plan expiration.
525 * - 5 days before the monthly plan expiration.
526 * This is to match the gifting banner logic.
527 */
528 $days_of_warning = false !== strpos( $purchase->product_slug, 'monthly' ) ? 5 : 54;
529 $seconds_until_expiration = strtotime( $purchase->expiry_date ) - time();
530 if ( $seconds_until_expiration >= $days_of_warning * DAY_IN_SECONDS ) {
531 return false;
532 }
533
534 // We set default to the inverse of auto-renew.
535 if ( isset( $purchase->auto_renew ) ) {
536 return ! $purchase->auto_renew;
537 } elseif ( isset( $purchase->user_allows_auto_renew ) ) {
538 return ! $purchase->user_allows_auto_renew;
539 }
540 }
541 }
542 }
543 return false;
544 }
545
546 /**
547 * Get locale.
548 *
549 * @param string $key Language.
550 */
551 protected function get_locale( $key ) {
552 if ( 'lang' === $key ) {
553 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
554 return (string) get_blog_lang_code();
555 } else {
556 return get_locale();
557 }
558 }
559
560 return false;
561 }
562
563 /**
564 * Get GA tracking code.
565 */
566 protected function get_google_analytics() {
567 $option_name = defined( 'IS_WPCOM' ) && IS_WPCOM ? 'wga' : 'jetpack_wga';
568 return get_option( $option_name );
569 }
570
571 /**
572 * Updates site settings for authorized users
573 *
574 * @return array
575 */
576 public function update_settings() {
577 /*
578 * $this->input() retrieves posted arguments whitelisted and casted to the $request_format
579 * specs that get passed in when this class is instantiated
580 */
581 $input = $this->input();
582 $unfiltered_input = $this->input( false, false );
583 /**
584 * Filters the settings to be updated on the site.
585 *
586 * @module json-api
587 *
588 * @since 3.6.0
589 * @since 6.1.1 Added $unfiltered_input parameter.
590 *
591 * @param array $input Associative array of site settings to be updated.
592 * Cast and filtered based on documentation.
593 * @param array $unfiltered_input Associative array of site settings to be updated.
594 * Neither cast nor filtered. Contains raw input.
595 */
596 $input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
597
598 $blog_id = get_current_blog_id();
599
600 $jetpack_relatedposts_options = array();
601 $sharing_options = array();
602 $updated = array();
603
604 foreach ( $input as $key => $value ) {
605
606 if ( ! is_array( $value ) ) {
607 $value = trim( $value );
608 }
609
610 // preserve the raw value before unslashing the value. The slashes need to be preserved for date and time formats.
611 $raw_value = $value;
612 $value = wp_unslash( $value );
613
614 switch ( $key ) {
615
616 case 'default_ping_status':
617 case 'default_comment_status':
618 // settings are stored as closed|open.
619 $coerce_value = ( $value ) ? 'open' : 'closed';
620 if ( update_option( $key, $coerce_value ) ) {
621 $updated[ $key ] = $value;
622 }
623 break;
624 case 'launchpad_screen':
625 if ( in_array( $value, array( 'full', 'off', 'minimized' ), true ) ) {
626 if ( update_option( $key, $value ) ) {
627 $updated[ $key ] = $value;
628 }
629 }
630 break;
631 case 'jetpack_protect_whitelist':
632 if ( class_exists( 'Brute_Force_Protection_Shared_Functions' ) ) {
633 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $value );
634 if ( is_wp_error( $result ) ) {
635 return $result;
636 }
637 $updated[ $key ] = Brute_Force_Protection_Shared_Functions::format_allow_list();
638 }
639 break;
640 case 'jetpack_sync_non_public_post_stati':
641 Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
642 break;
643 case 'jetpack_search_enabled':
644 if ( $value ) {
645 Jetpack::activate_module( $blog_id, 'search' );
646 } else {
647 Jetpack::deactivate_module( $blog_id, 'search' );
648 }
649 $updated[ $key ] = (bool) $value;
650 break;
651 case 'jetpack_relatedposts_enabled':
652 case 'jetpack_relatedposts_show_context':
653 case 'jetpack_relatedposts_show_date':
654 case 'jetpack_relatedposts_show_thumbnails':
655 case 'jetpack_relatedposts_show_headline':
656 if ( ! $this->jetpack_relatedposts_supported() ) {
657 break;
658 }
659 if ( 'jetpack_relatedposts_enabled' === $key ) {
660 if ( $value ) {
661 Jetpack::activate_module( $blog_id, 'related-posts' );
662 } else {
663 Jetpack::deactivate_module( $blog_id, 'related-posts' );
664 }
665 }
666 $just_the_key = substr( $key, 21 );
667 $jetpack_relatedposts_options[ $just_the_key ] = $value;
668 break;
669
670 case 'social_notifications_like':
671 case 'social_notifications_reblog':
672 case 'social_notifications_subscribe':
673 // settings are stored as on|off.
674 $coerce_value = ( $value ) ? 'on' : 'off';
675 if ( update_option( $key, $coerce_value ) ) {
676 $updated[ $key ] = $value;
677 }
678 break;
679 case 'wga':
680 case 'jetpack_wga':
681 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^(UA-\d+-\d+)|(G-[A-Z0-9]+)$/i', $value['code'] ) ) {
682 return new WP_Error( 'invalid_code', 'Invalid UA ID' );
683 }
684
685 $is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
686 $option_name = $is_wpcom ? 'wga' : 'jetpack_wga';
687
688 $wga = get_option( $option_name, array() );
689 $wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics.
690
691 /**
692 * Allow newer versions of this endpoint to filter in additional fields for Google Analytics
693 *
694 * @since 5.4.0
695 *
696 * @param array $wga Associative array of existing Google Analytics settings.
697 * @param array $value Associative array of new Google Analytics settings passed to the endpoint.
698 */
699 $wga = apply_filters( 'site_settings_update_wga', $wga, $value );
700
701 if ( update_option( $option_name, $wga ) ) {
702 $updated[ $key ] = $value;
703 }
704
705 $enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
706
707 /** This action is documented in modules/widgets/social-media-icons.php */
708 do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
709
710 if ( $is_wpcom ) {
711 $business_plugins = WPCOM_Business_Plugins::instance();
712 $business_plugins->activate_plugin( 'wp-google-analytics' );
713 }
714 break;
715
716 case 'cloudflare_analytics':
717 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
718 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
719 }
720
721 if ( update_option( $key, $value ) ) {
722 $updated[ $key ] = $value;
723 }
724 break;
725
726 case 'jetpack_testimonial':
727 case 'jetpack_portfolio':
728 case 'jetpack_comment_likes_enabled':
729 case 'wpcom_reader_views_enabled':
730 // settings are stored as 1|0.
731 $coerce_value = (int) $value;
732 if ( update_option( $key, $coerce_value ) ) {
733 $updated[ $key ] = (bool) $value;
734 }
735 break;
736
737 case 'jetpack_testimonial_posts_per_page':
738 case 'jetpack_portfolio_posts_per_page':
739 // settings are stored as numeric.
740 $coerce_value = (int) $value;
741 if ( update_option( $key, $coerce_value ) ) {
742 $updated[ $key ] = $coerce_value;
743 }
744 break;
745
746 // Sharing options.
747 case 'sharing_button_style':
748 case 'sharing_show':
749 case 'sharing_open_links':
750 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
751 break;
752 case 'sharing_label':
753 $sharing_options[ $key ] = $value;
754 break;
755
756 // Keyring token option.
757 case 'eventbrite_api_token':
758 // These options can only be updated for sites hosted on WordPress.com.
759 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
760 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
761 if ( delete_option( $key ) ) {
762 $updated[ $key ] = null;
763 }
764 } elseif ( update_option( $key, $value ) ) {
765 $updated[ $key ] = (int) $value;
766 }
767 }
768 break;
769
770 case 'api_cache':
771 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
772 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
773 $updated[ $key ] = false;
774 }
775 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
776 $updated[ $key ] = true;
777 }
778 break;
779
780 case 'timezone_string':
781 /*
782 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
783 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
784 */
785 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
786 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
787 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
788 $updated['gmt_offset'] = $gmt_offset;
789 }
790
791 $value = '';
792 }
793
794 /*
795 * Always set timezone_string either with the given value or with an
796 * empty string
797 */
798 if ( update_option( $key, $value ) ) {
799 $updated[ $key ] = $value;
800 }
801 break;
802
803 case 'subscription_options':
804 if ( ! is_array( $value ) ) {
805 break;
806 }
807
808 $allowed_keys = array( 'invitation', 'comment_follow' );
809 $filtered_value = array_filter(
810 $value,
811 function ( $key ) use ( $allowed_keys ) {
812 return in_array( $key, $allowed_keys, true );
813 },
814 ARRAY_FILTER_USE_KEY
815 );
816
817 if ( empty( $filtered_value ) ) {
818 break;
819 }
820
821 array_walk_recursive(
822 $filtered_value,
823 function ( &$value ) {
824 $value = wp_kses(
825 $value,
826 array(
827 'a' => array(
828 'href' => array(),
829 ),
830 )
831 );
832 }
833 );
834
835 $old_subscription_options = get_option( 'subscription_options' );
836 $new_subscription_options = array_merge( $old_subscription_options, $filtered_value );
837
838 if ( update_option( $key, $new_subscription_options ) ) {
839 $updated[ $key ] = $filtered_value;
840 }
841 break;
842
843 case 'woocommerce_onboarding_profile':
844 // Allow boolean values but sanitize_text_field everything else.
845 $sanitized_value = (array) $value;
846 array_walk_recursive(
847 $sanitized_value,
848 function ( &$value ) {
849 if ( ! is_bool( $value ) ) {
850 $value = sanitize_text_field( $value );
851 }
852 }
853 );
854 if ( update_option( $key, $sanitized_value ) ) {
855 $updated[ $key ] = $sanitized_value;
856 }
857 break;
858
859 case 'woocommerce_store_address':
860 case 'woocommerce_store_address_2':
861 case 'woocommerce_store_city':
862 case 'woocommerce_default_country':
863 case 'woocommerce_store_postcode':
864 $sanitized_value = sanitize_text_field( $value );
865 if ( update_option( $key, $sanitized_value ) ) {
866 $updated[ $key ] = $sanitized_value;
867 }
868 break;
869
870 case 'date_format':
871 case 'time_format':
872 // settings are stored as strings.
873 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
874 $sanitized_value = sanitize_text_field( $raw_value );
875 if ( update_option( $key, $sanitized_value ) ) {
876 $updated[ $key ] = $sanitized_value;
877 }
878 break;
879
880 case 'start_of_week':
881 // setting is stored as int in 0-6 range (days of week).
882 $coerce_value = (int) $value;
883 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
884 if ( update_option( $key, $limit_value ) ) {
885 $updated[ $key ] = $limit_value;
886 }
887 break;
888
889 case 'site_icon':
890 /*
891 * settings are stored as deletable numeric (all empty
892 * values as delete intent), validated as media image
893 */
894 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
895 /**
896 * Fallback mechanism to clear a third party site icon setting. Can be used
897 * to unset the option when an API request instructs the site to remove the site icon.
898 *
899 * @module json-api
900 *
901 * @since 4.10
902 */
903 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
904 $updated[ $key ] = null;
905 }
906 } elseif ( is_numeric( $value ) ) {
907 $coerce_value = (int) $value;
908 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
909 $updated[ $key ] = $coerce_value;
910 }
911 }
912 break;
913
914 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
915 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
916 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
917 }
918
919 if ( ! is_string( $value ) ) {
920 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
921 }
922
923 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
924
925 if ( ! empty( $new_description ) ) {
926 $updated[ $key ] = $new_description;
927 }
928 break;
929
930 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
931 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
932 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
933 break;
934 }
935 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
936 }
937
938 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
939 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
940 }
941
942 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
943
944 if ( ! empty( $new_title_formats ) ) {
945 $updated[ $key ] = $new_title_formats;
946 }
947 break;
948
949 case 'verification_services_codes':
950 $verification_codes = jetpack_verification_validate( $value );
951
952 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
953 $updated[ $key ] = $verification_codes;
954 }
955 break;
956
957 case 'wpcom_publish_posts_with_markdown':
958 case 'wpcom_publish_comments_with_markdown':
959 $coerce_value = (bool) $value;
960 if ( update_option( $key, $coerce_value ) ) {
961 $updated[ $key ] = $coerce_value;
962 }
963 break;
964
965 case 'wpcom_gifting_subscription':
966 $coerce_value = (bool) $value;
967
968 /*
969 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
970 * a serialized value. Knowing that we can check if the option already exists.
971 */
972 $gift_toggle = get_option( $key );
973 if ( false === $gift_toggle ) {
974 // update_option will not create a new option if the initial value is false. So use add_option.
975 if ( add_option( $key, $coerce_value ) ) {
976 $updated[ $key ] = $coerce_value;
977 }
978 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
979 $updated[ $key ] = $coerce_value;
980 }
981 break;
982
983 case 'amp_is_enabled':
984 if ( function_exists( 'wpcom_update_amp_enabled' ) ) {
985 $saved = wpcom_update_amp_enabled( $blog_id, $value );
986 if ( $saved ) {
987 $updated[ $key ] = (bool) $value;
988 }
989 }
990 break;
991
992 case 'rss_use_excerpt':
993 update_option( 'rss_use_excerpt', (int) (bool) $value );
994 break;
995
996 case 'wpcom_subscription_emails_use_excerpt':
997 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
998 $updated[ $key ] = (bool) $value;
999 break;
1000
1001 case 'instant_search_enabled':
1002 update_option( 'instant_search_enabled', (bool) $value );
1003 $updated[ $key ] = (bool) $value;
1004 break;
1005
1006 case 'lang_id':
1007 /*
1008 * Due to the fact that locale variants are set in a locale_variant option,
1009 * changing locale from variant to primary
1010 * would look like the same lang_id is being saved and update_option would return false,
1011 * even though the correct options would be set by pre_update_option_lang_id,
1012 * so we should always return lang_id as updated.
1013 */
1014 update_option( 'lang_id', (int) $value );
1015 $updated[ $key ] = (int) $value;
1016 break;
1017
1018 case 'wpcom_featured_image_in_email':
1019 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1020 $updated[ $key ] = (int) (bool) $value;
1021 break;
1022
1023 case 'show_on_front':
1024 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1025 $updated[ $key ] = $value;
1026 }
1027 break;
1028
1029 case 'page_on_front':
1030 case 'page_for_posts':
1031 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1032 if ( delete_option( $key ) ) {
1033 $updated[ $key ] = null;
1034 }
1035
1036 break;
1037 }
1038
1039 if ( ! $this->is_valid_page_id( $value ) ) {
1040 break;
1041 }
1042
1043 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1044 $related_option_value = get_option( $related_option_key );
1045 if ( $related_option_value === $value ) {
1046 // page_on_front and page_for_posts are not allowed to be the same
1047 break;
1048 }
1049
1050 if ( update_option( $key, $value ) ) {
1051 $updated[ $key ] = $value;
1052 }
1053
1054 break;
1055
1056 default:
1057 // allow future versions of this endpoint to support additional settings keys.
1058 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1059 /**
1060 * Filter current site setting value to be updated.
1061 *
1062 * @module json-api
1063 *
1064 * @since 3.9.3
1065 *
1066 * @param mixed $response_item A single site setting value.
1067 */
1068 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value );
1069 $updated[ $key ] = $value;
1070 break;
1071 }
1072 // no worries, we've already whitelisted and casted arguments above.
1073 if ( update_option( $key, $value ) ) {
1074 $updated[ $key ] = $value;
1075 }
1076 }
1077 }
1078
1079 if ( $jetpack_relatedposts_options !== array() ) {
1080 // track new jetpack_relatedposts options against old.
1081 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1082
1083 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1084 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1085 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1086 }
1087
1088 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1089 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1090 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1091 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1092 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1093
1094 if ( $has_initialized_option || $has_updated_option ) {
1095 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1096 }
1097 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1098 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1099 }
1100 }
1101 }
1102 }
1103
1104 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1105 $ss = new Sharing_Service();
1106
1107 /*
1108 * Merge current values with updated, since Sharing_Service expects
1109 * all values to be included when updating
1110 */
1111 $current_sharing_options = $ss->get_global_options();
1112 foreach ( $current_sharing_options as $key => $val ) {
1113 if ( ! isset( $sharing_options[ $key ] ) ) {
1114 $sharing_options[ $key ] = $val;
1115 }
1116 }
1117
1118 $updated_social_options = $ss->set_global_options( $sharing_options );
1119
1120 if ( isset( $input['sharing_button_style'] ) ) {
1121 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1122 }
1123 if ( isset( $input['sharing_label'] ) ) {
1124 // Sharing_Service won't report label as updated if set to default.
1125 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1126 }
1127 if ( isset( $input['sharing_show'] ) ) {
1128 $updated['sharing_show'] = (array) $updated_social_options['show'];
1129 }
1130 if ( isset( $input['sharing_open_links'] ) ) {
1131 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1132 }
1133 }
1134
1135 return array(
1136 'updated' => $updated,
1137 );
1138 }
1139
1140 /**
1141 * Get the value of the wpcom_subscription_emails_use_excerpt option.
1142 * When the option is not set, it will return the value of the rss_use_excerpt option.
1143 *
1144 * @return bool
1145 */
1146 protected function get_wpcom_subscription_emails_use_excerpt_option() {
1147 $wpcom_subscription_emails_use_excerpt = get_option( 'wpcom_subscription_emails_use_excerpt', null );
1148
1149 if ( $wpcom_subscription_emails_use_excerpt === null ) {
1150 $rss_use_excerpt = get_option( 'rss_use_excerpt', null );
1151 $wpcom_subscription_emails_use_excerpt = $rss_use_excerpt === null ? false : $rss_use_excerpt;
1152 }
1153
1154 return (bool) $wpcom_subscription_emails_use_excerpt;
1155 }
1156
1157 /**
1158 * Check if the given value is a valid page ID for the current site.
1159 *
1160 * @param mixed $value The value to check.
1161 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1162 */
1163 protected function is_valid_page_id( $value ) {
1164 $all_page_ids = get_all_page_ids();
1165
1166 $valid_page_id = false;
1167 foreach ( $all_page_ids as $page_id ) {
1168 if ( $page_id === (string) $value ) {
1169 $valid_page_id = true;
1170 break;
1171 }
1172 }
1173
1174 return $valid_page_id;
1175 }
1176 }
1177