Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 7
CRAP
n/a
0 / 0
Automattic\Jetpack\Extensions\CookieConsent\should_register_block
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
Automattic\Jetpack\Extensions\CookieConsent\register_block
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\CookieConsent\load_assets
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
Automattic\Jetpack\Extensions\CookieConsent\notify_batcache_that_content_changed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\CookieConsent\render_cookie_consent_template
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
Automattic\Jetpack\Extensions\CookieConsent\cookie_consent_register_settings
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\CookieConsent\add_css_fallback_values
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Cookie-consent Block.
4 *
5 * @since 12.0
6 *
7 * @package automattic/jetpack
8 */
9
10namespace Automattic\Jetpack\Extensions\CookieConsent;
11
12use Automattic\Jetpack\Blocks;
13use Jetpack_Gutenberg;
14
15if ( ! defined( 'ABSPATH' ) ) {
16    exit( 0 );
17}
18
19const COOKIE_NAME = 'eucookielaw';
20
21/**
22 * Should the block be registered?
23 * In wp-admin, we only want to show the block in the site editor.
24 *
25 * @since 12.0
26 *
27 * @return bool
28 */
29function should_register_block() {
30    global $pagenow;
31
32    // Always register the widget if we're on the front end
33    if ( ! is_admin() ) {
34        return true;
35    }
36
37    if ( is_admin() && $pagenow === 'site-editor.php' ) {
38        return true;
39    }
40}
41
42/**
43 * Registers the block for use in Gutenberg
44 * This is done via an action so that we can disable
45 * registration if we need to.
46 */
47function register_block() {
48    if ( ! should_register_block() ) {
49        return;
50    }
51
52    Blocks::jetpack_register_block(
53        __DIR__,
54        array(
55            'render_callback' => __NAMESPACE__ . '\load_assets',
56            'attributes'      => array(
57                'render_from_template' => array(
58                    'default' => false,
59                    'type'    => 'boolean',
60                ),
61            ),
62        )
63    );
64}
65add_action( 'init', __NAMESPACE__ . '\register_block' );
66
67/**
68 * Cookie-consent block registration/dependency declaration.
69 *
70 * @param array  $attr    Array containing the Cookie-consent block attributes.
71 * @param string $content String containing the Cookie-consent block content.
72 *
73 * @return string
74 */
75function load_assets( $attr, $content ) {
76    // We want to bust the cache even if the cookie isn't set.
77    // This is needed for when the cookie expires,
78    // and we should send fresh HTML with the cookie block in it.
79    notify_batcache_that_content_changed();
80
81    if (
82        /**
83         * Filters the display of the Cookie-consent Block e.g by GDPR CMP banner on WordAds sites.
84         *
85         * @since 13.2
86         *
87         * @param bool $disable_cookie_consent_block Whether to disable the Cookie-consent Block.
88         */
89        apply_filters( 'jetpack_disable_cookie_consent_block', false )
90    ) {
91        return '';
92    }
93
94    // If the user has already accepted the cookie consent, don't show the block.
95    if ( isset( $_COOKIE[ COOKIE_NAME ] ) ) {
96        return '';
97    }
98
99    $option = get_option( 'cookie_consent_template' );
100    if ( ! empty( $option ) && empty( $attr['render_from_template'] ) ) {
101        return '';
102    }
103
104    /*
105     * Enqueue necessary scripts and styles.
106     */
107    Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
108
109    /*
110     * Add a fallback color to block styles.
111     */
112    $content = add_css_fallback_values( $content );
113
114    return sprintf(
115        '<div class="%1$s">%2$s</div>',
116        esc_attr( Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr ) ),
117        $content
118    );
119}
120
121/**
122 * Batcache busting: since the cookie consent is part of the cached response HTML, it can still render even when the cookie is set (when it shouldn't).
123 * Because, by default, the cache doesn't vary around the cookie's value. This makes the cookie value part of the cache key.
124 *
125 * See: https://github.com/Automattic/batcache/blob/d4f617b335e9772a61b6d03ad3498b55c8137592/advanced-cache.php#L29
126 */
127function notify_batcache_that_content_changed() {
128    if ( function_exists( 'vary_cache_on_function' ) ) {
129        vary_cache_on_function( 'return isset( $_COOKIE[ "' . COOKIE_NAME . '" ] );' );
130    }
131}
132
133/**
134 * Render the cookie consent template.
135 *
136 * @since 12.4
137 */
138function render_cookie_consent_template() {
139
140    if ( is_admin() ) {
141        return;
142    }
143
144    // Check whether block theme functions exist.
145    if ( ! function_exists( 'parse_blocks' ) ) {
146        return;
147    }
148
149    $template = get_option( 'cookie_consent_template' );
150
151    if ( empty( $template ) ) {
152        return;
153    }
154
155    $parsed = parse_blocks( $template );
156    if ( ! empty( $parsed[0] ) ) {
157        $parsed[0]['attrs']['render_from_template'] = true;
158        echo render_block( $parsed[0] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
159    }
160}
161add_action( 'wp_footer', __NAMESPACE__ . '\render_cookie_consent_template' );
162
163/**
164 * Register cookie_consent_template setting
165 *
166 * @since 12.4
167 */
168function cookie_consent_register_settings() {
169    register_setting(
170        'general',
171        'cookie_consent_template',
172        array(
173            'type'         => 'string',
174            'show_in_rest' => true,
175        )
176    );
177}
178
179add_action( 'rest_api_init', __NAMESPACE__ . '\cookie_consent_register_settings' );
180
181/**
182 * Add a fallback color to block styles.
183 *
184 * @since 15.1
185 *
186 * @param string $content The block content.
187 * @return string The content with fallback values added to CSS custom properties.
188 */
189function add_css_fallback_values( $content ) {
190    // Define the CSS custom properties and their fallback values.
191    $css_fallbacks = array(
192        'var(--wp--preset--color--contrast)' => 'var(--wp--preset--color--contrast, #000000)',
193        'var(--wp--preset--color--tertiary)' => 'var(--wp--preset--color--tertiary, #f0f0f0)',
194    );
195
196    // Replace CSS custom properties with their fallback versions.
197    foreach ( $css_fallbacks as $original => $with_fallback ) {
198        $content = str_replace( $original, $with_fallback, $content );
199    }
200
201    return $content;
202}