03:04:05. */ function runline_countdown_span( $settings, $end ) { $countdown = $settings['countdown']; $left = $end - time(); $after = 'text' === $countdown['after'] ? $countdown['after_text'] : ''; $text = $left > 0 || '' === $after ? runline_countdown_text( $left ) : $after; return sprintf( '%4$s', $end * 1000, esc_attr( runline_countdown_days_format() ), esc_attr( $after ), esc_html( $text ) ); } /** * HTML of a bar. * * @param int $post_id Bar ID. * @param array $settings Bar settings. * @param string $place "top", "bottom", "manual" (shortcode, block, widget) or "preview". * @param array|null $gate What the browser still checks (runline_rules_decide()), plus "group" for one bar per place. * @return string */ function runline_render_bar( $post_id, $settings, $place, $gate = null ) { static $count = array(); $items = runline_items( $settings ); if ( ! $items ) { return ''; } $count[ $post_id ] = isset( $count[ $post_id ] ) ? $count[ $post_id ] + 1 : 1; $id = 'runline-' . $post_id . ( $count[ $post_id ] > 1 ? '-' . $count[ $post_id ] : '' ); $key = $post_id . '-' . substr( md5( wp_json_encode( $items ) ), 0, 8 ); $preview = 'preview' === $place; $end = runline_countdown_end( $settings ); $classes = array( 'runline', 'runline--' . $place, 'runline--' . $settings['direction'], 'runline--mode-' . $settings['mode'] ); if ( 'cards' === $settings['style'] ) { $classes[] = 'runline--cards'; } if ( 'none' !== $settings['shadow'] ) { $classes[] = 'runline--shadow-' . $settings['shadow']; } if ( '' !== $settings['css_class'] ) { $classes = array_merge( $classes, explode( ' ', $settings['css_class'] ) ); } if ( $settings['pause_hover'] ) { $classes[] = 'runline--pause-hover'; } if ( $settings['uppercase'] ) { $classes[] = 'runline--upper'; } if ( 'boxed' === $settings['width'] ) { $classes[] = 'runline--boxed'; $classes[] = 'runline--align-' . $settings['align']; } $hide = array(); if ( ! $preview ) { foreach ( $settings['devices'] as $device => $on ) { if ( ! $on ) { $hide[] = 'runline--hide-' . $device; } } } $classes = array_merge( $classes, $hide ); // Phones: is there room (about 120 px) for the moving text next to the form, the button and the coupon? $stack_parts = $settings['signup']['on'] || ( $settings['button']['on'] && '' !== $settings['button']['text'] && '' !== $settings['button']['url'] ) || ( $settings['coupon']['on'] && '' !== $settings['coupon']['code'] ); $crowded = $stack_parts && 'inline' !== $settings['mobile_layout'] && runline_mobile_crowded( $settings, $end ); if ( $crowded ) { $classes[] = 'runline--m-' . $settings['mobile_layout']; } $vars = array( '--rl-bg' => $settings['bg'], '--rl-color' => $settings['color'], '--rl-link' => $settings['link_color'] ? $settings['link_color'] : $settings['color'], '--rl-link-hover' => $settings['link_hover'] ? $settings['link_hover'] : ( $settings['link_color'] ? $settings['link_color'] : $settings['color'] ), '--rl-label-bg' => $settings['label_bg'], '--rl-label-color' => $settings['label_color'], '--rl-font' => runline_font_stack( $settings['font'] ), '--rl-fs' => $settings['font_size'] . 'px', '--rl-fs-m' => $settings['font_size_mobile'] . 'px', '--rl-weight' => $settings['font_weight'], '--rl-h' => $settings['height'] . 'px', '--rl-gap' => $settings['gap'] . 'px', // First guess of the duration for the time before runline.js measures the strip. '--rl-dur' => max( 8, round( count( $items ) * 300 / $settings['speed'] ) ) . 's', ); if ( 'boxed' === $settings['width'] ) { $vars['--rl-max'] = $settings['max_width'] . 'px'; } if ( $settings['radius'] ) { $vars['--rl-radius'] = $settings['radius'] . 'px'; } if ( '' !== $settings['bg2'] ) { $vars['--rl-bg-image'] = 'linear-gradient(' . $settings['bg_angle'] . 'deg,' . $settings['bg'] . ',' . $settings['bg2'] . ')'; } if ( $settings['border_width'] ) { $vars['--rl-border'] = $settings['border_width'] . 'px solid ' . $settings['border_color']; } if ( $end ) { $vars['--rl-cd-bg'] = $settings['countdown']['bg']; $vars['--rl-cd-color'] = $settings['countdown']['color']; } $has_image = false; foreach ( $items as $item ) { $has_image = $has_image || ! empty( $item['image'] ); } if ( $has_image ) { $vars['--rl-img-h'] = round( $settings['height'] * $settings['image_height'] / 100 ) . 'px'; } if ( $settings['button']['on'] ) { $vars['--rl-btn-bg'] = $settings['button']['bg']; $vars['--rl-btn-color'] = $settings['button']['color']; $vars['--rl-btn-radius'] = $settings['button']['radius'] . 'px'; } if ( $settings['coupon']['on'] ) { $vars['--rl-cp-bg'] = $settings['coupon']['bg']; $vars['--rl-cp-color'] = $settings['coupon']['color']; } $ship = runline_shipping_parts( $settings, $preview ); if ( $ship ) { $vars['--rl-ship'] = $settings['shipping']['color']; } $style = ''; foreach ( $vars as $name => $value ) { $style .= $name . ':' . $value . ';'; } $cd = $end ? runline_countdown_span( $settings, $end ) : ''; $text_vars = array_merge( runline_text_vars(), array( '{countdown}' => $cd, '{coupon}' => $settings['coupon']['on'] ? '' . esc_html( $settings['coupon']['code'] ) . '' : '', '{free_shipping_left}' => $ship ? $ship['left'] : '', ) ); $group = runline_render_group( $items, $settings['separator'], false, $text_vars ); $copy = runline_render_group( $items, $settings['separator'], true, $text_vars ); $track_b = ''; if ( runline_ab_active( $settings ) ) { // Variant B of the A/B test: the checker (gate.js) keeps one of the two strips for each visitor. $track_b = '
' . runline_render_group( $settings['ab']['items'], $settings['separator'], false, $text_vars ) . runline_render_group( $settings['ab']['items'], $settings['separator'], true, $text_vars ) . '
'; } $gate_config = $preview ? null : runline_gate_config( $key, $settings, (array) $gate, $end ); $html = ''; if ( 'bottom' === $place ) { // Keeps the end of the page visible above the fixed bar. $spacer = array_merge( array( 'runline-spacer' ), $hide, $crowded && 'stack' === $settings['mobile_layout'] ? array( 'runline-spacer--stack' ) : array() ); $html .= ''; } $html .= sprintf( '
', esc_attr( $id ), esc_attr( implode( ' ', $classes ) ), esc_attr( $style ), (int) $settings['speed'], (int) $settings['speed_mobile'], esc_attr( $key ), esc_attr( $settings['label'] ? $settings['label'] : __( 'Announcements', 'runline' ) ), ( ! empty( $gate['group'] ) ? ' data-group="' . esc_attr( $gate['group'] ) . '"' : '' ) . ( ! $preview && $gate_config ? ' data-gate="' . esc_attr( wp_json_encode( $gate_config ) ) . '"' : '' ) . ( $ship && ! $preview ? ' data-ship="' . esc_attr( wp_json_encode( $ship['data'] ) ) . '"' : '' ) . ( ! $preview && $post_id && runline_stats_on() ? ' data-bar="' . (int) $post_id . '" data-track="' . esc_url( rest_url( RUNLINE_REST_NS . '/track' ) ) . '"' : '' ), (int) $settings['interval'] ); if ( '' !== $settings['label'] ) { $html .= '
' . esc_html( $settings['label'] ) . '
'; } $fixed_cd = ''; if ( $end && 'text' !== $settings['countdown']['place'] ) { $fixed_cd = '
' . ( '' !== $settings['countdown']['prefix'] ? '' . esc_html( $settings['countdown']['prefix'] ) . ' ' : '' ) . $cd . '
'; } if ( 'left' === $settings['countdown']['place'] ) { $html .= $fixed_cd; } if ( $ship ) { $html .= $ship['block']; } $html .= '
' . $group . $copy . '
' . $track_b . '
'; if ( $crowded && 'stack' === $settings['mobile_layout'] ) { // On phones the form, the button and the coupon go to a second row after this break. $html .= ''; } if ( 'right' === $settings['countdown']['place'] ) { $html .= $fixed_cd; } $coupon = $settings['coupon']; if ( $coupon['on'] && '' !== $coupon['code'] ) { $html .= '
' . ( '' !== $coupon['prefix'] ? '' . esc_html( $coupon['prefix'] ) . ' ' : '' ) . '' . esc_html( $coupon['code'] ) . ' ' . '
'; } $html .= runline_signup_form( $post_id, $settings, $preview ); $button = $settings['button']; if ( $button['on'] && '' !== $button['text'] && '' !== $button['url'] ) { $html .= '' . esc_html( $button['text'] ) . ''; } if ( $ship && $settings['shipping']['progress'] ) { $html .= ''; } if ( $settings['closable'] ) { $html .= ''; } $html .= '
'; if ( 'none' !== $settings['ribbon'] ) { // A tilted ribbon (and a second one crossing it) in a box that hides what sticks out. $html = sprintf( '
%3$s%4$s
', esc_attr( $settings['ribbon'] ), esc_attr( '--rl-h:' . $settings['height'] . 'px;--rl-tilt:' . $settings['tilt'] . 'deg;--rl-ribbon2:' . $settings['ribbon2'] . ';' ), 'crossed' === $settings['ribbon'] ? '' : '', $html ); } if ( ! $preview ) { $html .= runline_gate_script( $id, $key, $settings, (array) $gate, $end ); runline_enqueue_font( $settings ); } return apply_filters( 'runline_bar_html', $html, $post_id, $settings, $place ); } /** * The inline call that decides, before the first paint, whether the visitor sees the bar (closed by the visitor, * visitor rules, schedule, ended countdown, one bar per place). The checker itself (gate.js) is printed once per page. */ function runline_gate_script( $id, $key, $settings, $gate, $end ) { $config = runline_gate_config( $key, $settings, $gate, $end ); if ( ! $config ) { return ''; } // The checker is printed in on pages known to have bars; a bar found later brings it to the footer // (before runline.js, which then checks such bars). Never inline in the content: content filters change "&". if ( ! did_action( 'runline_gate_printed' ) && ! wp_scripts()->get_data( 'runline', 'runline_gate' ) ) { wp_add_inline_script( 'runline', runline_gate_runtime(), 'before' ); wp_script_add_data( 'runline', 'runline_gate', true ); } return wp_get_inline_script_tag( 'if(window.runlineGate)runlineGate(' . wp_json_encode( $id ) . ');' ); } /** * What the browser checks for a bar (see gate.js), or null when there is nothing to check. */ function runline_gate_config( $key, $settings, $gate, $end ) { $config = array( 'key' => $key ); if ( $settings['closable'] ) { $config['closable'] = 1; $config['closeDays'] = (int) $settings['close_days']; } foreach ( array( 'show', 'hide', 'sched', 'group', 'cartUrl', 'countryUrl' ) as $part ) { if ( ! empty( $gate[ $part ] ) ) { $config[ $part ] = $gate[ $part ]; } } if ( $end && 'hide' === $settings['countdown']['after'] ) { $config['cdEnd'] = $end * 1000; } if ( runline_ab_active( $settings ) ) { $config['ab'] = (int) $settings['ab']['split']; } if ( $settings['signup']['on'] && $settings['signup']['hide_after'] ) { $config['signup'] = 1; } return 1 === count( $config ) ? null : $config; } /** * Whether a bar runs an A/B test (written messages and a second set of them). */ function runline_ab_active( $settings ) { return ! empty( $settings['ab']['on'] ) && ! empty( $settings['ab']['items'] ) && 'manual' === $settings['source']['type']; } /** * The checker (assets/js/gate.js, minified when available). */ function runline_gate_runtime() { $file = RUNLINE_DIR . 'assets/js/gate.min.js'; $file = file_exists( $file ) ? $file : RUNLINE_DIR . 'assets/js/gate.js'; return trim( (string) file_get_contents( $file ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- a file of this plugin. } /** * Whether the fixed parts of a bar leave less than about 120 px for the moving text on a phone (390 px). * A rough measure from the texts, the same in render.js — so the layout is decided before the page shows. * * @param array $settings Bar settings. * @param int $end End of the countdown (0 = none). */ function runline_mobile_crowded( $settings, $end ) { if ( $settings['signup']['on'] ) { return true; } $char = $settings['font_size_mobile'] * ( $settings['uppercase'] ? 0.72 : 0.6 ); $width = 0; $timer = $end && 'text' !== $settings['countdown']['place']; if ( '' !== $settings['label'] && ! $timer ) { $width += mb_strlen( $settings['label'] ) * $char + 32; } if ( $timer ) { $width += 11 * $char + 20; } if ( $settings['coupon']['on'] && '' !== $settings['coupon']['code'] ) { $width += ( mb_strlen( $settings['coupon']['code'] ) + mb_strlen( $settings['coupon']['copy'] ) ) * $char + 56; } if ( $settings['button']['on'] && '' !== $settings['button']['text'] && '' !== $settings['button']['url'] ) { $width += mb_strlen( $settings['button']['text'] ) * $char + 32; } if ( $settings['closable'] ) { $width += 40; } return $width > 390 - 120; } /** * One copy of the items. Copies are hidden from screen readers and from the Tab key. * * @param array $items Items. * @param string $separator Separator between items. * @param bool $copy A copy for the loop. * @param array $vars Text variables: "{name}" => HTML. * @return string */ function runline_render_group( $items, $separator, $copy, $vars = array() ) { $html = $copy ? '