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