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