PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / inc / modules / inactive-tab-message / class-inactive-tab-message.php

class-inactive-tab-message.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at inc/modules/inactive-tab-message/class-inactive-tab-message.php

386 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Inactive Tab Message.
5 *
6 * @package Merchant
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 require_once __DIR__ . '/class-inactive-tab-message-resolver.php';
14
15 /**
16 * Inactive Tab Message Class.
17 *
18 */
19 class Merchant_Inactive_Tab_Message extends Merchant_Add_Module {
20
21 /**
22 * Module ID.
23 *
24 */
25 const MODULE_ID = 'inactive-tab-message';
26
27 /**
28 * Is module preview.
29 *
30 * @var bool
31 */
32 public static $is_module_preview = false;
33
34 /**
35 * Initialize the module's option group definitions.
36 *
37 * @return array<int, array<string, mixed>> Array of option group arrays.
38 */
39 protected function init_option_groups() {
40 return require MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
41 }
42
43 /**
44 * Constructor.
45 *
46 */
47 public function __construct() {
48 $this->setup_module();
49 parent::__construct();
50
51 if ( is_admin() && $this->is_module_settings_page() ) {
52 $this->register_admin_hooks();
53 }
54
55 if ( Merchant_Modules::is_module_active( self::MODULE_ID ) && is_admin() ) {
56 $this->init_translations();
57 }
58
59 if ( ! Merchant_Modules::is_module_active( self::MODULE_ID ) ) {
60 return;
61 }
62
63 // Return early if it's on admin but not in the respective module settings page.
64 if ( is_admin() && ! $this->is_module_settings_page() ) {
65 return;
66 }
67
68 $this->register_frontend_hooks();
69 }
70
71 /**
72 * Set up module identity: ID, flags, section, defaults, data, and options path.
73 *
74 * @return void
75 */
76 protected function setup_module(): void {
77 $this->module_id = self::MODULE_ID;
78 $this->wc_only = true;
79 $this->module_section = 'reduce-abandonment';
80
81 $this->module_default_settings = array(
82 // Existing (backward compatible).
83 'message' => __( '✋ Don\'t forget this', 'merchant' ),
84 'abandoned_message' => __( '✋ You left something in the cart', 'merchant' ),
85
86 // High-value cart.
87 'high_value_message' => '',
88 'high_value_threshold' => 100,
89
90 // Rotating messages.
91 'enable_rotation' => 0,
92 'rotation_messages' => array(),
93 'rotation_interval' => 3,
94
95 // Favicon.
96 'enable_favicon' => 0,
97 'favicon_type' => 'emoji',
98 'favicon_emoji' => 'wave',
99 'favicon_url' => '',
100
101 // Return message.
102 'return_message' => '',
103 'return_duration' => 2,
104
105 // Scrolling text.
106 'enable_scroll' => 0,
107 );
108
109 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
110 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
111
112 // AI prompt examples.
113 $this->ai_examples = array(
114 'blurb' => esc_html__( 'See what AI can do for your inactive tab messages, from tailoring the text shoppers see when they switch away to swapping the favicon and rotating through a list of messages.', 'merchant' ),
115 'prompts' => array(
116 esc_html__( "Explore the abilities WPVibe gives you access to, then set the cart-items tab message to 'You left {cart_count} items behind'", 'merchant' ),
117 esc_html__( 'Check what abilities you have available through WPVibe, then show a high-value cart inactive tab message when the cart total goes over $150', 'merchant' ),
118 esc_html__( 'Look at your available WPVibe abilities, then change the browser favicon to the cart emoji while the tab is inactive', 'merchant' ),
119 esc_html__( 'See what abilities WPVibe exposes, then turn on rotating tab messages that cycle every 5 seconds', 'merchant' ),
120 ),
121 );
122 }
123
124 /**
125 * Register hooks that are only needed on the module's admin settings page.
126 *
127 * @return void
128 */
129 protected function register_admin_hooks(): void {
130 self::$is_module_preview = true;
131
132 // Enqueue admin styles.
133 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_css' ) );
134
135 // Admin preview box.
136 add_filter( 'merchant_module_preview', array( $this, 'render_admin_preview' ), 10, 2 );
137 }
138
139 /**
140 * Register hooks that power the frontend functionality.
141 *
142 * @return void
143 */
144 protected function register_frontend_hooks(): void {
145 // Enqueue scripts.
146 add_action( 'merchant_enqueue_after_main_css_js', array( $this, 'enqueue_scripts' ) );
147
148 // Localize script.
149 add_filter( 'merchant_localize_script', array( $this, 'localize_script' ) );
150
151 // Add merchant selector and content to cart fragments.
152 add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'cart_count_fragment' ) );
153 }
154
155 /**
156 * Init translations.
157 *
158 * @return void
159 */
160 public function init_translations() {
161 $settings = $this->get_module_settings();
162 $resolver = new Merchant_Inactive_Tab_Message_Resolver();
163
164 foreach ( $resolver->get_translatable_strings( $settings ) as $entry ) {
165 Merchant_Translator::register_string( $entry['string'], esc_html( $entry['name'] ) );
166 }
167 }
168
169 /**
170 * Admin enqueue CSS.
171 *
172 * @return void
173 */
174 public function admin_enqueue_css() {
175 $page = ( ! empty( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
176 $module = ( ! empty( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
177
178 if ( 'merchant' === $page && self::MODULE_ID === $module ) {
179 wp_enqueue_style( 'merchant-admin-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/admin/preview.min.css', array(), MERCHANT_VERSION );
180 wp_enqueue_script( 'merchant-admin-' . self::MODULE_ID . '-preview', MERCHANT_URI . 'assets/js/modules/' . self::MODULE_ID . '/admin/preview.min.js', array( 'jquery' ), MERCHANT_VERSION, true );
181 wp_localize_script(
182 'merchant-admin-' . self::MODULE_ID . '-preview',
183 'merchantItmPreview',
184 array(
185 'siteName' => get_bloginfo( 'name' ),
186 )
187 );
188 }
189 }
190
191 /**
192 * Enqueue scripts.
193 *
194 * @return void
195 */
196 public function enqueue_scripts() {
197 // Enqueue vendor favico.js (shared with cart-count-favicon module).
198 wp_enqueue_script( 'favico', MERCHANT_URI . 'assets/js/vendor/favico.js', array(), MERCHANT_VERSION, true );
199
200 // Register and enqueue the main module script.
201 wp_enqueue_script( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/js/modules/' . self::MODULE_ID . '/inactive-tab-message.min.js', array( 'favico' ), MERCHANT_VERSION, true );
202 }
203
204 /**
205 * Localize script with module settings.
206 *
207 * @param array<string, mixed> $setting The merchant global object setting parameter.
208 * @return array<string, mixed> The merchant global object setting parameter.
209 */
210 public function localize_script( $setting ) {
211 $module_settings = $this->get_module_settings();
212
213 $cart_count = 0;
214 $cart_total = '';
215 $wc = function_exists( 'WC' ) ? WC() : null;
216
217 if ( $wc && $wc->cart ) { // @phpstan-ignore booleanAnd.rightAlwaysTrue
218 $cart_count = $wc->cart->get_cart_contents_count();
219 $cart_total = html_entity_decode( wp_strip_all_tags( $wc->cart->get_cart_total() ), ENT_QUOTES, 'UTF-8' );
220 }
221
222 $site_name = get_bloginfo( 'name' );
223
224 $resolver = new Merchant_Inactive_Tab_Message_Resolver();
225 $data = $resolver->prepare_localized_data( $module_settings, $cart_count, $cart_total, $site_name );
226
227 // Translate messages.
228 $translate_keys = array(
229 'inactive_tab_message',
230 'inactive_tab_abandoned_message',
231 'inactive_tab_high_value_message',
232 'inactive_tab_return_message',
233 );
234
235 foreach ( $translate_keys as $key ) {
236 if ( ! empty( $data[ $key ] ) ) {
237 $data[ $key ] = Merchant_Translator::translate( $data[ $key ] );
238 }
239 }
240
241 // Translate rotation messages.
242 if ( ! empty( $data['inactive_tab_rotation_messages'] ) ) {
243 $data['inactive_tab_rotation_messages'] = array_map(
244 array( 'Merchant_Translator', 'translate' ),
245 $data['inactive_tab_rotation_messages']
246 );
247 }
248
249 // Also pass raw cart total for threshold comparison in JS.
250 $data['inactive_tab_cart_total_raw'] = 0;
251 if ( $wc && $wc->cart ) { // @phpstan-ignore booleanAnd.rightAlwaysTrue
252 $data['inactive_tab_cart_total_raw'] = (float) $wc->cart->get_cart_contents_total();
253 }
254
255 // Resolve favicon attachment ID to URL.
256 if ( ! empty( $data['inactive_tab_favicon_url'] ) ) {
257 $favicon_attachment_url = wp_get_attachment_url( (int) $data['inactive_tab_favicon_url'] );
258 $data['inactive_tab_favicon_url'] = $favicon_attachment_url ? $favicon_attachment_url : '';
259 }
260
261 return array_merge( $setting, $data );
262 }
263
264 /**
265 * Render admin preview.
266 *
267 * @param Merchant_Admin_Preview $preview
268 * @param string $module
269 *
270 * @return Merchant_Admin_Preview
271 */
272 public function render_admin_preview( $preview, $module ) {
273 if ( self::MODULE_ID === $module ) {
274 ob_start();
275 self::admin_preview_content();
276 $content = (string) ob_get_clean();
277
278 // HTML.
279 $preview->set_html( $content );
280
281 // All live preview updates (text, favicon, rotation) are handled
282 // by the module's own admin/preview.js to avoid conflicts with
283 // the global preview system's catch-all change handler.
284 }
285
286 return $preview;
287 }
288
289 /**
290 * Admin preview content.
291 *
292 * Enhanced preview showing a simulated browser tab with favicon and message.
293 *
294 * @return void
295 */
296 public function admin_preview_content() {
297 $settings = $this->get_module_settings();
298 $favicon_url = get_site_icon_url() ? get_site_icon_url( 512 ) : MERCHANT_URI . 'inc/modules/' . self::MODULE_ID . '/admin/images/wplogo.svg';
299
300 // Map string keys to emoji characters (must match JS emojiMap).
301 $emoji_map = array(
302 'wave' => '👋',
303 'bell' => '🔔',
304 'cart' => '🛒',
305 'clock' => '',
306 'alert' => '',
307 'money' => '💰',
308 'fire' => '🔥',
309 'star' => '',
310 );
311
312 $favicon_key = $settings['favicon_emoji'] ?? 'wave';
313 $favicon_emoji = $emoji_map[ $favicon_key ] ?? $emoji_map['wave'];
314
315 ?>
316
317 <div class="mrc-preview-inactive-tab-message">
318 <div class="mrc-inactive-tab-preview-tabs">
319 <!-- Active tab (original state) -->
320 <div class="mrc-inactive-tab-preview-tab mrc-inactive-tab-preview-tab--active">
321 <div class="mrc-inactive-tab-preview-tab__favicon">
322 <img src="<?php echo esc_url( $favicon_url ); ?>" alt="" />
323 </div>
324 <div class="mrc-inactive-tab-preview-tab__title">
325 <?php echo esc_html__( 'Your Page Title', 'merchant' ); ?>
326 </div>
327 <span class="mrc-inactive-tab-preview-tab__close dashicons dashicons-no-alt"></span>
328 </div>
329
330 <!-- Inactive tab (shows the message) -->
331 <div class="mrc-inactive-tab-preview-tab mrc-inactive-tab-preview-tab--inactive">
332 <div class="mrc-inactive-tab-preview-tab__favicon mrc-inactive-tab-preview-tab__favicon--swap">
333 <?php if ( ! empty( $settings['enable_favicon'] ) && 'image' === ( $settings['favicon_type'] ?? 'emoji' ) && ! empty( $settings['favicon_url'] ) ) : ?>
334 <?php $custom_favicon_url = wp_get_attachment_url( (int) $settings['favicon_url'] ); ?>
335 <?php if ( $custom_favicon_url ) : ?>
336 <img class="mrc-inactive-tab-favicon-custom" src="<?php echo esc_url( $custom_favicon_url ); ?>" alt="" />
337 <?php else : ?>
338 <img src="<?php echo esc_url( $favicon_url ); ?>" alt="" />
339 <?php endif; ?>
340 <?php elseif ( ! empty( $settings['enable_favicon'] ) && 'emoji' === ( $settings['favicon_type'] ?? 'emoji' ) ) : ?>
341 <span class="mrc-inactive-tab-favicon-emoji"><?php echo esc_html( $favicon_emoji ); ?></span>
342 <?php else : ?>
343 <img src="<?php echo esc_url( $favicon_url ); ?>" alt="" />
344 <?php endif; ?>
345 </div>
346 <div class="mrc-inactive-tab-preview-tab__title mrc-inactive-tab-message-text">
347 <?php echo esc_html( Merchant_Translator::translate( $settings['message'] ) ); ?>
348 </div>
349 <span class="mrc-inactive-tab-preview-tab__close dashicons dashicons-no-alt"></span>
350 </div>
351 </div>
352
353 <div class="mrc-inactive-tab-preview-label">
354 <?php echo esc_html__( 'Active tab', 'merchant' ); ?>
355 <span class="mrc-inactive-tab-preview-label--right"><?php echo esc_html__( 'Inactive tab (preview)', 'merchant' ); ?></span>
356 </div>
357 </div>
358
359 <?php
360 }
361
362 /**
363 * Cart count fragments.
364 *
365 * @param array<string, mixed> $fragments The cart fragments.
366 * @return array<string, mixed> The cart fragments.
367 */
368 public function cart_count_fragment( $fragments ) {
369 if ( Merchant_Modules::is_module_active( 'inactive-tab-message' ) ) {
370 $wc = function_exists( 'WC' ) ? WC() : null;
371 if ( $wc && $wc->cart ) { // @phpstan-ignore booleanAnd.rightAlwaysTrue
372 $fragments['.merchant_cart_count'] = $wc->cart->get_cart_contents_count();
373 $fragments['.merchant_cart_total'] = html_entity_decode( wp_strip_all_tags( $wc->cart->get_cart_total() ), ENT_QUOTES, 'UTF-8' );
374 $fragments['.merchant_cart_total_raw'] = (float) $wc->cart->get_cart_contents_total();
375 }
376 }
377
378 return $fragments;
379 }
380 }
381
382 // Initialize the module.
383 add_action( 'init', function() {
384 Merchant_Modules::create_module( new Merchant_Inactive_Tab_Message() );
385 } );
386