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