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