Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 5
CRAP
n/a
0 / 0
Automattic\Jetpack\Jetpack_Mu_Wpcom\EventCountdown\register_block
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Jetpack_Mu_Wpcom\EventCountdown\load_assets
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Jetpack_Mu_Wpcom\EventCountdown\enqueue_block_editor_assets
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Jetpack_Mu_Wpcom\EventCountdown\render_email
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
182
Automattic\Jetpack\Jetpack_Mu_Wpcom\EventCountdown\get_email_event_timestamp
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2/**
3 * Event Countdown Block
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8namespace Automattic\Jetpack\Jetpack_Mu_Wpcom\EventCountdown;
9
10require_once __DIR__ . '/../../../utils.php';
11
12/**
13 * Registers the block for use in Gutenberg
14 * This is done via an action so that we can disable
15 * registration if we need to.
16 */
17function register_block() {
18    register_block_type(
19        'jetpack/event-countdown',
20        array(
21            'render_callback'       => __NAMESPACE__ . '\load_assets',
22            'render_email_callback' => __NAMESPACE__ . '\render_email',
23        )
24    );
25}
26add_action( 'init', __NAMESPACE__ . '\register_block' );
27
28/**
29 * Load assets on frontend.
30 *
31 * @param array  $attr    Array containing the EventCountdown block attributes.
32 * @param string $content String containing the EventCountdown block content.
33 *
34 * @return string
35 */
36function load_assets( $attr, $content ) {
37    // A block's view assets will not be required in wp-admin.
38    if ( ! is_admin() ) {
39        \jetpack_mu_wpcom_enqueue_assets( 'wpcom-blocks-event-countdown-view', array( 'js', 'css' ) );
40    }
41
42    return $content;
43}
44
45/**
46 * Load assets on the editor.
47 */
48function enqueue_block_editor_assets() {
49    \jetpack_mu_wpcom_enqueue_assets( 'wpcom-blocks-event-countdown-editor', array( 'js', 'css' ) );
50}
51add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\enqueue_block_editor_assets' );
52
53/**
54 * WooCommerce Email Editor render callback. Email can't run the countdown
55 * script, so render the time left at send time plus the event date.
56 *
57 * @param string $block_content     The saved block markup.
58 * @param array  $parsed_block      The parsed block.
59 * @param object $rendering_context The email rendering context.
60 * @return string
61 */
62function render_email( $block_content, array $parsed_block, $rendering_context ) {
63    if ( ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper' ) ||
64        ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Utils\Html_Processing_Helper' ) ) {
65        return '';
66    }
67
68    $timestamp = get_email_event_timestamp( $parsed_block['attrs'] ?? array(), (string) $block_content );
69    if ( null === $timestamp ) {
70        return '';
71    }
72
73    $title = '';
74    if ( preg_match( '/class="event-countdown__event-title"[^>]*>\s*<p>(.*?)<\/p>/s', (string) $block_content, $m ) ) {
75        $title = trim( wp_strip_all_tags( $m[1] ) );
76    }
77
78    // Outlook does not inherit color into nested tables, so paint it on every cell like the engine's Text renderer.
79    $email_attrs  = $parsed_block['email_attrs'] ?? array();
80    $theme_styles = is_object( $rendering_context ) && method_exists( $rendering_context, 'get_theme_styles' ) ? (array) $rendering_context->get_theme_styles() : array();
81    $color        = $email_attrs['color'] ?? $theme_styles['color']['text'] ?? '#000000';
82    // @phan-suppress-next-line PhanUndeclaredClassMethod -- Optional WooCommerce dependency, checked with class_exists() above.
83    $color = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Html_Processing_Helper::sanitize_color( is_string( $color ) ? $color : '' );
84
85    // Each line is a stacked table with cell padding for the gaps: Outlook ignores margins on <p> and tables.
86    $lines     = array();
87    $remaining = $timestamp - time();
88    if ( $remaining > 0 ) {
89        if ( $remaining >= DAY_IN_SECONDS ) {
90            $count = (int) floor( $remaining / DAY_IN_SECONDS );
91            $label = _nx( 'day until', 'days until', $count, 'Countdown days remaining', 'jetpack-mu-wpcom' );
92        } elseif ( $remaining >= HOUR_IN_SECONDS ) {
93            $count = (int) floor( $remaining / HOUR_IN_SECONDS );
94            $label = _nx( 'hour until', 'hours until', $count, 'Countdown hours remaining', 'jetpack-mu-wpcom' );
95        } else {
96            $count = max( 1, (int) floor( $remaining / MINUTE_IN_SECONDS ) );
97            $label = _nx( 'minute until', 'minutes until', $count, 'Countdown minutes remaining', 'jetpack-mu-wpcom' );
98        }
99
100        $lines[] = array( number_format_i18n( $count ), 'font-size: 56px; line-height: 56px; font-weight: 700; padding: 0 0 4px;' );
101        $lines[] = array( $label, 'font-size: 14px; line-height: 20px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; padding: 0 0 16px;' );
102    }
103    if ( '' !== $title ) {
104        $lines[] = array( $title, 'font-size: 28px; line-height: 34px; font-weight: 700; padding: 0 0 8px;' );
105    }
106    $lines[] = array(
107        sprintf(
108            /* translators: 1: event date, 2: event time. */
109            __( '%1$s at %2$s', 'jetpack-mu-wpcom' ),
110            wp_date( get_option( 'date_format' ), $timestamp ),
111            wp_date( get_option( 'time_format' ), $timestamp )
112        ),
113        'font-size: 14px; line-height: 20px; padding: 0;',
114    );
115
116    $body = '';
117    foreach ( $lines as $line ) {
118        // @phan-suppress-next-line PhanUndeclaredClassMethod -- Optional WooCommerce dependency, checked with class_exists() above.
119        $body .= \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
120            esc_html( $line[0] ),
121            array(
122                'width' => '100%',
123                'style' => 'border-collapse: separate;',
124            ),
125            array(
126                'align' => 'center',
127                'style' => 'text-align: center; mso-line-height-rule: exactly; color: ' . $color . '; ' . $line[1],
128            )
129        );
130    }
131
132    // The engine sets margin-top on every block after the first; core renderers add no other margin.
133    $margin_style = (string) \WP_Style_Engine::compile_css( array_intersect_key( $email_attrs, array_flip( array( 'margin-top' ) ) ), '' );
134
135    // @phan-suppress-next-line PhanUndeclaredClassMethod -- Optional WooCommerce dependency, checked with class_exists() above.
136    return \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper(
137        $body,
138        array(
139            'class' => 'wp-block-jetpack-event-countdown',
140            'width' => '100%',
141            'style' => trim( $margin_style . ' min-width: 100%; border-collapse: separate;' ),
142        ),
143        array(
144            'align' => 'center',
145            'style' => 'text-align: center; word-break: break-word; color: ' . $color . '; padding: 16px 0;',
146        )
147    );
148}
149
150/**
151 * Resolve the event time as a unix timestamp. Newer posts store `eventTimestamp`;
152 * older ones only have the `eventDate` string in the saved markup.
153 *
154 * @param array  $attrs         Block attributes.
155 * @param string $block_content The saved block markup.
156 * @return int|null
157 */
158function get_email_event_timestamp( array $attrs, $block_content ) {
159    if ( ! empty( $attrs['eventTimestamp'] ) && is_numeric( $attrs['eventTimestamp'] ) ) {
160        return (int) $attrs['eventTimestamp'];
161    }
162
163    if ( ! preg_match( '/class="event-countdown__date"[^>]*>(.*?)<\/div>/s', $block_content, $m ) ) {
164        return null;
165    }
166
167    $date = trim( wp_strip_all_tags( $m[1] ) );
168    if ( '' === $date ) {
169        return null;
170    }
171    if ( preg_match( '/^\d+$/', $date ) ) {
172        return (int) $date;
173    }
174
175    $datetime = date_create_immutable( $date, wp_timezone() );
176    return $datetime ? $datetime->getTimestamp() : null;
177}