PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.2.14
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.2.14
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.14, at blocks/Blocks.php

453 lines 20.3 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 false // Editor script: must load in the head, which is the existing default.
85 );
86
87 $editor_css = 'notificationx/editor.css';
88 wp_register_style(
89 'notificationx-block-editor',
90 plugins_url( $editor_css, __FILE__ ),
91 array( 'notificationx-block-controls-css' ),
92 filemtime( "{$dir}/{$editor_css}" )
93 );
94
95 $style_css = 'notificationx/style.css';
96 wp_register_style(
97 'notificationx-block',
98 plugins_url( $style_css, __FILE__ ),
99 [],
100 filemtime( "{$dir}/{$style_css}" )
101 );
102 wp_register_script(
103 'notificationx-block-frontend',
104 plugins_url( 'notificationx/frontend.js', __FILE__ ),
105 [],
106 filemtime( "{$dir}/notificationx/frontend.js" ),
107 true
108 );
109 wp_localize_script('notificationx-block-frontend', 'notificationxBlockRest', [
110 'root' => rest_url(),
111 ]);
112 register_block_type( 'notificationx-pro/notificationx',
113 [
114 'editor_script' => 'notificationx-block-editor',
115 'editor_style' => 'notificationx-block-editor',
116 // 'style' => 'notificationx-block',
117 // 'script' => 'notificationx-block-frontend',
118 'render_callback' => [ $this, 'notificationx_render_callback' ],
119 'attributes' => array(
120 'nx_id' => array(
121 'type' => 'string',
122 ),
123 'blockId' => array(
124 'type' => 'string',
125 ),
126 'product_id' => array(
127 'type' => 'string',
128 ),
129 ),
130 ]
131 );
132 register_block_type( 'notificationx-pro/notificationx-render',
133 [
134 'render_callback' => [ $this, 'gutenberg_examples_dynamic_render_callback' ],
135 'attributes' => array(
136 'nx_id' => array(
137 'type' => 'string',
138 ),
139 'blockId' => array(
140 'type' => 'string',
141 ),
142 'product_id' => array(
143 'type' => 'string',
144 ),
145 'post_type' => array(
146 'type' => 'string',
147 ),
148 ),
149 ]
150 );
151
152 // ── NotificationX Countdown Timer block ──────────────────────────
153 $countdown_asset_path = NOTIFICATIONX_PATH . 'blocks/countdown/index.asset.php';
154 $countdown_asset = file_exists( $countdown_asset_path )
155 ? include $countdown_asset_path
156 : [ 'dependencies' => [], 'version' => NOTIFICATIONX_VERSION ];
157
158 wp_register_script(
159 'notificationx-countdown-editor',
160 plugins_url( 'countdown/index.js', __FILE__ ),
161 array_merge( $countdown_asset['dependencies'], [ 'notificationx-block-controls' ] ),
162 $countdown_asset['version'],
163 false // Editor script: must load in the head, which is the existing default.
164 );
165 wp_set_script_translations( 'notificationx-countdown-editor', 'notificationx' );
166
167 $countdown_editor_css = 'countdown/editor.css';
168 wp_register_style(
169 'notificationx-countdown-editor',
170 plugins_url( $countdown_editor_css, __FILE__ ),
171 [ 'notificationx-block-controls-css' ],
172 filemtime( "{$dir}/{$countdown_editor_css}" )
173 );
174
175 // Frontend base layout CSS — distinct handle from Elementor's `nx-countdown`
176 // (which is only registered on Elementor hooks), same source file.
177 wp_register_style(
178 'notificationx-countdown-style',
179 NOTIFICATIONX_PUBLIC_URL . 'css/nx-countdown.css',
180 [],
181 NOTIFICATIONX_VERSION
182 );
183 wp_register_script(
184 'notificationx-countdown-frontend',
185 plugins_url( 'countdown/frontend.js', __FILE__ ),
186 [],
187 filemtime( "{$dir}/countdown/frontend.js" ),
188 true
189 );
190
191 register_block_type( 'notificationx/countdown', [
192 'editor_script' => 'notificationx-countdown-editor',
193 'editor_style' => 'notificationx-countdown-editor',
194 // `style` is injected into both the editor canvas iframe and the
195 // frontend (only when the block is present), so the base layout CSS
196 // applies in the editor preview too.
197 'style' => 'notificationx-countdown-style',
198 'render_callback' => [ $this, 'countdown_render_callback' ],
199 'attributes' => $this->countdown_block_attributes(),
200 ] );
201 }
202
203 function notificationx_render_callback( $block_attributes, $content ) {
204 if( ! is_admin() ){
205 wp_enqueue_style('notificationx-block');
206 wp_enqueue_script('notificationx-block-frontend');
207 }
208 if ( is_admin() || $this->isRestUrl() ) {
209 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
210 do_action( 'nx_ignore_analytics' );
211 }
212 $nx_id = ! empty( $block_attributes['nx_id'] ) ? esc_attr($block_attributes['nx_id']) : '';
213 $product_id = ! empty( $block_attributes['product_id'] ) ? $block_attributes['product_id'] : '';
214 $block_id = ! empty( $block_attributes['blockId'] ) ? esc_attr($block_attributes['blockId']) : '';
215 $html = '<div class="' . $block_id . ' notificationx-block-wrapper" data-nx_id="' . $nx_id . '">';
216 $html .= do_shortcode( "[notificationx_inline product_id='{$product_id}' id='{$nx_id}']" );
217 $html .= '</div>';
218 return $html;
219 }
220
221 function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) {
222 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
223 do_action( 'nx_ignore_analytics' );
224 $nx_id = ! empty( $block_attributes['nx_id'] ) ? esc_attr($block_attributes['nx_id']) : '';
225 $product_id = ! empty( $block_attributes['product_id'] ) ? $block_attributes['product_id'] : '';
226 $post_type = ! empty( $block_attributes['post_type'] ) ? $block_attributes['post_type'] : '';
227 $html = '<div class="' . $block_attributes['blockId'] . ' notificationx-block-wrapper">';
228 if( 'wp_template' == $post_type ) {
229 add_filter('nx_is_preview',function(){
230 return true;
231 });
232 $product_id = wp_rand();
233 }
234 $shortcode = do_shortcode( "[notificationx_inline post_type='{$post_type}' product_id='{$product_id}' id='{$nx_id}' show_link=false]" );
235 if ( $shortcode ) {
236 $html .= $shortcode;
237 } else {
238 $html .= '<p class="nx-shortcode-notice">' . __( 'There is no data in this notification.', 'notificationx' ) . '</p>';
239 }
240 $html .= '</div>';
241
242 return wp_kses($html, Helper::nx_allowed_html());
243 }
244
245 /**
246 * Server-side attribute schema for the countdown block.
247 *
248 * Defaults MUST match the JS attribute defaults (blocks/countdown/components/attributes.js)
249 * — Gutenberg omits attributes left at their default from the saved markup,
250 * so the frontend relies on these defaults for any unchanged value.
251 *
252 * @return array
253 */
254 private function countdown_block_attributes() {
255 return [
256 'blockId' => [ 'type' => 'string' ],
257 'blockStyles' => [ 'type' => 'object' ],
258 'countdownType' => [ 'type' => 'string', 'default' => 'due_date' ],
259 'dueTime' => [ 'type' => 'string', 'default' => '' ],
260 'evergreenHours' => [ 'type' => 'number', 'default' => 11 ],
261 'evergreenMinutes' => [ 'type' => 'number', 'default' => 59 ],
262 'evergreenRecurring' => [ 'type' => 'boolean', 'default' => false ],
263 'recurringRestartAfter' => [ 'type' => 'number', 'default' => 0 ],
264 'recurringStopTime' => [ 'type' => 'string', 'default' => '' ],
265 'layout' => [ 'type' => 'string', 'default' => 'grid' ],
266 'labelView' => [ 'type' => 'string', 'default' => 'nx-countdown-label-block' ],
267 'showDays' => [ 'type' => 'boolean', 'default' => true ],
268 'daysLabel' => [ 'type' => 'string', 'default' => 'Days' ],
269 'showHours' => [ 'type' => 'boolean', 'default' => true ],
270 'hoursLabel' => [ 'type' => 'string', 'default' => 'Hours' ],
271 'showMinutes' => [ 'type' => 'boolean', 'default' => true ],
272 'minutesLabel' => [ 'type' => 'string', 'default' => 'Minutes' ],
273 'showSeconds' => [ 'type' => 'boolean', 'default' => true ],
274 'secondsLabel' => [ 'type' => 'string', 'default' => 'Seconds' ],
275 'showSeparator' => [ 'type' => 'boolean', 'default' => false ],
276 'separatorStyle' => [ 'type' => 'string', 'default' => 'dotted' ],
277 'expireType' => [ 'type' => 'string', 'default' => 'none' ],
278 'expiryTitle' => [ 'type' => 'string', 'default' => 'The countdown is finished!' ],
279 'expiryText' => [ 'type' => 'string', 'default' => 'Thank you for being part of this event.' ],
280 'redirectUrl' => [ 'type' => 'string', 'default' => '#' ],
281 ];
282 }
283
284 /**
285 * Renders the countdown block on the frontend.
286 *
287 * Mirrors \NotificationX\Extensions\Elementor\CountdownWidget::render() markup
288 * (so the shared frontend logic stays identical) and prints the per-instance
289 * scoped CSS computed in the editor.
290 *
291 * @param array $attributes Block attributes.
292 * @return string
293 */
294 function countdown_render_callback( $attributes, $content = '' ) {
295 if ( ! is_admin() ) {
296 wp_enqueue_style( 'notificationx-countdown-style' );
297 wp_enqueue_script( 'notificationx-countdown-frontend' );
298 }
299
300 $a = wp_parse_args( (array) $attributes, [
301 'blockId' => '',
302 'blockStyles' => [],
303 'countdownType' => 'due_date',
304 'dueTime' => '',
305 'evergreenHours' => 11,
306 'evergreenMinutes' => 59,
307 'evergreenRecurring' => false,
308 'recurringRestartAfter' => 0,
309 'recurringStopTime' => '',
310 'layout' => 'grid',
311 'labelView' => 'nx-countdown-label-block',
312 'showDays' => true,
313 'daysLabel' => 'Days',
314 'showHours' => true,
315 'hoursLabel' => 'Hours',
316 'showMinutes' => true,
317 'minutesLabel' => 'Minutes',
318 'showSeconds' => true,
319 'secondsLabel' => 'Seconds',
320 'showSeparator' => false,
321 'separatorStyle' => 'dotted',
322 'expireType' => 'none',
323 'expiryTitle' => '',
324 'expiryText' => '',
325 'redirectUrl' => '#',
326 ] );
327
328 $block_id = ! empty( $a['blockId'] ) ? sanitize_html_class( $a['blockId'] ) : 'nx-countdown-' . wp_rand( 1000, 9999 );
329
330 // GMT offset string (matches the JS `new Date(dateStr)` parsing).
331 $gmt_offset = get_option( 'gmt_offset' );
332 $offset_str = ( $gmt_offset < 0 ? '' : '+' ) . str_replace(
333 [ '.25', '.5', '.75' ],
334 [ ':15', ':30', ':45' ],
335 (string) $gmt_offset
336 );
337
338 $due_date_str = '';
339 if ( 'due_date' === $a['countdownType'] && ! empty( $a['dueTime'] ) ) {
340 $due_date_str = gmdate( 'M d Y G:i:s', strtotime( $a['dueTime'] ) ) . ' ' . $offset_str;
341 }
342
343 $evergreen_seconds = 0;
344 if ( 'evergreen' === $a['countdownType'] ) {
345 $evergreen_seconds = absint( $a['evergreenHours'] ) * HOUR_IN_SECONDS;
346 $evergreen_seconds += absint( $a['evergreenMinutes'] ) * MINUTE_IN_SECONDS;
347 }
348
349 $separator_class = '';
350 if ( ! empty( $a['showSeparator'] ) ) {
351 $separator_class = 'nx-countdown-show-separator nx-countdown-separator-' . sanitize_html_class( $a['separatorStyle'] );
352 }
353
354 // ── Wrapper data attributes ──
355 $wrapper_attrs = ' data-countdown-id="' . esc_attr( $block_id ) . '"';
356 $wrapper_attrs .= ' data-countdown-type="' . esc_attr( $a['countdownType'] ) . '"';
357 $wrapper_attrs .= ' data-expire-type="' . esc_attr( $a['expireType'] ) . '"';
358
359 if ( 'text' === $a['expireType'] ) {
360 $wrapper_attrs .= ' data-expiry-title="' . esc_attr( wp_kses_post( $a['expiryTitle'] ) ) . '"';
361 $wrapper_attrs .= ' data-expiry-text="' . esc_attr( wp_kses_post( $a['expiryText'] ) ) . '"';
362 } elseif ( 'url' === $a['expireType'] ) {
363 $wrapper_attrs .= ' data-redirect-url="' . esc_url( $a['redirectUrl'] ) . '"';
364 }
365
366 if ( 'evergreen' === $a['countdownType'] ) {
367 $wrapper_attrs .= ' data-evergreen-time="' . absint( $evergreen_seconds ) . '"';
368 if ( ! empty( $a['evergreenRecurring'] ) ) {
369 $stop_time = ! empty( $a['recurringStopTime'] )
370 ? gmdate( 'M d Y G:i:s', strtotime( $a['recurringStopTime'] ) ) . ' ' . $offset_str
371 : '';
372 $wrapper_attrs .= ' data-evergreen-recurring="' . absint( $a['recurringRestartAfter'] ) . '"';
373 $wrapper_attrs .= ' data-evergreen-recurring-stop="' . esc_attr( $stop_time ) . '"';
374 }
375 }
376
377 $layout_class = 'nx-countdown-layout-' . sanitize_html_class( ! empty( $a['layout'] ) ? $a['layout'] : 'grid' );
378 $label_view = sanitize_html_class( ! empty( $a['labelView'] ) ? $a['labelView'] : 'nx-countdown-label-block' );
379 $list_classes = trim( 'nx-countdown-container ' . $layout_class . ' ' . $label_view . ' ' . $separator_class );
380
381 // ── Time-unit items ──
382 $units = [
383 'days' => [ 'show' => $a['showDays'], 'label' => $a['daysLabel'], 'attr' => 'data-days' ],
384 'hours' => [ 'show' => $a['showHours'], 'label' => $a['hoursLabel'], 'attr' => 'data-hours' ],
385 'minutes' => [ 'show' => $a['showMinutes'], 'label' => $a['minutesLabel'], 'attr' => 'data-minutes' ],
386 'seconds' => [ 'show' => $a['showSeconds'], 'label' => $a['secondsLabel'], 'attr' => 'data-seconds' ],
387 ];
388
389 $items_html = '';
390 foreach ( $units as $unit => $data ) {
391 if ( empty( $data['show'] ) ) {
392 continue;
393 }
394 $label_html = '';
395 if ( ! empty( $data['label'] ) ) {
396 $label_html = '<span class="nx-countdown-label">' . esc_html( $data['label'] ) . '</span>';
397 }
398 $items_html .= '<li class="nx-countdown-item"><div class="nx-countdown-' . esc_attr( $unit ) . '">'
399 . '<span ' . $data['attr'] . ' class="nx-countdown-digits">00</span>'
400 . $label_html
401 . '</div></li>';
402 }
403
404 $html = $this->countdown_inline_styles( $block_id, $a['blockStyles'] );
405 $html .= '<div class="' . esc_attr( $block_id ) . ' nx-countdown-wrapper"' . $wrapper_attrs . '>';
406 $html .= '<ul id="nx-countdown-' . esc_attr( $block_id ) . '" class="' . esc_attr( $list_classes ) . '" data-date="' . esc_attr( $due_date_str ) . '">';
407 $html .= $items_html;
408 $html .= '</ul></div>';
409
410 return $html;
411 }
412
413 /**
414 * Builds the per-instance scoped <style> tag from the editor-computed CSS.
415 *
416 * @param string $block_id Unique block class (scopes every selector).
417 * @param array|object $block_styles { desktop, tab, mobile } raw CSS strings.
418 * @return string
419 */
420 private function countdown_inline_styles( $block_id, $block_styles ) {
421 $block_styles = (array) $block_styles;
422 $desktop = isset( $block_styles['desktop'] ) ? (string) $block_styles['desktop'] : '';
423 $tab = isset( $block_styles['tab'] ) ? (string) $block_styles['tab'] : '';
424 $mobile = isset( $block_styles['mobile'] ) ? (string) $block_styles['mobile'] : '';
425
426 $css = $desktop;
427 if ( '' !== trim( $tab ) ) {
428 $css .= '@media (max-width:1024px){' . $tab . '}';
429 }
430 if ( '' !== trim( $mobile ) ) {
431 $css .= '@media (max-width:767px){' . $mobile . '}';
432 }
433
434 $css = trim( $css );
435 if ( '' === $css ) {
436 return '';
437 }
438
439 // Defensive: CSS never needs "<"; strip it to prevent a </style> breakout.
440 $css = str_replace( '<', '', $css );
441
442 return '<style>' . $css . '</style>';
443 }
444
445 function isRestUrl() {
446 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
447 return false;
448 }
449 return true;
450 }
451
452 }
453