PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.1.3
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.1.3
9.1.3 9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 8.5.38 8.5.39 All 222 releases
wpvr / src / Admin / PromotionalBanner.php

PromotionalBanner.php in WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress 9.1.3, at src/Admin/PromotionalBanner.php

232 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace RexTheme\WPVR\Admin;
4
5 /**
6 * Renders and manages the promotional countdown banner on the WPVR tour listing page.
7 */
8 class PromotionalBanner {
9
10 const OPTION_DISMISSED = 'wpvr_new_ui_promo_banner_dismissed';
11 const POST_TYPE = 'wpvr_item';
12 const TARGET_SCREEN = 'edit-wpvr_item';
13 const END_DATE = '2026-09-30 23:59:59';
14 const CTA_URL = 'https://rextheme.com/wpvr/wpvr-pricing/';
15 const AJAX_ACTION = 'wpvr_dismiss_promo_banner';
16 const NONCE_ACTION = 'wpvr_promo_banner_dismiss';
17
18 /**
19 * Singleton instance.
20 *
21 * @var self|null
22 */
23 private static $instance = null;
24
25 public static function init(): self {
26 if ( null === self::$instance ) {
27 self::$instance = new self();
28 }
29 return self::$instance;
30 }
31
32 public function __construct() {
33 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
34 add_action( 'admin_notices', [ $this, 'render_banner' ] );
35 add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'handle_dismiss' ] );
36 }
37
38 /**
39 * Checks whether all conditions are met to display the banner.
40 */
41 public function should_display(): bool {
42 if ( ! is_admin() ) {
43 return false;
44 }
45
46 if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'manage_options' ) ) {
47 return false;
48 }
49
50 if ( ! function_exists( 'get_current_screen' ) ) {
51 return false;
52 }
53
54 $screen = get_current_screen();
55 if ( ! $screen ) {
56 return false;
57 }
58
59 $is_listing = in_array( $screen->id, [ self::TARGET_SCREEN, 'edit-wpvr' ], true )
60 || ( isset( $screen->base, $screen->post_type ) && 'edit' === $screen->base && in_array( $screen->post_type, [ self::POST_TYPE, 'wpvr' ], true ) );
61
62 if ( ! $is_listing ) {
63 return false;
64 }
65
66 // Suppress if WPVR Pro is active with a valid license.
67 if ( function_exists( 'wpvr_is_pro_active' ) && wpvr_is_pro_active() ) {
68 return false;
69 }
70
71 // Suppress if user permanently dismissed the banner.
72 if ( get_option( self::OPTION_DISMISSED, 'no' ) === 'yes' ) {
73 return false;
74 }
75
76 // Suppress if campaign has ended.
77 if ( time() >= strtotime( self::END_DATE ) ) {
78 return false;
79 }
80
81 return true;
82 }
83
84 /**
85 * Computes initial countdown values for server-side render.
86 *
87 * @return array<string, mixed>
88 */
89 public function get_countdown_data(): array {
90 $end_timestamp = strtotime( self::END_DATE );
91 $diff = max( 0, $end_timestamp - time() );
92
93 $days = floor( $diff / ( 60 * 60 * 24 ) );
94 $hours = floor( ( $diff % ( 60 * 60 * 24 ) ) / ( 60 * 60 ) );
95 $mins = floor( ( $diff % ( 60 * 60 ) ) / 60 );
96 $secs = floor( $diff % 60 );
97
98 return [
99 'days' => sprintf( '%02d', (int) $days ),
100 'hours' => sprintf( '%02d', (int) $hours ),
101 'mins' => sprintf( '%02d', (int) $mins ),
102 'secs' => sprintf( '%02d', (int) $secs ),
103 'end_timestamp' => $end_timestamp,
104 ];
105 }
106
107 /**
108 * Enqueue assets strictly when banner should display.
109 */
110 public function enqueue_assets(): void {
111 if ( ! $this->should_display() ) {
112 return;
113 }
114
115 $plugin_url = plugin_dir_url( WPVR_FILE );
116 $plugin_path = plugin_dir_path( WPVR_FILE );
117
118 $css_file = $plugin_path . 'app/admin/promotional-banner.css';
119 $css_ver = file_exists( $css_file ) ? (string) filemtime( $css_file ) : WPVR_VERSION;
120
121 $js_file = $plugin_path . 'app/admin/promotional-banner.js';
122 $js_ver = file_exists( $js_file ) ? (string) filemtime( $js_file ) : WPVR_VERSION;
123
124 wp_enqueue_style(
125 'wpvr-promotional-banner',
126 $plugin_url . 'app/admin/promotional-banner.css',
127 [],
128 $css_ver
129 );
130
131 wp_enqueue_script(
132 'wpvr-promotional-banner',
133 $plugin_url . 'app/admin/promotional-banner.js',
134 [],
135 $js_ver,
136 true
137 );
138
139 wp_localize_script(
140 'wpvr-promotional-banner',
141 'wpvrPromoBanner',
142 [
143 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
144 'action' => self::AJAX_ACTION,
145 'nonce' => wp_create_nonce( self::NONCE_ACTION ),
146 'endTimestamp' => strtotime( self::END_DATE ),
147 ]
148 );
149 }
150
151 /**
152 * Renders promotional banner HTML markup.
153 */
154 public function render_banner(): void {
155 if ( ! $this->should_display() ) {
156 return;
157 }
158
159 $countdown = $this->get_countdown_data();
160 ?>
161 <div id="wpvr-promotional-banner" class="wpvr-promotional-banner" data-end-time="<?php echo esc_attr( (string) $countdown['end_timestamp'] ); ?>" role="region" aria-label="<?php esc_attr_e( 'WPVR Promotional Offer', 'wpvr' ); ?>">
162 <div class="wpvr-promo-banner__content">
163 <div class="wpvr-promo-banner__badge" aria-hidden="true">
164 <svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" viewBox="0 0 54 54" fill="none" focusable="false">
165 <path d="M26.2456 23.3063L29.1152 20.7481L28.4685 17.0989L31.9848 18.8294L35.501 17.1365L34.8139 20.7857L37.6431 23.3816L33.6823 23.8706L31.9039 27.1813L30.166 23.8706L26.2456 23.3063Z" fill="white"></path>
166 <rect width="53.18" height="53.18" rx="9" fill="white" fill-opacity="0.1"></rect>
167 <rect x="6.22736" y="6.22733" width="40.7259" height="40.7259" rx="5.5" fill="#EFEAFF" stroke="white"></rect>
168 <path d="M30.6591 40.1589V37.234H33.0726V28.7661H30.6591V25.8412H38.9838V28.7661H36.5498V37.234H38.9838V40.1589H30.6591Z" fill="#3F04FE"></path>
169 <path d="M21.3401 40.2816C20.1537 40.2816 19.097 40.0429 18.1697 39.5657C17.2425 39.0748 16.513 38.4066 15.9812 37.5612C15.463 36.7158 15.2039 35.7476 15.2039 34.6568V25.8411H18.722V34.4727C18.722 34.9908 18.8379 35.4613 19.0697 35.884C19.3015 36.2931 19.6151 36.6203 20.0106 36.8658C20.406 37.0976 20.8492 37.2135 21.3401 37.2135C21.8582 37.2135 22.315 37.0976 22.7105 36.8658C23.1196 36.6203 23.4468 36.2931 23.6923 35.884C23.9377 35.4613 24.0604 34.9908 24.0604 34.4727V25.8411H27.4558V34.6568C27.4558 35.7476 27.1899 36.7158 26.6581 37.5612C26.1399 38.4066 25.4172 39.0748 24.49 39.5657C23.5627 40.0429 22.5128 40.2816 21.3401 40.2816Z" fill="#3F04FE"></path>
170 <path d="M31.0628 21.3415L28.8824 13.4177H30.2119L31.9775 20.2779H31.6478L33.4772 13.4177H34.7854L36.6148 20.2779H36.2745L38.0507 13.4177H39.3695L37.1998 21.3415H35.7852L33.9452 14.5877H34.3174L32.4668 21.3415H31.0628Z" fill="#3F04FE"></path>
171 <path d="M23.197 21.3415V13.4177H28.4192V14.5345H24.452V16.8106H28.2065V17.9274H24.452V20.2247H28.4192V21.3415H23.197Z" fill="#3F04FE"></path>
172 <path d="M15.3333 21.3415V13.4176H16.3437L20.7895 19.5546L20.3109 19.6291V13.4176H21.5553V21.3415H20.5449L16.131 15.1619L16.5883 15.0769V21.3415H15.3333Z" fill="#3F04FE"></path>
173 </svg>
174 </div>
175
176 <div class="wpvr-promo-banner__text">
177 <span class="wpvr-promo-banner__title"><?php esc_html_e( 'WPVR New UI, Save Big', 'wpvr' ); ?></span>
178 <span class="wpvr-promo-banner__subtitle"><?php esc_html_e( 'Flat 30% Off', 'wpvr' ); ?></span>
179 </div>
180
181 <div class="wpvr-promo-banner__timer" id="wpvr-promo-countdown" aria-label="<?php esc_attr_e( 'Offer countdown timer', 'wpvr' ); ?>">
182 <div class="wpvr-promo-timer__box">
183 <span class="wpvr-promo-timer__num" id="wpvr-promo-days"><?php echo esc_html( $countdown['days'] ); ?></span>
184 <span class="wpvr-promo-timer__unit"><?php esc_html_e( 'DAY', 'wpvr' ); ?></span>
185 </div>
186 <div class="wpvr-promo-timer__box">
187 <span class="wpvr-promo-timer__num" id="wpvr-promo-hours"><?php echo esc_html( $countdown['hours'] ); ?></span>
188 <span class="wpvr-promo-timer__unit"><?php esc_html_e( 'HR', 'wpvr' ); ?></span>
189 </div>
190 <div class="wpvr-promo-timer__box">
191 <span class="wpvr-promo-timer__num" id="wpvr-promo-mins"><?php echo esc_html( $countdown['mins'] ); ?></span>
192 <span class="wpvr-promo-timer__unit"><?php esc_html_e( 'MIN', 'wpvr' ); ?></span>
193 </div>
194 <div class="wpvr-promo-timer__box">
195 <span class="wpvr-promo-timer__num" id="wpvr-promo-secs"><?php echo esc_html( $countdown['secs'] ); ?></span>
196 <span class="wpvr-promo-timer__unit"><?php esc_html_e( 'SEC', 'wpvr' ); ?></span>
197 </div>
198 </div>
199
200 <a href="<?php echo esc_url( self::CTA_URL ); ?>" class="wpvr-promo-banner__cta" target="_blank" rel="noopener noreferrer">
201 <span><?php esc_html_e( 'Get 30% OFF', 'wpvr' ); ?></span>
202 <svg class="wpvr-promo-banner__cta-arrow" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
203 <path d="M2.5 9.5L9.5 2.5M9.5 2.5H4M9.5 2.5V8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
204 </svg>
205 </a>
206 </div>
207
208 <button type="button" class="wpvr-promo-banner__dismiss" id="wpvr-promo-banner-dismiss" aria-label="<?php esc_attr_e( 'Dismiss banner permanently', 'wpvr' ); ?>">
209 <svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
210 <path d="M1 1L10 10M10 1L1 10" stroke="#FFFFFF" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
211 </svg>
212 </button>
213 </div>
214 <?php
215 }
216
217 /**
218 * AJAX handler for permanent dismissal.
219 */
220 public function handle_dismiss(): void {
221 if ( ! current_user_can( 'edit_posts' ) ) {
222 wp_send_json_error( [ 'message' => esc_html__( 'Permission denied.', 'wpvr' ) ], 403 );
223 }
224
225 check_ajax_referer( self::NONCE_ACTION, 'nonce' );
226
227 update_option( self::OPTION_DISMISSED, 'yes' );
228
229 wp_send_json_success( [ 'message' => esc_html__( 'Banner permanently dismissed.', 'wpvr' ) ] );
230 }
231 }
232