PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.2.10
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.2.10
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
notificationx / blocks / Blocks.php

Blocks.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar 3.2.10, at blocks/Blocks.php

449 lines 19.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions to register client-side assets (scripts and stylesheets) for the
4 * Gutenberg block.
5 *
6 * @package notificationx
7 */
8
9 namespace NotificationX\Blocks;
10
11 use NotificationX\Core\Helper;
12 use NotificationX\GetInstance;
13
14 /**
15 *
16 * @method static Blocks get_instance($args = null)
17 */
18 class Blocks {
19 /**
20 * Instance of NotificationX
21 *
22 * @var Blocks
23 */
24 use GetInstance;
25
26
27 public function __construct() {
28 StyleHandler::get_instance();
29 add_action( 'init', [ $this, 'notificationx_block_init' ] );
30 }
31
32 /**
33 * Registers all block assets so that they can be enqueued through Gutenberg in
34 * the corresponding context.
35 *
36 * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets/
37 */
38 function notificationx_block_init() {
39 // Skip block registration if Gutenberg is not enabled/merged.
40 if ( ! function_exists( 'register_block_type' ) ) {
41 return;
42 }
43 $dir = dirname( __FILE__ );
44
45 // Enqueue Controls CSS & JS
46 $controls_css = 'controls/dist/index.css';
47 wp_register_style(
48 'notificationx-block-controls-css',
49 plugins_url( $controls_css, __FILE__ ),
50 [],
51 filemtime( "{$dir}/{$controls_css}" )
52 );
53
54 $asset_file = include NOTIFICATIONX_PATH . 'blocks/controls/dist/index.asset.php';
55 $index_js = 'controls/dist/index.js';
56 // The controls bundle relies on the global `lodash` but its asset file
57 // doesn't declare it; add it explicitly so the bundle works on any
58 // editor screen (not just where style-handler.js happens to load it).
59 wp_register_script(
60 'notificationx-block-controls',
61 plugins_url( $index_js, __FILE__ ),
62 array_merge( $asset_file['dependencies'], [ 'lodash' ] ),
63 $asset_file['version'],
64 false
65 );
66
67 // The inline block's edit.js reads `nx_style_handler.editor_type` to pick
68 // the right preview-device store. Attach that global to the lightweight,
69 // always-loaded controls handle so it is available on every editor screen
70 // — without dragging in the heavy style-handler.js / wp-edit-site bundle.
71 // The site editor overrides editor_type to 'edit-site' in StyleHandler.
72 wp_localize_script( 'notificationx-block-controls', 'nx_style_handler', [
73 'sth_nonce' => wp_create_nonce( 'nx_style_handler_nonce' ),
74 'editor_type' => 'edit-post',
75 ] );
76
77 $asset_file = include NOTIFICATIONX_PATH . 'blocks/notificationx/index.asset.php';
78 $index_js = 'notificationx/index.js';
79 wp_register_script(
80 'notificationx-block-editor',
81 plugins_url( $index_js, __FILE__ ),
82 array_merge($asset_file['dependencies'], ['notificationx-block-controls']),
83 $asset_file['version']
84 );
85
86 $editor_css = 'notificationx/editor.css';
87 wp_register_style(
88 'notificationx-block-editor',
89 plugins_url( $editor_css, __FILE__ ),
90 array( 'notificationx-block-controls-css' ),
91 filemtime( "{$dir}/{$editor_css}" )
92 );
93
94 $style_css = 'notificationx/style.css';
95 wp_register_style(
96 'notificationx-block',
97 plugins_url( $style_css, __FILE__ ),
98 [],
99 filemtime( "{$dir}/{$style_css}" )
100 );
101 wp_register_script(
102 'notificationx-block-frontend',
103 plugins_url( 'notificationx/frontend.js', __FILE__ ),
104 [],
105 filemtime( "{$dir}/notificationx/frontend.js" ),
106 true
107 );
108 wp_localize_script('notificationx-block-frontend', 'notificationxBlockRest', [
109 'root' => rest_url(),
110 ]);
111 register_block_type( 'notificationx-pro/notificationx',
112 [
113 'editor_script' => 'notificationx-block-editor',
114 'editor_style' => 'notificationx-block-editor',
115 // 'style' => 'notificationx-block',
116 // 'script' => 'notificationx-block-frontend',
117 'render_callback' => [ $this, 'notificationx_render_callback' ],
118 'attributes' => array(
119 'nx_id' => array(
120 'type' => 'string',
121 ),
122 'blockId' => array(
123 'type' => 'string',
124 ),
125 'product_id' => array(
126 'type' => 'string',
127 ),
128 ),
129 ]
130 );
131 register_block_type( 'notificationx-pro/notificationx-render',
132 [
133 'render_callback' => [ $this, 'gutenberg_examples_dynamic_render_callback' ],
134 'attributes' => array(
135 'nx_id' => array(
136 'type' => 'string',
137 ),
138 'blockId' => array(
139 'type' => 'string',
140 ),
141 'product_id' => array(
142 'type' => 'string',
143 ),
144 'post_type' => array(
145 'type' => 'string',
146 ),
147 ),
148 ]
149 );
150
151 // ── NotificationX Countdown Timer block ──────────────────────────
152 $countdown_asset_path = NOTIFICATIONX_PATH . 'blocks/countdown/index.asset.php';
153 $countdown_asset = file_exists( $countdown_asset_path )
154 ? include $countdown_asset_path
155 : [ 'dependencies' => [], 'version' => NOTIFICATIONX_VERSION ];
156
157 wp_register_script(
158 'notificationx-countdown-editor',
159 plugins_url( 'countdown/index.js', __FILE__ ),
160 array_merge( $countdown_asset['dependencies'], [ 'notificationx-block-controls' ] ),
161 $countdown_asset['version']
162 );
163 wp_set_script_translations( 'notificationx-countdown-editor', 'notificationx' );
164
165 $countdown_editor_css = 'countdown/editor.css';
166 wp_register_style(
167 'notificationx-countdown-editor',
168 plugins_url( $countdown_editor_css, __FILE__ ),
169 [ 'notificationx-block-controls-css' ],
170 filemtime( "{$dir}/{$countdown_editor_css}" )
171 );
172
173 // Frontend base layout CSS — distinct handle from Elementor's `nx-countdown`
174 // (which is only registered on Elementor hooks), same source file.
175 wp_register_style(
176 'notificationx-countdown-style',
177 NOTIFICATIONX_PUBLIC_URL . 'css/nx-countdown.css',
178 [],
179 NOTIFICATIONX_VERSION
180 );
181 wp_register_script(
182 'notificationx-countdown-frontend',
183 plugins_url( 'countdown/frontend.js', __FILE__ ),
184 [],
185 filemtime( "{$dir}/countdown/frontend.js" ),
186 true
187 );
188
189 register_block_type( 'notificationx/countdown', [
190 'editor_script' => 'notificationx-countdown-editor',
191 'editor_style' => 'notificationx-countdown-editor',
192 // `style` is injected into both the editor canvas iframe and the
193 // frontend (only when the block is present), so the base layout CSS
194 // applies in the editor preview too.
195 'style' => 'notificationx-countdown-style',
196 'render_callback' => [ $this, 'countdown_render_callback' ],
197 'attributes' => $this->countdown_block_attributes(),
198 ] );
199 }
200
201 function notificationx_render_callback( $block_attributes, $content ) {
202 if( ! is_admin() ){
203 wp_enqueue_style('notificationx-block');
204 wp_enqueue_script('notificationx-block-frontend');
205 }
206 if ( is_admin() || $this->isRestUrl() ) {
207 do_action( 'nx_ignore_analytics' );
208 }
209 $nx_id = ! empty( $block_attributes['nx_id'] ) ? esc_attr($block_attributes['nx_id']) : '';
210 $product_id = ! empty( $block_attributes['product_id'] ) ? $block_attributes['product_id'] : '';
211 $block_id = ! empty( $block_attributes['blockId'] ) ? esc_attr($block_attributes['blockId']) : '';
212 $html = '<div class="' . $block_id . ' notificationx-block-wrapper" data-nx_id="' . $nx_id . '">';
213 $html .= do_shortcode( "[notificationx_inline product_id='{$product_id}' id='{$nx_id}']" );
214 $html .= '</div>';
215 return $html;
216 }
217
218 function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) {
219 do_action( 'nx_ignore_analytics' );
220 $nx_id = ! empty( $block_attributes['nx_id'] ) ? esc_attr($block_attributes['nx_id']) : '';
221 $product_id = ! empty( $block_attributes['product_id'] ) ? $block_attributes['product_id'] : '';
222 $post_type = ! empty( $block_attributes['post_type'] ) ? $block_attributes['post_type'] : '';
223 $html = '<div class="' . $block_attributes['blockId'] . ' notificationx-block-wrapper">';
224 if( 'wp_template' == $post_type ) {
225 add_filter('nx_is_preview',function(){
226 return true;
227 });
228 $product_id = rand();
229 }
230 $shortcode = do_shortcode( "[notificationx_inline post_type='{$post_type}' product_id='{$product_id}' id='{$nx_id}' show_link=false]" );
231 if ( $shortcode ) {
232 $html .= $shortcode;
233 } else {
234 $html .= '<p class="nx-shortcode-notice">' . __( 'There is no data in this notification.', 'notificationx' ) . '</p>';
235 }
236 $html .= '</div>';
237
238 return wp_kses($html, Helper::nx_allowed_html());
239 }
240
241 /**
242 * Server-side attribute schema for the countdown block.
243 *
244 * Defaults MUST match the JS attribute defaults (blocks/countdown/components/attributes.js)
245 * — Gutenberg omits attributes left at their default from the saved markup,
246 * so the frontend relies on these defaults for any unchanged value.
247 *
248 * @return array
249 */
250 private function countdown_block_attributes() {
251 return [
252 'blockId' => [ 'type' => 'string' ],
253 'blockStyles' => [ 'type' => 'object' ],
254 'countdownType' => [ 'type' => 'string', 'default' => 'due_date' ],
255 'dueTime' => [ 'type' => 'string', 'default' => '' ],
256 'evergreenHours' => [ 'type' => 'number', 'default' => 11 ],
257 'evergreenMinutes' => [ 'type' => 'number', 'default' => 59 ],
258 'evergreenRecurring' => [ 'type' => 'boolean', 'default' => false ],
259 'recurringRestartAfter' => [ 'type' => 'number', 'default' => 0 ],
260 'recurringStopTime' => [ 'type' => 'string', 'default' => '' ],
261 'layout' => [ 'type' => 'string', 'default' => 'grid' ],
262 'labelView' => [ 'type' => 'string', 'default' => 'nx-countdown-label-block' ],
263 'showDays' => [ 'type' => 'boolean', 'default' => true ],
264 'daysLabel' => [ 'type' => 'string', 'default' => 'Days' ],
265 'showHours' => [ 'type' => 'boolean', 'default' => true ],
266 'hoursLabel' => [ 'type' => 'string', 'default' => 'Hours' ],
267 'showMinutes' => [ 'type' => 'boolean', 'default' => true ],
268 'minutesLabel' => [ 'type' => 'string', 'default' => 'Minutes' ],
269 'showSeconds' => [ 'type' => 'boolean', 'default' => true ],
270 'secondsLabel' => [ 'type' => 'string', 'default' => 'Seconds' ],
271 'showSeparator' => [ 'type' => 'boolean', 'default' => false ],
272 'separatorStyle' => [ 'type' => 'string', 'default' => 'dotted' ],
273 'expireType' => [ 'type' => 'string', 'default' => 'none' ],
274 'expiryTitle' => [ 'type' => 'string', 'default' => 'The countdown is finished!' ],
275 'expiryText' => [ 'type' => 'string', 'default' => 'Thank you for being part of this event.' ],
276 'redirectUrl' => [ 'type' => 'string', 'default' => '#' ],
277 ];
278 }
279
280 /**
281 * Renders the countdown block on the frontend.
282 *
283 * Mirrors \NotificationX\Extensions\Elementor\CountdownWidget::render() markup
284 * (so the shared frontend logic stays identical) and prints the per-instance
285 * scoped CSS computed in the editor.
286 *
287 * @param array $attributes Block attributes.
288 * @return string
289 */
290 function countdown_render_callback( $attributes, $content = '' ) {
291 if ( ! is_admin() ) {
292 wp_enqueue_style( 'notificationx-countdown-style' );
293 wp_enqueue_script( 'notificationx-countdown-frontend' );
294 }
295
296 $a = wp_parse_args( (array) $attributes, [
297 'blockId' => '',
298 'blockStyles' => [],
299 'countdownType' => 'due_date',
300 'dueTime' => '',
301 'evergreenHours' => 11,
302 'evergreenMinutes' => 59,
303 'evergreenRecurring' => false,
304 'recurringRestartAfter' => 0,
305 'recurringStopTime' => '',
306 'layout' => 'grid',
307 'labelView' => 'nx-countdown-label-block',
308 'showDays' => true,
309 'daysLabel' => 'Days',
310 'showHours' => true,
311 'hoursLabel' => 'Hours',
312 'showMinutes' => true,
313 'minutesLabel' => 'Minutes',
314 'showSeconds' => true,
315 'secondsLabel' => 'Seconds',
316 'showSeparator' => false,
317 'separatorStyle' => 'dotted',
318 'expireType' => 'none',
319 'expiryTitle' => '',
320 'expiryText' => '',
321 'redirectUrl' => '#',
322 ] );
323
324 $block_id = ! empty( $a['blockId'] ) ? sanitize_html_class( $a['blockId'] ) : 'nx-countdown-' . wp_rand( 1000, 9999 );
325
326 // GMT offset string (matches the JS `new Date(dateStr)` parsing).
327 $gmt_offset = get_option( 'gmt_offset' );
328 $offset_str = ( $gmt_offset < 0 ? '' : '+' ) . str_replace(
329 [ '.25', '.5', '.75' ],
330 [ ':15', ':30', ':45' ],
331 (string) $gmt_offset
332 );
333
334 $due_date_str = '';
335 if ( 'due_date' === $a['countdownType'] && ! empty( $a['dueTime'] ) ) {
336 $due_date_str = gmdate( 'M d Y G:i:s', strtotime( $a['dueTime'] ) ) . ' ' . $offset_str;
337 }
338
339 $evergreen_seconds = 0;
340 if ( 'evergreen' === $a['countdownType'] ) {
341 $evergreen_seconds = absint( $a['evergreenHours'] ) * HOUR_IN_SECONDS;
342 $evergreen_seconds += absint( $a['evergreenMinutes'] ) * MINUTE_IN_SECONDS;
343 }
344
345 $separator_class = '';
346 if ( ! empty( $a['showSeparator'] ) ) {
347 $separator_class = 'nx-countdown-show-separator nx-countdown-separator-' . sanitize_html_class( $a['separatorStyle'] );
348 }
349
350 // ── Wrapper data attributes ──
351 $wrapper_attrs = ' data-countdown-id="' . esc_attr( $block_id ) . '"';
352 $wrapper_attrs .= ' data-countdown-type="' . esc_attr( $a['countdownType'] ) . '"';
353 $wrapper_attrs .= ' data-expire-type="' . esc_attr( $a['expireType'] ) . '"';
354
355 if ( 'text' === $a['expireType'] ) {
356 $wrapper_attrs .= ' data-expiry-title="' . esc_attr( wp_kses_post( $a['expiryTitle'] ) ) . '"';
357 $wrapper_attrs .= ' data-expiry-text="' . esc_attr( wp_kses_post( $a['expiryText'] ) ) . '"';
358 } elseif ( 'url' === $a['expireType'] ) {
359 $wrapper_attrs .= ' data-redirect-url="' . esc_url( $a['redirectUrl'] ) . '"';
360 }
361
362 if ( 'evergreen' === $a['countdownType'] ) {
363 $wrapper_attrs .= ' data-evergreen-time="' . absint( $evergreen_seconds ) . '"';
364 if ( ! empty( $a['evergreenRecurring'] ) ) {
365 $stop_time = ! empty( $a['recurringStopTime'] )
366 ? gmdate( 'M d Y G:i:s', strtotime( $a['recurringStopTime'] ) ) . ' ' . $offset_str
367 : '';
368 $wrapper_attrs .= ' data-evergreen-recurring="' . absint( $a['recurringRestartAfter'] ) . '"';
369 $wrapper_attrs .= ' data-evergreen-recurring-stop="' . esc_attr( $stop_time ) . '"';
370 }
371 }
372
373 $layout_class = 'nx-countdown-layout-' . sanitize_html_class( ! empty( $a['layout'] ) ? $a['layout'] : 'grid' );
374 $label_view = sanitize_html_class( ! empty( $a['labelView'] ) ? $a['labelView'] : 'nx-countdown-label-block' );
375 $list_classes = trim( 'nx-countdown-container ' . $layout_class . ' ' . $label_view . ' ' . $separator_class );
376
377 // ── Time-unit items ──
378 $units = [
379 'days' => [ 'show' => $a['showDays'], 'label' => $a['daysLabel'], 'attr' => 'data-days' ],
380 'hours' => [ 'show' => $a['showHours'], 'label' => $a['hoursLabel'], 'attr' => 'data-hours' ],
381 'minutes' => [ 'show' => $a['showMinutes'], 'label' => $a['minutesLabel'], 'attr' => 'data-minutes' ],
382 'seconds' => [ 'show' => $a['showSeconds'], 'label' => $a['secondsLabel'], 'attr' => 'data-seconds' ],
383 ];
384
385 $items_html = '';
386 foreach ( $units as $unit => $data ) {
387 if ( empty( $data['show'] ) ) {
388 continue;
389 }
390 $label_html = '';
391 if ( ! empty( $data['label'] ) ) {
392 $label_html = '<span class="nx-countdown-label">' . esc_html( $data['label'] ) . '</span>';
393 }
394 $items_html .= '<li class="nx-countdown-item"><div class="nx-countdown-' . esc_attr( $unit ) . '">'
395 . '<span ' . $data['attr'] . ' class="nx-countdown-digits">00</span>'
396 . $label_html
397 . '</div></li>';
398 }
399
400 $html = $this->countdown_inline_styles( $block_id, $a['blockStyles'] );
401 $html .= '<div class="' . esc_attr( $block_id ) . ' nx-countdown-wrapper"' . $wrapper_attrs . '>';
402 $html .= '<ul id="nx-countdown-' . esc_attr( $block_id ) . '" class="' . esc_attr( $list_classes ) . '" data-date="' . esc_attr( $due_date_str ) . '">';
403 $html .= $items_html;
404 $html .= '</ul></div>';
405
406 return $html;
407 }
408
409 /**
410 * Builds the per-instance scoped <style> tag from the editor-computed CSS.
411 *
412 * @param string $block_id Unique block class (scopes every selector).
413 * @param array|object $block_styles { desktop, tab, mobile } raw CSS strings.
414 * @return string
415 */
416 private function countdown_inline_styles( $block_id, $block_styles ) {
417 $block_styles = (array) $block_styles;
418 $desktop = isset( $block_styles['desktop'] ) ? (string) $block_styles['desktop'] : '';
419 $tab = isset( $block_styles['tab'] ) ? (string) $block_styles['tab'] : '';
420 $mobile = isset( $block_styles['mobile'] ) ? (string) $block_styles['mobile'] : '';
421
422 $css = $desktop;
423 if ( '' !== trim( $tab ) ) {
424 $css .= '@media (max-width:1024px){' . $tab . '}';
425 }
426 if ( '' !== trim( $mobile ) ) {
427 $css .= '@media (max-width:767px){' . $mobile . '}';
428 }
429
430 $css = trim( $css );
431 if ( '' === $css ) {
432 return '';
433 }
434
435 // Defensive: CSS never needs "<"; strip it to prevent a </style> breakout.
436 $css = str_replace( '<', '', $css );
437
438 return '<style>' . $css . '</style>';
439 }
440
441 function isRestUrl() {
442 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
443 return false;
444 }
445 return true;
446 }
447
448 }
449