PluginProbe
Post Grid Gutenberg Blocks – PostX / 5.1.0
Post Grid Gutenberg Blocks – PostX v5.1.0
5.1.1 5.1.0 5.0.41 5.0.39 5.0.38 5.0.37 5.0.36 5.0.35 5.0.34 5.0.33 5.0.32 5.0.31 5.0.30 5.0.29 5.0.28 5.0.27 5.0.26 5.0.25 5.0.23 5.0.24 5.0.22 5.0.21 5.0.20 5.0.19 5.0.18 All 240 releases
ultimate-post / includes / notice / class-notice.php

class-notice.php in Post Grid Gutenberg Blocks – PostX 5.1.0, at includes/notice/class-notice.php

1,116 lines 34.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore
2 namespace ULTP\Includes\Notice;
3
4 defined( 'ABSPATH' ) || exit;
5
6 use ULTP\Includes\Durbin\Xpo;
7 use ULTP\Includes\Durbin\DurbinClient;
8
9 /**
10 * Plugin Notice
11 */
12 class Notice {
13
14
15 /**
16 * Notice version
17 *
18 * @var string
19 */
20 private $notice_version = 'v5132';
21
22 /**
23 * Notice JS/CSS applied
24 *
25 * @var boolean
26 */
27 private $notice_js_css_applied = false;
28
29 /**
30 * Notice Priority
31 *
32 * @var string $plugin_notice_priority
33 */
34 private $plugin_notice_priority = 1;
35
36 /**
37 * Notice Priority
38 *
39 * @var string $plugin_notice_priority
40 */
41 private $plugin_notice_priority_key = 'postx';
42
43 /**
44 * Notice Constructor
45 */
46 public function __construct() {
47 add_action( 'admin_notices', array( $this, 'admin_notices_callback' ) );
48 add_action( 'admin_init', array( $this, 'set_dismiss_notice_callback' ) );
49
50 // REST API routes.
51 add_action( 'rest_api_init', array( $this, 'register_rest_route' ) );
52
53 // Woocommerce Install Action
54 // add_action( 'wp_ajax_ultp_install', array( $this, 'install_activate_plugin' ) ); // this ajax not called anywhere in future need to removed, that is arise patchstack security issue
55 add_filter( 'xpo_active_notice_lists', array( $this, 'handle_xpo_active_notice_lists' ), 99, 1 );
56 }
57
58 /**
59 * Registers REST API endpoints.
60 *
61 * @return void
62 */
63 public function register_rest_route() {
64 $routes = array(
65 // Hello Bar.
66 array(
67 'endpoint' => 'hello_bar',
68 'methods' => 'POST',
69 'callback' => array( $this, 'hello_bar_callback' ),
70 'permission_callback' => function () {
71 return current_user_can( 'manage_options' );
72 },
73 ),
74 );
75
76 foreach ( $routes as $route ) {
77 register_rest_route(
78 'ultp',
79 $route['endpoint'],
80 array(
81 array(
82 'methods' => $route['methods'],
83 'callback' => $route['callback'],
84 'permission_callback' => $route['permission_callback'],
85 ),
86 )
87 );
88 }
89 }
90
91 /**
92 * Hellobar config
93 *
94 * @return array
95 */
96 public static function get_hellobar_config() {
97 return array(
98 // Final hours sale.
99 'ultp_helloBar_summer_sale_2026_v1' => Xpo::get_transient_without_cache( 'ultp_helloBar_summer_sale_2026_v1' ),
100 'ultp_helloBar_summer_sale_2026_v1_2' => Xpo::get_transient_without_cache( 'ultp_helloBar_summer_sale_2026_v1_2' ),
101 'ultp_helloBar_base_price_2026' => Xpo::get_transient_without_cache( 'ultp_helloBar_base_price_2026' ),
102 );
103 }
104
105 /**
106 * Handles Hello Bar dismissal action via REST API .
107 *
108 * @param \WP_REST_Request $request REST request object .
109 * @return \WP_REST_Response
110 */
111 public function hello_bar_callback( \WP_REST_Request $request ) {
112 $request_params = $request->get_params();
113 $type = isset( $request_params['type'] ) ? $request_params['type'] : '';
114 $id = isset( $request_params['id'] ) ? $request_params['id'] : '';
115
116 // Guideline 11 — a dismissal must stick. This previously expired after 15
117 // days (1296000s), so a dismissed sale bar came back on its own. Passing 0
118 // stores it with no timeout, which Xpo::get_transient_without_cache() reads
119 // back indefinitely.
120 //
121 // $id names the option that gets written, so it is checked against the
122 // known hellobar keys rather than trusted from the request.
123 if ( 'hello_bar' === $type && ! empty( $id ) && array_key_exists( $id, self::get_hellobar_config() ) ) {
124 Xpo::set_transient_without_cache( $id, 'hide', 0 );
125 }
126
127 return new \WP_REST_Response(
128 array(
129 'success' => true,
130 'message' => __( 'Hello Bar Action performed', 'ultimate-post' ),
131 ),
132 200
133 );
134 }
135
136 /**
137 * Set Notice Dismiss Callback
138 *
139 * @return void
140 */
141 public function set_dismiss_notice_callback() {
142
143 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['wpnonce'] ?? '' ) ), 'ultp-admin-nonce' ) ) {
144 return;
145 }
146
147 $durbin_key = sanitize_text_field( wp_unslash( $_GET['ultp_durbin_key'] ?? '' ) );
148
149 // Durbin notice dismiss.
150 if ( ! empty( $durbin_key ) ) {
151 Xpo::set_transient_without_cache( 'ultp_durbin_notice_' . $durbin_key, 'off' );
152
153 if ( 'get' === sanitize_text_field( wp_unslash( $_GET['ultp_get_durbin'] ?? '' ) ) ) {
154 DurbinClient::send( DurbinClient::ACTIVATE_ACTION );
155 }
156 }
157
158 // Install notice dismiss.
159 $install_key = sanitize_text_field( wp_unslash( $_GET['ultp_install_key'] ?? '' ) );
160 if ( ! empty( $install_key ) ) {
161 Xpo::set_transient_without_cache( 'ultp_install_notice_' . $install_key, 'off' );
162 }
163 $notice_key = sanitize_text_field( wp_unslash( $_GET['ultp_notice'] ?? '' ) );
164 if ( ! empty( $notice_key ) ) {
165 $interval = (int) sanitize_text_field( wp_unslash( $_GET['ultp_interval'] ?? '' ) );
166 if ( ! empty( $interval ) ) {
167 Xpo::set_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key, 'off', $interval );
168 } else {
169 Xpo::set_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key, 'off' );
170 }
171 }
172 }
173
174 /**
175 * Admin Notices Callback
176 *
177 * @return void
178 */
179 public function admin_notices_callback() {
180 $this->ultp_dashboard_notice_callback();
181 $this->ultp_dashboard_durbin_notice_callback();
182 }
183
184 /**
185 * Admin Dashboard Notice Callback
186 *
187 * @return void
188 */
189 public function ultp_dashboard_notice_callback() {
190 if ( $this->is_available_for_notice() ) {
191 $this->ultp_dashboard_banner_notice();
192 $this->ultp_dashboard_content_notice();
193 $this->ultp_dashboard_image_banner_notice();
194
195 }
196 }
197
198 /**
199 * Dashboard Banner Notice
200 *
201 * @return void
202 */
203 public function ultp_dashboard_banner_notice( $return_bool = false ) {
204 $ultp_db_nonce = wp_create_nonce( 'ultp-admin-nonce' );
205 $banner_notices = array(
206 // FLash sale.
207 array(
208 'key' => 'ultp_banner_flash_sale_2026_1',
209 'start' => '2026-05-18 00:00 Asia/Dhaka',
210 'end' => '2026-05-21 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
211
212 'brand_color' => '#0322ff',
213
214 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale/offer.png',
215 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale/btn.png',
216 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale/bg.png',
217 'text' => 'Deal ending soon',
218 'countdown_duration' => 259200, // Duration in seconds.
219 // 'countdown_color' => '#0322ff',
220 'countdown_color' => '#3CF357',
221 'url' => Xpo::generate_utm_link(
222 array(
223 'utmKey' => 'flash_sale_content',
224 )
225 ),
226
227 'visibility' => ! Xpo::is_lc_active(),
228 ),
229 // surprise sale
230 array(
231 'key' => 'ultp_banner_surprise_sale_2026_1',
232 'start' => '2026-05-29 00:00 Asia/Dhaka',
233 'end' => '2026-06-01 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
234
235 'brand_color' => '#0322ff',
236
237 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/surprise_sale/offer.png',
238 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/surprise_sale/btn.png',
239 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/surprise_sale/bg.png',
240 'text' => 'Limited Time Offer',
241 'countdown_duration' => 259200, // Duration in seconds.
242 // 'countdown_color' => '#0322ff',
243 'countdown_color' => '#3CF357',
244 'url' => Xpo::generate_utm_link(
245 array(
246 'utmKey' => 'surprise_sale_content',
247 )
248 ),
249
250 'visibility' => ! Xpo::is_lc_active(),
251 ),
252 // massive sale
253 array(
254 'key' => 'ultp_banner_massive_sale_2026_1',
255 'start' => '2026-06-17 00:00 Asia/Dhaka',
256 'end' => '2026-06-20 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
257
258 'brand_color' => '#0322ff',
259
260 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/massive_sale/offer.png',
261 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/massive_sale/btn.png',
262 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/massive_sale/bg.png',
263 'text' => 'Deal ending soon',
264 'countdown_duration' => 259200, // Duration in seconds.
265 // 'countdown_color' => '#0322ff',
266 'countdown_color' => '#3CF357',
267 'url' => Xpo::generate_utm_link(
268 array(
269 'utmKey' => 'massive_sale_content',
270 )
271 ),
272
273 'visibility' => ! Xpo::is_lc_active(),
274 ),
275 // Final hours sale
276 array(
277 'key' => 'ultp_banner_final_hours_sale_2026_1',
278 'start' => '2026-06-28 00:00 Asia/Dhaka',
279 'end' => '2026-06-30 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
280
281 'brand_color' => '#0322ff',
282
283 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/final_hours_sale/offer.png',
284 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/final_hours_sale/btn.png',
285 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/final_hours_sale/bg.png',
286 'text' => 'Hurry Before It Ends!',
287 'countdown_duration' => 259200, // Duration in seconds.
288 // 'countdown_color' => '#0322ff',
289 'countdown_color' => '#3CF357',
290 'url' => Xpo::generate_utm_link(
291 array(
292 'utmKey' => 'final_hours_content',
293 )
294 ),
295 'visibility' => ! Xpo::is_lc_active(),
296 ),
297 );
298 foreach ( $banner_notices as $notice ) {
299 $notice_key = isset( $notice['key'] ) ? $notice['key'] : $this->notice_version;
300
301 if ( isset( $_GET['ultp_notice'] ) && $notice_key === sanitize_text_field(wp_unslash($_GET['ultp_notice'])) ) { // phpcs:ignore
302 continue;
303 }
304
305 $current_time = gmdate( 'U' );
306 $notice_start = gmdate( 'U', strtotime( $notice['start'] ) );
307 $notice_end = gmdate( 'U', strtotime( $notice['end'] ) );
308 if ( $current_time >= $notice_start && $current_time <= $notice_end && $notice['visibility'] ) {
309
310 $notice_transient = Xpo::get_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key );
311
312 if ( 'off' === $notice_transient ) {
313 continue;
314 }
315 if ( $return_bool ) { // Early return for Other plugin notice.
316 return true;
317 }
318 if ( ! $this->notice_js_css_applied ) {
319 $this->ultp_banner_notice_js();
320 $this->notice_js_css_applied = true;
321 }
322 $query_args = array(
323 'ultp_notice' => $notice_key,
324 'wpnonce' => $ultp_db_nonce,
325 );
326 if ( isset( $notice['repeat_interval'] ) && $notice['repeat_interval'] ) {
327 $query_args['ultp_interval'] = $notice['repeat_interval'];
328 }
329 ?>
330 <style type="text/css">
331 .ultp-notice-wrapper.ultp-banner-notice {
332 height: auto !important;
333 padding: 0 !important;
334 position: relative;
335 box-sizing: border-box;
336 background-repeat: no-repeat;
337 background-size: cover;
338 background-position: center;
339 }
340 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-link {
341 width: 100%;
342 text-decoration: none;
343 display: block;
344 }
345 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-content {
346 display: flex;
347 justify-content: space-between;
348 align-items: center;
349 max-width: 700px;
350 margin: 0 auto;
351 padding: 10px 16px;
352 gap: 16px;
353 }
354 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-side-image {
355 display: block;
356 max-width: 100%;
357 height: auto;
358 max-height: 32px;
359 }
360 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-main .ultp-banner-main-text {
361 color: #ffffff;
362 }
363 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-main {
364 display: flex;
365 flex-direction: column;
366 gap: 4px;
367 align-items: center;
368 justify-content: center;
369 font-weight: 700;
370 font-size: 18px;
371 color: #333333;
372 line-height: 1.2;
373 text-align: center;
374 }
375
376 @media screen and (max-width: 1100px) {
377 /* .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-content {
378 flex-direction: column;
379 } */
380 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-content .ultp-banner-main-text {
381 display: none;
382 }
383 }
384 @media screen and (max-width: 490px) {
385 .ultp-notice-wrapper.ultp-banner-notice {
386 display: none;
387 }
388 }
389 @media screen and (max-width: 782px) {
390 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-content {
391 justify-content: center;
392 padding: 12px 32px 12px 12px;
393 }
394 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-main {
395 font-size: 22px;
396 line-height: 28px;
397 }
398 }
399 @media screen and (max-width: 480px) {
400 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-content {
401 padding: 10px 32px 10px 10px;
402 }
403 .ultp-notice-wrapper.ultp-banner-notice .ultp-banner-main {
404 font-size: 18px;
405 line-height: 24px;
406 }
407 }
408 </style>
409 <div
410 class="ultp-notice-wrapper ultp-banner-notice notice"
411 style="
412 border-left: 3px solid <?php echo esc_attr( $notice['brand_color'] ); ?>;
413 background-image: url('<?php echo esc_attr( $notice['bg_image'] ); ?>');
414 ">
415 <a
416 class="wc-dismiss-notice dashicons dashicons-no-alt"
417 style="
418 position: absolute;
419 top: 1px;
420 right: 1px;
421 border-radius: 50%;
422 background-color: black;
423 color: white;
424 font-size: 14px;
425 display: flex;
426 align-items: center;
427 justify-content: center;
428 "
429 aria-label="<?php esc_html_e( 'Close Banner', 'ultimate-post' ); ?>"
430 href="<?php echo esc_url( add_query_arg( $query_args ) ); ?>">
431 </a>
432
433 <a class="ultp-banner-link" target="_blank" href="<?php echo esc_url( $notice['url'] ); ?>">
434 <div class="ultp-banner-content">
435 <img class="ultp-banner-side-image" loading="lazy" src="<?php echo esc_url( $notice['left_image'] ); ?>" />
436 <div class="ultp-banner-main">
437 <span class="ultp-banner-main-text">
438 <?php echo esc_html( $notice['text'] ); ?>
439 </span>
440 <div
441 class="ultp-notice-countdown"
442 style="
443 color: <?php echo esc_attr( $notice['countdown_color'] ); ?>;
444 "
445 data-notice-key="<?php echo esc_attr( $notice_key . '-countdown' ); ?>"
446 data-duration="<?php echo esc_attr( $notice['countdown_duration'] ); ?>">
447 00:00:00:00
448 </div>
449 </div>
450 <img class="ultp-banner-side-image" loading="lazy" src="<?php echo esc_url( $notice['right_image'] ); ?>" />
451 </div>
452 </a>
453 </div>
454 <?php
455 }
456 }
457 }
458
459 /**
460 * Banner JS
461 *
462 * @return void
463 */
464 public function ultp_banner_notice_js() {
465 ?>
466 <script type="text/javascript">
467 jQuery(function($) {
468 'use strict';
469
470 const storagePrefix = 'ultp_notice_countdown_';
471
472 const formatCountdown = function(seconds) {
473 const days = Math.floor(seconds / 86400);
474 const hours = Math.floor((seconds % 86400) / 3600);
475 const minutes = Math.floor((seconds % 3600) / 60);
476 const secs = seconds % 60;
477
478 return String(days).padStart(2, '0') + ':' + String(hours).padStart(2, '0') + ':' + String(minutes).padStart(2, '0') + ':' + String(secs).padStart(2, '0');
479 };
480
481 const parseDurationToSeconds = function(duration) {
482 if (typeof duration === 'number' && Number.isFinite(duration) && duration > 0) {
483 return Math.floor(duration);
484 }
485
486 const durationString = String(duration || '').trim();
487 if (/^\d+$/.test(durationString)) {
488 return parseInt(durationString, 10);
489 }
490
491 return 0;
492 };
493
494 const nowInSeconds = function() {
495 return Math.floor(Date.now() / 1000);
496 };
497
498 $('.ultp-notice-countdown').each(function() {
499 const countdownElement = $(this);
500 const noticeKey = String(countdownElement.data('noticeKey') || '');
501 const duration = parseDurationToSeconds(countdownElement.data('duration'));
502
503 if (!noticeKey || duration <= 0) {
504 return;
505 }
506
507 const storageKey = storagePrefix + noticeKey;
508 let endAt = 0;
509
510 try {
511 const storedDataRaw = window.localStorage.getItem(storageKey);
512 if (storedDataRaw) {
513 const storedData = JSON.parse(storedDataRaw);
514 if (storedData && parseInt(storedData.duration, 10) === duration) {
515 endAt = parseInt(storedData.endAt, 10) || 0;
516 }
517 }
518 } catch (error) {
519 endAt = 0;
520 }
521
522 const saveTimerState = function(nextEndAt) {
523 try {
524 window.localStorage.setItem(
525 storageKey,
526 JSON.stringify({
527 endAt: nextEndAt,
528 duration: duration,
529 })
530 );
531 } catch (error) {
532 // No-op.
533 }
534 };
535
536 const resetTimer = function(currentTime) {
537 endAt = currentTime + duration;
538 saveTimerState(endAt);
539 };
540
541 const tick = function() {
542 const currentTime = nowInSeconds();
543
544 if (endAt <= currentTime) {
545 resetTimer(currentTime);
546 }
547
548 const remaining = Math.max(endAt - currentTime, 0);
549 countdownElement.text(formatCountdown(remaining));
550 };
551
552 if (endAt <= nowInSeconds()) {
553 resetTimer(nowInSeconds());
554 }
555
556 tick();
557 window.setInterval(tick, 1000);
558 });
559 });
560 </script>
561 <?php
562 }
563
564
565 /**
566 * The Durbin Html
567 *
568 * @return void
569 */
570 public function ultp_dashboard_durbin_notice_callback() {
571 $durbin_key = 'ultp_durbin_dc1';
572
573 // Guideline 11 — the consent notice is allowed on PostX's own screens and,
574 // as is standard immediately after activation, on plugins.php. Nowhere else.
575 global $pagenow;
576 if ( ! $this->is_postx_screen() && 'plugins.php' !== $pagenow ) {
577 return;
578 }
579
580 if (
581 isset( $_GET['ultp_durbin_key'] ) || // phpcs:ignore
582 'off' === Xpo::get_transient_without_cache( 'ultp_durbin_notice_' . $durbin_key )
583 ) {
584 return;
585 }
586
587 if ( ! $this->notice_js_css_applied ) {
588 $this->notice_js_css_applied = true;
589 }
590
591 $ultp_db_nonce = wp_create_nonce( 'ultp-admin-nonce' );
592
593 ?>
594 <style>
595 .ultp-consent-box {
596 width: 656px;
597 padding: 16px !important;
598 border: 1px solid #070707;
599 border-left-width: 4px;
600 border-radius: 4px;
601 background-color: #fff;
602 position: relative;
603 width: 100%;
604 box-sizing: border-box;
605 }
606 .ultp-consent-content {
607 display: flex;
608 justify-content: flex-start;
609 align-items: flex-end;
610 gap: 26px;
611 }
612
613 .ultp-consent-text-first {
614 font-size: 14px;
615 font-weight: 600;
616 color: #070707;
617 }
618 .ultp-consent-text-last {
619 margin: 4px 0 0;
620 font-size: 14px;
621 color: #070707;
622 }
623
624 .ultp-consent-accept {
625 background-color: #070707;
626 color: #fff !important;
627 border: none;
628 padding: 6px 10px;
629 border-radius: 4px;
630 cursor: pointer;
631 font-size: 12px;
632 font-weight: 600;
633 text-decoration: none;
634 }
635 .ultp-consent-accept:hover {
636 background-color:rgb(38, 38, 38);
637 color: #fff;
638 }
639 </style>
640 <div class="ultp-consent-box ultp-notice-wrapper notice data_collection_notice">
641 <div class="ultp-consent-content">
642 <div class="ultp-consent-text">
643 <div class="ultp-consent-text-first"><?php esc_html_e( 'Want to help make PostX even more awesome?', 'ultimate-post' ); ?></div>
644 <div class="ultp-consent-text-last">
645 <?php esc_html_e( 'Allow us to collect diagnostic data and usage information. see ', 'ultimate-post' ); ?>
646 <a href="https://www.wpxpo.com/data-collection-policy/" target="_blank" ><?php esc_html_e( 'what we collect.', 'ultimate-post' ); ?></a>
647 </div>
648 </div>
649 <a
650 class="ultp-consent-accept"
651 href=
652 <?php
653 echo esc_url(
654 add_query_arg(
655 array(
656 'ultp_durbin_key' => $durbin_key,
657 'ultp_get_durbin' => 'get',
658 'wpnonce' => $ultp_db_nonce,
659 )
660 )
661 );
662 ?>
663 class="ultp-notice-close"
664 ><?php esc_html_e( 'Accept & Close', 'ultimate-post' ); ?></a>
665 </div>
666 <a href=
667 <?php
668 echo esc_url(
669 add_query_arg(
670 array(
671 'ultp_durbin_key' => $durbin_key,
672 'wpnonce' => $ultp_db_nonce,
673 )
674 )
675 );
676 ?>
677 class="ultp-notice-close"
678 style="
679 position: absolute;
680 right: 2px;
681 top: 5px;
682 text-decoration: unset;
683 color: #b6b6b6;
684 font-family: dashicons;
685 font-size: 16px;
686 font-style: normal;
687 font-weight: 400;
688 line-height: 20px;
689 "
690 >
691 <span
692 style="font-size: 14px;"
693 class="ultp-notice-close-icon dashicons dashicons-dismiss"> </span></a>
694 </div>
695 <?php
696 }
697 /**
698 * Dashboard Content Notice
699 *
700 * @return void
701 */
702 public function ultp_dashboard_content_notice( $return_bool = false ) {
703 $content_notices = array(
704 array(
705 'key' => 'ultp_dashboard_content_notice_summer_sale_2026_vv1',
706 'start' => '2026-07-06 00:00 Asia/Dhaka',
707 'end' => '2026-07-12 23:59 Asia/Dhaka',
708 'url' => Xpo::generate_utm_link(
709 array(
710 'utmKey' => 'summer_db',
711 )
712 ),
713 'visibility' => ! Xpo::is_lc_active(),
714 'content_heading' => __( 'Summer Sale:', 'ultimate-post' ),
715 /* translators: %s: discount percentage */
716 'content_subheading' => __( 'Enjoy up to %s on PostX Pro.', 'ultimate-post' ),
717 'discount_content' => ' 50% OFF',
718 'border_color' => '#0322ff',
719 'icon' => ULTP_URL . 'assets/img/dashboard_banner/brand_logo.svg',
720 'button_text' => __( 'Upgrade Now!', 'ultimate-post' ),
721 'is_discount_logo' => true,
722 ),
723 array(
724 'key' => 'ultp_dashboard_content_notice_summer_sale_2026_vv3',
725 'start' => '2026-08-02 00:00 Asia/Dhaka',
726 'end' => '2026-08-08 23:59 Asia/Dhaka',
727 'url' => Xpo::generate_utm_link(
728 array(
729 'utmKey' => 'summer_db',
730 )
731 ),
732 'visibility' => ! Xpo::is_lc_active(),
733 'content_heading' => __( 'Summer Sale:', 'ultimate-post' ),
734 /* translators: %s: discount percentage */
735 'content_subheading' => __( 'Enjoy up to %s on PostX Pro.', 'ultimate-post' ),
736 'discount_content' => ' 50% OFF',
737 'border_color' => '#0322ff',
738 'icon' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale_50_discount_logo.png',
739 'button_text' => __( 'Upgrade Now!', 'ultimate-post' ),
740 'is_discount_logo' => true,
741 ),
742
743 );
744
745 $ultp_db_nonce = wp_create_nonce( 'ultp-admin-nonce' );
746
747 foreach ( $content_notices as $key => $notice ) {
748 $notice_key = isset( $notice['key'] ) ? $notice['key'] : $this->notice_version;
749 if ( isset( $_GET['ultp_notice'] ) && $notice_key === sanitize_text_field( wp_unslash( $_GET['ultp_notice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only; hides the notice on this page view.
750 continue;
751 } else {
752 $border_color = $notice['border_color'];
753
754 $current_time = gmdate( 'U' );
755 $notice_start = gmdate( 'U', strtotime( $notice['start'] ) );
756 $notice_end = gmdate( 'U', strtotime( $notice['end'] ) );
757 if ( $current_time >= $notice_start && $current_time <= $notice_end && $notice['visibility'] ) {
758 $notice_transient = Xpo::get_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key );
759
760 if ( 'off' !== $notice_transient ) {
761 if ( $return_bool ) { // Early return for Other plugin notice.
762 return true;
763 }
764 $query_args = array(
765 'ultp_notice' => $notice_key,
766 'wpnonce' => $ultp_db_nonce,
767 );
768 if ( isset( $notice['repeat_interval'] ) && $notice['repeat_interval'] ) {
769 $query_args['ultp_interval'] = $notice['repeat_interval'];
770 }
771
772 $url = isset( $notice['url'] ) ? $notice['url'] : Xpo::generate_utm_link(
773 array(
774 'utmKey' => 'content_notice',
775 )
776 );
777
778 ?>
779
780 <style id="ultp-notice-css" type="text/css">
781 .ultp-content-notice-wrapper {
782 border: 1px solid #c3c4c7;
783 border-left: 3px solid #037fff;
784 margin: 15px 0 !important;
785 display: flex;
786 align-items: center;
787 background-color: #eef0f4;
788 width: 100%;
789 padding: 10px 0;
790 position: relative;
791 box-sizing: border-box;
792 }
793
794 .ultp-content-notice-wrapper.notice {
795 margin: 10px 0;
796 width: calc(100% - 20px);
797 }
798
799 .wrap .ultp-content-notice-wrapper.notice {
800 width: 100%;
801 }
802
803 .ultp-content-notice-icon {
804 margin-left: 15px;
805 }
806
807 .ultp-content-notice-discout-icon {
808 margin-left: 10px;
809 }
810
811 .ultp-content-notice-icon img {
812 max-width: 42px;
813 height: 70px;
814 }
815
816 .ultp-content-notice-discout-icon img {
817 height: 70px;
818 width: 70px;
819 object-fit: contain;
820 }
821
822 .ultp-notice-content-wrapper {
823 display: flex;
824 flex-direction: column;
825 gap: 8px;
826 font-size: 14px;
827 line-height: 20px;
828 margin-left: 15px;
829 }
830
831 .ultp-content-notice-buttons {
832 display: flex;
833 align-items: center;
834 gap: 15px;
835 }
836
837 .ultp-content-notice-btn {
838 font-weight: 600;
839 text-transform: uppercase !important;
840 padding: 2px 10px !important;
841 background-color: #0322ff;
842 border: none !important;
843 }
844
845 .ultp-content-discount_btn {
846 background-color: #ffffff;
847 text-decoration: none;
848 border: 1px solid #0322ff;
849 padding: 5px 10px;
850 border-radius: 5px;
851 font-weight: 500;
852 text-transform: uppercase;
853 color: #0322ff !important;
854 }
855
856 .ultp-content-notice-close {
857 position: absolute;
858 right: 2px;
859 top: 5px;
860 text-decoration: none;
861 color: #b6b6b6;
862 font-family: dashicons;
863 font-size: 16px;
864 line-height: 20px;
865 }
866
867 .ultp-content-notice-close-icon {
868 font-size: 14px;
869 }
870 </style>
871 <div class="ultp-content-notice-wrapper notice data_collection_notice"
872 style="border-left: 3px solid <?php echo esc_attr( $border_color ); ?>;"
873 >
874 <?php
875 if ( $notice['is_discount_logo'] ) {
876 ?>
877 <div class="ultp-content-notice-discout-icon"> <img src="<?php echo esc_url( $notice['icon'] ); ?>"/> </div>
878 <?php
879 } else {
880 ?>
881 <div class="ultp-content-notice-icon"> <img src="<?php echo esc_url( $notice['icon'] ); ?>"/> </div>
882 <?php
883 }
884 ?>
885
886 <div class="ultp-notice-content-wrapper">
887 <div class="">
888 <strong><?php printf( esc_html( $notice['content_heading'] ) ); ?> </strong>
889 <?php
890 printf(
891 wp_kses_post( $notice['content_subheading'] ),
892 '<strong>' . esc_html( $notice['discount_content'] ) . '</strong>'
893 );
894 ?>
895 </div>
896 <div class="ultp-content-notice-buttons">
897 <?php if ( isset( $notice['is_discount_logo'] ) && $notice['is_discount_logo'] ) : ?>
898 <a class="ultp-content-discount_btn" href="<?php echo esc_url( $url ); ?>" target="_blank">
899 <?php echo esc_html( $notice['button_text'] ); ?>
900 </a>
901 <?php else : ?>
902 <a class="ultp-content-notice-btn button button-primary" href="<?php echo esc_url( $url ); ?>" target="_blank" style="background-color: <?php echo ! empty( $notice['background_color'] ) ? esc_attr( $notice['background_color'] ) : '#86a62c'; ?>;">
903 <?php echo esc_html( $notice['button_text'] ); ?>
904
905 </a>
906 <?php endif; ?>
907 </div>
908 </div>
909 <a href=
910 <?php
911 echo esc_url(
912 add_query_arg(
913 $query_args
914 )
915 );
916 ?>
917 class="ultp-content-notice-close"><span class="ultp-content-notice-close-icon dashicons dashicons-dismiss"> </span></a>
918 </div>
919 <?php
920 }
921 }
922 }
923 }
924 }
925
926 /**
927 * Handle Plugin Notice for all plugins
928 *
929 * @param array $active_lists Lists of all active plugin notice.
930 * @return array
931 */
932 public function handle_xpo_active_notice_lists( $active_lists ) {
933 if ( $this->ultp_dashboard_banner_notice( true ) || $this->ultp_dashboard_content_notice( true ) || $this->ultp_dashboard_image_banner_notice( true ) ) {
934 $active_lists[ $this->plugin_notice_priority_key ] = $this->plugin_notice_priority;
935 }
936 return $active_lists;
937 }
938
939 /**
940 * Handle Plugin Notice for all plugins
941 *
942 * @return bool
943 */
944 /**
945 * Whether the current admin screen belongs to PostX.
946 *
947 * Guideline 11 limits promotional UI to the plugin's own screens. Every PostX
948 * screen carries `ultp` in its screen id — `toplevel_page_ultp-settings`,
949 * `postx_page_ultp-setup-wizard`, and the `ultp_builder` / `ultp_templates` /
950 * `ultp_custom_font` post-type screens.
951 *
952 * @return BOOL True when the current screen is a PostX screen.
953 */
954 public function is_postx_screen() {
955 if ( ! function_exists( 'get_current_screen' ) ) {
956 return false;
957 }
958 $screen = get_current_screen();
959 return $screen && false !== strpos( $screen->id, 'ultp' );
960 }
961
962 public function is_available_for_notice() {
963 // Guideline 11 — sale banners and upgrade prompts belong on PostX's own
964 // screens, not on core screens or other plugins' pages.
965 if ( ! $this->is_postx_screen() ) {
966 return false;
967 }
968
969 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Shared across WPXPO plugins (ProductX, WholesaleX, …); renaming breaks them.
970 $active_notices = apply_filters( 'xpo_active_notice_lists', array() );
971 if ( empty( $active_notices ) ) {
972 return true;
973 }
974 asort( $active_notices );
975 return array_key_first( $active_notices ) === $this->plugin_notice_priority_key;
976 }
977
978 public function ultp_dashboard_image_banner_notice( $return_bool = false ) {
979 $ultp_db_nonce = wp_create_nonce( 'ultp-admin-nonce' );
980 $banner_notices = array(
981 array(
982 'key' => 'ultp_summer_sale_2612',
983 'start' => '2026-07-20 00:00 Asia/Dhaka', // format YY-MM-DD always set time 00:00 and zone Asia/Dhaka
984 'end' => '2026-08-01 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka
985 'banner_src' => ULTP_URL . 'assets/img/dashboard_banner/dashboard_banner_summer_sale.png',
986 'url' => Xpo::generate_utm_link(
987 array(
988 'utmKey' => 'sub_menu_offer',
989 )
990 ),
991 'close_color' => '#000000',
992 'visibility' => ! Xpo::is_lc_active(),
993 ),
994 array(
995 'key' => 'ultp_summer_sale_2613',
996 'start' => '2026-08-09 00:00 Asia/Dhaka', // format YY-MM-DD always set time 00:00 and zone Asia/Dhaka
997 'end' => '2026-08-16 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka
998 'banner_src' => ULTP_URL . 'assets/img/dashboard_banner/dashboard_banner_summer_sale.png',
999 'url' => Xpo::generate_utm_link(
1000 array(
1001 'utmKey' => 'sub_menu_offer',
1002 )
1003 ),
1004 'close_color' => '#000000',
1005 'visibility' => ! Xpo::is_lc_active(),
1006 ),
1007 array(
1008 'key' => 'ultp_preco_sale_campaign_262_1',
1009 'start' => '2026-08-23 00:00 Asia/Dhaka', // format YY-MM-DD always set time 00:00 and zone Asia/Dhaka
1010 'end' => '2026-08-29 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka
1011 'banner_src' => ULTP_URL . 'assets/img/dashboard_banner/preco_banner_insider_deal.png',
1012 'url' => 'https://www.wpxpo.com/product/wowrecommend/?utm_source=db-postx-notice&utm_medium=insider-deal&utm_campaign=postx-dashboard/#pricing',
1013 'close_color' => '#ffffff',
1014 'visibility' => ! Xpo::is_lc_active(),
1015 ),
1016 array(
1017 'key' => 'ultp_preco_sale_campaign_262_2',
1018 'start' => '2026-08-30 00:00 Asia/Dhaka', // format YY-MM-DD always set time 00:00 and zone Asia/Dhaka.
1019 'end' => '2026-09-19 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
1020 'banner_src' => ULTP_URL . 'assets/img/dashboard_banner/preco_banner_early_bird.png',
1021 'url' => 'https://www.wpxpo.com/product/wowrecommend/?utm_source=db-postx-notice&utm_medium=early-bird&utm_campaign=postx-dashboard/#pricing',
1022 'close_color' => '#ffffff',
1023 'visibility' => ! Xpo::is_lc_active(),
1024 ),
1025 );
1026
1027 foreach ( $banner_notices as $notice ) {
1028 $notice_key = isset( $notice['key'] ) ? $notice['key'] : $this->notice_version;
1029 if ( isset( $_GET['ultp_notice'] ) && $notice_key === sanitize_text_field(wp_unslash($_GET['ultp_notice'])) ) { // phpcs:ignore
1030 continue;
1031 }
1032
1033 $current_time = gmdate( 'U' );
1034 $notice_start = gmdate( 'U', strtotime( $notice['start'] ) );
1035 $notice_end = gmdate( 'U', strtotime( $notice['end'] ) );
1036 if ( $current_time >= $notice_start && $current_time <= $notice_end && $notice['visibility'] ) {
1037
1038 $notice_transient = Xpo::get_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key );
1039
1040 if ( 'off' === $notice_transient ) {
1041 continue;
1042 }
1043
1044 if ( $return_bool ) { // Early return for Other plugin notice.
1045 return true;
1046 }
1047
1048 if ( ! $this->notice_js_css_applied ) {
1049 $this->ultp_banner_notice_js();
1050 $this->notice_js_css_applied = true;
1051 }
1052 $query_args = array(
1053 'ultp_notice' => $notice_key,
1054 'wpnonce' => $ultp_db_nonce,
1055 );
1056 if ( isset( $notice['repeat_interval'] ) && $notice['repeat_interval'] ) {
1057 $query_args['ultp_interval'] = $notice['repeat_interval'];
1058 }
1059 ?>
1060 <style type="text/css">
1061 .ultp-notice-wrapper.ultp-image-notice-wrapper {
1062 padding: 0 !important;
1063 position: relative;
1064 box-sizing: border-box;
1065 overflow: hidden;
1066 border-radius: 0px;
1067 border: none !important;
1068 }
1069 .ultp-notice-wrapper.ultp-image-notice-wrapper .ultp-image-banner {
1070 position: relative;
1071 line-height: 0;
1072 }
1073 .ultp-notice-wrapper.ultp-image-notice-wrapper .ultp-btn-image {
1074 display: block;
1075 }
1076 .ultp-notice-wrapper.ultp-image-notice-wrapper .ultp-btn-image img {
1077 display: block;
1078 width: 100%;
1079 height: auto;
1080 border-radius: 0;
1081 }
1082 .ultp-notice-wrapper.ultp-image-notice-wrapper .ultp-content-notice-close {
1083 top: 4px;
1084 right: 4px;
1085 position: absolute;
1086 z-index: 999;
1087 }
1088 @media screen and (max-width: 650px) {
1089 .ultp-image-notice-wrapper {
1090 display: none;
1091 }
1092 }
1093 </style>
1094 <div class="ultp-notice-wrapper ultp-image-notice-wrapper notice wc-install ultp-free-notice">
1095 <div class="wc-install-body ultp-image-banner">
1096 <a class="wc-dismiss-notice ultp-content-notice-close" href="
1097 <?php
1098 echo esc_url(
1099 add_query_arg(
1100 $query_args
1101 )
1102 );
1103 ?>
1104 "><span class="ultp-content-notice-close-icon dashicons dashicons-dismiss" style="color: <?php echo esc_attr( $notice['close_color'] ); ?>;"> </span></a>
1105 <a class="ultp-btn-image" target="_blank" href="<?php echo esc_url( $notice['url'] ); ?>">
1106 <img loading="lazy" src="<?php echo esc_url( $notice['banner_src'] ); ?>" alt="Discount Banner"/>
1107 </a>
1108 </div>
1109 </div>
1110
1111 <?php
1112 }
1113 }
1114 }
1115 }
1116