PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.8.2
Jetpack – WP Security, Backup, Speed, & Growth v13.8.2
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / json-endpoints / class.wpcom-json-api-site-settings-endpoint.php
jetpack / json-endpoints Last commit date
jetpack 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 2 years ago class.wpcom-json-api-bulk-update-comments-endpoint.php 3 years ago class.wpcom-json-api-comment-endpoint.php 1 year 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 2 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 1 year ago class.wpcom-json-api-get-site-v1-2-endpoint.php 1 year 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 2 years ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 2 years ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 2 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 2 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 1 year ago class.wpcom-json-api-site-settings-v1-2-endpoint.php 2 years ago class.wpcom-json-api-site-settings-v1-3-endpoint.php 1 year ago class.wpcom-json-api-site-settings-v1-4-endpoint.php 1 year ago class.wpcom-json-api-site-user-endpoint.php 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 2 years ago class.wpcom-json-api-update-post-v1-2-endpoint.php 1 year 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
1338 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 'migration_source_site_domain' => '(string) The source site URL, from the migration flow',
49 'in_site_migration_flow' => '(string) The migration flow the site is in',
50 'blogname' => '(string) Blog name',
51 'blogdescription' => '(string) Blog description',
52 'default_pingback_flag' => '(bool) Notify blogs linked from article?',
53 'default_ping_status' => '(bool) Allow link notifications from other blogs?',
54 'default_comment_status' => '(bool) Allow comments on new articles?',
55 'blog_public' => '(string) Site visibility; -1: private, 0: discourage search engines, 1: allow search engines',
56 'wpcom_data_sharing_opt_out' => '(bool) Did the site opt out of sharing public content with third parties and research partners?',
57 'jetpack_sync_non_public_post_stati' => '(bool) allow sync of post and pages with non-public posts stati',
58 'jetpack_relatedposts_enabled' => '(bool) Enable related posts?',
59 'jetpack_relatedposts_show_context' => '(bool) Show post\'s tags and category in related posts?',
60 'jetpack_relatedposts_show_date' => '(bool) Show date in related posts?',
61 'jetpack_relatedposts_show_headline' => '(bool) Show headline in related posts?',
62 'jetpack_relatedposts_show_thumbnails' => '(bool) Show thumbnails in related posts?',
63 'jetpack_protect_whitelist' => '(array) List of IP addresses to always allow',
64 'instant_search_enabled' => '(bool) Enable the new Jetpack Instant Search interface',
65 'jetpack_search_enabled' => '(bool) Enable Jetpack Search',
66 'jetpack_search_supported' => '(bool) Jetpack Search is supported',
67 'infinite_scroll' => '(bool) Support infinite scroll of posts?',
68 'default_category' => '(int) Default post category',
69 'default_post_format' => '(string) Default post format',
70 'require_name_email' => '(bool) Require comment authors to fill out name and email?',
71 'comment_registration' => '(bool) Require users to be registered and logged in to comment?',
72 'close_comments_for_old_posts' => '(bool) Automatically close comments on old posts?',
73 'close_comments_days_old' => '(int) Age at which to close comments',
74 'thread_comments' => '(bool) Enable threaded comments?',
75 'thread_comments_depth' => '(int) Depth to thread comments',
76 'page_comments' => '(bool) Break comments into pages?',
77 'comments_per_page' => '(int) Number of comments to display per page',
78 'default_comments_page' => '(string) newest|oldest Which page of comments to display first',
79 'comment_order' => '(string) asc|desc Order to display comments within page',
80 'comments_notify' => '(bool) Email me when someone comments?',
81 'moderation_notify' => '(bool) Email me when a comment is helf for moderation?',
82 'social_notifications_like' => '(bool) Email me when someone likes my post?',
83 'social_notifications_reblog' => '(bool) Email me when someone reblogs my post?',
84 'social_notifications_subscribe' => '(bool) Email me when someone subscribes to my blog?',
85 'comment_moderation' => '(bool) Moderate comments for manual approval?',
86 'comment_previously_approved' => '(bool) Moderate comments unless author has a previously-approved comment?',
87 'comment_max_links' => '(int) Moderate comments that contain X or more links',
88 'moderation_keys' => '(string) Words or phrases that trigger comment moderation, one per line',
89 'disallowed_keys' => '(string) Words or phrases that mark comment spam, one per line',
90 'lang_id' => '(int) ID for language blog is written in',
91 'wga' => '(array) Google Analytics Settings',
92 'disabled_likes' => '(bool) Are likes globally disabled (they can still be turned on per post)?',
93 'disabled_reblogs' => '(bool) Are reblogs disabled on posts?',
94 'jetpack_comment_likes_enabled' => '(bool) Are comment likes enabled for all comments?',
95 'sharing_button_style' => '(string) Style to use for sharing buttons (icon-text, icon, text, or official)',
96 'sharing_label' => '(string) Label to use for sharing buttons, e.g. "Share this:"',
97 'sharing_show' => '(string|array:string) Post type or array of types where sharing buttons are to be displayed',
98 'sharing_open_links' => '(string) Link target for sharing buttons (same or new)',
99 'twitter_via' => '(string) Twitter username to include in tweets when people share using the Twitter button',
100 'jetpack-twitter-cards-site-tag' => '(string) The Twitter username of the owner of the site\'s domain.',
101 'eventbrite_api_token' => '(int) The Keyring token ID for an Eventbrite token to associate with the site',
102 'timezone_string' => '(string) PHP-compatible timezone string like \'UTC-5\'',
103 'gmt_offset' => '(int) Site offset from UTC in hours',
104 'date_format' => '(string) PHP Date-compatible date format',
105 'time_format' => '(string) PHP Date-compatible time format',
106 'start_of_week' => '(int) Starting day of week (0 = Sunday, 6 = Saturday)',
107 'jetpack_testimonial' => '(bool) Whether testimonial custom post type is enabled for the site',
108 'jetpack_testimonial_posts_per_page' => '(int) Number of testimonials to show per page',
109 'jetpack_portfolio' => '(bool) Whether portfolio custom post type is enabled for the site',
110 'jetpack_portfolio_posts_per_page' => '(int) Number of portfolio projects to show per page',
111 Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => '(string) The seo meta description for the site.',
112 Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => '(array) SEO meta title formats. Allowed keys: front_page, posts, pages, groups, archives',
113 'verification_services_codes' => '(array) Website verification codes. Allowed keys: google, pinterest, bing, yandex, facebook',
114 'markdown_supported' => '(bool) Whether markdown is supported for this site',
115 'wpcom_publish_posts_with_markdown' => '(bool) Whether markdown is enabled for posts',
116 'wpcom_publish_comments_with_markdown' => '(bool) Whether markdown is enabled for comments',
117 'site_icon' => '(int) Media attachment ID to use as site icon. Set to zero or an otherwise empty value to clear',
118 'api_cache' => '(bool) Turn on/off the Jetpack JSON API cache',
119 'posts_per_page' => '(int) Number of posts to show on blog pages',
120 'posts_per_rss' => '(int) Number of posts to show in the RSS feed',
121 'rss_use_excerpt' => '(bool) Whether the RSS feed will use post excerpts',
122 'launchpad_screen' => '(string) Whether or not launchpad is presented and what size it will be',
123 'sm_enabled' => '(bool) Whether the newsletter subscribe modal is enabled',
124 'jetpack_subscribe_overlay_enabled' => '(bool) Whether the newsletter subscribe overlay is enabled',
125 'jetpack_subscriptions_subscribe_post_end_enabled' => '(bool) Whether the Subscribe block at the end of each post placement is enabled',
126 'jetpack_subscriptions_login_navigation_enabled' => '(bool) Whether the Subscriber Login block navigation placement is enabled',
127 'jetpack_subscriptions_subscribe_navigation_enabled' => '(Bool) Whether the Subscribe block navigation placement is enabled',
128 'wpcom_ai_site_prompt' => '(string) User input in the AI site prompt',
129 'jetpack_waf_automatic_rules' => '(bool) Whether the WAF should enforce automatic firewall rules',
130 'jetpack_waf_ip_allow_list' => '(string) List of IP addresses to always allow',
131 'jetpack_waf_ip_allow_list_enabled' => '(bool) Whether the IP allow list is enabled',
132 'jetpack_waf_ip_block_list' => '(string) List of IP addresses the WAF should always block',
133 'jetpack_waf_ip_block_list_enabled' => '(bool) Whether the IP block list is enabled',
134 'jetpack_waf_share_data' => '(bool) Whether the WAF should share basic data with Jetpack',
135 'jetpack_waf_share_debug_data' => '(bool) Whether the WAF should share debug data with Jetpack',
136 'jetpack_waf_automatic_rules_last_updated_timestamp' => '(int) Timestamp of the last time the automatic rules were updated',
137 ),
138
139 'response_format' => array(
140 'updated' => '(array)',
141 ),
142
143 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/settings',
144 )
145 );
146
147 /**
148 * Manage Site settings endpoint.
149 */
150 class WPCOM_JSON_API_Site_Settings_Endpoint extends WPCOM_JSON_API_Endpoint {
151
152 /**
153 * Site format.
154 *
155 * @var array
156 */
157 public static $site_format = array(
158 'ID' => '(int) Site ID',
159 'name' => '(string) Title of site',
160 'description' => '(string) Tagline or description of site',
161 'URL' => '(string) Full URL to the site',
162 'lang' => '(string) Primary language code of the site',
163 'locale_variant' => '(string) Locale variant code for the site, if set',
164 'settings' => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
165 );
166
167 /**
168 * Endpoint response
169 *
170 * GET /sites/%s/settings
171 * POST /sites/%s/settings
172 *
173 * @param string $path Path.
174 * @param int $blog_id Blog ID.
175 */
176 public function callback( $path = '', $blog_id = 0 ) {
177 $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
178 if ( is_wp_error( $blog_id ) ) {
179 return $blog_id;
180 }
181
182 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
183 // Source & include the infinite scroll compatibility files prior to loading theme functions.
184 add_filter( 'restapi_theme_action_copy_dirs', array( 'WPCOM_JSON_API_Site_Settings_Endpoint', 'wpcom_restapi_copy_theme_plugin_actions' ) );
185 $this->load_theme_functions();
186 }
187
188 if ( ! is_user_logged_in() ) {
189 return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
190 } elseif ( ! current_user_can( 'manage_options' ) ) {
191 return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
192 }
193
194 if ( 'GET' === $this->api->method ) {
195 /**
196 * Fires on each GET request to a specific endpoint.
197 *
198 * @module json-api
199 *
200 * @since 3.2.0
201 *
202 * @param string sites.
203 */
204 do_action( 'wpcom_json_api_objects', 'sites' );
205 return $this->get_settings_response();
206 } elseif ( 'POST' === $this->api->method ) {
207 return $this->update_settings();
208 } else {
209 return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
210 }
211 }
212
213 /**
214 * Includes additional theme-specific files to be included in REST API theme
215 * context loading action copying.
216 *
217 * @see WPCOM_JSON_API_Endpoint#load_theme_functions
218 * @see the_neverending_home_page_theme_support
219 *
220 * @param array $copy_dirs Array of files to be included in theme context.
221 */
222 public static function wpcom_restapi_copy_theme_plugin_actions( $copy_dirs ) {
223 $theme_name = get_stylesheet();
224 $default_file_name = WP_CONTENT_DIR . "/mu-plugins/infinity/themes/{$theme_name}.php";
225
226 /**
227 * Filter the path to the Infinite Scroll compatibility file.
228 *
229 * @module infinite-scroll
230 *
231 * @since 2.0.0
232 *
233 * @param string $str IS compatibility file path.
234 * @param string $theme_name Theme name.
235 */
236 $customization_file = apply_filters( 'infinite_scroll_customization_file', $default_file_name, $theme_name );
237
238 if ( is_readable( $customization_file ) ) {
239 require_once $customization_file;
240 $copy_dirs[] = $customization_file;
241 }
242
243 return $copy_dirs;
244 }
245
246 /**
247 * Determines whether jetpack_relatedposts is supported
248 *
249 * @return bool
250 */
251 public function jetpack_relatedposts_supported() {
252 $wpcom_related_posts_theme_blacklist = array(
253 'Expound',
254 'Traveler',
255 'Opti',
256 'Currents',
257 );
258 return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist, true ) );
259 }
260
261 /**
262 * Returns category details
263 *
264 * @param WP_Term $category Category object.
265 *
266 * @return array
267 */
268 public function get_category_details( $category ) {
269 return array(
270 'value' => $category->term_id,
271 'name' => $category->name,
272 );
273 }
274
275 /**
276 * Returns an option value as the result of the callable being applied to
277 * it if a value is set, otherwise null.
278 *
279 * @param string $option_name Option name.
280 * @param callable $cast_callable Callable to invoke on option value.
281 *
282 * @return int|null Numeric option value or null.
283 */
284 protected function get_cast_option_value_or_null( $option_name, $cast_callable ) {
285 $option_value = get_option( $option_name, null );
286 if ( $option_value === null ) {
287 return $option_value;
288 }
289
290 return call_user_func( $cast_callable, $option_value );
291 }
292
293 /**
294 * Collects the necessary information to return for a get settings response.
295 *
296 * @return array
297 */
298 public function get_settings_response() {
299 $response = array();
300
301 // Allow update in later versions.
302 /**
303 * Filter the structure of site settings to return.
304 *
305 * @module json-api
306 *
307 * @since 3.9.3
308 *
309 * @param array $site_format Data structure.
310 */
311 $response_format = apply_filters( 'site_settings_site_format', self::$site_format );
312
313 $blog_id = (int) $this->api->get_blog_id_for_output();
314 $site = $this->get_platform()->get_site( $blog_id );
315
316 foreach ( array_keys( $response_format ) as $key ) {
317
318 // refactoring to change lang parameter to locale in 1.2.
319 $lang_or_locale = $this->get_locale( $key );
320 if ( $lang_or_locale ) {
321 $response[ $key ] = $lang_or_locale;
322 continue;
323 }
324
325 switch ( $key ) {
326 case 'ID':
327 $response[ $key ] = $blog_id;
328 break;
329 case 'name':
330 $response[ $key ] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
331 break;
332 case 'description':
333 $response[ $key ] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
334 break;
335 case 'URL':
336 $response[ $key ] = (string) home_url();
337 break;
338 case 'locale_variant':
339 if ( function_exists( 'wpcom_l10n_get_blog_locale_variant' ) ) {
340 $blog_locale_variant = wpcom_l10n_get_blog_locale_variant();
341 if ( $blog_locale_variant ) {
342 $response[ $key ] = $blog_locale_variant;
343 }
344 }
345 break;
346 case 'settings':
347 $jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts', array() );
348 // If the option's enabled key is NOT SET, it is considered enabled by the plugin.
349 if ( ! isset( $jetpack_relatedposts_options['enabled'] ) ) {
350 $jetpack_relatedposts_options['enabled'] = true;
351 }
352
353 $jetpack_relatedposts_options['enabled'] =
354 $jetpack_relatedposts_options['enabled']
355 && $site->is_module_active( 'related-posts' );
356
357 $jetpack_search_supported = false;
358 if ( function_exists( 'wpcom_is_jetpack_search_supported' ) ) {
359 $jetpack_search_supported = wpcom_is_jetpack_search_supported( $blog_id );
360 }
361
362 $jetpack_search_active =
363 $jetpack_search_supported
364 && $site->is_module_active( 'search' );
365
366 // array_values() is necessary to ensure the array starts at index 0.
367 $post_categories = array_values(
368 array_map(
369 array( $this, 'get_category_details' ),
370 get_categories( array( 'hide_empty' => false ) )
371 )
372 );
373
374 $newsletter_categories = maybe_unserialize( get_option( 'wpcom_newsletter_categories', array() ) );
375 $newsletter_category_ids = array_map(
376 function ( $newsletter_category ) {
377 return $newsletter_category['term_id'];
378 },
379 $newsletter_categories
380 );
381
382 $api_cache = $site->is_jetpack() ? (bool) get_option( 'jetpack_api_cache_enabled' ) : true;
383
384 $response[ $key ] = array(
385 // also exists as "options".
386 'admin_url' => get_admin_url(),
387 'default_ping_status' => 'closed' !== get_option( 'default_ping_status' ),
388 'default_comment_status' => 'closed' !== get_option( 'default_comment_status' ),
389
390 // new stuff starts here.
391 'instant_search_enabled' => (bool) get_option( 'instant_search_enabled' ),
392 'blog_public' => (int) get_option( 'blog_public' ),
393 'wpcom_data_sharing_opt_out' => (bool) get_option( 'wpcom_data_sharing_opt_out' ),
394 'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
395 'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
396 'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options['enabled'],
397 'jetpack_relatedposts_show_context' => ! empty( $jetpack_relatedposts_options['show_context'] ),
398 'jetpack_relatedposts_show_date' => ! empty( $jetpack_relatedposts_options['show_date'] ),
399 'jetpack_relatedposts_show_headline' => ! empty( $jetpack_relatedposts_options['show_headline'] ),
400 'jetpack_relatedposts_show_thumbnails' => ! empty( $jetpack_relatedposts_options['show_thumbnails'] ),
401 'jetpack_search_enabled' => (bool) $jetpack_search_active,
402 'jetpack_search_supported' => (bool) $jetpack_search_supported,
403 'default_category' => (int) get_option( 'default_category' ),
404 'post_categories' => (array) $post_categories,
405 'default_post_format' => get_option( 'default_post_format' ),
406 'default_pingback_flag' => (bool) get_option( 'default_pingback_flag' ),
407 'require_name_email' => (bool) get_option( 'require_name_email' ),
408 'comment_registration' => (bool) get_option( 'comment_registration' ),
409 'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
410 'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
411 'thread_comments' => (bool) get_option( 'thread_comments' ),
412 'thread_comments_depth' => (int) get_option( 'thread_comments_depth' ),
413 'page_comments' => (bool) get_option( 'page_comments' ),
414 'comments_per_page' => (int) get_option( 'comments_per_page' ),
415 'default_comments_page' => get_option( 'default_comments_page' ),
416 'comment_order' => get_option( 'comment_order' ),
417 'comments_notify' => (bool) get_option( 'comments_notify' ),
418 'moderation_notify' => (bool) get_option( 'moderation_notify' ),
419 'social_notifications_like' => ( 'on' === get_option( 'social_notifications_like' ) ),
420 'social_notifications_reblog' => ( 'on' === get_option( 'social_notifications_reblog' ) ),
421 'social_notifications_subscribe' => ( 'on' === get_option( 'social_notifications_subscribe' ) ),
422 'comment_moderation' => (bool) get_option( 'comment_moderation' ),
423 'comment_whitelist' => (bool) get_option( 'comment_previously_approved' ),
424 'comment_previously_approved' => (bool) get_option( 'comment_previously_approved' ),
425 'comment_max_links' => (int) get_option( 'comment_max_links' ),
426 'moderation_keys' => get_option( 'moderation_keys' ),
427 'blacklist_keys' => get_option( 'disallowed_keys' ),
428 'disallowed_keys' => get_option( 'disallowed_keys' ),
429 'lang_id' => defined( 'IS_WPCOM' ) && IS_WPCOM
430 ? get_lang_id_by_code( wpcom_l10n_get_blog_locale_variant( $blog_id, true ) )
431 : get_option( 'lang_id' ),
432 'site_vertical_id' => (string) get_option( 'site_vertical_id' ),
433 'jetpack_cloudflare_analytics' => get_option( 'jetpack_cloudflare_analytics' ),
434 'disabled_likes' => (bool) get_option( 'disabled_likes' ),
435 'disabled_reblogs' => (bool) get_option( 'disabled_reblogs' ),
436 'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
437 'twitter_via' => (string) get_option( 'twitter_via' ),
438 'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
439 'eventbrite_api_token' => $this->get_cast_option_value_or_null( 'eventbrite_api_token', 'intval' ),
440 'gmt_offset' => get_option( 'gmt_offset' ),
441 'timezone_string' => get_option( 'timezone_string' ),
442 'date_format' => get_option( 'date_format' ),
443 'time_format' => get_option( 'time_format' ),
444 'start_of_week' => get_option( 'start_of_week' ),
445 'woocommerce_onboarding_profile' => (array) get_option( 'woocommerce_onboarding_profile', array() ),
446 'woocommerce_store_address' => (string) get_option( 'woocommerce_store_address' ),
447 'woocommerce_store_address_2' => (string) get_option( 'woocommerce_store_address_2' ),
448 'woocommerce_store_city' => (string) get_option( 'woocommerce_store_city' ),
449 'woocommerce_default_country' => (string) get_option( 'woocommerce_default_country' ),
450 'woocommerce_store_postcode' => (string) get_option( 'woocommerce_store_postcode' ),
451 'jetpack_testimonial' => (bool) get_option( 'jetpack_testimonial', '0' ),
452 'jetpack_testimonial_posts_per_page' => (int) get_option( 'jetpack_testimonial_posts_per_page', '10' ),
453 'jetpack_portfolio' => (bool) get_option( 'jetpack_portfolio', '0' ),
454 'jetpack_portfolio_posts_per_page' => (int) get_option( 'jetpack_portfolio_posts_per_page', '10' ),
455 'markdown_supported' => true,
456 'site_icon' => $this->get_cast_option_value_or_null( 'site_icon', 'intval' ),
457 Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION => get_option( Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION, '' ),
458 Jetpack_SEO_Titles::TITLE_FORMATS_OPTION => get_option( Jetpack_SEO_Titles::TITLE_FORMATS_OPTION, array() ),
459 'api_cache' => $api_cache,
460 'posts_per_page' => (int) get_option( 'posts_per_page' ),
461 'posts_per_rss' => (int) get_option( 'posts_per_rss' ),
462 'rss_use_excerpt' => (bool) get_option( 'rss_use_excerpt' ),
463 'launchpad_screen' => (string) get_option( 'launchpad_screen' ),
464 'wpcom_featured_image_in_email' => (bool) get_option( 'wpcom_featured_image_in_email' ),
465 'jetpack_gravatar_in_email' => (bool) get_option( 'jetpack_gravatar_in_email', true ),
466 'jetpack_author_in_email' => (bool) get_option( 'jetpack_author_in_email', true ),
467 'jetpack_post_date_in_email' => (bool) get_option( 'jetpack_post_date_in_email', true ),
468 'wpcom_newsletter_categories' => $newsletter_category_ids,
469 'wpcom_newsletter_categories_enabled' => (bool) get_option( 'wpcom_newsletter_categories_enabled' ),
470 'sm_enabled' => (bool) get_option( 'sm_enabled' ),
471 'jetpack_subscribe_overlay_enabled' => (bool) get_option( 'jetpack_subscribe_overlay_enabled' ),
472 'jetpack_subscriptions_subscribe_post_end_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_post_end_enabled' ),
473 'jetpack_subscriptions_login_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_login_navigation_enabled' ),
474 'jetpack_subscriptions_subscribe_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_navigation_enabled' ),
475 'wpcom_gifting_subscription' => (bool) get_option( 'wpcom_gifting_subscription', $this->get_wpcom_gifting_subscription_default() ),
476 'wpcom_reader_views_enabled' => (bool) get_option( 'wpcom_reader_views_enabled', true ),
477 'wpcom_subscription_emails_use_excerpt' => $this->get_wpcom_subscription_emails_use_excerpt_option(),
478 'jetpack_subscriptions_reply_to' => (string) $this->get_subscriptions_reply_to_option(),
479 'jetpack_subscriptions_from_name' => (string) get_option( 'jetpack_subscriptions_from_name' ),
480 'show_on_front' => (string) get_option( 'show_on_front' ),
481 'page_on_front' => (string) get_option( 'page_on_front' ),
482 'page_for_posts' => (string) get_option( 'page_for_posts' ),
483 'subscription_options' => (array) get_option( 'subscription_options' ),
484 'jetpack_verbum_subscription_modal' => (bool) get_option( 'jetpack_verbum_subscription_modal', true ),
485 'enable_verbum_commenting' => (bool) get_option( 'enable_verbum_commenting', true ),
486 'enable_blocks_comments' => (bool) get_option( 'enable_blocks_comments', true ),
487 'highlander_comment_form_prompt' => $this->get_highlander_comment_form_prompt_option(),
488 'jetpack_comment_form_color_scheme' => (string) get_option( 'jetpack_comment_form_color_scheme' ),
489 'in_site_migration_flow' => (string) get_option( 'in_site_migration_flow', '' ),
490 'migration_source_site_domain' => (string) get_option( 'migration_source_site_domain' ),
491 'jetpack_waf_automatic_rules' => (bool) get_option( 'jetpack_waf_automatic_rules' ),
492 'jetpack_waf_ip_allow_list' => (string) get_option( 'jetpack_waf_ip_allow_list' ),
493 'jetpack_waf_ip_allow_list_enabled' => (bool) get_option( 'jetpack_waf_ip_allow_list_enabled' ),
494 'jetpack_waf_ip_block_list' => (string) get_option( 'jetpack_waf_ip_block_list' ),
495 'jetpack_waf_ip_block_list_enabled' => (bool) get_option( 'jetpack_waf_ip_block_list_enabled' ),
496 'jetpack_waf_share_data' => (bool) get_option( 'jetpack_waf_share_data' ),
497 'jetpack_waf_share_debug_data' => (bool) get_option( 'jetpack_waf_share_debug_data' ),
498 'jetpack_waf_automatic_rules_last_updated_timestamp' => (int) get_option( 'jetpack_waf_automatic_rules_last_updated_timestamp' ),
499 );
500
501 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
502 $response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_posting_enabled();
503 $response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_commenting_enabled();
504
505 // WPCOM-specific Infinite Scroll Settings.
506 if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
507 /**
508 * Clear the cached copy of widget info so it's pulled fresh from blog options.
509 * It was primed during the initial load under the __REST API site__'s context.
510 *
511 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
512 */
513 $GLOBALS['_wp_sidebars_widgets'] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
514
515 $infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
516 $response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && 'scroll' === $infinite_scroll_settings->type;
517 if ( $infinite_scroll_settings->footer_widgets || 'click' === $infinite_scroll_settings->requested_type ) {
518 // The blog has footer widgets -- infinite scroll is blocked.
519 $response[ $key ]['infinite_scroll_blocked'] = 'footer';
520 } else {
521 $response[ $key ]['infinite_scroll_blocked'] = false;
522 }
523 }
524 }
525
526 // allow future versions of this endpoint to support additional settings keys.
527 /**
528 * Filter the current site setting in the returned response.
529 *
530 * @module json-api
531 *
532 * @since 3.9.3
533 * @since 13.6 Added the API object parameter.
534 *
535 * @param mixed $response_item A single site setting.
536 * @param WPCOM_JSON_API_Site_Settings_Endpoint $this The API object.
537 */
538 $response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ], $this );
539
540 if ( class_exists( 'Sharing_Service' ) ) {
541 $ss = new Sharing_Service();
542 $sharing = $ss->get_global_options();
543 $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
544 $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
545 $response[ $key ]['sharing_show'] = (array) $sharing['show'];
546 $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
547 }
548
549 $response[ $key ]['jetpack_protect_whitelist'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
550
551 if ( ! current_user_can( 'edit_posts' ) ) {
552 unset( $response[ $key ] );
553 }
554 break;
555 }
556 }
557 return $response;
558 }
559
560 /**
561 * Get the default value for the wpcom_gifting_subscription option.
562 * The default value is the inverse of the plan's auto_renew setting.
563 *
564 * @return bool
565 */
566 protected function get_wpcom_gifting_subscription_default() {
567 if ( function_exists( 'wpcom_get_site_purchases' ) && function_exists( 'wpcom_purchase_has_feature' ) ) {
568 $purchases = wpcom_get_site_purchases();
569
570 foreach ( $purchases as $purchase ) {
571 if ( wpcom_purchase_has_feature( $purchase, \WPCOM_Features::SUBSCRIPTION_GIFTING ) ) {
572 /*
573 * We set default value as false when expiration date not match the following:
574 * - 54 days before the annual plan expiration.
575 * - 5 days before the monthly plan expiration.
576 * This is to match the gifting banner logic.
577 */
578 $days_of_warning = str_contains( $purchase->product_slug, 'monthly' ) ? 5 : 54;
579 $seconds_until_expiration = strtotime( $purchase->expiry_date ) - time();
580 if ( $seconds_until_expiration >= $days_of_warning * DAY_IN_SECONDS ) {
581 return false;
582 }
583
584 // We set default to the inverse of auto-renew.
585 if ( isset( $purchase->auto_renew ) ) {
586 return ! $purchase->auto_renew;
587 } elseif ( isset( $purchase->user_allows_auto_renew ) ) {
588 return ! $purchase->user_allows_auto_renew;
589 }
590 }
591 }
592 }
593 return false;
594 }
595
596 /**
597 * Get locale.
598 *
599 * @param string $key Language.
600 */
601 protected function get_locale( $key ) {
602 if ( 'lang' === $key ) {
603 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
604 return (string) get_blog_lang_code();
605 } else {
606 return get_locale();
607 }
608 }
609
610 return false;
611 }
612
613 /**
614 * Updates site settings for authorized users
615 *
616 * @return array|WP_Error
617 */
618 public function update_settings() {
619 /*
620 * $this->input() retrieves posted arguments whitelisted and casted to the $request_format
621 * specs that get passed in when this class is instantiated
622 */
623 $input = $this->input();
624 $unfiltered_input = $this->input( false, false );
625 /**
626 * Filters the settings to be updated on the site.
627 *
628 * @module json-api
629 *
630 * @since 3.6.0
631 * @since 6.1.1 Added $unfiltered_input parameter.
632 *
633 * @param array $input Associative array of site settings to be updated.
634 * Cast and filtered based on documentation.
635 * @param array $unfiltered_input Associative array of site settings to be updated.
636 * Neither cast nor filtered. Contains raw input.
637 */
638 $input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
639
640 $blog_id = get_current_blog_id();
641
642 $jetpack_relatedposts_options = array();
643 $sharing_options = array();
644 $updated = array();
645
646 foreach ( $input as $key => $value ) {
647
648 if ( ! is_array( $value ) ) {
649 $value = trim( $value );
650 }
651
652 // preserve the raw value before unslashing the value. The slashes need to be preserved for date and time formats.
653 $raw_value = $value;
654 $value = wp_unslash( $value );
655
656 switch ( $key ) {
657
658 case 'default_ping_status':
659 case 'default_comment_status':
660 // settings are stored as closed|open.
661 $coerce_value = ( $value ) ? 'open' : 'closed';
662 if ( update_option( $key, $coerce_value ) ) {
663 $updated[ $key ] = $value;
664 }
665 break;
666 case 'launchpad_screen':
667 if ( in_array( $value, array( 'full', 'off', 'minimized' ), true ) ) {
668 if ( update_option( $key, $value ) ) {
669 $updated[ $key ] = $value;
670 }
671 }
672 break;
673 case 'jetpack_protect_whitelist':
674 if ( class_exists( 'Brute_Force_Protection_Shared_Functions' ) ) {
675 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $value );
676 if ( is_wp_error( $result ) ) {
677 return $result;
678 }
679 $updated[ $key ] = Brute_Force_Protection_Shared_Functions::format_allow_list();
680 }
681 break;
682 case 'jetpack_sync_non_public_post_stati':
683 Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
684 break;
685 case 'jetpack_search_enabled':
686 if ( $value ) {
687 Jetpack::activate_module( $blog_id, 'search' );
688 } else {
689 Jetpack::deactivate_module( $blog_id, 'search' );
690 }
691 $updated[ $key ] = (bool) $value;
692 break;
693 case 'jetpack_relatedposts_enabled':
694 case 'jetpack_relatedposts_show_context':
695 case 'jetpack_relatedposts_show_date':
696 case 'jetpack_relatedposts_show_thumbnails':
697 case 'jetpack_relatedposts_show_headline':
698 if ( ! $this->jetpack_relatedposts_supported() ) {
699 break;
700 }
701 if ( 'jetpack_relatedposts_enabled' === $key ) {
702 if ( $value ) {
703 Jetpack::activate_module( $blog_id, 'related-posts' );
704 } else {
705 Jetpack::deactivate_module( $blog_id, 'related-posts' );
706 }
707 }
708 $just_the_key = substr( $key, 21 );
709 $jetpack_relatedposts_options[ $just_the_key ] = $value;
710 break;
711
712 case 'social_notifications_like':
713 case 'social_notifications_reblog':
714 case 'social_notifications_subscribe':
715 // settings are stored as on|off.
716 $coerce_value = ( $value ) ? 'on' : 'off';
717 if ( update_option( $key, $coerce_value ) ) {
718 $updated[ $key ] = $value;
719 }
720 break;
721
722 case 'cloudflare_analytics':
723 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
724 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
725 }
726
727 if ( update_option( $key, $value ) ) {
728 $updated[ $key ] = $value;
729 }
730 break;
731
732 case 'jetpack_testimonial':
733 case 'jetpack_portfolio':
734 case 'jetpack_comment_likes_enabled':
735 case 'wpcom_reader_views_enabled':
736 case 'jetpack_verbum_subscription_modal':
737 // settings are stored as 1|0.
738 $coerce_value = (int) $value;
739 if ( update_option( $key, $coerce_value ) ) {
740 $updated[ $key ] = (bool) $value;
741 }
742 break;
743
744 case 'jetpack_testimonial_posts_per_page':
745 case 'jetpack_portfolio_posts_per_page':
746 // settings are stored as numeric.
747 $coerce_value = (int) $value;
748 if ( update_option( $key, $coerce_value ) ) {
749 $updated[ $key ] = $coerce_value;
750 }
751 break;
752
753 // Sharing options.
754 case 'sharing_button_style':
755 case 'sharing_show':
756 case 'sharing_open_links':
757 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
758 break;
759 case 'sharing_label':
760 $sharing_options[ $key ] = $value;
761 break;
762
763 // Keyring token option.
764 case 'eventbrite_api_token':
765 // These options can only be updated for sites hosted on WordPress.com.
766 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
767 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
768 if ( delete_option( $key ) ) {
769 $updated[ $key ] = null;
770 }
771 } elseif ( update_option( $key, $value ) ) {
772 $updated[ $key ] = (int) $value;
773 }
774 }
775 break;
776
777 case 'api_cache':
778 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
779 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
780 $updated[ $key ] = false;
781 }
782 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
783 $updated[ $key ] = true;
784 }
785 break;
786
787 case 'timezone_string':
788 /*
789 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
790 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
791 */
792 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
793 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
794 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
795 $updated['gmt_offset'] = $gmt_offset;
796 }
797
798 $value = '';
799 }
800
801 /*
802 * Always set timezone_string either with the given value or with an
803 * empty string
804 */
805 if ( update_option( $key, $value ) ) {
806 $updated[ $key ] = $value;
807 }
808 break;
809
810 case 'subscription_options':
811 if ( ! is_array( $value ) ) {
812 break;
813 }
814
815 $allowed_keys = array( 'invitation', 'comment_follow', 'welcome' );
816 $filtered_value = array_filter(
817 $value,
818 function ( $key ) use ( $allowed_keys ) {
819 return in_array( $key, $allowed_keys, true );
820 },
821 ARRAY_FILTER_USE_KEY
822 );
823
824 if ( empty( $filtered_value ) ) {
825 break;
826 }
827
828 array_walk_recursive(
829 $filtered_value,
830 function ( &$value ) {
831 $value = wp_kses(
832 $value,
833 array(
834 'a' => array(
835 'href' => array(),
836 ),
837 )
838 );
839 }
840 );
841
842 $old_subscription_options = get_option( 'subscription_options' );
843 $new_subscription_options = array_merge( $old_subscription_options, $filtered_value );
844
845 if ( update_option( $key, $new_subscription_options ) ) {
846 $updated[ $key ] = $filtered_value;
847 }
848 break;
849
850 case 'woocommerce_onboarding_profile':
851 // Allow boolean values but sanitize_text_field everything else.
852 $sanitized_value = (array) $value;
853 array_walk_recursive(
854 $sanitized_value,
855 function ( &$value ) {
856 if ( ! is_bool( $value ) ) {
857 $value = sanitize_text_field( $value );
858 }
859 }
860 );
861 if ( update_option( $key, $sanitized_value ) ) {
862 $updated[ $key ] = $sanitized_value;
863 }
864 break;
865
866 case 'woocommerce_store_address':
867 case 'woocommerce_store_address_2':
868 case 'woocommerce_store_city':
869 case 'woocommerce_default_country':
870 case 'woocommerce_store_postcode':
871 $sanitized_value = sanitize_text_field( $value );
872 if ( update_option( $key, $sanitized_value ) ) {
873 $updated[ $key ] = $sanitized_value;
874 }
875 break;
876
877 case 'date_format':
878 case 'time_format':
879 // settings are stored as strings.
880 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
881 $sanitized_value = sanitize_text_field( $raw_value );
882 if ( update_option( $key, $sanitized_value ) ) {
883 $updated[ $key ] = $sanitized_value;
884 }
885 break;
886
887 case 'start_of_week':
888 // setting is stored as int in 0-6 range (days of week).
889 $coerce_value = (int) $value;
890 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
891 if ( update_option( $key, $limit_value ) ) {
892 $updated[ $key ] = $limit_value;
893 }
894 break;
895
896 case 'site_icon':
897 /*
898 * settings are stored as deletable numeric (all empty
899 * values as delete intent), validated as media image
900 */
901 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
902 /**
903 * Fallback mechanism to clear a third party site icon setting. Can be used
904 * to unset the option when an API request instructs the site to remove the site icon.
905 *
906 * @module json-api
907 *
908 * @since 4.10
909 */
910 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
911 $updated[ $key ] = null;
912 }
913 } elseif ( is_numeric( $value ) ) {
914 $coerce_value = (int) $value;
915 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
916 $updated[ $key ] = $coerce_value;
917 }
918 }
919 break;
920
921 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
922 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
923 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
924 }
925
926 if ( ! is_string( $value ) ) {
927 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
928 }
929
930 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
931
932 if ( ! empty( $new_description ) ) {
933 $updated[ $key ] = $new_description;
934 }
935 break;
936
937 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
938 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
939 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
940 break;
941 }
942 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
943 }
944
945 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
946 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
947 }
948
949 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
950
951 if ( ! empty( $new_title_formats ) ) {
952 $updated[ $key ] = $new_title_formats;
953 }
954 break;
955
956 case 'verification_services_codes':
957 $verification_codes = jetpack_verification_validate( $value );
958
959 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
960 $updated[ $key ] = $verification_codes;
961 }
962 break;
963
964 case 'wpcom_publish_posts_with_markdown':
965 case 'wpcom_publish_comments_with_markdown':
966 $coerce_value = (bool) $value;
967 if ( update_option( $key, $coerce_value ) ) {
968 $updated[ $key ] = $coerce_value;
969 }
970 break;
971
972 case 'wpcom_gifting_subscription':
973 $coerce_value = (bool) $value;
974
975 /*
976 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
977 * a serialized value. Knowing that we can check if the option already exists.
978 */
979 $gift_toggle = get_option( $key );
980 if ( false === $gift_toggle ) {
981 // update_option will not create a new option if the initial value is false. So use add_option.
982 if ( add_option( $key, $coerce_value ) ) {
983 $updated[ $key ] = $coerce_value;
984 }
985 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
986 $updated[ $key ] = $coerce_value;
987 }
988 break;
989
990 case 'rss_use_excerpt':
991 $sanitized_value = (int) (bool) $value;
992 update_option( $key, $sanitized_value );
993 $updated[ $key ] = $sanitized_value;
994 break;
995
996 case 'wpcom_subscription_emails_use_excerpt':
997 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
998 $updated[ $key ] = (bool) $value;
999 break;
1000
1001 case 'jetpack_subscriptions_reply_to':
1002 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1003 $to_set_value = Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value )
1004 ? (string) $value
1005 : Automattic\Jetpack\Modules\Subscriptions\Settings::get_default_reply_to();
1006
1007 if ( update_option( $key, $to_set_value ) ) {
1008 $updated[ $key ] = $to_set_value;
1009 }
1010 break;
1011
1012 case 'jetpack_subscriptions_from_name':
1013 $sanitized_value = sanitize_text_field( $value );
1014 if ( update_option( $key, $sanitized_value ) ) {
1015 $updated[ $key ] = $sanitized_value;
1016 }
1017 break;
1018
1019 case 'instant_search_enabled':
1020 update_option( 'instant_search_enabled', (bool) $value );
1021 $updated[ $key ] = (bool) $value;
1022 break;
1023
1024 case 'lang_id':
1025 /*
1026 * Due to the fact that locale variants are set in a locale_variant option,
1027 * changing locale from variant to primary
1028 * would look like the same lang_id is being saved and update_option would return false,
1029 * even though the correct options would be set by pre_update_option_lang_id,
1030 * so we should always return lang_id as updated.
1031 */
1032 update_option( 'lang_id', (int) $value );
1033 $updated[ $key ] = (int) $value;
1034 break;
1035
1036 case 'wpcom_featured_image_in_email':
1037 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1038 $updated[ $key ] = (int) (bool) $value;
1039 break;
1040
1041 case 'wpcom_newsletter_categories':
1042 $sanitized_category_ids = (array) $value;
1043
1044 array_walk_recursive(
1045 $sanitized_category_ids,
1046 function ( &$value ) {
1047 if ( is_int( $value ) && $value > 0 ) {
1048 return;
1049 }
1050
1051 $value = (int) $value;
1052 if ( $value <= 0 ) {
1053 $value = null;
1054 }
1055 }
1056 );
1057
1058 $sanitized_category_ids = array_unique(
1059 array_filter(
1060 $sanitized_category_ids,
1061 function ( $category_id ) {
1062 return $category_id !== null;
1063 }
1064 )
1065 );
1066
1067 $new_value = array_map(
1068 function ( $category_id ) {
1069 return array( 'term_id' => $category_id );
1070 },
1071 $sanitized_category_ids
1072 );
1073
1074 if ( update_option( $key, $new_value ) ) {
1075 $updated[ $key ] = $sanitized_category_ids;
1076 }
1077 break;
1078
1079 case 'wpcom_newsletter_categories_enabled':
1080 update_option( 'wpcom_newsletter_categories_enabled', (int) (bool) $value );
1081 $updated[ $key ] = (int) (bool) $value;
1082 break;
1083
1084 case 'sm_enabled':
1085 update_option( 'sm_enabled', (int) (bool) $value );
1086 $updated[ $key ] = (int) (bool) $value;
1087 break;
1088
1089 case 'jetpack_subscribe_overlay_enabled':
1090 update_option( 'jetpack_subscribe_overlay_enabled', (int) (bool) $value );
1091 $updated[ $key ] = (int) (bool) $value;
1092 break;
1093
1094 case 'jetpack_subscriptions_subscribe_post_end_enabled':
1095 update_option( 'jetpack_subscriptions_subscribe_post_end_enabled', (int) (bool) $value );
1096 $updated[ $key ] = (int) (bool) $value;
1097 break;
1098
1099 case 'jetpack_subscriptions_login_navigation_enabled':
1100 update_option( 'jetpack_subscriptions_login_navigation_enabled', (int) (bool) $value );
1101 $updated[ $key ] = (int) (bool) $value;
1102 break;
1103
1104 case 'jetpack_subscriptions_subscribe_navigation_enabled':
1105 update_option( 'jetpack_subscriptions_subscribe_navigation_enabled', (int) (bool) $value );
1106 $updated[ $key ] = (int) (bool) $value;
1107 break;
1108
1109 case 'show_on_front':
1110 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1111 $updated[ $key ] = $value;
1112 }
1113 break;
1114
1115 case 'page_on_front':
1116 case 'page_for_posts':
1117 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1118 if ( delete_option( $key ) ) {
1119 $updated[ $key ] = null;
1120 }
1121
1122 break;
1123 }
1124
1125 if ( ! $this->is_valid_page_id( $value ) ) {
1126 break;
1127 }
1128
1129 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1130 $related_option_value = get_option( $related_option_key );
1131 if ( $related_option_value === $value ) {
1132 // page_on_front and page_for_posts are not allowed to be the same
1133 break;
1134 }
1135
1136 if ( update_option( $key, $value ) ) {
1137 $updated[ $key ] = $value;
1138 }
1139
1140 break;
1141
1142 case 'in_site_migration_flow':
1143 if ( empty( $value ) ) {
1144 delete_option( 'in_site_migration_flow' );
1145 break;
1146 }
1147
1148 $migration_flow_whitelist = array(
1149 'site-migration',
1150 'migration-signup',
1151 );
1152
1153 if ( ! in_array( $value, $migration_flow_whitelist, true ) ) {
1154 break;
1155 }
1156
1157 update_option( 'in_site_migration_flow', $value );
1158 $updated[ $key ] = $value;
1159 break;
1160
1161 case 'migration_source_site_domain':
1162 // If we get an empty value, delete the option
1163 if ( empty( $value ) ) {
1164 delete_option( 'migration_source_site_domain' );
1165 break;
1166 }
1167
1168 // If we get a non-url value, don't update the option.
1169 if ( wp_http_validate_url( $value ) === false ) {
1170 break;
1171 }
1172
1173 update_option( 'migration_source_site_domain', $value );
1174 $updated[ $key ] = $value;
1175 break;
1176
1177 default:
1178 // allow future versions of this endpoint to support additional settings keys.
1179 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1180 /**
1181 * Filter current site setting value to be updated.
1182 *
1183 * @module json-api
1184 *
1185 * @since 3.9.3
1186 * @since 13.6 Added the API object parameter.
1187 *
1188 * @param mixed $response_item A single site setting value.
1189 * @param WPCOM_JSON_API_Site_Settings_Endpoint The API object parameter.
1190 */
1191 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value, $this );
1192
1193 if ( is_wp_error( $value ) ) {
1194 return $value;
1195 }
1196
1197 if ( $value ) {
1198 $updated[ $key ] = $value;
1199 }
1200 break;
1201 }
1202 // no worries, we've already whitelisted and casted arguments above.
1203 if ( update_option( $key, $value ) ) {
1204 $updated[ $key ] = $value;
1205 }
1206 }
1207 }
1208
1209 if ( $jetpack_relatedposts_options !== array() ) {
1210 // track new jetpack_relatedposts options against old.
1211 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1212
1213 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1214 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1215 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1216 }
1217
1218 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1219 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1220 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1221 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1222 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1223
1224 if ( $has_initialized_option || $has_updated_option ) {
1225 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1226 }
1227 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1228 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1229 }
1230 }
1231 }
1232 }
1233
1234 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1235 $ss = new Sharing_Service();
1236
1237 /*
1238 * Merge current values with updated, since Sharing_Service expects
1239 * all values to be included when updating
1240 */
1241 $current_sharing_options = $ss->get_global_options();
1242 foreach ( $current_sharing_options as $key => $val ) {
1243 if ( ! isset( $sharing_options[ $key ] ) ) {
1244 $sharing_options[ $key ] = $val;
1245 }
1246 }
1247
1248 $updated_social_options = $ss->set_global_options( $sharing_options );
1249
1250 if ( isset( $input['sharing_button_style'] ) ) {
1251 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1252 }
1253 if ( isset( $input['sharing_label'] ) ) {
1254 // Sharing_Service won't report label as updated if set to default.
1255 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1256 }
1257 if ( isset( $input['sharing_show'] ) ) {
1258 $updated['sharing_show'] = (array) $updated_social_options['show'];
1259 }
1260 if ( isset( $input['sharing_open_links'] ) ) {
1261 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1262 }
1263 }
1264
1265 return array(
1266 'updated' => $updated,
1267 );
1268 }
1269
1270 /**
1271 * Get the value of the wpcom_subscription_emails_use_excerpt option.
1272 * When the option is not set, it will return the value of the rss_use_excerpt option.
1273 *
1274 * @return bool
1275 */
1276 protected function get_wpcom_subscription_emails_use_excerpt_option() {
1277 $wpcom_subscription_emails_use_excerpt = get_option( 'wpcom_subscription_emails_use_excerpt', null );
1278
1279 if ( $wpcom_subscription_emails_use_excerpt === null ) {
1280 $rss_use_excerpt = get_option( 'rss_use_excerpt', null );
1281 $wpcom_subscription_emails_use_excerpt = $rss_use_excerpt === null ? false : $rss_use_excerpt;
1282 }
1283
1284 return (bool) $wpcom_subscription_emails_use_excerpt;
1285 }
1286
1287 /**
1288 * Get the string value of the jetpack_subscriptions_reply_to option.
1289 * When the option is not set, it will retun 'no-reply'.
1290 *
1291 * @return string
1292 */
1293 protected function get_subscriptions_reply_to_option() {
1294 $reply_to = get_option( 'jetpack_subscriptions_reply_to', null );
1295 if ( $reply_to === null ) {
1296 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1297 return Automattic\Jetpack\Modules\Subscriptions\Settings::get_default_reply_to();
1298 }
1299 return $reply_to;
1300 }
1301
1302 /**
1303 * Check if the given value is a valid page ID for the current site.
1304 *
1305 * @param mixed $value The value to check.
1306 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1307 */
1308 protected function is_valid_page_id( $value ) {
1309 $all_page_ids = get_all_page_ids();
1310
1311 $valid_page_id = false;
1312 foreach ( $all_page_ids as $page_id ) {
1313 if ( $page_id === (string) $value ) {
1314 $valid_page_id = true;
1315 break;
1316 }
1317 }
1318
1319 return $valid_page_id;
1320 }
1321
1322 /**
1323 * Get the value of the highlander_comment_form_prompt option.
1324 * When the option is not set, it will return the default value.
1325 *
1326 * @return string
1327 */
1328 protected function get_highlander_comment_form_prompt_option() {
1329 $highlander_comment_form_prompt_option = get_option( 'highlander_comment_form_prompt' );
1330
1331 if ( empty( $highlander_comment_form_prompt_option ) ) {
1332 return (string) __( 'Leave a comment', 'jetpack' );
1333 }
1334
1335 return (string) $highlander_comment_form_prompt_option;
1336 }
1337 }
1338