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