Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
36.71% covered (danger)
36.71%
29 / 79
41.67% covered (danger)
41.67%
5 / 12
CRAP
n/a
0 / 0
A8C\FSE\Coming_soon\should_show_coming_soon_page
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
110
A8C\FSE\Coming_soon\get_share_code
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
A8C\FSE\Coming_soon\is_accessed_by_valid_share_link
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
5.12
A8C\FSE\Coming_soon\maybe_add_x_robots_headers
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
A8C\FSE\Coming_soon\track_preview_link_event
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
A8C\FSE\Coming_soon\coming_soon_share_image
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
A8C\FSE\Coming_soon\render_fallback_coming_soon_page
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
A8C\FSE\Coming_soon\add_public_coming_soon_to_settings_endpoint_get
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
A8C\FSE\Coming_soon\add_public_coming_soon_to_settings_endpoint_post
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
A8C\FSE\Coming_soon\disable_coming_soon_on_privacy_change
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
A8C\FSE\Coming_soon\add_option_to_new_site
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
A8C\FSE\Coming_soon\coming_soon_page
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Coming Soon
4 *
5 * @package A8C\FSE\Coming_soon
6 */
7
8namespace A8C\FSE\Coming_soon;
9
10use Automattic\Jetpack\Jetpack_Mu_Wpcom;
11
12require_once __DIR__ . '/../../utils.php';
13
14/**
15 * Determines whether the coming soon page should be shown.
16 *
17 * @return boolean
18 */
19function should_show_coming_soon_page() {
20    if ( ! is_singular() && ! is_archive() && ! is_search() && ! is_front_page() && ! is_home() && ! is_404() ) {
21        return false;
22    }
23
24    $share_code = get_share_code();
25    if ( is_accessed_by_valid_share_link( $share_code ) ) {
26        track_preview_link_event();
27        setcookie( 'wp_share_code', $share_code, time() + 3600, '/', '', is_ssl(), true );
28        return false;
29    }
30
31    $should_show = ( (int) get_option( 'wpcom_public_coming_soon' ) === 1 );
32
33    // Everyone from Administrator to Subscriber will be able to see the site.
34    // See https://wordpress.org/support/article/roles-and-capabilities/ for all roles and capabilities.
35    // We can update to `edit_post` to be stricter, or open it up as an editable feature.
36    if ( is_user_logged_in() && current_user_can( 'read' ) ) {
37        $should_show = false;
38    }
39
40    // Allow folks to hook into this method to set their own rules.
41    // We'll use to on WordPress.com to check further user privileges.
42    return apply_filters( 'a8c_show_coming_soon_page', $should_show );
43}
44
45/**
46 * Gets share code from URL or Cookie.
47 *
48 * @return string
49 */
50function get_share_code() {
51    if ( isset( $_GET['share'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No data written to site, used in conditionally displaying coming-soon page and adding robots headers.
52        return sanitize_key( wp_unslash( $_GET['share'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No data written to site, used in conditionally displaying coming-soon page and adding robots headers.
53    } elseif ( isset( $_COOKIE['wp_share_code'] ) ) {
54        return sanitize_key( wp_unslash( $_COOKIE['wp_share_code'] ) );
55    }
56    return '';
57}
58
59/**
60 * Determines whether the coming soon page should be bypassed.
61 *
62 * It checks if share code is provided as GET parameter, or as a cookie.
63 * Then it validates the code against blog option, and if sharing code is valid,
64 * it allows bypassing the Coming Soon page.
65 *
66 * @param string $share_code Share code to check against blog option value.
67 *
68 * @return bool
69 */
70function is_accessed_by_valid_share_link( $share_code ) {
71    $preview_links_option = get_option( 'wpcom_public_preview_links' );
72
73    if ( ! is_array( $preview_links_option ) || array() === $preview_links_option || ! $share_code ) {
74        return false;
75    }
76
77    if ( ! in_array( $share_code, array_column( $preview_links_option, 'code' ), true ) ) {
78        return false;
79    }
80
81    return true;
82}
83
84/**
85 * Add X-Robots-Tag header to prevent from indexing page that includes share code.
86 *
87 * @param array $headers Headers.
88 * @return array Headers.
89 */
90function maybe_add_x_robots_headers( $headers ) {
91    if ( get_share_code() ) {
92        $headers['X-Robots-Tag'] = 'noindex, nofollow';
93    }
94    return $headers;
95}
96add_filter( 'wp_headers', __NAMESPACE__ . '\maybe_add_x_robots_headers' );
97
98/**
99 * Track site preview event.
100 *
101 * @return void
102 */
103function track_preview_link_event() {
104    $event_name = 'wpcom_site_previewed';
105    if ( function_exists( 'wpcomsh_record_tracks_event' ) ) {
106        wpcomsh_record_tracks_event( $event_name, array() );
107    } else {
108        require_lib( 'tracks/client' );
109        tracks_record_event( get_current_user_id(), $event_name, array() );
110    }
111}
112
113/**
114 * Returns the WP.com logo
115 *
116 * @return string
117 */
118function coming_soon_share_image() {
119    return 'https://s1.wp.com/home.logged-out/images/wpcom-og-image.jpg';
120}
121
122/**
123 * Renders a fallback coming soon page
124 */
125function render_fallback_coming_soon_page() {
126    if ( ! defined( 'GRAVATAR_HOVERCARDS__DISABLE' ) ) {
127        define( 'GRAVATAR_HOVERCARDS__DISABLE', true );
128    }
129
130    // Disable WP scripts, likes, social og meta, cookie banner.
131    remove_action( 'wp_enqueue_scripts', 'wpcom_actionbar_enqueue_scripts', 101 );
132    add_filter( 'jetpack_action_bar_enabled', '__return_false' );
133    remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
134    remove_action( 'wp_print_styles', 'print_emoji_styles' );
135    remove_action( 'wp_head', 'header_js', 5 );
136    remove_action( 'wp_head', 'global_css', 5 );
137    remove_action( 'wp_footer', 'stats_footer', 101 );
138    add_filter( 'infinite_scroll_archive_supported', '__return_false', 99 ); // Disable infinite scroll feature.
139    add_filter( 'jetpack_disable_eu_cookie_law_widget', '__return_true', 1 );
140    add_filter( 'wpcom_disable_logged_out_follow', '__return_true', 10, 1 ); // Disable follow actionbar.
141    add_filter( 'wpl_is_enabled_sitewide', '__return_false', 10, 1 ); // Disable likes.
142    add_filter( 'woocommerce_demo_store', '__return_false' ); // Prevent the the wocommerce demo store notice from displaying.
143    add_filter( 'jetpack_open_graph_image_default', __NAMESPACE__ . '\coming_soon_share_image' ); // Set the default OG image.
144    add_filter( 'jetpack_twitter_cards_image_default', __NAMESPACE__ . '\coming_soon_share_image' ); // Set the default Twitter Card image.
145    wp_enqueue_style( 'recoleta-font', '//s1.wp.com/i/fonts/recoleta/css/400.min.css', array(), Jetpack_Mu_Wpcom::PACKAGE_VERSION );
146
147    include __DIR__ . '/fallback-coming-soon-page.php';
148}
149
150/**
151 * This filter makes sure it's possible to fetch `wpcom_public_coming_soon` option via public-api.wordpress.com.
152 *
153 * @param object $options array Retrieved site options, ready to be returned as API respomnse.
154 *
155 * @return array current value of `wpcom_public_coming_soon`
156 */
157function add_public_coming_soon_to_settings_endpoint_get( $options ) {
158    $wpcom_public_coming_soon            = (int) get_option( 'wpcom_public_coming_soon' );
159    $options['wpcom_public_coming_soon'] = $wpcom_public_coming_soon;
160
161    return $options;
162}
163add_filter( 'site_settings_endpoint_get', __NAMESPACE__ . '\add_public_coming_soon_to_settings_endpoint_get' );
164
165/**
166 * This filter makes sure it's possible to change `wpcom_public_coming_soon` option via public-api.wordpress.com.
167 *
168 * @param object $input Filtered POST input.
169 * @param object $unfiltered_input Raw and unfiltered POST input.
170 *
171 * @return mixed
172 */
173function add_public_coming_soon_to_settings_endpoint_post( $input, $unfiltered_input ) {
174    if ( is_array( $unfiltered_input ) && array_key_exists( 'wpcom_public_coming_soon', $unfiltered_input ) ) {
175        $input['wpcom_public_coming_soon'] = (int) $unfiltered_input['wpcom_public_coming_soon'];
176
177        // Avoid updating the value of the `wpcom_public_coming_soon` if the request wants to change the option.
178        remove_action( 'update_option_blog_public', __NAMESPACE__ . '\disable_coming_soon_on_privacy_change', 10 );
179    }
180    return $input;
181}
182add_filter( 'rest_api_update_site_settings', __NAMESPACE__ . '\add_public_coming_soon_to_settings_endpoint_post', 10, 2 );
183
184/**
185 * Launch the site when the privacy mode changes from public-not-indexed
186 * This can happen due to clicking the launch button from the banner
187 * Or due to manually updating the setting in wp-admin or calypso settings page.
188 *
189 * @param  string $old_value the old value of blog_public.
190 * @param  string $value     the new value of blog_public.
191 * @return bool              whether an update occurred.
192 */
193function disable_coming_soon_on_privacy_change( $old_value, $value ) {
194    if ( 0 !== (int) $old_value || 0 === (int) $value ) {
195        // Do nothing if not moving from public-not-indexed.
196        return false;
197    }
198    update_option( 'wpcom_public_coming_soon', 0 );
199    return true;
200}
201add_action( 'update_option_blog_public', __NAMESPACE__ . '\disable_coming_soon_on_privacy_change', 10, 2 );
202
203/**
204 * Adds the `wpcom_public_coming_soon` option to new sites  if requested by the client.
205 *
206 * @param int    $blog_id    Blog ID.
207 * @param int    $user_id    User ID.
208 * @param string $domain     Site domain.
209 * @param string $path       Site path.
210 * @param int    $network_id Network ID. Only relevant on multi-network installations.
211 * @param array  $meta       Meta data. Used to set initial site options.
212 * @return bool              whether an update occurred.
213 */
214function add_option_to_new_site( $blog_id, $user_id, $domain, $path, $network_id, $meta ) {
215    if ( isset( $meta['public'] )
216        && 0 === $meta['public']
217        && isset( $meta['options']['wpcom_public_coming_soon'] )
218        && 1 === (int) $meta['options']['wpcom_public_coming_soon']
219    ) {
220        if ( function_exists( 'add_blog_option' ) ) {
221            add_blog_option( $blog_id, 'wpcom_public_coming_soon', 1 );
222        }
223        return true;
224    }
225    return false;
226}
227
228add_action( 'wpmu_new_blog', __NAMESPACE__ . '\add_option_to_new_site', 10, 6 );
229
230/**
231 * Decides whether to render to the site's coming soon page and performs
232 * the render.
233 *
234 * @param string $template The template to render.
235 */
236function coming_soon_page( $template ) {
237    if ( ! should_show_coming_soon_page() ) {
238        return $template;
239    }
240
241    render_fallback_coming_soon_page();
242    die( 0 );
243}
244add_filter( 'template_include', __NAMESPACE__ . '\coming_soon_page' );