PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.3.3
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.3.3
3.3.3 3.3.2 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 All 158 releases
notificationx / includes / FrontEnd / FrontEnd.php

FrontEnd.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar 3.3.3, at includes/FrontEnd/FrontEnd.php

1,282 lines 60.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * FrontEnd Class
5 *
6 * @package NotificationX\FrontEnd
7 */
8
9 namespace NotificationX\FrontEnd;
10
11 use NotificationX\Admin\Entries;
12 use NotificationX\Admin\Settings;
13 use NotificationX\Core\Analytics;
14 use NotificationX\Core\Database;
15 use NotificationX\Core\GetData;
16 use NotificationX\NotificationX;
17 use NotificationX\Core\Locations;
18 use NotificationX\Core\PostType;
19 use NotificationX\Core\REST;
20 use NotificationX\GetInstance;
21 use NotificationX\Extensions\PressBar\PressBar;
22 use NotificationX\Core\Helper;
23 use NotificationX\Extensions\ExtensionFactory;
24 use NotificationX\Types\GDPR;
25
26 /**
27 * This class is responsible for all Front-End actions.
28 * @method static FrontEnd get_instance($args = null)
29 */
30 class FrontEnd {
31 /**
32 * Instance of FrontEnd
33 *
34 * @var FrontEnd
35 */
36 use GetInstance;
37 /**
38 * Assets Path and URL
39 */
40 const ASSET_URL = NOTIFICATIONX_ASSETS . 'public/';
41 const ASSET_PATH = NOTIFICATIONX_ASSETS_PATH . 'public/';
42 protected $notificationXArr = [];
43
44 /**
45 * Block-editor bars whose block assets are already on this page, keyed by
46 * the bar's `gutenberg_id` (see enqueue_gutenberg_bar_assets()).
47 *
48 * @var array<int, true>
49 */
50 protected $gutenberg_bar_assets = [];
51
52 /**
53 * Initially Invoked
54 * when its initialized.
55 */
56 public function __construct() {
57 Analytics::get_instance();
58 BarSpace::get_instance();
59 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed for the NotificationX codebase: acceptable in this context.
60 if (!is_admin() || !empty($_GET['frontend'])) {
61 add_action('init', [$this, 'init'], 10);
62 }
63 add_filter('nx_frontend_localize_data', [$this, 'get_localize_data']);
64 Preview::get_instance();
65 }
66
67 /**
68 * This method is reponsible for Admin Menu of
69 * NotificationX
70 *
71 * @return void
72 */
73 public function init() {
74 add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts'], 10);
75 add_filter('nx_fallback_data', [$this, 'fallback_data'], 10, 3);
76 add_filter('nx_filtered_data', [$this, 'filtered_data'], 9999, 3);
77 add_filter('nx_filtered_post', [$this, 'filtered_post'], 9999, 2);
78 add_action('wp_print_footer_scripts', [$this, 'footer_scripts']);
79 // After wp_enqueue_scripts (wp_head priority 1) has collected the page's bars.
80 add_action('wp_head', [$this, 'print_bar_reserve'], 3);
81 add_filter('body_class', [ $this, 'nx_add_body_class' ] );
82 add_filter('style_loader_tag', [$this, 'non_blocking_style_tag'], 10, 2);
83
84 }
85
86 /**
87 * Third-party stylesheets used by the notification themes, keyed by style
88 * handle.
89 *
90 * They used to be CSS `@import`s at the top of frontend.css, which the
91 * browser only discovers once frontend.css has downloaded: a serial,
92 * render-blocking chain to two more origins on every page. They are now
93 * enqueued next to `notificationx-public` and loaded without blocking the
94 * first paint (see non_blocking_style_tag()).
95 *
96 * @since 3.3.3
97 * @return array<string, string> Style handle => URL.
98 */
99 public function get_external_styles() {
100 $styles = [];
101 /**
102 * Filters whether NotificationX loads Open Sans from Google Fonts.
103 *
104 * Return false if the site already loads Open Sans or must not
105 * contact Google Fonts; the themes then fall back to sans-serif.
106 *
107 * @since 3.3.3
108 * @param bool $load Default true.
109 */
110 if (apply_filters('notificationx_load_open_sans', true)) {
111 $styles['notificationx-open-sans'] = 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&display=swap';
112 }
113 /**
114 * Filters whether NotificationX loads FontAwesome 4.7 from cdnjs.
115 *
116 * The icons are drawn in pseudo-elements of a few themes only. Return
117 * false if the site already loads FontAwesome 4.
118 *
119 * @since 3.3.3
120 * @param bool $load Default true.
121 */
122 if (apply_filters('notificationx_load_fontawesome', true)) {
123 $styles['notificationx-fontawesome-4'] = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css';
124 }
125 return $styles;
126 }
127
128 /**
129 * Enqueue the third-party font and icon stylesheets (get_external_styles()).
130 *
131 * Call this wherever `notificationx-public` (or a stylesheet that bundles
132 * the frontend themes) is enqueued.
133 *
134 * @since 3.3.3
135 * @return void
136 */
137 public function enqueue_external_styles() {
138 foreach ($this->get_external_styles() as $handle => $src) {
139 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party URL; a ?ver= query would only split the CDN cache.
140 wp_enqueue_style($handle, $src, [], null);
141 }
142 }
143
144 /**
145 * Load some stylesheets without blocking the first paint.
146 *
147 * - `notificationx-public` styles only what the runtime renders — bars,
148 * popups and shortcodes all mount from REST data after the page has
149 * loaded (the bar's reserved space is a separate inline style), and the
150 * runtime waits for this sheet before its first render (whenStyled()).
151 * At ~600 KB it was the largest render-blocking stylesheet on every page.
152 * - `notificationx-gdpr-modal` styles only the cookie-preferences modal,
153 * which opens on a click.
154 * - The font and icon stylesheets from get_external_styles(): the fonts
155 * swap in, and the FontAwesome glyphs are drawn only in pseudo-elements
156 * of notifications the runtime renders after the page has loaded.
157 *
158 * The stylesheet is requested as `print` and switched to `all` once it has
159 * loaded; the <noscript> copy keeps it for visitors without JavaScript.
160 * `data-nx-style` marks it for the runtime, which also switches it (see
161 * applyDeferredStyles() in external-styles.ts) in case the inline handler
162 * was stripped by an optimizer or blocked by a Content Security Policy.
163 *
164 * @param string $tag The link tag.
165 * @param string $handle Style handle.
166 * @return string
167 */
168 public function non_blocking_style_tag($tag, $handle) {
169 $handles = ['notificationx-public', 'notificationx-gdpr-modal', 'notificationx-open-sans', 'notificationx-fontawesome-4'];
170 if (!in_array($handle, $handles, true) || false !== strpos($tag, 'onload=')) {
171 return $tag;
172 }
173 $deferred = preg_replace('/\smedia=([\'"])all\1/', ' media=$1print$1 onload="this.media=\'all\'" data-nx-style=$1print$1', $tag, 1, $count);
174 if (!$count) {
175 return $tag;
176 }
177 return $deferred . '<noscript>' . trim($tag) . "</noscript>\n";
178 }
179
180 /**
181 * Script dependencies of the `notificationx-public` runtime.
182 *
183 * `wp-hooks` provides `window.wp.hooks`, the registry the runtime reads its
184 * frontend filters from (see nxdev/notificationx/frontend/core/hooks.ts and
185 * docs/api/frontend-js-hooks.md). It is always kept, even if a filter drops
186 * it, because without it every add-on filter silently stops firing.
187 *
188 * @since 3.3.3
189 * @return string[]
190 */
191 public function get_script_dependencies() {
192 /**
193 * Filters the script dependencies of the `notificationx-public` runtime.
194 *
195 * @since 3.3.3
196 * @param string[] $deps Script handles.
197 */
198 $deps = apply_filters( 'nx_frontend_script_deps', [ 'wp-hooks' ] );
199 $deps = is_array( $deps ) ? array_filter( $deps, 'is_string' ) : [];
200 array_unshift( $deps, 'wp-hooks' );
201 return array_values( array_unique( $deps ) );
202 }
203
204 /**
205 * This method is responsible for enqueueing scripts for public use.
206 *
207 * @return void
208 */
209 public function enqueue_scripts() {
210 $custom_css = $this->generate_custom_css();
211 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
212 wp_register_script('notificationx-public', Helper::file('public/js/frontend.js', true), $this->get_script_dependencies(), apply_filters('nx_frontend_js_version', NOTIFICATIONX_VERSION ), true);
213 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
214 wp_register_style('notificationx-public', Helper::file('public/css/frontend.css', true), [], apply_filters('nx_frontend_css_version', NOTIFICATIONX_VERSION ), 'all');
215 // GDPR cookie-customisation modal styles, split out of frontend.css
216 // (~305 KB) so only pages with an active GDPR notice load them.
217 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
218 wp_register_style('notificationx-gdpr-modal', Helper::file('public/css/gdpr-modal.css', true), ['notificationx-public'], apply_filters('nx_frontend_css_version', NOTIFICATIONX_VERSION ), 'all');
219 // wp_register_style('notificationx-icon-pack', Helper::file('public/icon/style.css', true), [], NOTIFICATIONX_VERSION, 'all');
220 // Localize scripts for frontend
221 wp_localize_script(
222 'notificationx-public',
223 'notificationxPublic',
224 array(
225 'necessary_tab_info' => [
226 'title' => __('Necessary', 'notificationx'),
227 'desc' => __('Necessary cookies are needed to ensure the basic functions of this site, like allowing secure log-ins and managing your consent settings. These cookies do not collect any personal information.', 'notificationx'),
228 ],
229 'functional_tab_info' => [
230 'title' => __('Functional', 'notificationx'),
231 'desc' => __('Functional cookies assist in performing tasks like sharing website content on social media, collecting feedback, and enabling other third-party features.', 'notificationx'),
232 ],
233 'analytics_tab_info' => [
234 'title' => __('Analytics', 'notificationx'),
235 'desc' => __('Analytical cookies help us understand how visitors use the website. They provide data on metrics like the number of visitors, bounce rate, traffic sources etc.', 'notificationx'),
236 ],
237 'performance_tab_info' => [
238 'title' => __('Performance', 'notificationx'),
239 'desc' => __("Performance cookies help analyze the website's key performance indicators, which in turn helps improve the user experience for visitors.", 'notificationx'),
240 ],
241 'advertising_tab_info' => [
242 'title' => __('Advertisement', 'notificationx'),
243 'desc' => __("Advertisement cookies help analyze the website's key advertising indicators, which in turn helps improve the user experience for visitors.", 'notificationx'),
244 ],
245 'uncategorized_tab_info' => [
246 'title' => __('Uncategorized', 'notificationx'),
247 'desc' => __("Uncategorized cookies are those that don't fall into any specific category but may still be used for various purposes on the site. These cookies help us improve user experience by tracking interactions that don't fit into other cookie types.", 'notificationx'),
248 ],
249 'is_enabled_wp_consent_api' => is_plugin_active('wp-consent-api/wp-consent-api.php'),
250 )
251 );
252
253 $exit = false;
254 $referer = isset($_SERVER['HTTP_REFERER']) ? esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : '';
255 if($referer && strpos($referer, 'wp-admin/widgets.php') !== false){
256 $exit = ['total' => 0];
257 }
258
259 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
260 $exit = apply_filters('nx_before_enqueue_scripts', $exit);
261 if(!empty($exit)){
262 $this->notificationXArr = $exit;
263 return;
264 }
265
266 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reviewed for the NotificationX codebase: acceptable in this context.
267 if (!$exit && empty($_GET['elementor-preview'])) {
268 $this->notificationXArr = $this->get_notifications_ids();
269 if ($this->notificationXArr['total'] > 0) {
270 $lang = get_locale();
271 $lang = str_replace('_', '-', $lang);
272 $lang = strtolower($lang);
273 $_lang = explode('-', $lang);
274 if ($lang !== "en" && $lang !== "en-us") {
275 $script = Helper::file("public/locale/$lang.js", false);
276 if (file_exists($script)) {
277 wp_enqueue_script('notificationx-moment-locale', Helper::file("public/locale/$lang.js", true), [], NOTIFICATIONX_VERSION, true);
278 } else if (!empty($_lang[1])) {
279 $lang = $_lang[0];
280 $script = Helper::file("public/locale/$lang.js", false);
281 if (file_exists($script)) {
282 wp_enqueue_script('notificationx-moment-locale', Helper::file("public/locale/$lang.js", true), [], NOTIFICATIONX_VERSION, true);
283 }
284 }
285 }
286
287 wp_enqueue_style('notificationx-public');
288 $this->enqueue_external_styles();
289 if ( ! empty( $this->notificationXArr['gdpr'] ) ) {
290 wp_enqueue_style('notificationx-gdpr-modal');
291 }
292 wp_enqueue_script('notificationx-public');
293 wp_enqueue_style('dashicons');
294 do_action('notificationx_scripts', $this->notificationXArr);
295 wp_add_inline_style( 'notificationx-public', $custom_css );
296 }
297 } else {
298 // @todo maybe elementor edit mode CSS. to move to top.
299 // LATER
300 }
301 }
302
303 /**
304 * Reserve the top bar's measured height before it mounts (see BarSpace).
305 *
306 * @return void
307 */
308 public function print_bar_reserve() {
309 // The builder preview passes preview data here, not bar IDs.
310 if (!empty($this->notificationXArr['nxPreview']) || empty($this->notificationXArr['pressbar']) || !is_array($this->notificationXArr['pressbar'])) {
311 return;
312 }
313 $ids = array_filter($this->notificationXArr['pressbar'], 'is_numeric');
314 if ($ids) {
315 BarSpace::get_instance()->print_reserve($ids);
316 }
317 }
318
319 public function nx_add_body_class($classes) {
320 $classes[] = 'has-notificationx';
321 // Tells the runtime it need not fetch these bars' pages for their assets.
322 foreach (array_keys($this->gutenberg_bar_assets) as $gutenberg_id) {
323 $classes[] = 'nx-bar-assets-' . $gutenberg_id;
324 }
325 return $classes;
326 }
327
328 /**
329 * Put a block-editor bar's block assets on the current page.
330 *
331 * The bar's HTML arrives over REST after load, where anything its blocks
332 * enqueue is discarded. The runtime used to recover those assets by
333 * fetching the bar's own permalink — a full themed page (~300 KB on
334 * essential-blocks.com) — and loading each stylesheet and script it had
335 * that this page did not, one after another, before the bar could settle.
336 * Rendering the blocks here, while this page's assets are still being
337 * collected, lets each block enqueue what it needs (e.g. a countdown's
338 * frontend script) exactly as it would in post content; the output is
339 * discarded. Mirrors the Elementor branch above.
340 *
341 * Runs for page loads only: REST and admin requests have no page to add
342 * assets to.
343 *
344 * @param int|string $gutenberg_id The bar's block post ID.
345 * @return void
346 */
347 protected function enqueue_gutenberg_bar_assets($gutenberg_id) {
348 $gutenberg_id = absint($gutenberg_id);
349 if (!$gutenberg_id || isset($this->gutenberg_bar_assets[$gutenberg_id]) || !doing_action('wp_enqueue_scripts')) {
350 return;
351 }
352 // Same lookup as PressBar::print_bar_notice(), which renders the HTML the runtime shows.
353 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
354 $post_id = apply_filters('wpml_object_id', $gutenberg_id, 'wp_block', true);
355 $post = $post_id ? get_post($post_id) : null;
356 if (!$post || '' === trim((string) $post->post_content)) {
357 return;
358 }
359 // This runs inside <head> (wp_enqueue_scripts). A block whose render
360 // callback echoes instead of returning would print into <head> and push
361 // every later head tag into <body>; buffer and discard all output.
362 ob_start();
363 try {
364 do_blocks($post->post_content);
365 } finally {
366 ob_end_clean();
367 }
368 $this->gutenberg_bar_assets[$gutenberg_id] = true;
369 }
370
371 private function separate_css($css) {
372 $media_css = '';
373 $normal_css = '';
374
375 // Match @media blocks
376 preg_match_all('/@media[^{]*{([^}]*{[^}]*})*[^}]*}/', $css, $media_matches);
377
378 // Extract normal CSS
379 $normal_css = preg_replace('/@media[^{]*{([^}]*{[^}]*})*[^}]*}/', '', $css);
380
381 // Extract @media CSS
382 if (!empty($media_matches[0])) {
383 $media_css = implode("\n", $media_matches[0]);
384 }
385
386 // Clean up normal CSS (remove extra whitespace)
387 $normal_css = trim($normal_css);
388
389 return [
390 'normal_css' => $normal_css,
391 'media_css' => $media_css
392 ];
393 }
394
395 public function generate_custom_css() {
396 $posts = Database::get_instance()->get_posts(Database::$table_posts, '*', ['enabled' => true] );
397 $combine_css = "";
398 foreach ($posts as $post) {
399 // Raw rows (not normalize_post()), so check the type here.
400 if( !empty( $post['data']['add_custom_css'] ) && is_string( $post['data']['add_custom_css'] ) && !empty( $post['nx_id'] ) ) {
401 $separatedCss = $this->separate_css($post['data']['add_custom_css']);
402 if( !empty( $post['data']['source'] ) && $post['data']['source'] == 'press_bar' ) {
403 $combine_css .= "{$separatedCss['normal_css']} {$separatedCss['media_css']} ";
404 } else if( !empty( $post['data']['source'] ) && $post['data']['source'] == 'gdpr_notification' ) {
405 $combine_css .= "{$separatedCss['normal_css']} {$separatedCss['media_css']} ";
406 }
407 else {
408 $combine_css .= "{$separatedCss['normal_css']} {$separatedCss['media_css']} ";
409 }
410 }
411 }
412 return $combine_css;
413 }
414
415 public function footer_scripts() {
416 if (!empty($this->notificationXArr['total']) && $this->notificationXArr['total'] > 0) {
417 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
418 $this->notificationXArr = apply_filters('nx_frontend_localize_data', $this->notificationXArr);
419 ?>
420 <script data-no-optimize="1">
421 (function() {
422 window.notificationXArr = window.notificationXArr || [];
423 window.notificationXArr.push(<?php echo json_encode($this->notificationXArr); ?>);
424 })();
425 </script>
426 <?php
427 }
428 }
429
430 // @todo deprecated. use get_localize_data instead
431 public function localizeScripts() {
432 return [];
433 }
434
435 /**
436 * The URL WordPress prints for a registered stylesheet.
437 *
438 * @param string $handle Style handle.
439 * @return string Empty when the handle has no source.
440 */
441 protected function style_url($handle) {
442 $style = wp_styles()->query($handle, 'registered');
443 if (!$style || empty($style->src) || !is_string($style->src)) {
444 return '';
445 }
446 $src = $style->src;
447 // Same version rule as WP_Styles::_css_href().
448 $ver = null === $style->ver ? '' : ($style->ver ? $style->ver : get_bloginfo('version'));
449 if ('' !== $ver) {
450 $src = add_query_arg('ver', $ver, $src);
451 }
452 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core filter, so CDN rewrites apply.
453 return (string) apply_filters('style_loader_src', $src, $handle);
454 }
455
456 public function get_localize_data($data) {
457 $data['rest'] = REST::get_instance()->rest_data(false);
458 $data['assets'] = self::ASSET_URL;
459 if (empty($data['cross'])) {
460 // The runtime re-adds these if an optimizer combined them into a
461 // bundle that does not apply (e.g. a print-only one).
462 $styles = [];
463 foreach (['notificationx-public', 'notificationx-gdpr-modal'] as $handle) {
464 if (wp_style_is($handle, 'enqueued') || wp_style_is($handle, 'done')) {
465 $url = $this->style_url($handle);
466 if ($url) {
467 $styles[$handle] = $url;
468 }
469 }
470 }
471 if ($styles) {
472 $data['styles'] = $styles;
473 }
474 }
475 $data['is_pro'] = false;
476 $data['gmt_offset'] = get_option('gmt_offset');
477 $data['lang'] = get_locale();
478 $data['common_assets'] = NOTIFICATIONX_COMMON_URL;
479 $data['extra'] = [
480 'is_singular' => is_singular(),
481 'query' => $GLOBALS['wp_query']->query,
482 'queried_id' => get_queried_object_id(),
483 'pid' => !empty($GLOBALS['post']->ID) ? $GLOBALS['post']->ID : 0,
484 ];
485 $data['localeData'] = load_script_textdomain('notificationx-public', 'notificationx');
486 if (!empty($data['cross'])) {
487 // Cross-domain embeds have no WordPress enqueue: the runtime adds these.
488 $data['external_styles'] = $this->get_external_styles();
489 }
490 return $data;
491 }
492
493 public function get_notifications_data($params) {
494 $_params = $params;
495 $result = [
496 'global' => [],
497 'active' => [],
498 'pressbar' => [],
499 'gdpr' => [],
500 'shortcode' => [],
501 'popup' => [],
502 'exit_intent' => [],
503 ];
504 if (!empty($_params['all_active'])) {
505 $params = $this->get_notifications_ids();
506 }
507 $params = wp_parse_args(
508 $params,
509 [
510 'global' => [],
511 'active' => [],
512 'pressbar' => [],
513 'gdpr' => [],
514 'popup' => [],
515 'exit_intent' => [],
516 'shortcode' => [],
517 'inline_shortcode' => false,
518 ]
519 );
520 // These come straight from the public REST `notice` request, where
521 // `?global=1` arrives as a string; the code below needs ID lists.
522 foreach (['global', 'active', 'pressbar', 'gdpr', 'popup', 'exit_intent', 'shortcode'] as $list_key) {
523 $params[$list_key] = NotificationX::get_instance()->normalize_multiple_value($params[$list_key]);
524 }
525 $global = $params['global'];
526 $active = $params['active'];
527 $pressbar = $params['pressbar'];
528 $gdpr = $params['gdpr'];
529 $popup = $params['popup'];
530 $exit_intent = $params['exit_intent'];
531 $shortcode = $params['shortcode'];
532 $all = array_merge($global, $active, $shortcode);
533 $_defaults = array(
534 'none' => '',
535 'name' => __('Someone', 'notificationx'),
536 'first_name' => __('Someone', 'notificationx'),
537 'last_name' => __('Someone', 'notificationx'),
538 'anonymous_title' => __('Anonymous Title', 'notificationx'),
539 'sometime' => __('Some time ago', 'notificationx'),
540 );
541
542 // foreach (['global', 'active'] as $key => $type) {
543 // foreach ($params[$type] as $id) {
544 // $result[$type][$id] = [];
545 // }
546 // }
547
548 $device = isset($params['deviceType']) && !empty($params['deviceType']) ? $params['deviceType'] : '';
549 if (!empty($all)) {
550 $notifications = $this->get_notifications($all, $device);
551 $entries = $this->get_entries($all, $notifications, $params);
552
553 foreach ($entries as $entry) {
554 $nx_id = $entry['nx_id'];
555 $settings = $notifications[$nx_id];
556
557 // check if notification is enabled or not.
558 if (!$settings['enabled']) {
559 continue;
560 }
561
562 $type = $settings['type'];
563 $source = $settings['source'];
564
565 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
566 $should_continue = apply_filters("nx_entry_show_on_frontend_$source", false, $entry, $settings);
567 if ( $should_continue ) {
568 continue;
569 }
570
571 if (!empty($entry['timestamp'])) {
572 $timestamp = $entry['timestamp'];
573 $display_from = !empty($settings['display_from']) ? $settings['display_from'] : 2;
574 $display_from = Helper::generate_time_string($settings);
575 if (!is_numeric($timestamp)) {
576 $entry['timestamp'] = $timestamp = is_string($timestamp) ? strtotime($timestamp) : false;
577 }
578 if ($timestamp && $display_from > $timestamp) {
579 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
580 if (apply_filters("nx_entry_display_$source", true, $entry, $settings)) {
581 continue;
582 }
583 }
584 }
585
586 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
587 $defaults = apply_filters("nx_fallback_data_$source", $_defaults, $entry, $settings);
588 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
589 $defaults = apply_filters('nx_fallback_data', $defaults, $entry, $settings);
590
591 $entry = $this->apply_defaults($entry, $defaults);
592 $entry['image_data'] = $this->get_image_url($entry, $settings);
593 if (!empty($entry['title'])) {
594 $entry['title'] = wp_strip_all_tags(html_entity_decode($entry['title']));
595 }
596
597 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
598 $entry = apply_filters("nx_filtered_entry_$type", $entry, $settings);
599 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
600 $entry = apply_filters("nx_filtered_entry_$source", $entry, $settings);
601 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
602 $entry = apply_filters('nx_filtered_entry', $entry, $settings);
603 $entry = $this->link_url($entry, $settings, $params);
604
605 // @todo shortcode
606 // @todo check if the current page have shortcode.
607 if (in_array($nx_id, $shortcode)) {
608 $position = $settings['position'];
609 $settings['position'] = "notificationx-shortcode-$nx_id";
610 if (empty($result['shortcode'][$nx_id]['post'])) {
611 $result['shortcode'][$nx_id]['post'] = $settings;
612 }
613 $result['shortcode'][$nx_id]['entries'][] = $entry;
614 $settings['position'] = $position;
615 if ($settings['show_on'] === 'only_shortcode' || 'inline' === $settings['type'] || 'woocommerce_sales_inline' == $settings['source']) {
616 continue;
617 }
618 if (empty($global) && empty($active)) {
619 continue;
620 }
621 }
622
623 if (!empty($settings['global_queue'])) {
624 if (empty($result['global'][$nx_id]['post'])) {
625 $result['global'][$nx_id]['post'] = $settings;
626 }
627 $result['global'][$nx_id]['entries'][] = $entry;
628 } else {
629 if (empty($result['active'][$nx_id]['post'])) {
630 $result['active'][$nx_id]['post'] = $settings;
631 }
632 $result['active'][$nx_id]['entries'][] = $entry;
633 }
634 }
635
636 foreach ($result as &$group) {
637 foreach ($group as &$value) {
638 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
639 $value['entries'] = apply_filters("nx_filtered_data_{$value['post']['type']}", $value['entries'], $value['post'], $params);
640 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
641 $value['entries'] = apply_filters("nx_filtered_data_{$value['post']['source']}", $value['entries'], $value['post'], $params);
642 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
643 $value['entries'] = apply_filters('nx_filtered_data', $value['entries'], $value['post'], $params);
644 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
645 $value['post'] = apply_filters('nx_filtered_post', $value['post'], $params);
646 }
647 }
648 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
649 $result = apply_filters('nx_filtered_notice', $result, $params);
650 }
651
652 if (!empty($pressbar)) {
653 $notifications = $this->get_notifications($pressbar, $device);
654 foreach ($notifications as $key => $settings) {
655 $_nx_id = $settings['nx_id'];
656 // check if position is bottom_left then modify it to top
657 if($settings['position'] == 'bottom_left'){
658 $settings['position'] = 'top';
659 }
660 $elementor_post_id = isset($settings['elementor_id']) ? $settings['elementor_id'] : '';
661 if ($elementor_post_id == '' || get_post_status($elementor_post_id) !== 'publish' | !class_exists('\Elementor\Plugin')) {
662 $settings['elementor_id'] = false;
663 }
664 if ( ( !empty( $_params['all_active']) && $elementor_post_id ) || !$settings['enabled'] ) {
665 continue;
666 }
667
668 // $settings['button_url'] = apply_filters("nx_notification_link_{$settings['source']}", $settings['button_url'], $settings);
669 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
670 $settings['button_url'] = apply_filters('nx_notification_link', $settings['button_url'], $settings);
671 if (!empty($settings['button_url']) && strpos($settings['button_url'], '//') === false && strpos($settings['button_url'], './') === false) {
672 $settings['button_url'] = "//{$settings['button_url']}";
673 }
674 $bar_content = $this->get_bar_content($settings, false, $params);
675 if ($bar_content !== '&nbsp;' || !empty($settings['enable_countdown'])) {
676 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
677 $settings = apply_filters('nx_filtered_post', $settings, $params);
678 $result['pressbar'][$_nx_id]['post'] = $settings;
679 $result['pressbar'][$_nx_id]['content'] = $bar_content;
680 }
681
682 unset($_nx_id);
683 }
684 }
685
686 if (!empty($gdpr)) {
687 $notifications = $this->get_notifications($gdpr);
688 foreach ($notifications as $key => $settings) {
689 $_nx_id = $settings['nx_id'];
690 if (!empty($_params['all_active'])) {
691 continue;
692 }
693
694 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
695 $settings = apply_filters('nx_filtered_post', $settings, $params);
696
697 $result['gdpr'][$_nx_id]['post'] = $settings;
698 $result['gdpr'][$_nx_id]['content'] = "";
699 unset($_nx_id);
700 }
701 }
702
703 // Popup Notification
704 if (!empty($popup)) {
705 $popup_notifications = $this->get_notifications($popup);
706 foreach ($popup_notifications as $key => $settings) {
707 $_nx_id = $settings['nx_id'];
708 if ( !$settings['enabled'] ) {
709 continue;
710 }
711
712 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
713 $settings = apply_filters('nx_filtered_post', $settings, $params);
714
715 $result['popup'][$_nx_id]['post'] = $settings;
716 $result['popup'][$_nx_id]['content'] = "";
717 unset($_nx_id);
718 }
719 }
720
721 // Exit Intent Notification
722 if (!empty($exit_intent)) {
723 $exit_intent_notifications = $this->get_notifications($exit_intent);
724 foreach ($exit_intent_notifications as $key => $settings) {
725 $_nx_id = $settings['nx_id'];
726 if ( !$settings['enabled'] ) {
727 continue;
728 }
729 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
730 $settings = apply_filters('nx_filtered_post', $settings, $params);
731 $result['exit_intent'][$_nx_id]['post'] = $settings;
732 $result['exit_intent'][$_nx_id]['content'] = "";
733 unset($_nx_id);
734 }
735 }
736
737 $result['settings'] = $this->get_settings();
738 return $result;
739 }
740
741 public function get_settings(){
742
743 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
744 $branding_url = apply_filters('nx_branding_url', NOTIFICATIONX_PLUGIN_URL . '?utm_source=' . esc_url(home_url()) . '&utm_medium=notificationx');
745 $settings = [
746 'disable_powered_by' => Settings::get_instance()->get('settings.disable_powered_by'),
747 'affiliate_link' => $branding_url,
748 'enable_analytics' => Settings::get_instance()->get('settings.enable_analytics', true),
749 'analytics_from' => Settings::get_instance()->get('settings.analytics_from'),
750 'delay_before' => Settings::get_instance()->get('settings.delay_before', 5),
751 'display_for' => Settings::get_instance()->get('settings.display_for', 5),
752 'delay_between' => Settings::get_instance()->get('settings.delay_between', 5),
753 'loop' => Settings::get_instance()->get('settings.loop', 5),
754 'random' => Settings::get_instance()->get('settings.random', 5),
755 'analytics_nonce' => wp_create_nonce('analytics_nonce'),
756 ];
757 return $settings;
758 }
759
760 public function get_notifications_ids($return_posts = false, $args = []) {
761 $args = wp_parse_args($args, [
762 'enabled' => true,
763 'updated_at' => ['<=', Helper::mysql_time()],
764 // 'global_queue' => true,
765 ]);
766 $notifications = PostType::get_instance()->get_posts($args);
767
768 $active_notifications = $global_notifications = $bar_notifications = $gdpr_notification = $popup_notifications = $exit_intent_notifications = array();
769
770 foreach ($notifications as $key => $settings) {
771 // $settings = NotificationX::get_instance()->normalize_post($post);
772
773 // IF PRESSBAR TIME RE_CONFIG THEN REMOVE COOKIE
774 // if( $settings['source'] == 'press_bar'){
775 // if( $_settings->enable_countdown && ( Helper::current_timestamp($_settings->countdown_start_date) < time() || Helper::current_timestamp($_settings->countdown_end_date) > time() ) ) {
776 // unset( $_COOKIE["notificationx_{$settings['nx_id']}"] );
777 // \setcookie("notificationx_{$settings['nx_id']}", null);
778 // }
779 // }
780
781 /**
782 * Check if it's a pro source and pro plugin is disabled.
783 */
784 if(!NotificationX::is_pro()){
785 $ext = ExtensionFactory::get_instance()->get($settings['source']);
786 if($ext && $ext->is_pro){
787 continue;
788 }
789 }
790
791 $countdown_rand = !empty($settings['countdown_rand']) ? "-{$settings['countdown_rand']}" : '';
792
793 if (!empty($_COOKIE["notificationx_{$settings['nx_id']}$countdown_rand"]) && $_COOKIE["notificationx_{$settings['nx_id']}$countdown_rand"] == true) {
794 unset($notifications[$key]);
795 continue;
796 }
797
798 if($this->is_logged_in($settings['show_on_display'])){
799 continue;
800 }
801
802 $locations = isset($settings['all_locations']) ? $settings['all_locations'] : [];
803 $custom_ids = isset($settings['custom_ids']) ? $settings['custom_ids'] : [];
804 $taxonomy_ids = isset($settings['taxonomy_ids']) ? $settings['taxonomy_ids'] : [];
805
806 if($this->check_show_on($locations, $custom_ids, $settings['show_on'], $taxonomy_ids)){
807 continue;
808 }
809
810 /**
811 * Check for hiding in mobile device
812 */
813 // if ($settings['hide_on_mobile'] && wp_is_mobile()) {
814 // continue;
815 // }
816
817 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
818 $show_on_exclude = apply_filters('nx_show_on_exclude', false, $settings);
819 if ($show_on_exclude) {
820 continue;
821 }
822
823 // excluding pressBar on rest request. PressBar is printed directly in head.
824 // if((empty($args['source']) || $args['source'] !== 'press_bar') && $settings['source'] == 'press_bar'){
825 // continue;
826 // }
827
828 $active_global_queue = boolval($settings['global_queue']);
829 if ($settings['source'] == 'press_bar') {
830 // if(
831 // !$_settings->elementor_id &&
832 // $_settings->enable_countdown &&
833 // !$_settings->evergreen_timer &&
834 // (
835 // strtotime($_settings->countdown_start_date) > time() ||
836 // strtotime($_settings->countdown_end_date) < time()
837 // )
838 // ){
839 // continue;
840 // }
841
842 $bar_notifications[] = $return_posts ? $settings : $settings['nx_id'];
843 if (!empty($settings['elementor_id']) && class_exists('\Elementor\Plugin')) {
844 // @todo Find a function to only load css instead of building content.
845 \Elementor\Plugin::$instance->frontend->get_builder_content($settings['elementor_id'], false);
846 } elseif (!empty($settings['gutenberg_id'])) {
847 $this->enqueue_gutenberg_bar_assets($settings['gutenberg_id']);
848 }
849 } elseif($settings['source'] == 'gdpr_notification') {
850 $gdpr_notification[] = $return_posts ? $settings : $settings['nx_id'];
851 } elseif($settings['source'] == 'popup_notification') {
852 $popup_notifications[] = $settings['nx_id'];
853 } elseif($settings['source'] == 'exit_intent_custom') {
854 $exit_intent_notifications[] = $settings['nx_id'];
855 // Force Elementor's frontend runtime + per-widget assets onto the
856 // page (elementor-frontend.js, the document CSS, and each widget's
857 // get_style_depends()/get_script_depends() — e.g. nx-countdown).
858 // The popup HTML itself is rendered later via REST, where these
859 // enqueues would be discarded, so the countdown widget would lose
860 // its layout CSS and timer JS. Mirrors the press_bar branch above.
861 if (!empty($settings['elementor_id']) && class_exists('\Elementor\Plugin')) {
862 \Elementor\Plugin::$instance->frontend->get_builder_content($settings['elementor_id'], false);
863 }
864 } elseif ($active_global_queue && NotificationX::is_pro()) {
865 $global_notifications[] = $return_posts ? $settings : $settings['nx_id'];
866 } else {
867 $active_notifications[] = $return_posts ? $settings : $settings['nx_id'];
868 }
869
870 unset($notifications[$key]);
871 }
872 // do_action('nx_active_notificationx', $notifications);
873
874 // @todo maybe combine two hooks.
875
876 return apply_filters(
877 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
878 'get_notifications_ids',
879 [
880 'global' => $global_notifications,
881 'active' => $active_notifications,
882 'pressbar' => $bar_notifications,
883 'gdpr' => $gdpr_notification,
884 'popup' => $popup_notifications,
885 'exit_intent' => $exit_intent_notifications,
886 'total' => (count($global_notifications) + count($active_notifications) + count($bar_notifications) + count($gdpr_notification) + count($popup_notifications) + count($exit_intent_notifications)),
887 ],
888 $notifications
889 );
890 }
891
892 public function is_logged_in($show_on_display){
893 $logged_in = is_user_logged_in();
894
895 if (($logged_in && 'logged_out_user' == $show_on_display) || (!$logged_in && 'logged_in_user' == $show_on_display)) {
896 return true;
897 }
898 return false;
899 }
900
901 /**
902 * @todo filter is not extensive enough.
903 */
904 public function check_show_on($locations, $custom_ids, $show_on, $taxonomy_ids = ''){
905 $check_location = false;
906
907 if ($locations == 'is_custom' || is_array($locations) && in_array('is_custom', $locations)) {
908 $custom_ids = !empty($custom_ids) ? $custom_ids : [];
909 }
910 if (!empty($locations)) {
911 // @todo need to pass url.
912 $check_location = Locations::get_instance()->check_location($locations, $custom_ids, $taxonomy_ids);
913 }
914
915 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
916 $check_location = apply_filters('nx_check_location', $check_location, $custom_ids, $show_on);
917
918 if ($show_on == 'on_selected') {
919 // show if the page is on selected
920 if (!$check_location) {
921 return true;
922 }
923 } elseif ($show_on == 'hide_on_selected') {
924 // hide if the page is on selected
925 if ($check_location) {
926 return true;
927 }
928 } elseif ($show_on === 'only_shortcode') {
929 return true;
930 }
931 return false;
932 }
933
934 public function get_notifications($ids, $device = '') {
935 $results = [];
936 $notifications = PostType::get_instance()->get_posts_by_ids($ids);
937
938 foreach ($notifications as $key => $value) {
939 /**
940 * Check for hiding in mobile device
941 */
942 if (empty($value['hide_on_mobile']) && $device === 'mobile') {
943 continue;
944 }
945
946 /**
947 * Check for hiding in tablet device
948 */
949 if (empty($value['hide_on_tab']) && $device === 'tablet') {
950 continue;
951 }
952
953 /**
954 * Check for hiding in desktop device
955 */
956 if (empty($value['hide_on_desktop']) && $device === 'desktop') {
957 continue;
958 }
959
960 /**
961 * Check if it's a pro source and pro plugin is disabled.
962 */
963 if(!NotificationX::is_pro()){
964 $ext = ExtensionFactory::get_instance()->get($value['source']);
965 if($ext && $ext->is_pro){
966 continue;
967 }
968 }
969
970 $results[$value['nx_id']] = $value;
971 }
972 return $results;
973 }
974
975 public function get_entries($ids, $notifications,$params) {
976 $entries = [];
977 if (!empty($ids) && is_array($ids)) {
978 $query = [];
979 foreach ($ids as $id) {
980 if (!empty($notifications[$id])) {
981 $post = $notifications[$id];
982 $global_query = " nx_id = " . absint($id) . " AND source = '" . esc_sql($post['source']) . "'";
983 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
984 $_q = apply_filters("nx_get_entries_query_part_{$notifications[$id]['source']}",$global_query, $notifications[$id], $params );
985 $query[$id] = " (" . $_q . ")";
986 }
987 }
988 if (!empty($query)) {
989 $entries = Entries::get_instance()->get_entries('WHERE' . implode(' OR ', $query));
990 foreach ($entries as $key => $value) {
991 if (!empty($value['data'])) {
992 $entries[$key] = array_merge($value, $value['data']);
993 unset($entries[$key]['data']);
994 }
995 }
996 }
997 }
998 if (!is_array($entries)) {
999 $entries = [];
1000 }
1001 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1002 $entries = apply_filters('nx_frontend_get_entries', $entries, $ids, $notifications,$params);
1003 return $entries;
1004 }
1005
1006 /**
1007 * This function is responsible for make ready the link for notifications.
1008 *
1009 * @param array $data
1010 * @return void
1011 */
1012 public static function link_url($entry, $post, $params = []) {
1013 if (empty($entry)) {
1014 return false;
1015 }
1016 $link = isset($entry['link']) ? $entry['link'] : '';
1017 // removing link if link type is none.
1018 if (empty($post['link_type']) || $post['link_type'] === 'none') {
1019 $link = '';
1020 }
1021
1022 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1023 $link = apply_filters("nx_notification_link_{$post['source']}", $link, $post, $entry, $params);
1024 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1025 $entry['link'] = apply_filters('nx_notification_link', $link, $post, $entry, $params);
1026 return $entry;
1027 }
1028
1029 /**
1030 * This function is responsible for getting the image url
1031 * using Product ID or from default image settings.
1032 *
1033 * @param array $data
1034 * @param array $settings
1035 * @return array of image data, contains url and title as alt text
1036 */
1037 public function get_image_url($data, $settings) {
1038 $source = $settings['source'];
1039 $alt_title = isset($data['name']) ? $data['name'] : '';
1040 $image_type = isset($settings['show_notification_image']) ? $settings['show_notification_image'] : false;
1041 if (empty($alt_title)) {
1042 $alt_title = isset($data['title']) ? $data['title'] : '';
1043 }
1044 $image_data = [
1045 'url' => '',
1046 'alt' => $alt_title,
1047 ];
1048
1049 if ($settings['show_default_image']) {
1050 if (!empty($settings['image_url']['url'])) {
1051 $image_size = (string) Settings::get_instance()->get('settings.notification_image_size', '100_100');
1052 $image_size = explode('_', $image_size);
1053 $width = 100;
1054 $height = 100;
1055 if( !empty( $image_size[0] ) && !empty( $image_size[1] ) ) {
1056 $width = $image_size[0];
1057 $height = $image_size[1];
1058 }
1059 $image = wp_get_attachment_image_src($settings['image_url']['id'], [$width, $height], true);
1060 $image_data['url'] = $image[0];
1061 } else {
1062 $default_avatar = $settings['default_avatar'];
1063 $image_data['url'] = NOTIFICATIONX_PUBLIC_URL . 'image/icons/' . $default_avatar;
1064 }
1065 } else {
1066 if ($image_type === 'gravatar') {
1067 $_data = array_change_key_case($data);
1068 if (isset($_data['email'])) {
1069 $image_data['url'] = get_avatar_url($_data['email'], ['size' => '100']);
1070 }
1071 }
1072 }
1073
1074 $image_data['classes'] = $image_type;
1075 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1076 $image_data = apply_filters("nx_notification_image_$source", $image_data, $data, $settings);
1077 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1078 $image_data = apply_filters('nx_notification_image', $image_data, $data, $settings);
1079
1080 if (!empty($image_data['url'])) {
1081 return $image_data;
1082 }
1083
1084 return false;
1085 }
1086
1087 public function apply_defaults($entry, $defaults) {
1088
1089 foreach ($defaults as $key => $value) {
1090 // @todo mukul remove `|| empty(trim($entry[$key]))`
1091 if (empty($entry[$key]) || (is_string($entry[$key]) && empty(trim($entry[$key])))) {
1092 $entry[$key] = $value;
1093 }
1094 }
1095
1096 return $entry;
1097 }
1098
1099 public function fallback_data($data, $saved_data, $settings) {
1100 if ((empty($saved_data['name']) || $data['name'] == __('Someone', 'notificationx')) && isset($saved_data['first_name']) || isset($saved_data['last_name'])) {
1101 $first_name = isset($saved_data['first_name']) ? $saved_data['first_name'] : '';
1102 $last_name = isset($saved_data['last_name']) ? $saved_data['last_name'] : '';
1103 $data['name'] = Helper::name($first_name, $last_name);
1104 }
1105 if (!empty($saved_data['name']) && empty($saved_data['first_name']) && empty($saved_data['last_name'])) {
1106 $data['first_name'] = $saved_data['name'];
1107 }
1108 $data['title'] = isset($saved_data['post_title']) ? $saved_data['post_title'] : (isset($data['title']) ? $data['title'] : '');
1109 return $data;
1110 }
1111
1112 /**
1113 * Add NotificationX in Footer
1114 *
1115 * @return void
1116 */
1117 public function filtered_data($entries, $post, $params) {
1118 if (is_array($entries) && (!defined('NX_DEBUG') || !NX_DEBUG)) {
1119 if (!empty($post['display_last']) && !in_array($post['source'], ['google', 'woo_inline', 'edd_inline', 'tutor_inline', 'learndash_inline', 'google_reviews', 'youtube','woocommerce_sales_inline','fluentcart_inline'])) {
1120 $entries = array_slice($entries, 0, $post['display_last']);
1121 }
1122 foreach ($entries as $index => $entry) {
1123 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1124 $_entry = apply_filters("nx_frontend_keep_entry_{$post['source']}", [
1125 'nx_id' => $entry['nx_id'],
1126 'timestamp' => isset($entry['timestamp']) ? $entry['timestamp'] : Helper::current_timestamp($entry['updated_at']),
1127 'updated_at' => isset( $entry['updated_at'] ) ? $entry['updated_at'] : '',
1128 'image_data' => $entry['image_data'],
1129 'link' => $entry['link'],
1130 // Location is rendered by themes (e.g. conv-theme-fifteen) via the
1131 // hardcoded {{city_country}} tag, not through a notification-template
1132 // param, so the template loop below never whitelists it. Keep the
1133 // raw parts here so the frontend can compose "City, Country".
1134 'city' => $entry['city'] ?? '',
1135 'country' => $entry['country'] ?? '',
1136 ], $entry, $post, $params);
1137 if (!empty($params['inline_shortcode']) && isset($entry['product_id'])) {
1138 $_entry['product_id'] = $entry['product_id'];
1139 }
1140
1141 // `notification-template` can be absent for some posts (e.g. advanced-
1142 // template notifications or imported/migrated posts), so guard against
1143 // passing null to array_values() — that is a fatal TypeError on PHP 8.
1144 $template_arr = ( ! empty( $post['notification-template'] ) && is_array( $post['notification-template'] ) )
1145 ? array_values( $post['notification-template'] )
1146 : [];
1147 if ( ! empty( $post['template_adv'] ) ) {
1148 $adv_template = isset( $post['advanced_template'] ) ? $post['advanced_template'] : '';
1149 $pattern = "/{{(.+?)}}/i";
1150 if (preg_match_all($pattern, $adv_template, $matches)) {
1151 $template_arr = $matches[1];
1152 }
1153 }
1154 if (is_array($template_arr)) {
1155 foreach ($template_arr as $entry_key) {
1156 $_entry_key = $entry_key;
1157 if ($entry_key == 'tag_siteview' || $entry_key == 'tag_realtime_siteview') {
1158 $entry_key = 'views';
1159 } elseif ($entry_key == 'ga_title' || $entry_key == 'ga_page_title') {
1160 $entry_key = 'title';
1161 } elseif (strpos($entry_key, 'tag_product_') === 0) {
1162 $entry_key = str_replace('tag_product_', '', $entry_key);
1163 } elseif (strpos($entry_key, 'tag_') === 0) {
1164 $entry_key = str_replace('tag_', '', $entry_key);
1165 } elseif (strpos($entry_key, 'product_') === 0) {
1166 $entry_key = str_replace('product_', '', $entry_key);
1167 }
1168
1169 if (isset($entry[$entry_key])) {
1170 $_entry[$entry_key] = $entry[$entry_key];
1171 }
1172 if (isset($entry[$_entry_key])) {
1173 $_entry[$_entry_key] = $entry[$_entry_key];
1174 }
1175 }
1176 $entries[$index] = $_entry;
1177 }
1178 }
1179 }
1180 return $entries;
1181 }
1182
1183 /**
1184 * Remove unnecessary props from post in frontend.
1185 *
1186 * @return void
1187 */
1188 public function filtered_post($post, $params = null) {
1189 if (is_array($post) && empty($params['inline_shortcode']) && (!defined('NX_DEBUG') || !NX_DEBUG)) {
1190 $ignore_props = [
1191 'all_locations',
1192 'category_list',
1193 'combine_multiorder_display',
1194 'combine_multiorder_text',
1195 'content_trim_length',
1196 'convertkit_form',
1197 'currentTab',
1198 'custom_contents',
1199 'custom_ids',
1200 'default_avatar',
1201 'elementor_edit_link',
1202 'enabled',
1203 'exclude_categories',
1204 'exclude_products',
1205 'form_list',
1206 'freemius_item_type',
1207 'freemius_plugins',
1208 'freemius_themes',
1209 'give_form_list',
1210 'give_forms_control',
1211 'image_url',
1212 'inline_location',
1213 'is_confirmed',
1214 'is_elementor',
1215 'is_inline',
1216 'ld_course_list',
1217 'ld_product_control',
1218 'activecampaign_form',
1219 'brevo_list',
1220 'mailchimp_list',
1221 'max_stock',
1222 'nx-bar_with_elementor',
1223 'nx-bar_with_elementor-remove',
1224 'nx-bar_with_elementor_install',
1225 'order_status',
1226 'press_content',
1227 'preview',
1228 'product_control',
1229 'product_exclude_by',
1230 'product_list',
1231 'rest_route',
1232 'show_default_image',
1233 'show_notification_image',
1234 'show_on',
1235 'show_on_display',
1236 'source_error',
1237 'utm_campaign',
1238 'utm_medium',
1239 'utm_source',
1240 'wp_reviews_product_type',
1241 'wp_reviews_slug',
1242 'wp_stats_product_type',
1243 'wp_stats_slug',
1244 '_locale',
1245 '__product_list',
1246 '__form_list',
1247 '__ld_course_list',
1248 '__give_form_list',
1249 ];
1250 foreach ($ignore_props as $prop) {
1251 if (isset($post[$prop])) {
1252 unset($post[$prop]);
1253 }
1254 }
1255 }
1256 if (isset($post['template_adv'], $post['advanced_template'])) {
1257 $post['advanced_template'] = do_shortcode($post['advanced_template']);
1258 }
1259 if( !empty( $post['notification-template'] ) ) {
1260 $post['notification-template'] = array_map( 'esc_html', $post['notification-template'] );
1261 }
1262 return $post;
1263 }
1264
1265 public function get_bar_content($settings, $suppress_filters = false, $params = []){
1266 $bar_content = PressBar::get_instance()->print_bar_notice($settings);
1267 if(!$suppress_filters){
1268 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
1269 $bar_content = apply_filters("nx_filtered_data_{$settings['source']}", $bar_content, $settings, $params);
1270 }
1271
1272 // checking if content is empty
1273 $_bar_content = str_replace(array("\r\n", "\n", "\r"), '', $bar_content);
1274 $_bar_content = trim(wp_strip_all_tags($_bar_content));
1275 if (empty($_bar_content) && !empty($settings['enable_countdown'])) {
1276 $bar_content = '&nbsp;';
1277 }
1278 return $bar_content;
1279 }
1280
1281 }
1282