PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.3.1
Jetpack – WP Security, Backup, Speed, & Growth v14.3.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 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 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 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 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 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
1338 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 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
693 Jetpack::deactivate_module( $blog_id, 'search' );
694 }
695 $updated[ $key ] = (bool) $value;
696 break;
697 case 'jetpack_relatedposts_enabled':
698 case 'jetpack_relatedposts_show_context':
699 case 'jetpack_relatedposts_show_date':
700 case 'jetpack_relatedposts_show_thumbnails':
701 case 'jetpack_relatedposts_show_headline':
702 if ( ! $this->jetpack_relatedposts_supported() ) {
703 break;
704 }
705 if ( 'jetpack_relatedposts_enabled' === $key ) {
706 if ( $value ) {
707 Jetpack::activate_module( $blog_id, 'related-posts' );
708 } else {
709 // @phan-suppress-next-line PhanParamTooMany -- Phan doesn't know about the WP.com variant of the Jetpack class.
710 Jetpack::deactivate_module( $blog_id, 'related-posts' );
711 }
712 }
713 $just_the_key = substr( $key, 21 );
714 $jetpack_relatedposts_options[ $just_the_key ] = $value;
715 break;
716
717 case 'social_notifications_like':
718 case 'social_notifications_reblog':
719 case 'social_notifications_subscribe':
720 // settings are stored as on|off.
721 $coerce_value = ( $value ) ? 'on' : 'off';
722 if ( update_option( $key, $coerce_value ) ) {
723 $updated[ $key ] = $value;
724 }
725 break;
726
727 case 'cloudflare_analytics':
728 if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) {
729 return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) );
730 }
731
732 if ( update_option( $key, $value ) ) {
733 $updated[ $key ] = $value;
734 }
735 break;
736
737 case 'jetpack_testimonial':
738 case 'jetpack_portfolio':
739 case 'jetpack_comment_likes_enabled':
740 case 'wpcom_reader_views_enabled':
741 case 'jetpack_verbum_subscription_modal':
742 // settings are stored as 1|0.
743 $coerce_value = (int) $value;
744 if ( update_option( $key, $coerce_value ) ) {
745 $updated[ $key ] = (bool) $value;
746 }
747 break;
748
749 case 'jetpack_testimonial_posts_per_page':
750 case 'jetpack_portfolio_posts_per_page':
751 // settings are stored as numeric.
752 $coerce_value = (int) $value;
753 if ( update_option( $key, $coerce_value ) ) {
754 $updated[ $key ] = $coerce_value;
755 }
756 break;
757
758 // Sharing options.
759 case 'sharing_button_style':
760 case 'sharing_show':
761 case 'sharing_open_links':
762 $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
763 break;
764 case 'sharing_label':
765 $sharing_options[ $key ] = $value;
766 break;
767
768 // Keyring token option.
769 case 'eventbrite_api_token':
770 // These options can only be updated for sites hosted on WordPress.com.
771 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
772 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
773 if ( delete_option( $key ) ) {
774 $updated[ $key ] = null;
775 }
776 } elseif ( update_option( $key, $value ) ) {
777 $updated[ $key ] = (int) $value;
778 }
779 }
780 break;
781
782 case 'api_cache':
783 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
784 if ( delete_option( 'jetpack_api_cache_enabled' ) ) {
785 $updated[ $key ] = false;
786 }
787 } elseif ( update_option( 'jetpack_api_cache_enabled', true ) ) {
788 $updated[ $key ] = true;
789 }
790 break;
791
792 case 'timezone_string':
793 /*
794 * Map UTC+- timezones to gmt_offsets and set timezone_string to empty
795 * https://github.com/WordPress/WordPress/blob/4.4.2/wp-admin/options.php#L175
796 */
797 if ( ! empty( $value ) && preg_match( '/^UTC[+-]/', $value ) ) {
798 $gmt_offset = preg_replace( '/UTC\+?/', '', $value );
799 if ( update_option( 'gmt_offset', $gmt_offset ) ) {
800 $updated['gmt_offset'] = $gmt_offset;
801 }
802
803 $value = '';
804 }
805
806 /*
807 * Always set timezone_string either with the given value or with an
808 * empty string
809 */
810 if ( update_option( $key, $value ) ) {
811 $updated[ $key ] = $value;
812 }
813 break;
814
815 case 'subscription_options':
816 if ( ! is_array( $value ) ) {
817 break;
818 }
819
820 $allowed_keys = array( 'invitation', 'comment_follow', 'welcome' );
821 $filtered_value = array_filter(
822 $value,
823 function ( $key ) use ( $allowed_keys ) {
824 return in_array( $key, $allowed_keys, true );
825 },
826 ARRAY_FILTER_USE_KEY
827 );
828
829 if ( empty( $filtered_value ) ) {
830 break;
831 }
832
833 array_walk_recursive(
834 $filtered_value,
835 function ( &$value ) {
836 $value = wp_kses(
837 $value,
838 array(
839 'a' => array(
840 'href' => array(),
841 ),
842 )
843 );
844 }
845 );
846
847 $old_subscription_options = get_option( 'subscription_options' );
848 $new_subscription_options = array_merge( $old_subscription_options, $filtered_value );
849
850 if ( update_option( $key, $new_subscription_options ) ) {
851 $updated[ $key ] = $filtered_value;
852 }
853 break;
854
855 case 'woocommerce_onboarding_profile':
856 // Allow boolean values but sanitize_text_field everything else.
857 $sanitized_value = (array) $value;
858 array_walk_recursive(
859 $sanitized_value,
860 function ( &$value ) {
861 if ( ! is_bool( $value ) ) {
862 $value = sanitize_text_field( $value );
863 }
864 }
865 );
866 if ( update_option( $key, $sanitized_value ) ) {
867 $updated[ $key ] = $sanitized_value;
868 }
869 break;
870
871 case 'woocommerce_store_address':
872 case 'woocommerce_store_address_2':
873 case 'woocommerce_store_city':
874 case 'woocommerce_default_country':
875 case 'woocommerce_store_postcode':
876 $sanitized_value = sanitize_text_field( $value );
877 if ( update_option( $key, $sanitized_value ) ) {
878 $updated[ $key ] = $sanitized_value;
879 }
880 break;
881
882 case 'date_format':
883 case 'time_format':
884 // settings are stored as strings.
885 // raw_value is used to help preserve any escaped characters that might exist in the formatted string.
886 $sanitized_value = sanitize_text_field( $raw_value );
887 if ( update_option( $key, $sanitized_value ) ) {
888 $updated[ $key ] = $sanitized_value;
889 }
890 break;
891
892 case 'start_of_week':
893 // setting is stored as int in 0-6 range (days of week).
894 $coerce_value = (int) $value;
895 $limit_value = ( $coerce_value >= 0 && $coerce_value <= 6 ) ? $coerce_value : 0;
896 if ( update_option( $key, $limit_value ) ) {
897 $updated[ $key ] = $limit_value;
898 }
899 break;
900
901 case 'site_icon':
902 /*
903 * settings are stored as deletable numeric (all empty
904 * values as delete intent), validated as media image
905 */
906 if ( empty( $value ) || WPCOM_JSON_API::is_falsy( $value ) ) {
907 /**
908 * Fallback mechanism to clear a third party site icon setting. Can be used
909 * to unset the option when an API request instructs the site to remove the site icon.
910 *
911 * @module json-api
912 *
913 * @since 4.10
914 */
915 if ( delete_option( $key ) || apply_filters( 'rest_api_site_icon_cleared', false ) ) {
916 $updated[ $key ] = null;
917 }
918 } elseif ( is_numeric( $value ) ) {
919 $coerce_value = (int) $value;
920 if ( wp_attachment_is_image( $coerce_value ) && update_option( $key, $coerce_value ) ) {
921 $updated[ $key ] = $coerce_value;
922 }
923 }
924 break;
925
926 case Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION:
927 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() && ! Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
928 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
929 }
930
931 if ( ! is_string( $value ) ) {
932 return new WP_Error( 'invalid_input', __( 'Invalid SEO meta description value.', 'jetpack' ), 400 );
933 }
934
935 $new_description = Jetpack_SEO_Utils::update_front_page_meta_description( $value );
936
937 if ( ! empty( $new_description ) ) {
938 $updated[ $key ] = $new_description;
939 }
940 break;
941
942 case Jetpack_SEO_Titles::TITLE_FORMATS_OPTION:
943 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
944 if ( Jetpack_SEO_Utils::has_legacy_front_page_meta() ) {
945 break;
946 }
947 return new WP_Error( 'unauthorized', __( 'SEO tools are not enabled for this site.', 'jetpack' ), 403 );
948 }
949
950 if ( ! Jetpack_SEO_Titles::are_valid_title_formats( $value ) ) {
951 return new WP_Error( 'invalid_input', __( 'Invalid SEO title format.', 'jetpack' ), 400 );
952 }
953
954 $new_title_formats = Jetpack_SEO_Titles::update_title_formats( $value );
955
956 if ( ! empty( $new_title_formats ) ) {
957 $updated[ $key ] = $new_title_formats;
958 }
959 break;
960
961 case 'verification_services_codes':
962 $verification_codes = jetpack_verification_validate( $value );
963
964 if ( update_option( 'verification_services_codes', $verification_codes ) ) {
965 $updated[ $key ] = $verification_codes;
966 }
967 break;
968
969 case 'wpcom_publish_posts_with_markdown':
970 case 'wpcom_publish_comments_with_markdown':
971 $coerce_value = (bool) $value;
972 if ( update_option( $key, $coerce_value ) ) {
973 $updated[ $key ] = $coerce_value;
974 }
975 break;
976
977 case 'wpcom_gifting_subscription':
978 $coerce_value = (bool) $value;
979
980 /*
981 * get_option returns a boolean false if the option doesn't exist, otherwise it always returns
982 * a serialized value. Knowing that we can check if the option already exists.
983 */
984 $gift_toggle = get_option( $key );
985 if ( false === $gift_toggle ) {
986 // update_option will not create a new option if the initial value is false. So use add_option.
987 if ( add_option( $key, $coerce_value ) ) {
988 $updated[ $key ] = $coerce_value;
989 }
990 } elseif ( update_option( $key, $coerce_value ) ) { // If the option already exists use update_option.
991 $updated[ $key ] = $coerce_value;
992 }
993 break;
994
995 case 'rss_use_excerpt':
996 $sanitized_value = (int) (bool) $value;
997 update_option( $key, $sanitized_value );
998 $updated[ $key ] = $sanitized_value;
999 break;
1000
1001 case 'wpcom_subscription_emails_use_excerpt':
1002 update_option( 'wpcom_subscription_emails_use_excerpt', (bool) $value );
1003 $updated[ $key ] = (bool) $value;
1004 break;
1005
1006 case 'jetpack_subscriptions_reply_to':
1007 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1008 $to_set_value = Automattic\Jetpack\Modules\Subscriptions\Settings::is_valid_reply_to( $value )
1009 ? (string) $value
1010 : Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1011
1012 if ( update_option( $key, $to_set_value ) ) {
1013 $updated[ $key ] = $to_set_value;
1014 }
1015 break;
1016
1017 case 'jetpack_subscriptions_from_name':
1018 $sanitized_value = sanitize_text_field( $value );
1019 if ( update_option( $key, $sanitized_value ) ) {
1020 $updated[ $key ] = $sanitized_value;
1021 }
1022 break;
1023
1024 case 'instant_search_enabled':
1025 update_option( 'instant_search_enabled', (bool) $value );
1026 $updated[ $key ] = (bool) $value;
1027 break;
1028
1029 case 'lang_id':
1030 /*
1031 * Due to the fact that locale variants are set in a locale_variant option,
1032 * changing locale from variant to primary
1033 * would look like the same lang_id is being saved and update_option would return false,
1034 * even though the correct options would be set by pre_update_option_lang_id,
1035 * so we should always return lang_id as updated.
1036 */
1037 update_option( 'lang_id', (int) $value );
1038 $updated[ $key ] = (int) $value;
1039 break;
1040
1041 case 'wpcom_featured_image_in_email':
1042 update_option( 'wpcom_featured_image_in_email', (int) (bool) $value );
1043 $updated[ $key ] = (int) (bool) $value;
1044 break;
1045
1046 case 'wpcom_newsletter_categories':
1047 $sanitized_category_ids = (array) $value;
1048
1049 array_walk_recursive(
1050 $sanitized_category_ids,
1051 function ( &$value ) {
1052 if ( is_int( $value ) && $value > 0 ) {
1053 return;
1054 }
1055
1056 $value = (int) $value;
1057 if ( $value <= 0 ) {
1058 $value = null;
1059 }
1060 }
1061 );
1062
1063 $sanitized_category_ids = array_unique(
1064 array_filter(
1065 $sanitized_category_ids,
1066 function ( $category_id ) {
1067 return $category_id !== null;
1068 }
1069 )
1070 );
1071
1072 $new_value = array_map(
1073 function ( $category_id ) {
1074 return array( 'term_id' => $category_id );
1075 },
1076 $sanitized_category_ids
1077 );
1078
1079 if ( update_option( $key, $new_value ) ) {
1080 $updated[ $key ] = $sanitized_category_ids;
1081 }
1082 break;
1083
1084 case 'wpcom_newsletter_categories_enabled':
1085 update_option( 'wpcom_newsletter_categories_enabled', (int) (bool) $value );
1086 $updated[ $key ] = (int) (bool) $value;
1087 break;
1088
1089 case 'sm_enabled':
1090 update_option( 'sm_enabled', (int) (bool) $value );
1091 $updated[ $key ] = (int) (bool) $value;
1092 break;
1093
1094 case 'jetpack_subscribe_overlay_enabled':
1095 update_option( 'jetpack_subscribe_overlay_enabled', (int) (bool) $value );
1096 $updated[ $key ] = (int) (bool) $value;
1097 break;
1098
1099 case 'jetpack_subscribe_floating_button_enabled':
1100 update_option( 'jetpack_subscribe_floating_button_enabled', (int) (bool) $value );
1101 $updated[ $key ] = (int) (bool) $value;
1102 break;
1103
1104 case 'jetpack_subscriptions_subscribe_post_end_enabled':
1105 update_option( 'jetpack_subscriptions_subscribe_post_end_enabled', (int) (bool) $value );
1106 $updated[ $key ] = (int) (bool) $value;
1107 break;
1108
1109 case 'jetpack_subscriptions_login_navigation_enabled':
1110 update_option( 'jetpack_subscriptions_login_navigation_enabled', (int) (bool) $value );
1111 $updated[ $key ] = (int) (bool) $value;
1112 break;
1113
1114 case 'jetpack_subscriptions_subscribe_navigation_enabled':
1115 update_option( 'jetpack_subscriptions_subscribe_navigation_enabled', (int) (bool) $value );
1116 $updated[ $key ] = (int) (bool) $value;
1117 break;
1118
1119 case 'show_on_front':
1120 if ( in_array( $value, array( 'page', 'posts' ), true ) && update_option( $key, $value ) ) {
1121 $updated[ $key ] = $value;
1122 }
1123 break;
1124
1125 case 'page_on_front':
1126 case 'page_for_posts':
1127 if ( $value === '' ) { // empty function is not applicable here because '0' may be a valid page id
1128 if ( delete_option( $key ) ) {
1129 $updated[ $key ] = null;
1130 }
1131
1132 break;
1133 }
1134
1135 if ( ! $this->is_valid_page_id( $value ) ) {
1136 break;
1137 }
1138
1139 $related_option_key = $key === 'page_on_front' ? 'page_for_posts' : 'page_on_front';
1140 $related_option_value = get_option( $related_option_key );
1141 if ( $related_option_value === $value ) {
1142 // page_on_front and page_for_posts are not allowed to be the same
1143 break;
1144 }
1145
1146 if ( update_option( $key, $value ) ) {
1147 $updated[ $key ] = $value;
1148 }
1149
1150 break;
1151
1152 case 'in_site_migration_flow':
1153 if ( empty( $value ) ) {
1154 delete_option( 'in_site_migration_flow' );
1155 break;
1156 }
1157
1158 $migration_flow_whitelist = array(
1159 'site-migration',
1160 'migration-signup',
1161 );
1162
1163 if ( ! in_array( $value, $migration_flow_whitelist, true ) ) {
1164 break;
1165 }
1166
1167 update_option( 'in_site_migration_flow', $value );
1168 $updated[ $key ] = $value;
1169 break;
1170
1171 case 'migration_source_site_domain':
1172 // If we get an empty value, delete the option
1173 if ( empty( $value ) ) {
1174 delete_option( 'migration_source_site_domain' );
1175 break;
1176 }
1177
1178 // If we get a non-url value, don't update the option.
1179 if ( wp_http_validate_url( $value ) === false ) {
1180 break;
1181 }
1182
1183 update_option( 'migration_source_site_domain', $value );
1184 $updated[ $key ] = $value;
1185 break;
1186
1187 case 'is_fully_managed_agency_site':
1188 $coerce_value = (int) (bool) $value;
1189 if ( update_option( $key, $coerce_value ) ) {
1190 $updated[ $key ] = (bool) $coerce_value;
1191 }
1192 break;
1193
1194 default:
1195 // allow future versions of this endpoint to support additional settings keys.
1196 if ( has_filter( 'site_settings_endpoint_update_' . $key ) ) {
1197 /**
1198 * Filter current site setting value to be updated.
1199 *
1200 * @module json-api
1201 *
1202 * @since 3.9.3
1203 * @since 13.6 Added the API object parameter.
1204 *
1205 * @param mixed $response_item A single site setting value.
1206 * @param WPCOM_JSON_API_Site_Settings_Endpoint The API object parameter.
1207 */
1208 $value = apply_filters( 'site_settings_endpoint_update_' . $key, $value, $this );
1209
1210 if ( is_wp_error( $value ) ) {
1211 return $value;
1212 }
1213
1214 if ( $value ) {
1215 $updated[ $key ] = $value;
1216 }
1217 break;
1218 }
1219 // no worries, we've already whitelisted and casted arguments above.
1220 if ( update_option( $key, $value ) ) {
1221 $updated[ $key ] = $value;
1222 }
1223 }
1224 }
1225
1226 if ( $jetpack_relatedposts_options !== array() ) {
1227 // track new jetpack_relatedposts options against old.
1228 $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
1229
1230 $jetpack_relatedposts_options_to_save = $old_relatedposts_options;
1231 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1232 $jetpack_relatedposts_options_to_save[ $key ] = $value;
1233 }
1234
1235 if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options_to_save ) ) {
1236 foreach ( $jetpack_relatedposts_options as $key => $value ) {
1237 if ( in_array( $key, array( 'show_context', 'show_date' ), true ) ) {
1238 $has_initialized_option = ! isset( $old_relatedposts_options[ $key ] ) && $value;
1239 $has_updated_option = isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ];
1240
1241 if ( $has_initialized_option || $has_updated_option ) {
1242 $updated[ 'jetpack_relatedposts_' . $key ] = (bool) $value;
1243 }
1244 } elseif ( isset( $old_relatedposts_options[ $key ] ) && $value !== $old_relatedposts_options[ $key ] ) {
1245 $updated[ 'jetpack_relatedposts_' . $key ] = $value;
1246 }
1247 }
1248 }
1249 }
1250
1251 if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
1252 $ss = new Sharing_Service();
1253
1254 /*
1255 * Merge current values with updated, since Sharing_Service expects
1256 * all values to be included when updating
1257 */
1258 $current_sharing_options = $ss->get_global_options();
1259 foreach ( $current_sharing_options as $key => $val ) {
1260 if ( ! isset( $sharing_options[ $key ] ) ) {
1261 $sharing_options[ $key ] = $val;
1262 }
1263 }
1264
1265 $updated_social_options = $ss->set_global_options( $sharing_options );
1266
1267 if ( isset( $input['sharing_button_style'] ) ) {
1268 $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
1269 }
1270 if ( isset( $input['sharing_label'] ) ) {
1271 // Sharing_Service won't report label as updated if set to default.
1272 $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
1273 }
1274 if ( isset( $input['sharing_show'] ) ) {
1275 $updated['sharing_show'] = (array) $updated_social_options['show'];
1276 }
1277 if ( isset( $input['sharing_open_links'] ) ) {
1278 $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
1279 }
1280 }
1281
1282 return array(
1283 'updated' => $updated,
1284 );
1285 }
1286
1287 /**
1288 * Get the string value of the jetpack_subscriptions_reply_to option.
1289 * When the option is not set, it will retun 'no-reply'.
1290 *
1291 * @return string
1292 */
1293 protected function get_subscriptions_reply_to_option() {
1294 $reply_to = get_option( 'jetpack_subscriptions_reply_to', null );
1295 if ( $reply_to === null ) {
1296 require_once JETPACK__PLUGIN_DIR . 'modules/subscriptions/class-settings.php';
1297 return Automattic\Jetpack\Modules\Subscriptions\Settings::$default_reply_to;
1298 }
1299 return $reply_to;
1300 }
1301
1302 /**
1303 * Check if the given value is a valid page ID for the current site.
1304 *
1305 * @param mixed $value The value to check.
1306 * @return bool True if the value is a valid page ID for the current site, false otherwise.
1307 */
1308 protected function is_valid_page_id( $value ) {
1309 $all_page_ids = get_all_page_ids();
1310
1311 $valid_page_id = false;
1312 foreach ( $all_page_ids as $page_id ) {
1313 if ( $page_id === (string) $value ) {
1314 $valid_page_id = true;
1315 break;
1316 }
1317 }
1318
1319 return $valid_page_id;
1320 }
1321
1322 /**
1323 * Get the value of the highlander_comment_form_prompt option.
1324 * When the option is not set, it will return the default value.
1325 *
1326 * @return string
1327 */
1328 protected function get_highlander_comment_form_prompt_option() {
1329 $highlander_comment_form_prompt_option = get_option( 'highlander_comment_form_prompt' );
1330
1331 if ( empty( $highlander_comment_form_prompt_option ) ) {
1332 return (string) __( 'Leave a comment', 'jetpack' );
1333 }
1334
1335 return (string) $highlander_comment_form_prompt_option;
1336 }
1337 }
1338