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