PluginProbe
Post Grid Gutenberg Blocks – PostX / 5.1.1
Post Grid Gutenberg Blocks – PostX v5.1.1
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
← All changes | includes/notice/class-notice.php +239 -891 5.0.19 → 5.1.1 View file →
@@ -3,10 +3,18 @@
3 3
4 4 defined( 'ABSPATH' ) || exit;
5 5
6 6 use ULTP\Includes\Durbin\Xpo;
7 -use ULTP\Includes\Durbin\DurbinClient;
8 7
8 +require_once __DIR__ . '/interface-notice-type.php';
9 +require_once __DIR__ . '/class-notice-assets.php';
10 +require_once __DIR__ . '/class-abstract-campaign-notice.php';
11 +require_once __DIR__ . '/types/class-banner-notice.php';
12 +require_once __DIR__ . '/types/class-content-notice.php';
13 +require_once __DIR__ . '/types/class-image-notice.php';
14 +require_once __DIR__ . '/types/class-consent-notice.php';
15 +require_once __DIR__ . '/class-promo-links.php';
16 +
9 17 /**
10 18 * Plugin Notice
11 19 */
12 20 class Notice {
@@ -12,34 +20,50 @@
12 20 class Notice {
13 21
14 22
15 23 /**
16 - * Notice version
24 + * Campaigns showing on this request. Null until first worked out.
17 25 *
18 - * @var string
26 + * @var Abstract_Campaign_Notice[]|null
19 27 */
20 - private $notice_version = 'v5132';
28 + private $active_campaigns = null;
21 29
22 30 /**
23 - * Notice JS/CSS applied
31 + * Data-collection consent notice. Null until first needed.
24 32 *
25 - * @var boolean
33 + * @var Consent_Notice|null
26 34 */
27 - private $notice_js_css_applied = false;
35 + private $consent_notice = null;
28 36
37 + /**
38 + * Notice Priority
39 + *
40 + * @var string $plugin_notice_priority
41 + */
42 + private $plugin_notice_priority = 1;
29 43
30 44 /**
45 + * Notice Priority
46 + *
47 + * @var string $plugin_notice_priority
48 + */
49 + private $plugin_notice_priority_key = 'postx';
50 +
51 + /**
31 52 * Notice Constructor
32 53 */
33 54 public function __construct() {
34 55 add_action( 'admin_notices', array( $this, 'admin_notices_callback' ) );
35 56 add_action( 'admin_init', array( $this, 'set_dismiss_notice_callback' ) );
57 + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
36 58
37 59 // REST API routes.
38 60 add_action( 'rest_api_init', array( $this, 'register_rest_route' ) );
39 61
40 - // Woocommerce Install Action
41 - // 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
62 + add_filter( 'xpo_active_notice_lists', array( $this, 'handle_xpo_active_notice_lists' ), 99, 1 );
63 +
64 + // Upgrade link on the Plugins page and upgrade item in the PostX menu.
65 + new Promo_Links();
42 66 }
43 67
44 68 /**
45 69 * Registers REST API endpoints.
@@ -55,8 +79,20 @@
55 79 'callback' => array( $this, 'hello_bar_callback' ),
56 80 'permission_callback' => function () {
57 81 return current_user_can( 'manage_options' );
58 82 },
83 + 'args' => array(
84 + 'type' => array(
85 + 'type' => 'string',
86 + 'required' => true,
87 + 'enum' => array( 'hello_bar' ),
88 + ),
89 + 'id' => array(
90 + 'type' => 'string',
91 + 'required' => true,
92 + 'enum' => self::get_hellobar_keys(),
93 + ),
94 + ),
59 95 ),
60 96 );
61 97
62 98 foreach ( $routes as $route ) {
@@ -67,8 +103,9 @@
67 103 array(
68 104 'methods' => $route['methods'],
69 105 'callback' => $route['callback'],
70 106 'permission_callback' => $route['permission_callback'],
107 + 'args' => isset( $route['args'] ) ? $route['args'] : array(),
71 108 ),
72 109 )
73 110 );
74 111 }
@@ -79,24 +116,34 @@
79 116 *
80 117 * @return array
81 118 */
82 119 public static function get_hellobar_config() {
83 - return array(
84 - 'ultp_helloBar_spring_sale_2026_1' => Xpo::get_transient_without_cache( 'ultp_helloBar_spring_sale_2026_1' ),
85 - 'ultp_helloBar_spring_sale_2026_2' => Xpo::get_transient_without_cache( 'ultp_helloBar_spring_sale_2026_2' ),
86 - 'ultp_helloBar_spring_sale_2026_3' => Xpo::get_transient_without_cache( 'ultp_helloBar_spring_sale_2026_3' ),
87 - // Flash sale
88 - 'ultp_helloBar_flash_sale_2026_4' => Xpo::get_transient_without_cache( 'ultp_helloBar_flash_sale_2026_4' ),
89 - // Surprise sale
90 - 'ultp_helloBar_surprise_sale_2026_5' => Xpo::get_transient_without_cache( 'ultp_helloBar_surprise_sale_2026_5' ),
91 - // Massive sale
92 - 'ultp_helloBar_massive_sale_2026_6' => Xpo::get_transient_without_cache( 'ultp_helloBar_massive_sale_2026_6' ),
93 - // Final hours sale
94 - 'ultp_helloBar_final_hours_sale_2026_7' => Xpo::get_transient_without_cache( 'ultp_helloBar_final_hours_sale_2026_7' ),
95 - );
120 + $config = array();
121 + foreach ( self::get_hellobar_keys() as $key ) {
122 + $config[ $key ] = Xpo::get_transient_without_cache( $key );
123 + }
124 + return $config;
96 125 }
97 126
98 127 /**
128 + * Hello bar ids the dashboard may dismiss, from Campaigns::hellobar_keys().
129 + *
130 + * class-campaigns.php is edited for every sale, so it is loaded inside a try: a mistake in it
131 + * empties this list (hello bars can't be closed until it's fixed) instead of breaking wp-admin.
132 + *
133 + * @return string[]
134 + */
135 + private static function get_hellobar_keys() {
136 + try {
137 + require_once __DIR__ . '/class-campaigns.php';
138 + $keys = Campaigns::hellobar_keys();
139 + } catch ( \Throwable $e ) {
140 + return array();
141 + }
142 + return is_array( $keys ) ? array_values( array_filter( $keys, 'is_string' ) ) : array();
143 + }
144 +
145 + /**
99 146 * Handles Hello Bar dismissal action via REST API .
100 147 *
101 148 * @param \WP_REST_Request $request REST request object .
102 149 * @return \WP_REST_Response
@@ -101,13 +148,13 @@
101 148 * @param \WP_REST_Request $request REST request object .
102 149 * @return \WP_REST_Response
103 150 */
104 151 public function hello_bar_callback( \WP_REST_Request $request ) {
105 - $request_params = $request->get_params();
106 - $type = isset( $request_params['type'] ) ? $request_params['type'] : '';
107 - $id = isset( $request_params['id'] ) ? $request_params['id'] : '';
152 + $type = $request->get_param( 'type' );
153 + $id = $request->get_param( 'id' );
108 154
109 - if ( 'hello_bar' === $type && ! empty( $id ) ) {
155 + // The route's args already reject unknown ids; check again in case this is called directly.
156 + if ( 'hello_bar' === $type && is_string( $id ) && in_array( $id, self::get_hellobar_keys(), true ) ) {
110 157 Xpo::set_transient_without_cache( $id, 'hide', 1296000 );
111 158 }
112 159
113 160 return new \WP_REST_Response(
@@ -129,19 +176,11 @@
129 176 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['wpnonce'] ?? '' ) ), 'ultp-nonce' ) ) {
130 177 return;
131 178 }
132 179
133 - $durbin_key = sanitize_text_field( wp_unslash( $_GET['ultp_durbin_key'] ?? '' ) );
134 -
135 180 // Durbin notice dismiss.
136 - if ( ! empty( $durbin_key ) ) {
137 - Xpo::set_transient_without_cache( 'ultp_durbin_notice_' . $durbin_key, 'off' );
181 + Consent_Notice::handle_dismiss_request();
138 182
139 - if ( 'get' === sanitize_text_field( wp_unslash( $_GET['ultp_get_durbin'] ?? '' ) ) ) {
140 - DurbinClient::send( DurbinClient::ACTIVATE_ACTION );
141 - }
142 - }
143 -
144 183 // Install notice dismiss.
145 184 $install_key = sanitize_text_field( wp_unslash( $_GET['ultp_install_key'] ?? '' ) );
146 185 if ( ! empty( $install_key ) ) {
147 186 Xpo::set_transient_without_cache( 'ultp_install_notice_' . $install_key, 'off' );
@@ -172,400 +211,56 @@
172 211 *
173 212 * @return void
174 213 */
175 214 public function ultp_dashboard_notice_callback() {
176 - $this->ultp_dashboard_banner_notice();
177 - $this->ultp_dashboard_content_notice();
215 + if ( $this->is_available_for_notice() ) {
216 + $this->ultp_dashboard_banner_notice();
217 + $this->ultp_dashboard_content_notice();
218 + $this->ultp_dashboard_image_banner_notice();
219 +
220 + }
178 221 }
179 222
180 223 /**
181 224 * Dashboard Banner Notice
182 225 *
226 + * @param bool $return_bool True to only report whether a banner would show.
227 + * @return bool|void
228 + */
229 + public function ultp_dashboard_banner_notice( $return_bool = false ) {
230 + return $this->render_campaigns( Banner_Notice::class, $return_bool );
231 + }
232 +
233 + /**
234 + * Queue the stylesheets and scripts for the notices that will show on this page.
235 + *
236 + * Runs on admin_enqueue_scripts, before admin_notices, so the styles load in <head>.
237 + *
183 238 * @return void
184 239 */
185 - public function ultp_dashboard_banner_notice() {
186 - $ultp_db_nonce = wp_create_nonce( 'ultp-nonce' );
187 - $banner_notices = array(
188 - array(
189 - 'key' => 'ultp_flash_sale_2026_1',
190 - 'start' => '2026-02-19 00:00 Asia/Dhaka',
191 - 'end' => '2026-02-23 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
192 - 'countdown_duration' => 259200, // Duration in seconds.
193 - 'brand_color' => '#0322ff',
194 -
195 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/2026_flash_sale_logo.png',
196 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/2026_final_hours_and_flash_sale_right_btn.png',
197 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/2026_spring_sale_bg.png',
198 - 'text' => 'Hurry Before It Ends!',
199 - 'countdown_color' => '#3CF357',
200 - 'url' => Xpo::generate_utm_link(
201 - array(
202 - 'utmKey' => 'flash_sale',
203 - )
204 - ),
205 -
206 - 'visibility' => ! Xpo::is_lc_active(),
207 - ),
208 - array(
209 - 'key' => 'ultp_final_hour_sale_2026_1',
210 - 'start' => '2026-02-25 00:00 Asia/Dhaka',
211 - 'end' => '2026-03-01 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
212 - 'brand_color' => '#0322ff',
213 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/2026_final_hours_sale_logo.png',
214 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/2026_final_hours_and_flash_sale_right_btn.png',
215 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/2026_final_hours_and_flash_sale_bg.png',
216 - 'text' => 'Hurry Before It Ends!',
217 - 'countdown_duration' => 172800, // Duration in seconds.
218 - 'countdown_color' => '#0322ff',
219 - 'url' => Xpo::generate_utm_link(
220 - array(
221 - 'utmKey' => 'final_hour',
222 - )
223 - ),
224 -
225 - 'visibility' => ! Xpo::is_lc_active(),
226 - ),
227 - array(
228 - 'key' => 'ultp_banner_spring_sale_2026_1',
229 - 'start' => '2026-04-05 00:00 Asia/Dhaka',
230 - 'end' => '2026-04-14 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
231 -
232 - 'brand_color' => '#0322ff',
233 -
234 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/spring_sale/2026_spring_sale_offer.png',
235 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/spring_sale/2026_spring_sale_button.png',
236 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/spring_sale/2026_spring_sale_bg.png',
237 - 'text' => 'Hurry Before It Ends!',
238 - 'countdown_duration' => 259200, // Duration in seconds.
239 - // 'countdown_color' => '#0322ff',
240 - 'countdown_color' => '#000',
241 - 'url' => Xpo::generate_utm_link(
242 - array(
243 - 'utmKey' => 'spring_sale',
244 - )
245 - ),
246 -
247 - 'visibility' => ! Xpo::is_lc_active(),
248 - ),
249 - // FLash sale
250 - array(
251 - 'key' => 'ultp_banner_flash_sale_2026_1',
252 - 'start' => '2026-05-18 00:00 Asia/Dhaka',
253 - 'end' => '2026-05-21 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
254 -
255 - 'brand_color' => '#0322ff',
256 -
257 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale/offer.png',
258 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale/btn.png',
259 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale/bg.png',
260 - 'text' => 'Deal ending soon',
261 - 'countdown_duration' => 259200, // Duration in seconds.
262 - // 'countdown_color' => '#0322ff',
263 - 'countdown_color' => '#3CF357',
264 - 'url' => Xpo::generate_utm_link(
265 - array(
266 - 'utmKey' => 'flash_sale',
267 - )
268 - ),
269 -
270 - 'visibility' => ! Xpo::is_lc_active(),
271 - ),
272 - // surprise sale
273 - array(
274 - 'key' => 'ultp_banner_surprise_sale_2026_1',
275 - 'start' => '2026-05-29 00:00 Asia/Dhaka',
276 - 'end' => '2026-06-01 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
277 -
278 - 'brand_color' => '#0322ff',
279 -
280 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/surprise_sale/offer.png',
281 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/surprise_sale/btn.png',
282 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/surprise_sale/bg.png',
283 - 'text' => 'Limited Time Offer',
284 - 'countdown_duration' => 259200, // Duration in seconds.
285 - // 'countdown_color' => '#0322ff',
286 - 'countdown_color' => '#3CF357',
287 - 'url' => Xpo::generate_utm_link(
288 - array(
289 - 'utmKey' => 'surprise_sale',
290 - )
291 - ),
292 -
293 - 'visibility' => ! Xpo::is_lc_active(),
294 - ),
295 - // massive sale
296 - array(
297 - 'key' => 'ultp_banner_massive_sale_2026_1',
298 - 'start' => '2026-06-17 00:00 Asia/Dhaka',
299 - 'end' => '2026-06-20 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
300 -
301 - 'brand_color' => '#0322ff',
302 -
303 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/massive_sale/offer.png',
304 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/massive_sale/btn.png',
305 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/massive_sale/bg.png',
306 - 'text' => 'Deal ending soon',
307 - 'countdown_duration' => 259200, // Duration in seconds.
308 - // 'countdown_color' => '#0322ff',
309 - 'countdown_color' => '#3CF357',
310 - 'url' => Xpo::generate_utm_link(
311 - array(
312 - 'utmKey' => 'massive_sale',
313 - )
314 - ),
315 -
316 - 'visibility' => ! Xpo::is_lc_active(),
317 - ),
318 - // Final hours sale
319 - array(
320 - 'key' => 'ultp_banner_final_hours_sale_2026_1',
321 - 'start' => '2026-06-28 00:00 Asia/Dhaka',
322 - 'end' => '2026-06-30 23:59 Asia/Dhaka', // format YY-MM-DD always set time 23:59 and zone Asia/Dhaka.
323 -
324 - 'brand_color' => '#0322ff',
325 -
326 - 'left_image' => ULTP_URL . 'assets/img/dashboard_banner/final_hours_sale/offer.png',
327 - 'right_image' => ULTP_URL . 'assets/img/dashboard_banner/final_hours_sale/btn.png',
328 - 'bg_image' => ULTP_URL . 'assets/img/dashboard_banner/final_hours_sale/bg.png',
329 - 'text' => 'Hurry Before It Ends!',
330 - 'countdown_duration' => 259200, // Duration in seconds.
331 - // 'countdown_color' => '#0322ff',
332 - 'countdown_color' => '#3CF357',
333 - 'url' => Xpo::generate_utm_link(
334 - array(
335 - 'utmKey' => 'final_hours_sale',
336 - )
337 - ),
338 - 'visibility' => ! Xpo::is_lc_active(),
339 - ),
340 - );
341 - foreach ( $banner_notices as $notice ) {
342 - $notice_key = isset( $notice['key'] ) ? $notice['key'] : $this->notice_version;
343 -
344 - if ( isset( $_GET['ultp_notice'] ) && $notice_key === sanitize_text_field(wp_unslash($_GET['ultp_notice'])) ) { // phpcs:ignore
345 - continue;
240 + public function enqueue_assets() {
241 + if ( $this->is_available_for_notice() ) {
242 + foreach ( $this->get_active_campaigns() as $campaign ) {
243 + $campaign->enqueue_assets();
346 244 }
347 -
348 - $current_time = gmdate( 'U' );
349 - $notice_start = gmdate( 'U', strtotime( $notice['start'] ) );
350 - $notice_end = gmdate( 'U', strtotime( $notice['end'] ) );
351 - if ( $current_time >= $notice_start && $current_time <= $notice_end && $notice['visibility'] ) {
352 -
353 - $notice_transient = Xpo::get_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key );
354 -
355 - if ( 'off' === $notice_transient ) {
356 - continue;
357 - }
358 -
359 - if ( ! $this->notice_js_css_applied ) {
360 - $this->ultp_banner_notice_js();
361 - $this->notice_js_css_applied = true;
362 - }
363 - $query_args = array(
364 - 'ultp_notice' => $notice_key,
365 - 'wpnonce' => $ultp_db_nonce,
366 - );
367 - if ( isset( $notice['repeat_interval'] ) && $notice['repeat_interval'] ) {
368 - $query_args['ultp_interval'] = $notice['repeat_interval'];
369 - }
370 - ?>
371 - <style>
372 - @media only screen and (max-width: 1200px) {
373 - .ultp-notice-wrapper.notice .ultp-banner-logo-img {
374 - max-width: 230px;
375 - }
376 - .ultp-banner-grab-btn-img {
377 - max-width: 130px;
378 - }
379 - .ultp-notice-countdown {
380 - font-size: 18px;
381 - }
382 - .ultp-notice-banner-title {
383 - font-size: 20px;
384 - }
385 - }
386 - </style>
387 - <div
388 - class="ultp-notice-wrapper notice"
389 - style="
390 - height:90px;
391 - border-radius: 7px 0px 0px 7px;
392 - padding:0;
393 - position:relative;
394 - box-sizing: border-box;
395 - border-left: 3px solid <?php echo esc_attr( $notice['brand_color'] ); ?>;
396 - background-image: url('<?php echo esc_attr( $notice['bg_image'] ); ?>');
397 - ">
398 - <a
399 - class="wc-dismiss-notice dashicons dashicons-no-alt"
400 - style="
401 - position: absolute;
402 - top: 1px;
403 - right: 1px;
404 - border-radius: 50%;
405 - background-color: black;
406 - color: white;
407 - font-size: 14px;
408 - display: flex;
409 - align-items: center;
410 - justify-content: center;
411 - "
412 - aria-label="<?php esc_html_e( 'Close Banner', 'ultimate-post' ); ?>"
413 - href="<?php echo esc_url( add_query_arg( $query_args ) ); ?>">
414 - </a>
415 -
416 - <a style="width:100%;text-decoration: none;" target="_blank" href="<?php echo esc_url( $notice['url'] ); ?>">
417 - <div style="
418 - display: flex;
419 - justify-content: space-between;
420 - align-items: center;
421 - max-width: 1358px;
422 - margin: 0px auto;
423 - padding: 10px 15px;
424 - height: 90px;
425 - box-sizing: border-box;
426 -
427 - ">
428 - <img class="ultp-banner-logo-img" style="" loading="lazy" src="<?php echo esc_url( $notice['left_image'] ); ?>" />
429 - <div style="
430 - display: flex;
431 - flex-direction: column;
432 - gap: 4px;
433 - align-items: center;
434 - justify-content: center;
435 - font-weight: 700;
436 - font-size: 28px;
437 - color: #000;
438 - line-height: 32px;
439 - ">
440 - <span class="ultp-notice-banner-title evgn-titile-text-white" style="color: #fff;">
441 - <?php echo esc_html( $notice['text'] ); ?>
442 - </span>
443 - <div
444 - class="ultp-notice-countdown"
445 - style="
446 - color: <?php echo esc_attr( $notice['countdown_color'] ); ?>;
447 - "
448 - data-notice-key="<?php echo esc_attr( $notice_key . '-countdown' ); ?>"
449 - data-duration="<?php echo esc_attr( $notice['countdown_duration'] ); ?>">
450 - 00:00:00:00
451 - </div>
452 - </div>
453 - <img class="ultp-banner-grab-btn-img" style="" loading="lazy" src="<?php echo esc_url( $notice['right_image'] ); ?>" />
454 - </div>
455 - </a>
456 - </div>
457 - <?php
458 - }
459 245 }
246 + $consent = $this->get_consent_notice();
247 + if ( $consent->should_display() ) {
248 + $consent->enqueue_assets();
249 + }
460 250 }
461 251
462 252 /**
463 253 * Banner JS
464 254 *
255 + * @deprecated The countdown script is now the file assets/js/ultp-notice-countdown.js. This queues it.
256 + *
465 257 * @return void
466 258 */
467 259 public function ultp_banner_notice_js() {
468 - ?>
469 - <script type="text/javascript">
470 - jQuery(function($) {
471 - 'use strict';
472 -
473 - const storagePrefix = 'ultp_notice_countdown_';
474 -
475 - const formatCountdown = function(seconds) {
476 - const days = Math.floor(seconds / 86400);
477 - const hours = Math.floor((seconds % 86400) / 3600);
478 - const minutes = Math.floor((seconds % 3600) / 60);
479 - const secs = seconds % 60;
480 -
481 - return String(days).padStart(2, '0') + ':' + String(hours).padStart(2, '0') + ':' + String(minutes).padStart(2, '0') + ':' + String(secs).padStart(2, '0');
482 - };
483 -
484 - const parseDurationToSeconds = function(duration) {
485 - if (typeof duration === 'number' && Number.isFinite(duration) && duration > 0) {
486 - return Math.floor(duration);
487 - }
488 -
489 - const durationString = String(duration || '').trim();
490 - if (/^\d+$/.test(durationString)) {
491 - return parseInt(durationString, 10);
492 - }
493 -
494 - return 0;
495 - };
496 -
497 - const nowInSeconds = function() {
498 - return Math.floor(Date.now() / 1000);
499 - };
500 -
501 - $('.ultp-notice-countdown').each(function() {
502 - const countdownElement = $(this);
503 - const noticeKey = String(countdownElement.data('noticeKey') || '');
504 - const duration = parseDurationToSeconds(countdownElement.data('duration'));
505 -
506 - if (!noticeKey || duration <= 0) {
507 - return;
508 - }
509 -
510 - const storageKey = storagePrefix + noticeKey;
511 - let endAt = 0;
512 -
513 - try {
514 - const storedDataRaw = window.localStorage.getItem(storageKey);
515 - if (storedDataRaw) {
516 - const storedData = JSON.parse(storedDataRaw);
517 - if (storedData && parseInt(storedData.duration, 10) === duration) {
518 - endAt = parseInt(storedData.endAt, 10) || 0;
519 - }
520 - }
521 - } catch (error) {
522 - endAt = 0;
523 - }
524 -
525 - const saveTimerState = function(nextEndAt) {
526 - try {
527 - window.localStorage.setItem(
528 - storageKey,
529 - JSON.stringify({
530 - endAt: nextEndAt,
531 - duration: duration,
532 - })
533 - );
534 - } catch (error) {
535 - // No-op.
536 - }
537 - };
538 -
539 - const resetTimer = function(currentTime) {
540 - endAt = currentTime + duration;
541 - saveTimerState(endAt);
542 - };
543 -
544 - const tick = function() {
545 - const currentTime = nowInSeconds();
546 -
547 - if (endAt <= currentTime) {
548 - resetTimer(currentTime);
549 - }
550 -
551 - const remaining = Math.max(endAt - currentTime, 0);
552 - countdownElement.text(formatCountdown(remaining));
553 - };
554 -
555 - if (endAt <= nowInSeconds()) {
556 - resetTimer(nowInSeconds());
557 - }
558 -
559 - tick();
560 - window.setInterval(tick, 1000);
561 - });
562 - });
563 - </script>
564 - <?php
260 + Notice_Assets::enqueue_countdown_script();
565 261 }
566 262
567 -
568 263 /**
569 264 * The Durbin Html
570 265 *
571 266 * @return void
@@ -570,510 +265,163 @@
570 265 *
571 266 * @return void
572 267 */
573 268 public function ultp_dashboard_durbin_notice_callback() {
574 - $durbin_key = 'ultp_durbin_dc1';
575 -
576 - if (
577 - isset( $_GET['ultp_durbin_key'] ) || // phpcs:ignore
578 - 'off' === Xpo::get_transient_without_cache( 'ultp_durbin_notice_' . $durbin_key )
579 - ) {
580 - return;
269 + $consent = $this->get_consent_notice();
270 + if ( $consent->should_display() ) {
271 + $this->render_safely( $consent );
581 272 }
273 + }
582 274
583 - if ( ! $this->notice_js_css_applied ) {
584 - $this->notice_js_css_applied = true;
275 + /**
276 + * The data-collection consent notice, created once per request.
277 + *
278 + * @return Consent_Notice
279 + */
280 + private function get_consent_notice() {
281 + if ( null === $this->consent_notice ) {
282 + $this->consent_notice = new Consent_Notice();
585 283 }
586 -
587 - $ultp_db_nonce = wp_create_nonce( 'ultp-nonce' );
588 -
589 - ?>
590 - <style>
591 - .ultp-consent-box {
592 - width: 656px;
593 - padding: 16px !important;
594 - border: 1px solid #070707;
595 - border-left-width: 4px;
596 - border-radius: 4px;
597 - background-color: #fff;
598 - position: relative;
599 - width: 100%;
600 - box-sizing: border-box;
601 - }
602 - .ultp-consent-content {
603 - display: flex;
604 - justify-content: flex-start;
605 - align-items: flex-end;
606 - gap: 26px;
607 - }
608 -
609 - .ultp-consent-text-first {
610 - font-size: 14px;
611 - font-weight: 600;
612 - color: #070707;
613 - }
614 - .ultp-consent-text-last {
615 - margin: 4px 0 0;
616 - font-size: 14px;
617 - color: #070707;
618 - }
619 -
620 - .ultp-consent-accept {
621 - background-color: #070707;
622 - color: #fff;
623 - border: none;
624 - padding: 6px 10px;
625 - border-radius: 4px;
626 - cursor: pointer;
627 - font-size: 12px;
628 - font-weight: 600;
629 - text-decoration: none;
630 - }
631 - .ultp-consent-accept:hover {
632 - background-color:rgb(38, 38, 38);
633 - color: #fff;
634 - }
635 - </style>
636 - <div class="ultp-consent-box ultp-notice-wrapper notice data_collection_notice">
637 - <div class="ultp-consent-content">
638 - <div class="ultp-consent-text">
639 - <div class="ultp-consent-text-first"><?php esc_html_e( 'Want to help make PostX even more awesome?', 'ultimate-post' ); ?></div>
640 - <div class="ultp-consent-text-last">
641 - <?php esc_html_e( 'Allow us to collect diagnostic data and usage information. see ', 'ultimate-post' ); ?>
642 - <a href="https://www.wpxpo.com/data-collection-policy/" target="_blank" ><?php esc_html_e( 'what we collect.', 'ultimate-post' ); ?></a>
643 - </div>
644 - </div>
645 - <a
646 - class="ultp-consent-accept"
647 - href=
648 - <?php
649 - echo esc_url(
650 - add_query_arg(
651 - array(
652 - 'ultp_durbin_key' => $durbin_key,
653 - 'ultp_get_durbin' => 'get',
654 - 'wpnonce' => $ultp_db_nonce,
655 - )
656 - )
657 - );
658 - ?>
659 - class="ultp-notice-close"
660 - ><?php esc_html_e( 'Accept & Close', 'ultimate-post' ); ?></a>
661 - </div>
662 - <a href=
663 - <?php
664 - echo esc_url(
665 - add_query_arg(
666 - array(
667 - 'ultp_durbin_key' => $durbin_key,
668 - 'wpnonce' => $ultp_db_nonce,
669 - )
670 - )
671 - );
672 - ?>
673 - class="ultp-notice-close"
674 - style="
675 - position: absolute;
676 - right: 2px;
677 - top: 5px;
678 - text-decoration: unset;
679 - color: #b6b6b6;
680 - font-family: dashicons;
681 - font-size: 16px;
682 - font-style: normal;
683 - font-weight: 400;
684 - line-height: 20px;
685 - "
686 - >
687 - <span
688 - style="font-size: 14px;"
689 - class="ultp-notice-close-icon dashicons dashicons-dismiss"> </span></a>
690 - </div>
691 - <?php
284 + return $this->consent_notice;
692 285 }
693 286 /**
694 287 * Dashboard Content Notice
695 288 *
696 - * @return void
289 + * @param bool $return_bool True to only report whether a content notice would show.
290 + * @return bool|void
697 291 */
698 - public function ultp_dashboard_content_notice() {
699 - $content_notices = array(
700 - array(
701 - 'key' => 'ultp_dashboard_content_notice_spring_sale_v1',
702 - 'start' => '2026-03-16 00:00 Asia/Dhaka',
703 - 'end' => '2026-03-25 23:59 Asia/Dhaka',
704 - 'url' => Xpo::generate_utm_link(
705 - array(
706 - 'utmKey' => 'content_notice',
707 - )
708 - ),
709 - 'visibility' => ! Xpo::is_lc_active(),
710 - 'content_heading' => __( 'Spring Sale:', 'ultimate-post' ),
711 - 'content_subheading' => __( 'PostX offers are live - Enjoy %s off on PostX Pro.', 'ultimate-post' ),
712 - 'discount_content' => ' up to 50% OFF',
713 - 'brand_color' => '#0322ff',
714 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/spring_sale/2026_spring_sale_brand_logo.png',
715 - 'button_text' => __( 'Claim Your Discount!', 'ultimate-post' ),
716 - 'is_discount_logo' => true,
717 - 'border_color' => '#0322ff',
718 - ),
719 - array(
720 - 'key' => 'ultp_dashboard_content_notice_spring_sale_v2',
721 - 'start' => '2026-03-26 00:00 Asia/Dhaka',
722 - 'end' => '2026-04-04 23:59 Asia/Dhaka',
723 - 'url' => Xpo::generate_utm_link(
724 - array(
725 - 'utmKey' => 'content_notice',
726 - )
727 - ),
728 - 'visibility' => ! Xpo::is_lc_active(),
729 - 'content_heading' => __( 'Spring Sale:', 'ultimate-post' ),
730 - 'content_subheading' => __( 'PostX offers are live - Enjoy %s off on PostX Pro.', 'ultimate-post' ),
731 - 'discount_content' => ' up to 50% OFF',
732 - 'brand_color' => '#0322ff',
733 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/spring_sale/2026_spring_discount_logo.png',
734 - 'button_text' => __( 'Claim Your Discount!', 'ultimate-post' ),
735 - 'is_discount_logo' => true,
736 - 'border_color' => '#0322ff',
737 - ),
292 + public function ultp_dashboard_content_notice( $return_bool = false ) {
293 + return $this->render_campaigns( Content_Notice::class, $return_bool );
294 + }
738 295
739 - // Flash sale
740 - array(
741 - 'key' => 'ultp_dashboard_content_notice_flash_sale_brand_logo',
742 - 'start' => '2026-05-07 00:00 Asia/Dhaka',
743 - 'end' => '2026-05-12 23:59 Asia/Dhaka',
744 - 'url' => Xpo::generate_utm_link(
745 - array(
746 - 'utmKey' => 'flash_sale_content',
747 - )
748 - ),
749 - 'visibility' => ! Xpo::is_lc_active(),
750 - 'content_heading' => __( 'Flash Sale:', 'ultimate-post' ),
751 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
752 - 'discount_content' => ' up to 45% Off',
753 - 'brand_color' => '#0322ff',
754 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/brand_logo.svg',
755 - 'button_text' => __( 'Claim Your Discount!', 'ultimate-post' ),
756 - 'is_discount_logo' => true,
757 - 'border_color' => '#0322ff',
758 - ),
759 - array(
760 - 'key' => 'ultp_dashboard_content_notice_flash_sale_discount_logo',
761 - 'start' => '2026-05-13 00:00 Asia/Dhaka',
762 - 'end' => '2026-05-17 23:59 Asia/Dhaka',
763 - 'url' => Xpo::generate_utm_link(
764 - array(
765 - 'utmKey' => 'flash_sale_content',
766 - )
767 - ),
768 - 'visibility' => ! Xpo::is_lc_active(),
769 - 'content_heading' => __( 'Flash Sale:', 'ultimate-post' ),
770 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
771 - 'discount_content' => ' up to 45% Off',
772 - 'brand_color' => '#0322ff',
773 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale_45_discount_logo.png',
774 - 'button_text' => __( 'Upgrade Now!', 'ultimate-post' ),
775 - 'is_discount_logo' => true,
776 - 'border_color' => '#0322ff',
777 - ),
296 + /**
297 + * Handle Plugin Notice for all plugins
298 + *
299 + * @param array $active_lists Lists of all active plugin notice.
300 + * @return array
301 + */
302 + public function handle_xpo_active_notice_lists( $active_lists ) {
303 + if ( ! empty( $this->get_active_campaigns() ) ) {
304 + $active_lists[ $this->plugin_notice_priority_key ] = $this->plugin_notice_priority;
305 + }
306 + return $active_lists;
307 + }
778 308
779 - // Surprise Sale
780 - array(
781 - 'key' => 'ultp_dashboard_content_notice_surprise_sale_brand_logo',
782 - 'start' => '2026-05-22 00:00 Asia/Dhaka',
783 - 'end' => '2026-05-25 23:59 Asia/Dhaka',
784 - 'url' => Xpo::generate_utm_link(
785 - array(
786 - 'utmKey' => 'surprise_sale_content',
787 - )
788 - ),
789 - 'visibility' => ! Xpo::is_lc_active(),
790 - 'content_heading' => __( 'Surprise Sale:', 'ultimate-post' ),
791 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
792 - 'discount_content' => ' up to 50% Off',
793 - 'brand_color' => '#0322ff',
794 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/brand_logo.svg',
795 - 'button_text' => __( 'Claim Your Discount!', 'ultimate-post' ),
796 - 'is_discount_logo' => true,
797 - 'border_color' => '#0322ff',
798 - ),
799 - array(
800 - 'key' => 'ultp_dashboard_content_notice_surprise_sale_discount_logo',
801 - 'start' => '2026-05-26 00:00 Asia/Dhaka',
802 - 'end' => '2026-05-28 23:59 Asia/Dhaka',
803 - 'url' => Xpo::generate_utm_link(
804 - array(
805 - 'utmKey' => 'surprise_sale_content',
806 - )
807 - ),
808 - 'visibility' => ! Xpo::is_lc_active(),
809 - 'content_heading' => __( 'Surprise Sale:', 'ultimate-post' ),
810 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
811 - 'discount_content' => ' up to 50% Off',
812 - 'brand_color' => '#0322ff',
813 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale_50_discount_logo.png',
814 - 'button_text' => __( 'Upgrade Now!', 'ultimate-post' ),
815 - 'is_discount_logo' => true,
816 - 'border_color' => '#0322ff',
817 - ),
309 + /**
310 + * Handle Plugin Notice for all plugins
311 + *
312 + * @return bool
313 + */
314 + public function is_available_for_notice() {
315 + $active_notices = apply_filters( 'xpo_active_notice_lists', array() );
316 + if ( empty( $active_notices ) ) {
317 + return true;
318 + }
319 + asort( $active_notices );
320 + return array_key_first( $active_notices ) === $this->plugin_notice_priority_key;
321 + }
818 322
819 - // Massive Sale
820 - array(
821 - 'key' => 'ultp_dashboard_content_notice_massive_sale_brand_logo',
822 - 'start' => '2026-06-02 00:00 Asia/Dhaka',
823 - 'end' => '2026-06-10 23:59 Asia/Dhaka',
824 - 'url' => Xpo::generate_utm_link(
825 - array(
826 - 'utmKey' => 'massive_sale_content',
827 - )
828 - ),
829 - 'visibility' => ! Xpo::is_lc_active(),
830 - 'content_heading' => __( 'Massive Sale:', 'ultimate-post' ),
831 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
832 - 'discount_content' => ' up to 50% Off',
833 - 'brand_color' => '#0322ff',
834 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/brand_logo.svg',
835 - 'button_text' => __( 'Claim Your Discount!', 'ultimate-post' ),
836 - 'is_discount_logo' => true,
837 - 'border_color' => '#0322ff',
838 - ),
839 - array(
840 - 'key' => 'ultp_dashboard_content_notice_massive_sale_discount_logo',
841 - 'start' => '2026-06-11 00:00 Asia/Dhaka',
842 - 'end' => '2026-06-16 23:59 Asia/Dhaka',
843 - 'url' => Xpo::generate_utm_link(
844 - array(
845 - 'utmKey' => 'massive_sale_content',
846 - )
847 - ),
848 - 'visibility' => ! Xpo::is_lc_active(),
849 - 'content_heading' => __( 'Massive Sale:', 'ultimate-post' ),
850 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
851 - 'discount_content' => ' up to 50% Off',
852 - 'brand_color' => '#0322ff',
853 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale_50_discount_logo.png',
854 - 'button_text' => __( 'Upgrade Now!', 'ultimate-post' ),
855 - 'is_discount_logo' => true,
856 - 'border_color' => '#0322ff',
857 - ),
323 + /**
324 + * Dashboard Image Banner Notice
325 + *
326 + * @param bool $return_bool True to only report whether an image banner would show.
327 + * @return bool|void
328 + */
329 + public function ultp_dashboard_image_banner_notice( $return_bool = false ) {
330 + return $this->render_campaigns( Image_Notice::class, $return_bool );
331 + }
858 332
859 - // Final Hours Sale
860 - array(
861 - 'key' => 'ultp_dashboard_content_notice_final_hours_sale_brand_logo',
862 - 'start' => '2026-06-21 00:00 Asia/Dhaka',
863 - 'end' => '2026-06-24 23:59 Asia/Dhaka',
864 - 'url' => Xpo::generate_utm_link(
865 - array(
866 - 'utmKey' => 'final_hour_content',
867 - )
868 - ),
869 - 'visibility' => ! Xpo::is_lc_active(),
870 - 'content_heading' => __( 'Final Hours Sale:', 'ultimate-post' ),
871 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
872 - 'discount_content' => ' up to 50% Off',
873 - 'brand_color' => '#0322ff',
874 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/brand_logo.svg',
875 - 'button_text' => __( 'Claim Your Discount!', 'ultimate-post' ),
876 - 'is_discount_logo' => true,
877 - 'border_color' => '#0322ff',
878 - ),
879 - array(
880 - 'key' => 'ultp_dashboard_content_notice_final_hours_sale_discount_logo',
881 - 'start' => '2026-06-25 00:00 Asia/Dhaka',
882 - 'end' => '2026-06-27 23:59 Asia/Dhaka',
883 - 'url' => Xpo::generate_utm_link(
884 - array(
885 - 'utmKey' => 'final_hour_content',
886 - )
887 - ),
888 - 'visibility' => ! Xpo::is_lc_active(),
889 - 'content_heading' => __( 'Final Hours Sale:', 'ultimate-post' ),
890 - 'content_subheading' => __( 'Enjoy %s on PostX Pro.', 'ultimate-post' ),
891 - 'discount_content' => ' up to 50% Off',
892 - 'brand_color' => '#0322ff',
893 - 'icon' => ULTP_URL . 'assets/img/dashboard_banner/flash_sale_50_discount_logo.png',
894 - 'button_text' => __( 'Upgrade Now!', 'ultimate-post' ),
895 - 'is_discount_logo' => true,
896 - 'border_color' => '#0322ff',
897 - ),
333 + /**
334 + * Campaigns that should show on this request, in display order.
335 + *
336 + * Worked out once per request: the xpo_active_notice_lists filter and the render pass both need it,
337 + * and each campaign's dismissal check is an uncached database read.
338 + *
339 + * @param string $type Optional class name to keep only one kind of campaign.
340 + * @return Abstract_Campaign_Notice[]
341 + */
342 + private function get_active_campaigns( $type = '' ) {
343 + if ( null === $this->active_campaigns ) {
344 + $this->active_campaigns = array();
898 345
899 - );
346 + // The registry is the file edited for every sale. Load it here, not at the top, so a
347 + // mistake in it (even a syntax error) hides the campaigns instead of breaking wp-admin.
348 + try {
349 + require_once __DIR__ . '/class-campaigns.php';
350 + $campaigns = Campaigns::all();
351 + } catch ( \Throwable $e ) {
352 + self::log_error( $e );
353 + $campaigns = array();
354 + }
900 355
901 - $ultp_db_nonce = wp_create_nonce( 'ultp-nonce' );
356 + foreach ( $campaigns as $campaign ) {
357 + try {
358 + if ( $campaign instanceof Abstract_Campaign_Notice && $campaign->should_display() ) {
359 + $this->active_campaigns[] = $campaign;
360 + }
361 + } catch ( \Throwable $e ) {
362 + self::log_error( $e );
363 + }
364 + }
365 + }
902 366
903 - foreach ( $content_notices as $key => $notice ) {
904 - $notice_key = isset( $notice['key'] ) ? $notice['key'] : $this->notice_version;
905 - if ( isset( $_GET['ultp_notice'] ) && $notice_key === $_GET['ultp_notice'] ) {
906 - continue;
907 - } else {
908 - $border_color = $notice['border_color'];
367 + if ( '' === $type ) {
368 + return $this->active_campaigns;
369 + }
370 + return array_values(
371 + array_filter(
372 + $this->active_campaigns,
373 + function ( $campaign ) use ( $type ) {
374 + return $campaign instanceof $type;
375 + }
376 + )
377 + );
378 + }
909 379
910 - $current_time = gmdate( 'U' );
911 - $notice_start = gmdate( 'U', strtotime( $notice['start'] ) );
912 - $notice_end = gmdate( 'U', strtotime( $notice['end'] ) );
913 - if ( $current_time >= $notice_start && $current_time <= $notice_end && $notice['visibility'] ) {
914 - $notice_transient = Xpo::get_transient_without_cache( 'ultp_get_pro_notice_' . $notice_key );
380 + /**
381 + * Print every live campaign of one kind, or report whether there is one.
382 + *
383 + * @param string $type Campaign class name.
384 + * @param bool $return_bool True to only report whether one would show.
385 + * @return bool|void
386 + */
387 + private function render_campaigns( $type, $return_bool ) {
388 + $campaigns = $this->get_active_campaigns( $type );
915 389
916 - if ( 'off' !== $notice_transient ) {
390 + if ( $return_bool ) { // Early return for Other plugin notice.
391 + return ! empty( $campaigns );
392 + }
917 393
918 - $query_args = array(
919 - 'ultp_notice' => $notice_key,
920 - 'wpnonce' => $ultp_db_nonce,
921 - );
922 - if ( isset( $notice['repeat_interval'] ) && $notice['repeat_interval'] ) {
923 - $query_args['ultp_interval'] = $notice['repeat_interval'];
924 - }
394 + foreach ( $campaigns as $campaign ) {
395 + $this->render_safely( $campaign );
396 + }
397 + }
925 398
926 - $url = isset( $notice['url'] ) ? $notice['url'] : Xpo::generate_utm_link(
927 - array(
928 - 'utmKey' => 'content_notice',
929 - )
930 - );
399 + /**
400 + * Print one notice. It's buffered, so a notice that fails halfway leaves no broken markup behind.
401 + *
402 + * @param Notice_Type $notice Notice to print.
403 + * @return void
404 + */
405 + private function render_safely( Notice_Type $notice ) {
406 + ob_start();
407 + try {
408 + $notice->render();
409 + ob_end_flush();
410 + } catch ( \Throwable $e ) {
411 + ob_end_clean();
412 + self::log_error( $e );
413 + }
414 + }
931 415
932 - ?>
933 -
934 - <style id="ultp-notice-css" type="text/css">
935 - .ultp-content-notice-wrapper {
936 - border: 1px solid #c3c4c7;
937 - border-left: 3px solid #037fff;
938 - margin: 15px 0 !important;
939 - display: flex;
940 - align-items: center;
941 - background-color: #eef0f4;
942 - width: 100%;
943 - padding: 10px 0;
944 - position: relative;
945 - box-sizing: border-box;
946 - }
947 -
948 - .ultp-content-notice-wrapper.notice {
949 - margin: 10px 0;
950 - width: calc(100% - 20px);
951 - }
952 -
953 - .wrap .ultp-content-notice-wrapper.notice {
954 - width: 100%;
955 - }
956 -
957 - .ultp-content-notice-icon {
958 - margin-left: 15px;
959 - }
960 -
961 - .ultp-content-notice-discout-icon {
962 - margin-left: 10px;
963 - }
964 -
965 - .ultp-content-notice-icon img {
966 - max-width: 42px;
967 - height: 70px;
968 - }
969 -
970 - .ultp-content-notice-discout-icon img {
971 - height: 70px;
972 - width: 70px;
973 - object-fit: contain;
974 - }
975 -
976 - .ultp-notice-content-wrapper {
977 - display: flex;
978 - flex-direction: column;
979 - gap: 8px;
980 - font-size: 14px;
981 - line-height: 20px;
982 - margin-left: 15px;
983 - }
984 -
985 - .ultp-content-notice-buttons {
986 - display: flex;
987 - align-items: center;
988 - gap: 15px;
989 - }
990 -
991 - .ultp-content-notice-btn {
992 - font-weight: 600;
993 - text-transform: uppercase !important;
994 - padding: 2px 10px !important;
995 - background-color: #0322ff;
996 - border: none !important;
997 - }
998 -
999 - .ultp-content-discount_btn {
1000 - background-color: #ffffff;
1001 - text-decoration: none;
1002 - border: 1px solid #0322ff;
1003 - padding: 5px 10px;
1004 - border-radius: 5px;
1005 - font-weight: 500;
1006 - text-transform: uppercase;
1007 - color: #0322ff !important;
1008 - }
1009 -
1010 - .ultp-content-notice-close {
1011 - position: absolute;
1012 - right: 2px;
1013 - top: 5px;
1014 - text-decoration: none;
1015 - color: #b6b6b6;
1016 - font-family: dashicons;
1017 - font-size: 16px;
1018 - line-height: 20px;
1019 - }
1020 -
1021 - .ultp-content-notice-close-icon {
1022 - font-size: 14px;
1023 - }
1024 - </style>
1025 - <div class="ultp-content-notice-wrapper notice data_collection_notice"
1026 - style="border-left: 3px solid <?php echo esc_attr( $border_color ); ?>;"
1027 - >
1028 - <?php
1029 - if ( $notice['is_discount_logo'] ) {
1030 - ?>
1031 - <div class="ultp-content-notice-discout-icon"> <img src="<?php echo esc_url( $notice['icon'] ); ?>"/> </div>
1032 - <?php
1033 - } else {
1034 - ?>
1035 - <div class="ultp-content-notice-icon"> <img src="<?php echo esc_url( $notice['icon'] ); ?>"/> </div>
1036 - <?php
1037 - }
1038 - ?>
1039 -
1040 - <div class="ultp-notice-content-wrapper">
1041 - <div class="">
1042 - <strong><?php printf( esc_html( $notice['content_heading'] ) ); ?> </strong>
1043 - <?php
1044 - printf(
1045 - wp_kses_post( $notice['content_subheading'] ),
1046 - '<strong>' . esc_html( $notice['discount_content'] ) . '</strong>'
1047 - );
1048 - ?>
1049 - </div>
1050 - <div class="ultp-content-notice-buttons">
1051 - <?php if ( isset( $notice['is_discount_logo'] ) && $notice['is_discount_logo'] ) : ?>
1052 - <a class="ultp-content-discount_btn" href="<?php echo esc_url( $url ); ?>" target="_blank">
1053 - <?php echo esc_html( $notice['button_text'] ); ?>
1054 - </a>
1055 - <?php else : ?>
1056 - <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'; ?>;">
1057 - <?php echo esc_html( $notice['button_text'] ); ?>
1058 -
1059 - </a>
1060 - <?php endif; ?>
1061 - </div>
1062 - </div>
1063 - <a href=
1064 - <?php
1065 - echo esc_url(
1066 - add_query_arg(
1067 - $query_args
1068 - )
1069 - );
1070 - ?>
1071 - class="ultp-content-notice-close"><span class="ultp-content-notice-close-icon dashicons dashicons-dismiss"> </span></a>
1072 - </div>
1073 - <?php
1074 - }
1075 - }
1076 - }
416 + /**
417 + * Record a notice failure when debugging. Notices are never worth breaking wp-admin for.
418 + *
419 + * @param \Throwable $e What went wrong.
420 + * @return void
421 + */
422 + private static function log_error( \Throwable $e ) {
423 + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
424 + error_log( 'PostX notice: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug-only.
1077 425 }
1078 426 }
1079 427 }