PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.7.1
Jetpack – WP Security, Backup, Speed, & Growth v14.7.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 1 year ago class.wpcom-json-api-add-widget-endpoint.php 2 years ago class.wpcom-json-api-autosave-post-v1-1-endpoint.php 5 years ago class.wpcom-json-api-bulk-delete-post-endpoint.php 4 years ago class.wpcom-json-api-bulk-restore-post-endpoint.php 2 years ago class.wpcom-json-api-bulk-update-comments-endpoint.php 3 years ago class.wpcom-json-api-comment-endpoint.php 1 year ago class.wpcom-json-api-delete-media-endpoint.php 4 years ago class.wpcom-json-api-delete-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-edit-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-autosave-v1-1-endpoint.php 5 years ago class.wpcom-json-api-get-comment-counts-endpoint.php 1 year ago class.wpcom-json-api-get-comment-endpoint.php 4 years ago class.wpcom-json-api-get-comment-history-endpoint.php 1 year ago class.wpcom-json-api-get-comments-tree-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-comments-tree-v1-2-endpoint.php 4 years ago class.wpcom-json-api-get-customcss.php 2 years ago class.wpcom-json-api-get-media-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-1-endpoint.php 4 years ago class.wpcom-json-api-get-media-v1-2-endpoint.php 2 years ago class.wpcom-json-api-get-post-counts-v1-1-endpoint.php 2 years ago class.wpcom-json-api-get-post-endpoint.php 2 years ago class.wpcom-json-api-get-post-v1-1-endpoint.php 2 years ago class.wpcom-json-api-get-site-endpoint.php 1 year ago class.wpcom-json-api-get-site-v1-2-endpoint.php 1 year ago class.wpcom-json-api-get-taxonomies-endpoint.php 2 years ago class.wpcom-json-api-get-taxonomy-endpoint.php 4 years ago class.wpcom-json-api-get-term-endpoint.php 4 years ago class.wpcom-json-api-list-comments-endpoint.php 1 year 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 1 year ago class.wpcom-json-api-list-posts-v1-1-endpoint.php 1 year ago class.wpcom-json-api-list-posts-v1-2-endpoint.php 1 year 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 1 year ago class.wpcom-json-api-menus-v1-1-endpoint.php 1 year 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 1 year 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 1 year ago class.wpcom-json-api-update-post-v1-1-endpoint.php 1 year 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 1 year 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
1355 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' => ( function () use ( $site ) {
466 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
467 $registered_date = method_exists( $site, 'get_registered_date' ) ? $site->get_registered_date() : '';
468 // Compare to May 2, 2025 (ISO 8601 format)
469 if ( $registered_date && $registered_date !== '0000-00-00T00:00:00+00:00' && strtotime( $registered_date ) >= strtotime( '2025-05-02T00:00:00+00:00' ) ) {
470 return (bool) get_option( 'wpcom_featured_image_in_email', true );
471 }
472 }
473 // For all other sites, use the saved value or default to false for legacy behavior.
474 return (bool) get_option( 'wpcom_featured_image_in_email', false );
475 } )(),
476 'jetpack_gravatar_in_email' => (bool) get_option( 'jetpack_gravatar_in_email', true ),
477 'jetpack_author_in_email' => (bool) get_option( 'jetpack_author_in_email', true ),
478 'jetpack_post_date_in_email' => (bool) get_option( 'jetpack_post_date_in_email', true ),
479 'wpcom_newsletter_categories' => $newsletter_category_ids,
480 'wpcom_newsletter_categories_enabled' => (bool) get_option( 'wpcom_newsletter_categories_enabled' ),
481 'sm_enabled' => (bool) get_option( 'sm_enabled' ),
482 'jetpack_subscribe_overlay_enabled' => (bool) get_option( 'jetpack_subscribe_overlay_enabled' ),
483 'jetpack_subscribe_floating_button_enabled' => (bool) get_option( 'jetpack_subscribe_floating_button_enabled' ),
484 'jetpack_subscriptions_subscribe_post_end_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_post_end_enabled' ),
485 'jetpack_subscriptions_login_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_login_navigation_enabled' ),
486 'jetpack_subscriptions_subscribe_navigation_enabled' => (bool) get_option( 'jetpack_subscriptions_subscribe_navigation_enabled' ),
487 'wpcom_gifting_subscription' => (bool) get_option( 'wpcom_gifting_subscription', $this->get_wpcom_gifting_subscription_default() ),
488 'wpcom_reader_views_enabled' => (bool) get_option( 'wpcom_reader_views_enabled', true ),
489 'wpcom_subscription_emails_use_excerpt' => (bool) get_option( 'wpcom_subscription_emails_use_excerpt' ),
490 'jetpack_subscriptions_reply_to' => (string) $this->get_subscriptions_reply_to_option(),
491 'jetpack_subscriptions_from_name' => (string) get_option( 'jetpack_subscriptions_from_name' ),
492 'show_on_front' => (string) get_option( 'show_on_front' ),
493 'page_on_front' => (string) get_option( 'page_on_front' ),
494 'page_for_posts' => (string) get_option( 'page_for_posts' ),
495 'subscription_options' => (array) get_option( 'subscription_options' ),
496 'jetpack_verbum_subscription_modal' => (bool) get_option( 'jetpack_verbum_subscription_modal', true ),
497 'enable_verbum_commenting' => (bool) get_option( 'enable_verbum_commenting', true ),
498 'enable_blocks_comments' => (bool) get_option( 'enable_blocks_comments', true ),
499 'highlander_comment_form_prompt' => $this->get_highlander_comment_form_prompt_option(),
500 'jetpack_comment_form_color_scheme' => (string) get_option( 'jetpack_comment_form_color_scheme' ),
501 'in_site_migration_flow' => (string) get_option( 'in_site_migration_flow', '' ),
502 'migration_source_site_domain' => (string) get_option( 'migration_source_site_domain' ),
503 'jetpack_waf_automatic_rules' => (bool) get_option( 'jetpack_waf_automatic_rules' ),
504 'jetpack_waf_ip_allow_list' => (string) get_option( 'jetpack_waf_ip_allow_list' ),
505 'jetpack_waf_ip_allow_list_enabled' => (bool) get_option( 'jetpack_waf_ip_allow_list_enabled' ),
506 'jetpack_waf_ip_block_list' => (string) get_option( 'jetpack_waf_ip_block_list' ),
507 'jetpack_waf_ip_block_list_enabled' => (bool) get_option( 'jetpack_waf_ip_block_list_enabled' ),
508 'jetpack_waf_share_data' => (bool) get_option( 'jetpack_waf_share_data' ),
509 'jetpack_waf_share_debug_data' => (bool) get_option( 'jetpack_waf_share_debug_data' ),
510 'jetpack_waf_automatic_rules_last_updated_timestamp' => (int) get_option( 'jetpack_waf_automatic_rules_last_updated_timestamp' ),
511 'is_fully_managed_agency_site' => (bool) get_option( 'is_fully_managed_agency_site' ),
512 'wpcom_hide_action_bar' => (bool) get_option( 'wpcom_hide_action_bar' ),
513 );
514
515 require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
516 if ( class_exists( 'Jetpack_Memberships' ) ) {
517 $response[ $key ]['newsletter_has_active_plan'] = count( Jetpack_Memberships::get_all_newsletter_plan_ids( false ) ) > 0;
518 }
519
520 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
521 $response[ $key ]['wpcom_publish_posts_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_posting_enabled();
522 $response[ $key ]['wpcom_publish_comments_with_markdown'] = (bool) WPCom_Markdown::get_instance()->is_commenting_enabled();
523
524 // WPCOM-specific Infinite Scroll Settings.
525 if ( is_callable( array( 'The_Neverending_Home_Page', 'get_settings' ) ) ) {
526 /**
527 * Clear the cached copy of widget info so it's pulled fresh from blog options.
528 * It was primed during the initial load under the __REST API site__'s context.
529 *
530 * @see wp_get_sidebars_widgets https://core.trac.wordpress.org/browser/trunk/src/wp-includes/widgets.php?rev=42374#L931
531 */
532 $GLOBALS['_wp_sidebars_widgets'] = array(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
533
534 $infinite_scroll_settings = The_Neverending_Home_Page::get_settings();
535 $response[ $key ]['infinite_scroll'] = get_option( 'infinite_scroll', true ) && 'scroll' === $infinite_scroll_settings->type;
536 if ( $infinite_scroll_settings->footer_widgets || 'click' === $infinite_scroll_settings->requested_type ) {
537 // The blog has footer widgets -- infinite scroll is blocked.
538 $response[ $key ]['infinite_scroll_blocked'] = 'footer';
539 } else {
540 $response[ $key ]['infinite_scroll_blocked'] = false;
541 }
542 }
543 }
544
545 // allow future versions of this endpoint to support additional settings keys.
546 /**
547 * Filter the current site setting in the returned response.
548 *
549 * @module json-api
550 *
551 * @since 3.9.3
552 * @since 13.6 Added the API object parameter.
553 *
554 * @param mixed $response_item A single site setting.
555 * @param WPCOM_JSON_API_Site_Settings_Endpoint $this The API object.
556 */
557 $response[ $key ] = apply_filters( 'site_settings_endpoint_get', $response[ $key ], $this );
558
559 if ( class_exists( 'Sharing_Service' ) ) {
560 $ss = new Sharing_Service();
561 $sharing = $ss->get_global_options();
562 $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
563 $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
564 $response[ $key ]['sharing_show'] = (array) $sharing['show'];
565 $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
566 }
567
568 $response[ $key ]['jetpack_protect_whitelist'] = Brute_Force_Protection_Shared_Functions::format_allow_list();
569
570 if ( ! current_user_can( 'edit_posts' ) ) {
571 unset( $response[ $key ] );
572 }
573 break;
574 }
575 }
576 return $response;
577 }
578
579 /**
580 * Get the default value for the wpcom_gifting_subscription option.
581 * The default value is the inverse of the plan's auto_renew setting.
582 *
583 * @return bool
584 */
585 protected function get_wpcom_gifting_subscription_default() {
586 if ( function_exists( 'wpcom_get_site_purchases' ) && function_exists( 'wpcom_purchase_has_feature' ) ) {
587 $purchases = wpcom_get_site_purchases();
588
589 foreach ( $purchases as $purchase ) {
590 if ( wpcom_purchase_has_feature( $purchase, \WPCOM_Features::SUBSCRIPTION_GIFTING ) ) {
591 /*
592 * We set default value as false when expiration date not match the following:
593 * - 54 days before the annual plan expiration.
594 * - 5 days before the monthly plan expiration.
595 * This is to match the gifting banner logic.
596 */
597 $days_of_warning = str_contains( $purchase->product_slug, 'monthly' ) ? 5 : 54;
598 $seconds_until_expiration = strtotime( $purchase->expiry_date ) - time();
599 if ( $seconds_until_expiration >= $days_of_warning * DAY_IN_SECONDS ) {
600 return false;
601 }
602
603 // We set default to the inverse of auto-renew.
604 if ( isset( $purchase->auto_renew ) ) {
605 return ! $purchase->auto_renew;
606 } elseif ( isset( $purchase->user_allows_auto_renew ) ) {
607 return ! $purchase->user_allows_auto_renew;
608 }
609 }
610 }
611 }
612 return false;
613 }
614
615 /**
616 * Get locale.
617 *
618 * @param string $key Language.
619 */
620 protected function get_locale( $key ) {
621 if ( 'lang' === $key ) {
622 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
623 return (string) get_blog_lang_code();
624 } else {
625 return get_locale();
626 }
627 }
628
629 return false;
630 }
631
632 /**
633 * Updates site settings for authorized users
634 *
635 * @return array|WP_Error
636 */
637 public function update_settings() {
638 /*
639 * $this->input() retrieves posted arguments whitelisted and casted to the $request_format
640 * specs that get passed in when this class is instantiated
641 */
642 $input = $this->input();
643 $unfiltered_input = $this->input( false, false );
644 /**
645 * Filters the settings to be updated on the site.
646 *
647 * @module json-api
648 *
649 * @since 3.6.0
650 * @since 6.1.1 Added $unfiltered_input parameter.
651 *
652 * @param array $input Associative array of site settings to be updated.
653 * Cast and filtered based on documentation.
654 * @param array $unfiltered_input Associative array of site settings to be updated.
655 * Neither cast nor filtered. Contains raw input.
656 */
657 $input = apply_filters( 'rest_api_update_site_settings', $input, $unfiltered_input );
658
659 $blog_id = get_current_blog_id();
660
661 $jetpack_relatedposts_options = array();
662 $sharing_options = array();
663 $updated = array();
664
665 foreach ( $input as $key => $value ) {
666
667 if ( ! is_array( $value ) ) {
668 $value = trim( $value );
669 }
670
671 // preserve the raw value before unslashing the value. The slashes need to be preserved for date and time formats.
672 $raw_value = $value;
673 $value = wp_unslash( $value );
674
675 switch ( $key ) {
676
677 case 'default_ping_status':
678 case 'default_comment_status':
679 // settings are stored as closed|open.
680 $coerce_value = ( $value ) ? 'open' : 'closed';
681 if ( update_option( $key, $coerce_value ) ) {
682 $updated[ $key ] = $value;
683 }
684 break;
685 case 'launchpad_screen':
686 if ( in_array( $value, array( 'full', 'off', 'minimized' ), true ) ) {
687 if ( update_option( $key, $value ) ) {
688 $updated[ $key ] = $value;
689 }
690 }
691 break;
692 case 'jetpack_protect_whitelist':
693 if ( class_exists( 'Brute_Force_Protection_Shared_Functions' ) ) {
694 $result = Brute_Force_Protection_Shared_Functions::save_allow_list( $value );
695 if ( is_wp_error( $result ) ) {
696 return $result;
697 }
698 $updated[ $key ] = Brute_Force_Protection_Shared_Functions::format_allow_list();
699 }
700 break;
701 case 'jetpack_sync_non_public_post_stati':
702 Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
703 break;
704 case 'jetpack_search_enabled':
705 if ( $value ) {
706 Jetpack::activate_module( $blog_id, 'search' );
707 } else {
708 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
709 Jetpack::deactivate_module( $blog_id, 'search' );
710 }
711 $updated[ $key ] = (bool) $value;
712 break;
713 case 'jetpack_relatedposts_enabled':
714 case 'jetpack_relatedposts_show_context':
715 case 'jetpack_relatedposts_show_date':
716 case 'jetpack_relatedposts_show_thumbnails':
717 case 'jetpack_relatedposts_show_headline':
718 if ( ! $this->jetpack_relatedposts_supported() ) {
719 break;
720 }
721 if ( 'jetpack_relatedposts_enabled' === $key ) {
722 if ( $value ) {
723 Jetpack::activate_module( $blog_id, 'related-posts' );
724 } else {
725 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
726 Jetpack::deactivate_module( $blog_id, 'related-posts' );
727 }
728 }
729 $just_the_key = substr( $key, 21 );
730 $jetpack_relatedposts_options[ $just_the_key ] = $value;
731 break;
732
733 case 'social_notifications_like':
734 case 'social_notifications_reblog':
735 case 'social_notifications_subscribe':
736 // settings are stored as on|off.
737 $coerce_value = ( $value ) ? 'on' : 'off';
738 if ( update_option( $key, $coerce_value ) ) {
739 $updated[ $key ] = $value;
740 }
741 break;
742
743 case 'cloudflare_analytics':
744 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
745 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
746 }
747
748 if ( update_option( $key, $value ) ) {
749 $updated[ $key ] = $value;
750 }
751 break;
752
753 case 'jetpack_testimonial':
754 case 'jetpack_portfolio':
755 case 'jetpack_comment_likes_enabled':
756 case 'wpcom_reader_views_enabled':
757 case 'jetpack_verbum_subscription_modal':
758 // settings are stored as 1|0.
759 $coerce_value = (int) $value;
760 if ( update_option( $key, $coerce_value ) ) {
761 $updated[ $key ] = (bool) $value;
762 }
763 break;
764
765 case 'jetpack_testimonial_posts_per_page':
766 case 'jetpack_portfolio_posts_per_page':
767 // settings are stored as numeric.
768 $coerce_value = (int) $value;
769 if ( update_option( $key, $coerce_value ) ) {
770 $updated[ $key ] = $coerce_value;
771 }
772 break;
773
774 // Sharing options.
775 case 'sharing_button_style':
776 case 'sharing_show':
777 case 'sharing_open_links':
778 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
779 break;
780 case 'sharing_label':
781 $sharing_options[ $key ] = $value;
782 break;
783
784 // Keyring token option.
785 case 'eventbrite_api_token':
786 // These options can only be updated for sites hosted on WordPress.com.
787 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
788 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
789 if ( delete_option( $key ) ) {
790 $updated[ $key ] = null;
791 }
792 } elseif ( update_option( $key, $value ) ) {
793 $updated[ $key ] = (int) $value;
794 }
795 }
796 break;
797
798 case 'api_cache':
799 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
800 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
801 $updated[ $key ] = false;
802 }
803 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
804 $updated[ $key ] = true;
805 }
806 break;
807
808 case 'timezone_string':
809 /*
810 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
811 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
812 */
813 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
814 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
815 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
816 $updated['gmt_offset'] = $gmt_offset;
817 }
818
819 $value = '';
820 }
821
822 /*
823 * Always set timezone_string either with the given value or with an
824 * empty string
825 */
826 if ( update_option( $key, $value ) ) {
827 $updated[ $key ] = $value;
828 }
829 break;
830
831 case 'subscription_options':
832 if ( ! is_array( $value ) ) {
833 break;
834 }
835
836 $allowed_keys = array( 'invitation', 'comment_follow', 'welcome' );
837 $filtered_value = array_filter(
838 $value,
839 function ( $key ) use ( $allowed_keys ) {
840 return in_array( $key, $allowed_keys, true );
841 },
842 ARRAY_FILTER_USE_KEY
843 );
844
845 if ( empty( $filtered_value ) ) {
846 break;
847 }
848
849 array_walk_recursive(
850 $filtered_value,
851 function ( &$value ) {
852 $value = wp_kses(
853 $value,
854 array(
855 'a' => array(
856 'href' => array(),
857 ),
858 )
859 );
860 }
861 );
862
863 $old_subscription_options = get_option( 'subscription_options' );
864 $new_subscription_options = array_merge( $old_subscription_options, $filtered_value );
865
866 if ( update_option( $key, $new_subscription_options ) ) {
867 $updated[ $key ] = $filtered_value;
868 }
869 break;
870
871 case 'woocommerce_onboarding_profile':
872 // Allow boolean values but sanitize_text_field everything else.
873 $sanitized_value = (array) $value;
874 array_walk_recursive(
875 $sanitized_value,
876 function ( &$value ) {
877 if ( ! is_bool( $value ) ) {
878 $value = sanitize_text_field( $value );
879 }
880 }
881 );
882 if ( update_option( $key, $sanitized_value ) ) {
883 $updated[ $key ] = $sanitized_value;
884 }
885 break;
886
887 case 'woocommerce_store_address':
888 case 'woocommerce_store_address_2':
889 case 'woocommerce_store_city':
890 case 'woocommerce_default_country':
891 case 'woocommerce_store_postcode':
892 $sanitized_value = sanitize_text_field( $value );
893 if ( update_option( $key, $sanitized_value ) ) {
894 $updated[ $key ] = $sanitized_value;
895 }
896 break;
897
898 case 'date_format':
899 case 'time_format':
900 // settings are stored as strings.
901 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
902 $sanitized_value = sanitize_text_field( $raw_value );
903 if ( update_option( $key, $sanitized_value ) ) {
904 $updated[ $key ] = $sanitized_value;
905 }
906 break;
907
908 case 'start_of_week':
909 // setting is stored as int in 0-6 range (days of week).
910 $coerce_value = (int) $value;
911 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
912 if ( update_option( $key, $limit_value ) ) {
913 $updated[ $key ] = $limit_value;
914 }
915 break;
916
917 case 'site_icon':
918 /*
919 * settings are stored as deletable numeric (all empty
920 * values as delete intent), validated as media image
921 */
922 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
923 /**
924 * Fallback mechanism to clear a third party site icon setting. Can be used
925 * to unset the option when an API request instructs the site to remove the site icon.
926 *
927 * @module json-api
928 *
929 * @since 4.10
930 */
931 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
932 $updated[ $key ] = null;
933 }
934 } elseif ( is_numeric( $value ) ) {
935 $coerce_value = (int) $value;
936 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
937 $updated[ $key ] = $coerce_value;
938 }
939 }
940 break;
941
942 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
943 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
944 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
945 }
946
947 if ( ! is_string( $value ) ) {
948 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
949 }
950
951 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
952
953 if ( ! empty( $new_description ) ) {
954 $updated[ $key ] = $new_description;
955 }
956 break;
957
958 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
959 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
960 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
961 break;
962 }
963 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
964 }
965
966 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
967 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
968 }
969
970 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
971
972 if ( ! empty( $new_title_formats ) ) {
973 $updated[ $key ] = $new_title_formats;
974 }
975 break;
976
977 case 'verification_services_codes':
978 $verification_codes = jetpack_verification_validate( $value );
979
980 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
981 $updated[ $key ] = $verification_codes;
982 }
983 break;
984
985 case 'wpcom_publish_posts_with_markdown':
986 case 'wpcom_publish_comments_with_markdown':
987 $coerce_value = (bool) $value;
988 if ( update_option( $key, $coerce_value ) ) {
989 $updated[ $key ] = $coerce_value;
990 }
991 break;
992
993 case 'wpcom_gifting_subscription':
994 $coerce_value = (bool) $value;
995
996 /*
997 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
998 * a serialized value. Knowing that we can check if the option already exists.
999 */
1000 $gift_toggle = get_option( $key );
1001 if ( false === $gift_toggle ) {
1002 // update_option will not create a new option if the initial value is false. So use add_option.
1003 if ( add_option( $key, $coerce_value ) ) {
1004 $updated[ $key ] = $coerce_value;
1005 }
1006 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
1007 $updated[ $key ] = $coerce_value;
1008 }
1009 break;
1010
1011 case 'rss_use_excerpt':
1012 $sanitized_value = (int) (bool) $value;
1013 update_option( $key, $sanitized_value );
1014 $updated[ $key ] = $sanitized_value;
1015 break;
1016
1017 case 'wpcom_subscription_emails_use_excerpt':
1018 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
1019 $updated[ $key ] = (bool) $value;
1020 break;
1021
1022 case 'jetpack_subscriptions_reply_to':
1023 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1024 $to_set_value = Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value )
1025 ? (string) $value
1026 : Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1027
1028 if ( update_option( $key, $to_set_value ) ) {
1029 $updated[ $key ] = $to_set_value;
1030 }
1031 break;
1032
1033 case 'jetpack_subscriptions_from_name':
1034 $sanitized_value = sanitize_text_field( $value );
1035 if ( update_option( $key, $sanitized_value ) ) {
1036 $updated[ $key ] = $sanitized_value;
1037 }
1038 break;
1039
1040 case 'instant_search_enabled':
1041 update_option( 'instant_search_enabled', (bool) $value );
1042 $updated[ $key ] = (bool) $value;
1043 break;
1044
1045 case 'lang_id':
1046 /*
1047 * Due to the fact that locale variants are set in a locale_variant option,
1048 * changing locale from variant to primary
1049 * would look like the same lang_id is being saved and update_option would return false,
1050 * even though the correct options would be set by pre_update_option_lang_id,
1051 * so we should always return lang_id as updated.
1052 */
1053 update_option( 'lang_id', (int) $value );
1054 $updated[ $key ] = (int) $value;
1055 break;
1056
1057 case 'wpcom_featured_image_in_email':
1058 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1059 $updated[ $key ] = (int) (bool) $value;
1060 break;
1061
1062 case 'wpcom_newsletter_categories':
1063 $sanitized_category_ids = (array) $value;
1064
1065 array_walk_recursive(
1066 $sanitized_category_ids,
1067 function ( &$value ) {
1068 if ( is_int( $value ) && $value > 0 ) {
1069 return;
1070 }
1071
1072 $value = (int) $value;
1073 if ( $value <= 0 ) {
1074 $value = null;
1075 }
1076 }
1077 );
1078
1079 $sanitized_category_ids = array_unique(
1080 array_filter(
1081 $sanitized_category_ids,
1082 function ( $category_id ) {
1083 return $category_id !== null;
1084 }
1085 )
1086 );
1087
1088 $new_value = array_map(
1089 function ( $category_id ) {
1090 return array( 'term_id' => $category_id );
1091 },
1092 $sanitized_category_ids
1093 );
1094
1095 if ( update_option( $key, $new_value ) ) {
1096 $updated[ $key ] = $sanitized_category_ids;
1097 }
1098 break;
1099
1100 case 'wpcom_newsletter_categories_enabled':
1101 update_option( 'wpcom_newsletter_categories_enabled', (int) (bool) $value );
1102 $updated[ $key ] = (int) (bool) $value;
1103 break;
1104
1105 case 'sm_enabled':
1106 update_option( 'sm_enabled', (int) (bool) $value );
1107 $updated[ $key ] = (int) (bool) $value;
1108 break;
1109
1110 case 'jetpack_subscribe_overlay_enabled':
1111 update_option( 'jetpack_subscribe_overlay_enabled', (int) (bool) $value );
1112 $updated[ $key ] = (int) (bool) $value;
1113 break;
1114
1115 case 'jetpack_subscribe_floating_button_enabled':
1116 update_option( 'jetpack_subscribe_floating_button_enabled', (int) (bool) $value );
1117 $updated[ $key ] = (int) (bool) $value;
1118 break;
1119
1120 case 'jetpack_subscriptions_subscribe_post_end_enabled':
1121 update_option( 'jetpack_subscriptions_subscribe_post_end_enabled', (int) (bool) $value );
1122 $updated[ $key ] = (int) (bool) $value;
1123 break;
1124
1125 case 'jetpack_subscriptions_login_navigation_enabled':
1126 update_option( 'jetpack_subscriptions_login_navigation_enabled', (int) (bool) $value );
1127 $updated[ $key ] = (int) (bool) $value;
1128 break;
1129
1130 case 'jetpack_subscriptions_subscribe_navigation_enabled':
1131 update_option( 'jetpack_subscriptions_subscribe_navigation_enabled', (int) (bool) $value );
1132 $updated[ $key ] = (int) (bool) $value;
1133 break;
1134
1135 case 'show_on_front':
1136 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1137 $updated[ $key ] = $value;
1138 }
1139 break;
1140
1141 case 'page_on_front':
1142 case 'page_for_posts':
1143 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1144 if ( delete_option( $key ) ) {
1145 $updated[ $key ] = null;
1146 }
1147
1148 break;
1149 }
1150
1151 if ( ! $this->is_valid_page_id( $value ) ) {
1152 break;
1153 }
1154
1155 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1156 $related_option_value = get_option( $related_option_key );
1157 if ( $related_option_value === $value ) {
1158 // page_on_front and page_for_posts are not allowed to be the same
1159 break;
1160 }
1161
1162 if ( update_option( $key, $value ) ) {
1163 $updated[ $key ] = $value;
1164 }
1165
1166 break;
1167
1168 case 'in_site_migration_flow':
1169 if ( empty( $value ) ) {
1170 delete_option( 'in_site_migration_flow' );
1171 break;
1172 }
1173
1174 $migration_flow_whitelist = array(
1175 'site-migration',
1176 'migration-signup',
1177 );
1178
1179 if ( ! in_array( $value, $migration_flow_whitelist, true ) ) {
1180 break;
1181 }
1182
1183 update_option( 'in_site_migration_flow', $value );
1184 $updated[ $key ] = $value;
1185 break;
1186
1187 case 'migration_source_site_domain':
1188 // If we get an empty value, delete the option
1189 if ( empty( $value ) ) {
1190 delete_option( 'migration_source_site_domain' );
1191 break;
1192 }
1193
1194 // If we get a non-url value, don't update the option.
1195 if ( wp_http_validate_url( $value ) === false ) {
1196 break;
1197 }
1198
1199 update_option( 'migration_source_site_domain', $value );
1200 $updated[ $key ] = $value;
1201 break;
1202
1203 case 'is_fully_managed_agency_site':
1204 case 'wpcom_hide_action_bar':
1205 $coerce_value = (int) (bool) $value;
1206 if ( update_option( $key, $coerce_value ) ) {
1207 $updated[ $key ] = (bool) $coerce_value;
1208 }
1209 break;
1210
1211 default:
1212 // allow future versions of this endpoint to support additional settings keys.
1213 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1214 /**
1215 * Filter current site setting value to be updated.
1216 *
1217 * @module json-api
1218 *
1219 * @since 3.9.3
1220 * @since 13.6 Added the API object parameter.
1221 *
1222 * @param mixed $response_item A single site setting value.
1223 * @param WPCOM_JSON_API_Site_Settings_Endpoint The API object parameter.
1224 */
1225 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value, $this );
1226
1227 if ( is_wp_error( $value ) ) {
1228 return $value;
1229 }
1230
1231 if ( $value ) {
1232 $updated[ $key ] = $value;
1233 }
1234 break;
1235 }
1236 // no worries, we've already whitelisted and casted arguments above.
1237 if ( update_option( $key, $value ) ) {
1238 $updated[ $key ] = $value;
1239 }
1240 }
1241 }
1242
1243 if ( $jetpack_relatedposts_options !== array() ) {
1244 // track new jetpack_relatedposts options against old.
1245 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1246
1247 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1248 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1249 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1250 }
1251
1252 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1253 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1254 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1255 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1256 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1257
1258 if ( $has_initialized_option || $has_updated_option ) {
1259 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1260 }
1261 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1262 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1263 }
1264 }
1265 }
1266 }
1267
1268 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1269 $ss = new Sharing_Service();
1270
1271 /*
1272 * Merge current values with updated, since Sharing_Service expects
1273 * all values to be included when updating
1274 */
1275 $current_sharing_options = $ss->get_global_options();
1276 foreach ( $current_sharing_options as $key => $val ) {
1277 if ( ! isset( $sharing_options[ $key ] ) ) {
1278 $sharing_options[ $key ] = $val;
1279 }
1280 }
1281
1282 $updated_social_options = $ss->set_global_options( $sharing_options );
1283
1284 if ( isset( $input['sharing_button_style'] ) ) {
1285 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1286 }
1287 if ( isset( $input['sharing_label'] ) ) {
1288 // Sharing_Service won't report label as updated if set to default.
1289 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1290 }
1291 if ( isset( $input['sharing_show'] ) ) {
1292 $updated['sharing_show'] = (array) $updated_social_options['show'];
1293 }
1294 if ( isset( $input['sharing_open_links'] ) ) {
1295 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1296 }
1297 }
1298
1299 return array(
1300 'updated' => $updated,
1301 );
1302 }
1303
1304 /**
1305 * Get the string value of the jetpack_subscriptions_reply_to option.
1306 * When the option is not set, it will retun 'no-reply'.
1307 *
1308 * @return string
1309 */
1310 protected function get_subscriptions_reply_to_option() {
1311 $reply_to = get_option( 'jetpack_subscriptions_reply_to', null );
1312 if ( $reply_to === null ) {
1313 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1314 return Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1315 }
1316 return $reply_to;
1317 }
1318
1319 /**
1320 * Check if the given value is a valid page ID for the current site.
1321 *
1322 * @param mixed $value The value to check.
1323 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1324 */
1325 protected function is_valid_page_id( $value ) {
1326 $all_page_ids = get_all_page_ids();
1327
1328 $valid_page_id = false;
1329 foreach ( $all_page_ids as $page_id ) {
1330 if ( $page_id === (string) $value ) {
1331 $valid_page_id = true;
1332 break;
1333 }
1334 }
1335
1336 return $valid_page_id;
1337 }
1338
1339 /**
1340 * Get the value of the highlander_comment_form_prompt option.
1341 * When the option is not set, it will return the default value.
1342 *
1343 * @return string
1344 */
1345 protected function get_highlander_comment_form_prompt_option() {
1346 $highlander_comment_form_prompt_option = get_option( 'highlander_comment_form_prompt' );
1347
1348 if ( empty( $highlander_comment_form_prompt_option ) ) {
1349 return (string) __( 'Leave a comment', 'jetpack' );
1350 }
1351
1352 return (string) $highlander_comment_form_prompt_option;
1353 }
1354 }
1355