PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.1
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.1
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 / buy-now / class-buy-now.php

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

316 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Buy Now.
5 *
6 * @package Merchant
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 /**
14 * Buy Now Class.
15 *
16 */
17 class Merchant_Buy_Now extends Merchant_Add_Module {
18
19 /**
20 * Module ID.
21 *
22 */
23 const MODULE_ID = 'buy-now';
24
25 /**
26 * Is module preview.
27 *
28 * @var bool
29 */
30 public static $is_module_preview = false;
31
32 /**
33 * Campaign resolver instance.
34 *
35 * @var Merchant_Buy_Now_Resolver|null
36 */
37 private $resolver;
38
39
40
41 /**
42 * Assets instance.
43 *
44 * @var Merchant_Buy_Now_Assets|null
45 */
46 private $assets;
47
48 /**
49 * Button renderer instance.
50 *
51 * @var Merchant_Buy_Now_Button_Renderer|null
52 */
53 private $button_renderer;
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 * Constructor.
66 *
67 */
68 public function __construct() {
69
70 // Module id.
71 $this->module_id = self::MODULE_ID;
72
73 // WooCommerce only.
74 $this->wc_only = true;
75
76 parent::__construct();
77
78 // Module section.
79 $this->module_section = 'reduce-abandonment';
80
81 // Module default settings.
82 $this->module_default_settings = array(
83 'button-text' => __( 'Buy Now', 'merchant' ),
84 'customize-button' => 1,
85 );
86
87 // Module data.
88 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
89
90 // AI prompt examples.
91 $this->ai_examples = array(
92 'blurb' => esc_html__( "See what AI can do for your Buy Now campaigns, from launching one scoped to a category to customizing the button's text, icon, and where it sends shoppers after purchase.", 'merchant' ),
93 'prompts' => array(
94 esc_html__( 'Explore the abilities WPVibe gives you access to, then create a Buy Now campaign called Summer Rush that shows the button on the Summer Essentials category', 'merchant' ),
95 esc_html__( "Check what abilities you have available through WPVibe, then change the Summer Rush Buy Now campaign's button text to 'Checkout Now' and show the lightning icon before it", 'merchant' ),
96 esc_html__( 'Look at your available WPVibe abilities, then set the Summer Rush Buy Now campaign to clear the existing cart before adding the product and redirect shoppers to a custom URL after purchase', 'merchant' ),
97 esc_html__( 'See what abilities WPVibe exposes, then exclude the Beach Towel product from the Summer Rush Buy Now campaign', 'merchant' ),
98 ),
99 );
100
101 // Module options path.
102 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
103
104 // Is module preview page.
105 if ( is_admin() && parent::is_module_settings_page() ) {
106 self::$is_module_preview = true;
107
108 // Admin preview (delegated to extracted class).
109 $admin_preview = new Merchant_Buy_Now_Admin_Preview();
110 add_action( 'admin_enqueue_scripts', array( $admin_preview, 'enqueue_css' ) );
111 add_filter( 'merchant_module_preview', function ( $preview, $module ) use ( $admin_preview ) {
112 return $admin_preview->render( $preview, $module, $this->get_module_settings() );
113 }, 10, 2 );
114
115 // Custom CSS.
116 // The custom CSS should be added here as well due to ensure preview box works properly.
117 add_filter( 'merchant_custom_css', array( $this, 'admin_custom_css' ) );
118
119 }
120
121 if ( Merchant_Modules::is_module_active( self::MODULE_ID ) && is_admin() ) {
122 // Init translations.
123 $this->init_translations();
124 }
125
126 if ( ! Merchant_Modules::is_module_active( self::MODULE_ID ) ) {
127 return;
128 }
129
130 // Run migration if needed.
131 $migration = new Merchant_Buy_Now_Migration();
132 $migration->maybe_migrate();
133
134 // Initialize the resolver with campaigns from settings.
135 $settings = $this->get_module_settings();
136 $campaigns = $settings['campaigns'] ?? array();
137 $this->resolver = new Merchant_Buy_Now_Resolver( $campaigns );
138
139
140 $this->assets = new Merchant_Buy_Now_Assets();
141
142 // Allow other modules (like Quick View) to check if a product is excluded.
143 add_filter( 'merchant_buy_now_is_excluded', array( $this, 'check_product_exclusion' ), 10, 2 );
144
145 // Return early if it's on admin but not in the respective module settings page.
146 if ( is_admin() && ! wp_doing_ajax() && ! parent::is_module_settings_page() ) {
147 return;
148 }
149
150 // Enqueue frontend assets (delegated to extracted class).
151 add_action( 'merchant_enqueue_before_main_css_js', array( $this->assets, 'enqueue_css' ) );
152 add_action( 'merchant_enqueue_before_main_css_js', array( $this->assets, 'enqueue_scripts' ) );
153
154 // Buy now listener (delegated to extracted class).
155 $listener = new Merchant_Buy_Now_Listener( $this->resolver );
156 add_action( 'wp_loaded', array( $listener, 'handle_simple' ) );
157 add_action( 'wp_loaded', array( $listener, 'handle_grouped' ), 999 );
158 add_action( 'wp_ajax_merchant_buy_now_add_to_cart', array( $listener, 'ajax_add_to_cart' ) );
159 add_action( 'wp_ajax_nopriv_merchant_buy_now_add_to_cart', array( $listener, 'ajax_add_to_cart' ) );
160
161 // Button rendering (delegated to extracted class).
162 $this->button_renderer = new Merchant_Buy_Now_Button_Renderer( $this->resolver, $settings );
163
164 // Single product buy now button.
165 $single_product_hook = ! empty( $settings['hook-order-single-product'] ) ? $settings['hook-order-single-product'] : array(
166 'hook_name' => 'woocommerce_after_add_to_cart_button',
167 'hook_priority' => 10,
168 );
169 add_action( $single_product_hook['hook_name'], array( $this->button_renderer, 'single_product_button' ), $single_product_hook['hook_priority'] );
170
171 // Shop archive buy now button.
172 $shop_archive_hook = ! empty( $settings['hook-order-shop-archive'] ) ? $settings['hook-order-shop-archive'] : array(
173 'hook_name' => 'woocommerce_after_shop_loop_item',
174 'hook_priority' => 10,
175 );
176 add_action( $shop_archive_hook['hook_name'], array( $this->button_renderer, 'shop_archive_button' ), $shop_archive_hook['hook_priority'] );
177
178 // Custom CSS.
179 add_filter( 'merchant_custom_css', array( $this, 'frontend_custom_css' ) );
180
181 // Module wrapper class.
182 add_filter( 'merchant_module_buy_now_wrapper_class', array( $this, 'html_wrapper_class' ) );
183 }
184
185 /**
186 * Init translations.
187 *
188 * @return void
189 */
190 public function init_translations() {
191 $settings = $this->get_module_settings();
192 $campaigns = $settings['campaigns'] ?? array();
193
194 foreach ( $campaigns as $campaign ) {
195 if ( ! empty( $campaign['button_text'] ) ) {
196 Merchant_Translator::register_string( $campaign['button_text'], esc_html__( 'Buy now button text', 'merchant' ) );
197 }
198
199
200 }
201
202 // Legacy support.
203 if ( ! empty( $settings['button-text'] ) ) {
204 Merchant_Translator::register_string( $settings['button-text'], esc_html__( 'Buy now button text', 'merchant' ) );
205 }
206 }
207
208 /**
209 * Get the button renderer instance.
210 *
211 * Used by compatibility layers (e.g. Ohio theme) that need to
212 * re-hook the archive button to a different action.
213 *
214 * @return Merchant_Buy_Now_Button_Renderer|null
215 */
216 public function get_button_renderer() {
217 return $this->button_renderer;
218 }
219
220 /**
221 * Custom CSS.
222 *
223 * @return string
224 */
225 public function get_module_custom_css() {
226 if ( ! $this->assets ) {
227 $this->assets = new Merchant_Buy_Now_Assets();
228 }
229
230 return $this->assets->get_custom_css();
231 }
232
233 /**
234 * Admin custom CSS.
235 *
236 * @param string $css The custom CSS.
237 * @return string $css The custom CSS.
238 */
239 public function admin_custom_css( $css ) {
240 $css .= $this->get_module_custom_css();
241
242 return $css;
243 }
244
245 /**
246 * Frontend custom CSS.
247 *
248 * @param string $css The custom CSS.
249 * @return string $css The custom CSS.
250 */
251 public function frontend_custom_css( $css ) {
252 $css .= $this->get_module_custom_css();
253
254 return $css;
255 }
256
257 /**
258 * HTML wrapper class.
259 *
260 * @param array<int, string> $classes The wrapper classes.
261 *
262 * @return array<int, string> $classes The wrapper classes.
263 */
264 public function html_wrapper_class( $classes ) {
265 $settings = $this->get_module_settings();
266
267 if ( ! empty( $settings['customize-button'] ) ) {
268 $classes[] = 'merchant-custom-buy-now-button';
269 }
270
271 return $classes;
272 }
273
274 /**
275 * Check if a product should be excluded from the Buy Now button.
276 *
277 * This method is called by the 'merchant_buy_now_is_excluded' filter,
278 * allowing other modules (like Quick View) to check exclusion status
279 * without directly accessing the resolver.
280 *
281 * @param bool $is_excluded Current exclusion status.
282 * @param WC_Product $product The product to check.
283 *
284 * @return bool True if the product should be excluded, false otherwise.
285 * @since 2.2.4
286 */
287 public function check_product_exclusion( $is_excluded, $product ) {
288 // If already excluded by another filter, respect that decision.
289 if ( $is_excluded ) {
290 return $is_excluded;
291 }
292
293 // No matching campaign = product is excluded.
294 if ( $this->resolver ) {
295 return null === $this->resolver->resolve( $product );
296 }
297
298 return false;
299 }
300 }
301
302 // Load campaign architecture classes.
303 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-icons.php';
304 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-resolver.php';
305 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-migration.php';
306
307 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-assets.php';
308 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-listener.php';
309 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-admin-preview.php';
310 require_once MERCHANT_DIR . 'inc/modules/buy-now/class-buy-now-button-renderer.php';
311
312 // Initialize the module.
313 add_action( 'init', function() {
314 Merchant_Modules::create_module( new Merchant_Buy_Now() );
315 } );
316