wp_create_nonce( 'nx_style_handler_nonce' ), 'editor_type' => 'edit-post', ] ); $asset_file = include NOTIFICATIONX_PATH . 'blocks/notificationx/index.asset.php'; $index_js = 'notificationx/index.js'; wp_register_script( 'notificationx-block-editor', plugins_url( $index_js, __FILE__ ), array_merge($asset_file['dependencies'], ['notificationx-block-controls']), $asset_file['version'], false // Editor script: must load in the head, which is the existing default. ); $editor_css = 'notificationx/editor.css'; wp_register_style( 'notificationx-block-editor', plugins_url( $editor_css, __FILE__ ), array( 'notificationx-block-controls-css' ), filemtime( "{$dir}/{$editor_css}" ) ); $style_css = 'notificationx/style.css'; wp_register_style( 'notificationx-block', plugins_url( $style_css, __FILE__ ), [], filemtime( "{$dir}/{$style_css}" ) ); wp_register_script( 'notificationx-block-frontend', plugins_url( 'notificationx/frontend.js', __FILE__ ), [], filemtime( "{$dir}/notificationx/frontend.js" ), true ); wp_localize_script('notificationx-block-frontend', 'notificationxBlockRest', [ 'root' => rest_url(), ]); register_block_type( 'notificationx-pro/notificationx', [ 'editor_script' => 'notificationx-block-editor', 'editor_style' => 'notificationx-block-editor', // 'style' => 'notificationx-block', // 'script' => 'notificationx-block-frontend', 'render_callback' => [ $this, 'notificationx_render_callback' ], 'attributes' => array( 'nx_id' => array( 'type' => 'string', ), 'blockId' => array( 'type' => 'string', ), 'product_id' => array( 'type' => 'string', ), ), ] ); register_block_type( 'notificationx-pro/notificationx-render', [ 'render_callback' => [ $this, 'gutenberg_examples_dynamic_render_callback' ], 'attributes' => array( 'nx_id' => array( 'type' => 'string', ), 'blockId' => array( 'type' => 'string', ), 'product_id' => array( 'type' => 'string', ), 'post_type' => array( 'type' => 'string', ), ), ] ); // ── NotificationX Countdown Timer block ────────────────────────── $countdown_asset_path = NOTIFICATIONX_PATH . 'blocks/countdown/index.asset.php'; $countdown_asset = file_exists( $countdown_asset_path ) ? include $countdown_asset_path : [ 'dependencies' => [], 'version' => NOTIFICATIONX_VERSION ]; wp_register_script( 'notificationx-countdown-editor', plugins_url( 'countdown/index.js', __FILE__ ), array_merge( $countdown_asset['dependencies'], [ 'notificationx-block-controls' ] ), $countdown_asset['version'], false // Editor script: must load in the head, which is the existing default. ); wp_set_script_translations( 'notificationx-countdown-editor', 'notificationx' ); $countdown_editor_css = 'countdown/editor.css'; wp_register_style( 'notificationx-countdown-editor', plugins_url( $countdown_editor_css, __FILE__ ), [ 'notificationx-block-controls-css' ], filemtime( "{$dir}/{$countdown_editor_css}" ) ); // Frontend base layout CSS — distinct handle from Elementor's `nx-countdown` // (which is only registered on Elementor hooks), same source file. wp_register_style( 'notificationx-countdown-style', NOTIFICATIONX_PUBLIC_URL . 'css/nx-countdown.css', [], NOTIFICATIONX_VERSION ); wp_register_script( 'notificationx-countdown-frontend', plugins_url( 'countdown/frontend.js', __FILE__ ), [], filemtime( "{$dir}/countdown/frontend.js" ), true ); register_block_type( 'notificationx/countdown', [ 'editor_script' => 'notificationx-countdown-editor', 'editor_style' => 'notificationx-countdown-editor', // `style` is injected into both the editor canvas iframe and the // frontend (only when the block is present), so the base layout CSS // applies in the editor preview too. 'style' => 'notificationx-countdown-style', 'render_callback' => [ $this, 'countdown_render_callback' ], 'attributes' => $this->countdown_block_attributes(), ] ); } function notificationx_render_callback( $block_attributes, $content ) { if( ! is_admin() ){ wp_enqueue_style('notificationx-block'); wp_enqueue_script('notificationx-block-frontend'); } if ( is_admin() || $this->isRestUrl() ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context. do_action( 'nx_ignore_analytics' ); } $nx_id = ! empty( $block_attributes['nx_id'] ) ? esc_attr($block_attributes['nx_id']) : ''; $product_id = ! empty( $block_attributes['product_id'] ) ? $block_attributes['product_id'] : ''; $block_id = ! empty( $block_attributes['blockId'] ) ? esc_attr($block_attributes['blockId']) : ''; $html = '
'; $html .= do_shortcode( "[notificationx_inline product_id='{$product_id}' id='{$nx_id}']" ); $html .= '
'; return $html; } function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context. do_action( 'nx_ignore_analytics' ); $nx_id = ! empty( $block_attributes['nx_id'] ) ? esc_attr($block_attributes['nx_id']) : ''; $product_id = ! empty( $block_attributes['product_id'] ) ? $block_attributes['product_id'] : ''; $post_type = ! empty( $block_attributes['post_type'] ) ? $block_attributes['post_type'] : ''; $html = '
'; if( 'wp_template' == $post_type ) { add_filter('nx_is_preview',function(){ return true; }); $product_id = wp_rand(); } $shortcode = do_shortcode( "[notificationx_inline post_type='{$post_type}' product_id='{$product_id}' id='{$nx_id}' show_link=false]" ); if ( $shortcode ) { $html .= $shortcode; } else { $html .= '

' . __( 'There is no data in this notification.', 'notificationx' ) . '

'; } $html .= '
'; return wp_kses($html, Helper::nx_allowed_html()); } /** * Server-side attribute schema for the countdown block. * * Defaults MUST match the JS attribute defaults (blocks/countdown/components/attributes.js) * — Gutenberg omits attributes left at their default from the saved markup, * so the frontend relies on these defaults for any unchanged value. * * @return array */ private function countdown_block_attributes() { return [ 'blockId' => [ 'type' => 'string' ], 'blockStyles' => [ 'type' => 'object' ], 'countdownType' => [ 'type' => 'string', 'default' => 'due_date' ], 'dueTime' => [ 'type' => 'string', 'default' => '' ], 'evergreenHours' => [ 'type' => 'number', 'default' => 11 ], 'evergreenMinutes' => [ 'type' => 'number', 'default' => 59 ], 'evergreenRecurring' => [ 'type' => 'boolean', 'default' => false ], 'recurringRestartAfter' => [ 'type' => 'number', 'default' => 0 ], 'recurringStopTime' => [ 'type' => 'string', 'default' => '' ], 'layout' => [ 'type' => 'string', 'default' => 'grid' ], 'labelView' => [ 'type' => 'string', 'default' => 'nx-countdown-label-block' ], 'showDays' => [ 'type' => 'boolean', 'default' => true ], 'daysLabel' => [ 'type' => 'string', 'default' => 'Days' ], 'showHours' => [ 'type' => 'boolean', 'default' => true ], 'hoursLabel' => [ 'type' => 'string', 'default' => 'Hours' ], 'showMinutes' => [ 'type' => 'boolean', 'default' => true ], 'minutesLabel' => [ 'type' => 'string', 'default' => 'Minutes' ], 'showSeconds' => [ 'type' => 'boolean', 'default' => true ], 'secondsLabel' => [ 'type' => 'string', 'default' => 'Seconds' ], 'showSeparator' => [ 'type' => 'boolean', 'default' => false ], 'separatorStyle' => [ 'type' => 'string', 'default' => 'dotted' ], 'expireType' => [ 'type' => 'string', 'default' => 'none' ], 'expiryTitle' => [ 'type' => 'string', 'default' => 'The countdown is finished!' ], 'expiryText' => [ 'type' => 'string', 'default' => 'Thank you for being part of this event.' ], 'redirectUrl' => [ 'type' => 'string', 'default' => '#' ], ]; } /** * Renders the countdown block on the frontend. * * Mirrors \NotificationX\Extensions\Elementor\CountdownWidget::render() markup * (so the shared frontend logic stays identical) and prints the per-instance * scoped CSS computed in the editor. * * @param array $attributes Block attributes. * @return string */ function countdown_render_callback( $attributes, $content = '' ) { if ( ! is_admin() ) { wp_enqueue_style( 'notificationx-countdown-style' ); wp_enqueue_script( 'notificationx-countdown-frontend' ); } $a = wp_parse_args( (array) $attributes, [ 'blockId' => '', 'blockStyles' => [], 'countdownType' => 'due_date', 'dueTime' => '', 'evergreenHours' => 11, 'evergreenMinutes' => 59, 'evergreenRecurring' => false, 'recurringRestartAfter' => 0, 'recurringStopTime' => '', 'layout' => 'grid', 'labelView' => 'nx-countdown-label-block', 'showDays' => true, 'daysLabel' => 'Days', 'showHours' => true, 'hoursLabel' => 'Hours', 'showMinutes' => true, 'minutesLabel' => 'Minutes', 'showSeconds' => true, 'secondsLabel' => 'Seconds', 'showSeparator' => false, 'separatorStyle' => 'dotted', 'expireType' => 'none', 'expiryTitle' => '', 'expiryText' => '', 'redirectUrl' => '#', ] ); $block_id = ! empty( $a['blockId'] ) ? sanitize_html_class( $a['blockId'] ) : 'nx-countdown-' . wp_rand( 1000, 9999 ); // GMT offset string (matches the JS `new Date(dateStr)` parsing). $gmt_offset = get_option( 'gmt_offset' ); $offset_str = ( $gmt_offset < 0 ? '' : '+' ) . str_replace( [ '.25', '.5', '.75' ], [ ':15', ':30', ':45' ], (string) $gmt_offset ); $due_date_str = ''; if ( 'due_date' === $a['countdownType'] && ! empty( $a['dueTime'] ) ) { $due_date_str = gmdate( 'M d Y G:i:s', strtotime( $a['dueTime'] ) ) . ' ' . $offset_str; } $evergreen_seconds = 0; if ( 'evergreen' === $a['countdownType'] ) { $evergreen_seconds = absint( $a['evergreenHours'] ) * HOUR_IN_SECONDS; $evergreen_seconds += absint( $a['evergreenMinutes'] ) * MINUTE_IN_SECONDS; } $separator_class = ''; if ( ! empty( $a['showSeparator'] ) ) { $separator_class = 'nx-countdown-show-separator nx-countdown-separator-' . sanitize_html_class( $a['separatorStyle'] ); } // ── Wrapper data attributes ── $wrapper_attrs = ' data-countdown-id="' . esc_attr( $block_id ) . '"'; $wrapper_attrs .= ' data-countdown-type="' . esc_attr( $a['countdownType'] ) . '"'; $wrapper_attrs .= ' data-expire-type="' . esc_attr( $a['expireType'] ) . '"'; if ( 'text' === $a['expireType'] ) { $wrapper_attrs .= ' data-expiry-title="' . esc_attr( wp_kses_post( $a['expiryTitle'] ) ) . '"'; $wrapper_attrs .= ' data-expiry-text="' . esc_attr( wp_kses_post( $a['expiryText'] ) ) . '"'; } elseif ( 'url' === $a['expireType'] ) { $wrapper_attrs .= ' data-redirect-url="' . esc_url( $a['redirectUrl'] ) . '"'; } if ( 'evergreen' === $a['countdownType'] ) { $wrapper_attrs .= ' data-evergreen-time="' . absint( $evergreen_seconds ) . '"'; if ( ! empty( $a['evergreenRecurring'] ) ) { $stop_time = ! empty( $a['recurringStopTime'] ) ? gmdate( 'M d Y G:i:s', strtotime( $a['recurringStopTime'] ) ) . ' ' . $offset_str : ''; $wrapper_attrs .= ' data-evergreen-recurring="' . absint( $a['recurringRestartAfter'] ) . '"'; $wrapper_attrs .= ' data-evergreen-recurring-stop="' . esc_attr( $stop_time ) . '"'; } } $layout_class = 'nx-countdown-layout-' . sanitize_html_class( ! empty( $a['layout'] ) ? $a['layout'] : 'grid' ); $label_view = sanitize_html_class( ! empty( $a['labelView'] ) ? $a['labelView'] : 'nx-countdown-label-block' ); $list_classes = trim( 'nx-countdown-container ' . $layout_class . ' ' . $label_view . ' ' . $separator_class ); // ── Time-unit items ── $units = [ 'days' => [ 'show' => $a['showDays'], 'label' => $a['daysLabel'], 'attr' => 'data-days' ], 'hours' => [ 'show' => $a['showHours'], 'label' => $a['hoursLabel'], 'attr' => 'data-hours' ], 'minutes' => [ 'show' => $a['showMinutes'], 'label' => $a['minutesLabel'], 'attr' => 'data-minutes' ], 'seconds' => [ 'show' => $a['showSeconds'], 'label' => $a['secondsLabel'], 'attr' => 'data-seconds' ], ]; $items_html = ''; foreach ( $units as $unit => $data ) { if ( empty( $data['show'] ) ) { continue; } $label_html = ''; if ( ! empty( $data['label'] ) ) { $label_html = '' . esc_html( $data['label'] ) . ''; } $items_html .= '
  • ' . '00' . $label_html . '
  • '; } $html = $this->countdown_inline_styles( $block_id, $a['blockStyles'] ); $html .= '
    '; $html .= '
    '; return $html; } /** * Builds the per-instance scoped breakout. $css = str_replace( '<', '', $css ); return ''; } function isRestUrl() { if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { return false; } return true; } }