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 / pre-orders / class-pre-orders.php

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

567 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Merchant - Pre Orders
4 *
5 * @package Merchant
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly
10 }
11
12 /**
13 * Pre Orders Class.
14 *
15 * This class handles the initialization and setup of the Pre Orders module, including
16 * admin settings, asset enqueueing, and frontend styling.
17 *
18 * @property string $module_id The module ID.
19 * @property bool $wc_only The WooCommerce only flag.
20 * @property string $module_section The module section.
21 * @property array $module_default_settings The module default settings.
22 * @property array $module_data The module data.
23 * @property string $module_options_path The module options path.
24 */
25 class Merchant_Pre_Orders extends Merchant_Add_Module {
26
27 /**
28 * Module ID.
29 *
30 * @var string The module ID.
31 */
32 const MODULE_ID = 'pre-orders';
33
34 /**
35 * Is module preview.
36 *
37 * @var bool The is module preview flag.
38 */
39 public static $is_module_preview = false;
40
41 /**
42 * Main functionality dependency.
43 *
44 * @var Merchant_Pre_Orders_Main_Functionality The main functionality instance.
45 */
46 public $main_func;
47
48 /**
49 * Set the module as having analytics.
50 *
51 * @var bool
52 */
53 protected $has_analytics = true;
54
55 /**
56 * Initialize the module's option group definitions.
57 *
58 * @return array<int, array<string, mixed>> Array of option group arrays.
59 */
60 protected function init_option_groups() {
61 return require MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
62 }
63
64 /**
65 * Fire action before rendering option groups.
66 *
67 * @return void
68 */
69 protected function before_render_option_groups() {
70 /**
71 * Fires before module options are rendered.
72 *
73 * @since 1.0
74 *
75 * @param string $module_id The module ID.
76 */
77 do_action( 'merchant_admin_before_include_modules_options', self::MODULE_ID );
78 }
79
80 /**
81 * Constructor.
82 *
83 * @param Merchant_Pre_Orders_Main_Functionality $main_func The main functionality instance.
84 */
85 public function __construct( Merchant_Pre_Orders_Main_Functionality $main_func ) {
86 // Module id.
87 $this->module_id = self::MODULE_ID;
88
89 // WooCommerce only.
90 $this->wc_only = true;
91
92 // Parent construct.
93 parent::__construct();
94
95 $this->main_func = $main_func;
96
97 // Module section.
98 $this->module_section = 'boost-revenue';
99
100 // Module default settings.
101 $this->module_default_settings = array(
102 'button_text' => __( 'Pre Order Now!', 'merchant' ),
103 'additional_text' => __( 'Ships on {date}.', 'merchant' ),
104 );
105
106 // Module data.
107 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
108
109 // AI prompt examples.
110 $this->ai_examples = array(
111 'blurb' => esc_html__( 'See what AI can do for your pre-orders, from launching a new campaign to adjusting the discount, shipping date, or button text on one already running.', 'merchant' ),
112 'prompts' => array(
113 esc_html__( 'Explore the abilities WPVibe gives you access to, then create a pre-order for the Galaxy Pro Headphones with an expected shipping date of March 15, 2026', 'merchant' ),
114 esc_html__( 'Check what abilities you have available through WPVibe, then set up a pre-order for the entire Spring Collection category with a fixed $10 discount instead of a percentage', 'merchant' ),
115 esc_html__( "Look at your available WPVibe abilities, then change the button text on the Galaxy Pro Headphones pre-order to 'Reserve Yours'", 'merchant' ),
116 esc_html__( 'See what abilities WPVibe exposes, then exclude the Wired Earbuds product from the Spring Collection pre-order', 'merchant' ),
117 ),
118 );
119
120 // Module options path.
121 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
122
123 // Is module preview page.
124 if ( is_admin() && parent::is_module_settings_page() ) {
125 self::$is_module_preview = true;
126
127 // Enqueue admin styles.
128 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_css' ) );
129 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_js' ) );
130
131 // Localize Script.
132 add_filter( 'merchant_admin_localize_script', array( $this, 'localize_script' ) );
133
134 // Admin preview box.
135 add_filter( 'merchant_module_preview', array( $this, 'render_admin_preview' ), 10, 2 );
136
137 // Custom CSS.
138 // The custom CSS should be added here as well due to ensure preview box works properly.
139 add_filter( 'merchant_custom_css', array( $this, 'admin_custom_css' ) );
140 }
141
142 add_action( 'merchant_admin_before_include_modules_options', array( $this, 'help_banner' ) );
143
144 if ( Merchant_Modules::is_module_active( self::MODULE_ID ) && is_admin() ) {
145 // Init translations.
146 $this->init_translations();
147
148 // Include product metabox for per-product settings.
149 require_once MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/class-pre-orders-metabox.php';
150 }
151
152
153 if ( ! Merchant_Modules::is_module_active( self::MODULE_ID ) ) {
154 return;
155 }
156
157 // TODO: Refactor the 'Merchant_Pre_Orders_Main_Functionality' class to load admin things separated from frontend things.
158 $main_func->init();
159
160 // Return early if it's on admin but not in the respective module settings page.
161 if ( is_admin() && ! wp_doing_ajax() && ! parent::is_module_settings_page() ) {
162 return;
163 }
164
165 // Enqueue styles.
166 add_action( 'merchant_enqueue_before_main_css_js', array( $this, 'enqueue_css' ) );
167
168 // Enqueue scripts.
169 add_action( 'merchant_enqueue_after_main_css_js', array( $this, 'enqueue_scripts' ) );
170
171 // Localize script.
172 add_filter( 'merchant_localize_script', array( $this, 'localize_script' ) );
173
174 // Custom CSS.
175 add_filter( 'merchant_custom_css', array( $this, 'frontend_custom_css' ) );
176
177 $this->supply_missing_id( 'rules' );
178 }
179
180 /**
181 * Init translations for module settings.
182 *
183 * Registers strings for the Merchant translator tool.
184 *
185 * @return void
186 */
187 public function init_translations() {
188 $settings = $this->get_module_settings();
189 if ( ! empty( $settings['rules'] ) ) {
190 foreach ( $settings['rules'] as $rule ) {
191 if ( ! empty( $rule['button_text'] ) ) {
192 Merchant_Translator::register_string( $rule['button_text'], 'Pre order button text' );
193 }
194 if ( ! empty( $rule['additional_text'] ) ) {
195 Merchant_Translator::register_string( $rule['additional_text'], 'Pre order additional information' );
196 }
197 if ( ! empty( $rule['cart_label_text'] ) ) {
198 Merchant_Translator::register_string( $rule['cart_label_text'], 'Label text on cart' );
199 }
200 }
201 }
202 }
203
204 /**
205 * Enqueue admin-specific CSS.
206 *
207 * @return void
208 */
209 public function admin_enqueue_css() {
210 if ( parent::is_module_settings_page() ) {
211 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/pre-orders.min.css', array(), MERCHANT_VERSION );
212 wp_enqueue_style( 'merchant-admin-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/admin/preview.min.css', array(), MERCHANT_VERSION );
213 }
214 }
215
216 /**
217 * Enqueue admin-specific JavaScript.
218 *
219 * @return void
220 */
221 public function admin_enqueue_js() {
222 if ( parent::is_module_settings_page() ) {
223 wp_enqueue_script(
224 'merchant-admin-' . self::MODULE_ID,
225 MERCHANT_URI . 'assets/js/modules/' . self::MODULE_ID . '/admin/preview.min.js',
226 array( 'jquery' ),
227 MERCHANT_VERSION,
228 true
229 );
230 }
231 }
232
233 /**
234 * Enqueue frontend-specific CSS.
235 *
236 * @return void
237 */
238 public function enqueue_css() {
239 // Specific module styles.
240 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/pre-orders.min.css', array(), MERCHANT_VERSION );
241
242 // add inline style
243 if ( is_singular( 'product' ) ) {
244 wp_add_inline_style( 'merchant-' . self::MODULE_ID, $this->add_inline_style() );
245 }
246 }
247
248 /**
249 * Add inline CSS styles based on the active rule.
250 *
251 * @return string The generated inline CSS.
252 */
253 public function add_inline_style() {
254 ob_start();
255 $rule = $this->current_rule();
256 if ( ! empty( $rule ) ) {
257 ?>
258 .woocommerce .merchant-pre-ordered-product{
259 --mrc-po-text-color: <?php echo esc_attr( $rule['text-color'] ?? '#FFF' ); ?>;
260 --mrc-po-text-hover-color: <?php echo esc_attr( $rule['text-hover-color'] ?? '#FFF' ); ?>;
261 --mrc-po-border-color: <?php echo esc_attr( $rule['border-color'] ?? '#212121' ); ?>;
262 --mrc-po-border-hover-color: <?php echo esc_attr( $rule['border-hover-color'] ?? '#414141' ); ?>;
263 --mrc-po-border-width: <?php echo esc_attr( ( $rule['border-width'] ?? 0 ) . 'px' ); ?>;
264 --mrc-po-border-radius: <?php echo esc_attr( ( $rule['border-radius'] ?? 0 ) . 'px' ); ?>;
265 --mrc-po-background-color: <?php echo esc_attr( $rule['background-color'] ?? '#212121' ); ?>;
266 --mrc-po-background-hover-color: <?php echo esc_attr( $rule['background-hover-color'] ?? '#414141' ); ?>;
267 }
268 <?php
269 }
270
271 return ob_get_clean();
272 }
273
274 /**
275 * Enqueue frontend-specific scripts.
276 *
277 * @return void
278 */
279 public function enqueue_scripts() {
280 // Register and enqueue the main module script.
281 wp_enqueue_script( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/js/modules/' . self::MODULE_ID . '/pre-orders.min.js', array(), MERCHANT_VERSION, true );
282 }
283
284 /**
285 * Localize the main module script with settings and translations.
286 *
287 * @param array $setting The localized settings array.
288 *
289 * @return array The updated settings array.
290 */
291 public function localize_script( $setting ) {
292 $setting['pre_orders'] = true;
293 $rule = $this->current_rule();
294 if ( ! empty( $rule ) && ! empty( $rule['button_text'] ) ) {
295 $setting['pre_orders_add_button_title'] = Merchant_Translator::translate( $rule['button_text'] );
296 } else {
297 $setting['pre_orders_add_button_title'] = esc_html__( 'Pre Order Now!', 'merchant' );
298 }
299
300 $setting['shipping_date_missing_text'] = esc_html__( 'Please set a shipping date first', 'merchant' );
301
302 return $setting;
303 }
304
305 /**
306 * Render the admin preview for the module.
307 *
308 * @param Merchant_Admin_Preview $preview The preview object.
309 * @param string $module The module ID.
310 *
311 * @return Merchant_Admin_Preview The updated preview object.
312 */
313 public function render_admin_preview( $preview, $module ) {
314 if ( self::MODULE_ID === $module ) {
315 $settings = $this->get_module_settings();
316
317 // Additional text.
318 $additional_text = $settings['additional_text'];
319 $time_format = date_i18n( get_option( 'date_format' ), strtotime( gmdate( 'Y-m-d', strtotime( '+2 days' ) ) ) );
320 $text = $this->main_func->replace_date_text( $additional_text, $time_format );
321
322 ob_start();
323 self::admin_preview_content( $settings, $text );
324 $content = ob_get_clean();
325
326 // HTML.
327 $preview->set_html( $content );
328
329 // Button Text.
330 $preview->set_text( 'button_text', '.add_to_cart_button' );
331
332 // Additional Text.
333 $preview->set_text( 'additional_text', '.merchant-pre-orders-date', array(
334 array(
335 '{date}',
336 ),
337 array(
338 $time_format,
339 ),
340 ) );
341 }
342
343 return $preview;
344 }
345
346 /**
347 * Admin preview HTML content.
348 *
349 * @param array $settings The module settings.
350 * @param string $text The formatted additional text.
351 *
352 * @return void
353 */
354 public function admin_preview_content( $settings, $text ) {
355 ?>
356 <div class="mrc-preview-single-product-elements">
357 <div class="mrc-preview-left-column">
358 <div class="mrc-preview-product-image-wrapper">
359 <div class="mrc-preview-product-image"></div>
360 <div class="mrc-preview-product-image-thumbs">
361 <div class="mrc-preview-product-image-thumb"></div>
362 <div class="mrc-preview-product-image-thumb"></div>
363 <div class="mrc-preview-product-image-thumb"></div>
364 </div>
365 </div>
366 </div>
367 <div class="mrc-preview-right-column">
368 <div class="mrc-preview-text-placeholder"></div>
369 <div class="mrc-preview-text-placeholder mrc-mw-70"></div>
370 <div class="mrc-preview-text-placeholder mrc-mw-40 mrc-hide-on-smaller-screens"></div>
371 <div class="mrc-preview-text-placeholder mrc-mw-30 mrc-hide-on-smaller-screens"></div>
372 <div class="merchant-pre-ordered-product">
373 <div class="merchant-pre-orders-date"><?php echo esc_html( $text ); ?></div>
374 <a href="#" class="add_to_cart_button"><?php echo esc_html( $settings['button_text'] ); ?></a>
375 </div>
376 </div>
377 </div>
378 <?php
379 }
380
381 /**
382 * Get Custom CSS for the module.
383 *
384 * @return string The generated CSS.
385 */
386 public function get_module_custom_css() {
387 $css = '';
388
389 if ( is_admin() || is_singular( 'product' ) ) {
390 // Text Color.
391 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'text-color', '#FFF', '.merchant-pre-ordered-product', '--mrc-po-text-color' );
392
393 // Text Color (hover).
394 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'text-hover-color', '#FFF', '.merchant-pre-ordered-product', '--mrc-po-text-hover-color' );
395
396 // Border Color.
397 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'border-color', '#212121', '.merchant-pre-ordered-product', '--mrc-po-border-color' );
398
399 // Border Color (hover).
400 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'border-hover-color', '#414141', '.merchant-pre-ordered-product', '--mrc-po-border-hover-color' );
401
402 // Border Width.
403 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'border-width', 0, '.merchant-pre-ordered-product', '--mrc-po-border-width', 'px' );
404
405 // Border Radius.
406 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'border-radius', 0, '.merchant-pre-ordered-product', '--mrc-po-border-radius', 'px' );
407
408 // Background Color.
409 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'background-color', '#212121', '.merchant-pre-ordered-product', '--mrc-po-background-color' );
410
411 // Background Color (hover).
412 $css .= Merchant_Custom_CSS::get_variable_css( 'pre-orders', 'background-hover-color', '#414141', '.merchant-pre-ordered-product', '--mrc-po-background-hover-color' );
413 }
414
415 return $css;
416 }
417
418 /**
419 * Filter for admin custom CSS.
420 *
421 * @param string $css The custom CSS.
422 *
423 * @return string The updated custom CSS.
424 */
425 public function admin_custom_css( $css ) {
426 $css .= $this->get_module_custom_css();
427
428 return $css;
429 }
430
431 /**
432 * Filter for frontend custom CSS.
433 *
434 * @param string $css The custom CSS.
435 *
436 * @return string The updated custom CSS.
437 */
438 public function frontend_custom_css( $css ) {
439 $css .= $this->get_module_custom_css();
440
441 return $css;
442 }
443
444 /**
445 * Get the currently applicable product rule.
446 *
447 * @return array The rule array if found, empty array otherwise.
448 */
449 private function current_rule() {
450 if ( is_singular( 'product' ) ) {
451 $product_id = get_queried_object_id();
452 $product = wc_get_product( $product_id );
453
454 if ( ! $product ) {
455 return array();
456 }
457
458 $rule = Merchant_Pre_Orders_Main_Functionality::available_product_rule( $product->get_id() );
459 if ( empty( $rule ) && $product instanceof \WC_Product_Variable ) {
460 $available_variations = $product->get_available_variations();
461 foreach ( $available_variations as $variation ) {
462 $rule = Merchant_Pre_Orders_Main_Functionality::available_product_rule( $variation['variation_id'] );
463 if ( ! empty( $rule ) ) {
464 break;
465 }
466 }
467 }
468
469 return $rule;
470 }
471
472 return array();
473 }
474
475 /**
476 * Get the WooCommerce orders screen URL, aware of HPOS.
477 *
478 * @return string The orders screen URL.
479 */
480 private function orders_screen_url() {
481 if ( class_exists( \Automattic\WooCommerce\Utilities\OrderUtil::class ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) {
482 return admin_url( 'admin.php?page=wc-orders' );
483 }
484
485 return admin_url( 'edit.php?post_type=shop_order' );
486 }
487
488 /**
489 * Display a help banner in the module settings page.
490 *
491 * @param string $module_id The module ID.
492 *
493 * @return void
494 */
495 public function help_banner( $module_id ) {
496 if ( $module_id === self::MODULE_ID ) {
497 $orders_url = $this->orders_screen_url();
498 ?>
499 <div class="merchant-module-page-setting-fields">
500 <div class="merchant-module-page-setting-field merchant-module-page-setting-field-content">
501 <div class="merchant-module-page-setting-field-inner">
502 <div class="merchant-tag-pre-orders">
503 <i class="dashicons dashicons-info"></i>
504 <p>
505 <?php
506 echo esc_html__(
507 'Pre-orders captured by Merchant are tagged with "Merchant PreOrder" and can be found in your WooCommerce Order Section. You can control pre-order settings on a per-product basis from the individual product page.',
508 'merchant'
509 );
510 printf(
511 '<a href="%1s" target="_blank">%2s</a>',
512 esc_url( $orders_url ),
513 esc_html__( 'View Pre-Orders', 'merchant' )
514 );
515 ?></p>
516 </div>
517 </div>
518 </div>
519 </div>
520 <?php
521 }
522 }
523
524 /**
525 * Supply missing flexible_id for the rules setting.
526 *
527 * @param string $setting_key The setting key for the flexible items.
528 *
529 * @return void
530 */
531 public function supply_missing_id( $setting_key = 'offers' ) {
532 $option = 'merchant_' . $this->module_id . '_missing_id_flag';
533
534 if ( get_option( $option, false ) || ! method_exists( 'Merchant_Admin_Options', 'set' ) ) {
535 return;
536 }
537
538 $flexible_items = Merchant_Admin_Options::get( $this->module_id, $setting_key, array() );
539 if ( ! empty( $flexible_items ) ) {
540 $update = 0;
541 foreach ( $flexible_items as $key => $item ) {
542 if ( empty( $item['flexible_id'] ) ) {
543 $flexible_items[ $key ]['flexible_id'] = wp_generate_uuid4();
544 ++ $update;
545 }
546 }
547
548 if ( $update > 0 ) {
549 Merchant_Admin_Options::set( $this->module_id, $setting_key, $flexible_items );
550 }
551
552 update_option( $option, true, false );
553 }
554 }
555 }
556
557 // Rules Repository.
558 require_once MERCHANT_DIR . 'inc/modules/pre-orders/classes/class-pre-orders-rules.php';
559
560 // Main functionality.
561 require_once MERCHANT_DIR . 'inc/modules/pre-orders/class-pre-orders-main-functionality.php';
562
563 // Initialize the module.
564 add_action( 'init', function () {
565 Merchant_Modules::create_module( new Merchant_Pre_Orders( new Merchant_Pre_Orders_Main_Functionality() ) );
566 } );
567