settings, highest priority first (then newest). Read once per request. * * @param bool $refresh Read again (after a bar was switched on or off in the same request). */ function runline_active_bars( $refresh = false ) { static $bars = null; if ( null !== $bars && ! $refresh ) { return $bars; } $ids = get_posts( array( 'post_type' => RUNLINE_POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => 100, 'fields' => 'ids', 'meta_key' => RUNLINE_ACTIVE_META, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_value' => '1', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, ) ); $list = array(); foreach ( $ids as $i => $id ) { $settings = runline_translate_settings( $id, runline_get_settings( $id ) ); if ( $settings['enabled'] && ( $settings['items'] || 'manual' !== $settings['source']['type'] ) ) { $list[] = array( $id, $settings, $i ); } } usort( $list, function ( $a, $b ) { return $b[1]['priority'] === $a[1]['priority'] ? $a[2] - $b[2] : $b[1]['priority'] - $a[1]['priority']; } ); $bars = array(); foreach ( $list as $entry ) { $bars[ $entry[0] ] = $entry[1]; } return $bars; } /** * Plugin-wide options. */ function runline_options() { return wp_parse_args( (array) get_option( 'runline_options', array() ), array( 'delete_data' => false, 'one_per_place' => false, 'push_header' => false, 'stats' => true, 'mailchimp_key' => '', 'mailerlite_key' => '', 'brevo_key' => '', 'language' => '', ) ); } /** * What a bar needs on this page: false = not here, otherwise the checks left for the browser (see gate.js). * * @param int $post_id Bar ID. * @param array $settings Bar settings. * @param string $place "top", "bottom" or "manual". * @return array|false */ function runline_bar_gate( $post_id, $settings, $place ) { $gate = runline_rules_decide( $settings['rules'], $settings['schedule'] ); $end = runline_countdown_end( $settings ); if ( false !== $gate && $end && $end <= time() && 'hide' === $settings['countdown']['after'] ) { $gate = false; } $gate = apply_filters( 'runline_bar_gate', $gate, $post_id, $settings, $place ); if ( false === $gate ) { return false; } foreach ( array( 'show', 'hide' ) as $list ) { $types = ! empty( $gate[ $list ] ) ? wp_list_pluck( $gate[ $list ], 'type' ) : array(); if ( in_array( 'cart_total', $types, true ) ) { $gate['cartUrl'] = esc_url_raw( rest_url( 'wc/store/v1/cart' ) ); } if ( in_array( 'country', $types, true ) ) { $gate['countryUrl'] = esc_url_raw( rest_url( 'runline/v1/country' ) ); } } if ( 'manual' !== $place && runline_options()['one_per_place'] ) { $gate['group'] = $place; } return $gate; } /** * Bars placed automatically at the top or the bottom on this page: list of [ id, settings, gate ]. * * @param string $position "top" or "bottom". */ function runline_bars_at( $position ) { if ( is_admin() || is_feed() || is_embed() ) { return array(); } $bars = array(); foreach ( runline_active_bars() as $id => $settings ) { if ( $position !== $settings['position'] ) { continue; } $gate = runline_bar_gate( $id, $settings, $position ); if ( false !== $gate ) { $bars[] = array( $id, $settings, $gate ); } } return $bars; } // Registered early where the handles are asked for by name: the admin (block editor) and Elementor (widget // dependencies). Other pages register them only when a bar is printed. add_action( 'init', function () { if ( is_admin() || did_action( 'elementor/loaded' ) ) { runline_register_assets(); } } ); function runline_register_assets() { if ( wp_script_is( 'runline', 'registered' ) ) { return; } $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; if ( ! file_exists( RUNLINE_DIR . 'assets/js/runline' . $suffix . '.js' ) ) { $suffix = ''; } wp_register_style( 'runline', RUNLINE_URL . 'assets/css/runline' . $suffix . '.css', array(), RUNLINE_VERSION ); wp_register_script( 'runline', RUNLINE_URL . 'assets/js/runline' . $suffix . '.js', array(), RUNLINE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer', ) ); } /** * Whether the content of a post shows a bar that is switched on (with the block or the shortcode). * * @param WP_Post|null $post Post. */ function runline_content_has_bar( $post ) { if ( ! $post || ( ! has_shortcode( $post->post_content, 'runline' ) && ! has_block( 'runline/bar', $post ) ) ) { return false; } $active = array_keys( runline_active_bars() ); if ( ! $active ) { return false; } $ids = array(); $walk = function ( $blocks ) use ( &$walk, &$ids ) { foreach ( $blocks as $block ) { if ( 'runline/bar' === $block['blockName'] ) { $ids[] = isset( $block['attrs']['barId'] ) ? (int) $block['attrs']['barId'] : 0; } $walk( $block['innerBlocks'] ); } }; $walk( parse_blocks( $post->post_content ) ); if ( preg_match_all( '/' . get_shortcode_regex( array( 'runline' ) ) . '/', $post->post_content, $found, PREG_SET_ORDER ) ) { foreach ( $found as $shortcode ) { $atts = shortcode_parse_atts( $shortcode[3] ); $ids[] = is_array( $atts ) && isset( $atts['id'] ) ? absint( $atts['id'] ) : 0; } } // ID 0 = the first bar that is on. return in_array( 0, $ids, true ) || (bool) array_intersect( $ids, $active ); } function runline_enqueue_assets() { if ( ! wp_script_is( 'runline', 'registered' ) ) { runline_register_assets(); } wp_enqueue_style( 'runline' ); wp_enqueue_script( 'runline' ); } add_action( 'wp_enqueue_scripts', function () { $needed = runline_bars_at( 'top' ) || runline_bars_at( 'bottom' ); if ( ! $needed && is_singular() ) { // A shortcode or block of a bar that is on: load the style in
so the bar does not jump. $needed = runline_content_has_bar( get_post() ); } foreach ( array_merge( runline_bars_at( 'top' ), runline_bars_at( 'bottom' ) ) as $bar ) { runline_enqueue_font( $bar[1] ); } if ( $needed ) { runline_enqueue_assets(); // The checker of visitor rules goes into , before any bar. add_action( 'wp_head', function () { wp_print_inline_script_tag( runline_gate_runtime() ); do_action( 'runline_gate_printed' ); }, 1 ); } } ); add_action( 'wp_body_open', function () { runline_print_bars( 'top', false ); }, 5 ); add_action( 'wp_footer', function () { // Themes without wp_body_open: the top bars are printed here and moved to the top at once. if ( ! did_action( 'wp_body_open' ) ) { runline_print_bars( 'top', true ); } runline_print_bars( 'bottom', false ); }, 5 ); /** * Prints the bars of one position as a stack (several bars one under another). At the top, bars that keep * their place while scrolling share one sticky stack above the others. * * @param string $position "top" or "bottom". * @param bool $move Move the stack to the top of the page with a line of JavaScript. */ function runline_print_bars( $position, $move ) { $bars = runline_bars_at( $position ); if ( ! $bars ) { return; } $stacks = array( 'sticky' => '', 'normal' => '', ); foreach ( $bars as $bar ) { $stack = 'top' === $position && $bar[1]['sticky'] ? 'sticky' : 'normal'; $stacks[ $stack ] .= runline_render_bar( $bar[0], $bar[1], $position, $bar[2] ); } $html = ''; foreach ( $stacks as $stack => $content ) { if ( '' === $content ) { continue; } $id = 'runline-stack-' . $position . '-' . $stack; if ( 'bottom' === $position ) { // Spacers stay in the page; the bars go into the fixed stack. preg_match_all( '#