PluginProbe
seQura / 3.1.1
seQura v3.1.1
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / src / Controllers / Hooks / Product / class-product-controller.php

class-product-controller.php in seQura 3.1.1, at src/Controllers/Hooks/Product/class-product-controller.php

457 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Product Controller
4 *
5 * @package SeQura/WC
6 * @subpackage SeQura/WC/Controllers
7 */
8
9 namespace SeQura\WC\Controllers\Hooks\Product;
10
11 use SeQura\Core\Infrastructure\Logger\LogContextData;
12 use SeQura\WC\Controllers\Controller;
13 use SeQura\WC\Core\Extension\Infrastructure\Configuration\Configuration;
14 use SeQura\WC\Services\I18n\Interface_I18n;
15 use SeQura\WC\Services\Interface_Logger_Service;
16 use SeQura\WC\Services\Payment\Interface_Payment_Method_Service;
17 use SeQura\WC\Services\Payment\Interface_Payment_Service;
18 use SeQura\WC\Services\Product\Interface_Product_Service;
19 use SeQura\WC\Services\Regex\Regex;
20 use WC_Product;
21 use WP_Post;
22
23 /**
24 * Product Controller implementation
25 */
26 class Product_Controller extends Controller implements Interface_Product_Controller {
27
28 private const FIELD_NAME_IS_BANNED = 'is_sequra_banned';
29 private const FIELD_NAME_IS_SERVICE = 'is_sequra_service';
30 private const FIELD_NAME_SERVICE_END_DATE = 'sequra_service_end_date';
31 private const FIELD_NAME_SERVICE_DESIRED_FIRST_CHARGE_DATE = 'sequra_desired_first_charge_date';
32 private const FIELD_NAME_REGISTRATION_AMOUNT = 'sequra_registration_amount';
33 private const NONCE_SEQURA_PRODUCT = '_sequra_product_nonce';
34
35 /**
36 * I18n service
37 *
38 * @var Interface_I18n
39 */
40 private $i18n;
41
42 /**
43 * Settings service
44 *
45 * @var Configuration
46 */
47 private $configuration;
48
49 /**
50 * Product service
51 *
52 * @var Interface_Product_Service
53 */
54 private $product_service;
55
56 /**
57 * Payment service
58 *
59 * @var Interface_Payment_Service
60 */
61 private $payment_service;
62
63 /**
64 * Payment method service
65 *
66 * @var Interface_Payment_Method_Service
67 */
68 private $payment_method_service;
69
70 /**
71 * RegEx service
72 *
73 * @var Regex
74 */
75 private $regex;
76
77 /**
78 * Constructor
79 */
80 public function __construct(
81 Interface_Logger_Service $logger,
82 string $templates_path,
83 Configuration $configuration,
84 Interface_Product_Service $product_service,
85 Interface_Payment_Service $payment_service,
86 Interface_Payment_Method_Service $payment_method_service,
87 Interface_I18n $i18n,
88 Regex $regex
89 ) {
90 parent::__construct( $logger, $templates_path );
91 $this->logger = $logger;
92 $this->configuration = $configuration;
93 $this->product_service = $product_service;
94 $this->payment_service = $payment_service;
95 $this->payment_method_service = $payment_method_service;
96 $this->i18n = $i18n;
97 $this->regex = $regex;
98 }
99
100 /**
101 * Handle the widget shortcode callback
102 * [sequra_widget]
103 * Expected attributes:
104 * - product: The seQura product identifier. Required.
105 * - campaign: The seQura campaign name. Optional.
106 * - dest: A CSS selector to place the widget. Required.
107 * - product_id: The WooCommerce product identifier. Required
108 * - price: A CSS selector to get the product price. Optional.
109 * - alt_price: An alternative CSS selector to retrieve the product price for special product page layouts. Optional.
110 * - is_alt_price: A CSS selector to determine if the product has an alternative price. Optional.
111 * - reg_amount: The registration amount. Optional.
112 * - theme: The theme to use. Accepted values are: L, R, legacy, legacyL, legacyR, minimal, minimalL, minimalR or JSON formatted string. Optional.
113 *
114 * @param array<string, string> $atts The shortcode attributes
115 * @return string
116 */
117 public function do_widget_shortcode( $atts ) {
118 $this->logger->log_debug( 'Shortcode called', __FUNCTION__, __CLASS__ );
119 $atts = (array) $atts;
120
121 // Check for required attributes.
122 foreach ( array( 'product', 'product_id' ) as $required ) {
123 if ( ! isset( $atts[ $required ] ) ) {
124 $this->logger->log_error( "\"$required\" attribute is required", __FUNCTION__, __CLASS__ );
125 return '';
126 }
127 }
128
129 $current_country = $this->i18n->get_current_country();
130 $campaign = isset( $atts['campaign'] ) && '' !== $atts['campaign'] ? $atts['campaign'] : null;
131
132 if ( ! $this->configuration->is_widget_enabled( $atts['product'], $campaign, $current_country ) ) {
133 $this->logger->log_info( 'Widget is disabled', __FUNCTION__, __CLASS__ );
134 return '';
135 }
136
137 // Replace old attribute names introduced in 2.0.0 with the new ones.
138 $atts_replacements = array(
139 'variation_price' => 'alt_price',
140 'is_variable' => 'is_alt_price',
141 );
142
143 foreach ( $atts_replacements as $old => $new ) {
144 if ( isset( $atts[ $old ] ) ) {
145 $atts[ $new ] = $atts[ $old ];
146 unset( $atts[ $old ] );
147 }
148 }
149
150 $atts = \shortcode_atts(
151 array(
152 'product' => '',
153 'campaign' => '',
154 'product_id' => '',
155 'theme' => $this->configuration->get_widget_theme( $atts['product'], $campaign, $current_country ),
156 'reverse' => 0,
157 'reg_amount' => $this->product_service->get_registration_amount( (int) $atts['product_id'], true ),
158 'dest' => $this->configuration->get_widget_dest_css_sel( $atts['product'], $campaign, $current_country ),
159 'price' => $this->configuration->get_widget_price_css_sel(),
160 'alt_price' => $this->configuration->get_widget_alt_price_css_sel(),
161 'is_alt_price' => $this->configuration->get_widget_is_alt_price_css_sel(),
162 ),
163 $atts,
164 'sequra_widget'
165 );
166
167 if ( ! $this->product_service->can_display_widget_for_method( (int) $atts['product_id'], $atts['product'] ) ) {
168 $this->logger->log_info(
169 'Widget cannot be displayed for product',
170 __FUNCTION__,
171 __CLASS__,
172 array(
173 new LogContextData( 'payment_method', $atts['product'] ),
174 new LogContextData( 'campaign', $atts['campaign'] ),
175 new LogContextData( 'product_id', $atts['product_id'] ),
176 )
177 );
178 return '';
179 }
180
181 ob_start();
182 \wc_get_template( 'front/widget.php', $atts, '', $this->templates_path );
183 return ob_get_clean();
184 }
185
186 /**
187 * Handle the cart widget shortcode callback
188 *
189 * @param array<string, string> $atts The shortcode attributes
190 * @return string
191 */
192 public function do_cart_widget_shortcode( $atts ) {
193 $this->logger->log_debug( 'Shortcode called', __FUNCTION__, __CLASS__ );
194 $atts = (array) $atts;
195
196 $current_country = $this->i18n->get_current_country();
197
198 if ( ! $this->configuration->is_cart_widget_enabled( $current_country ) ) {
199 $this->logger->log_info( 'Cart Widget is disabled', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
200 return '';
201 }
202 if ( ! $this->product_service->can_display_mini_widgets() ) {
203 $this->logger->log_info( 'SeQura payment gateway is disabled', __FUNCTION__, __CLASS__ );
204 return '';
205 }
206
207 try {
208 $store_id = $this->configuration->get_store_id();
209 $merchant = $this->payment_service->get_merchant_id();
210 $methods = $this->payment_method_service->get_all_mini_widget_compatible_payment_methods( $store_id, $merchant );
211
212 $config = $this->configuration->get_cart_widget_config( $current_country );
213 if ( ! $config ) {
214 $this->logger->log_info( 'Cart Widget configuration not found', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
215 return '';
216 }
217
218
219
220 foreach ( $methods as $method ) {
221 if ( $method['product'] !== $config['product'] ) {
222 continue;
223 }
224
225 $atts = \shortcode_atts(
226 array(
227 'product' => $method['product'] ?? '',
228 'campaign' => $method['campaign'] ?? '',
229 'dest' => $config['selForLocation'],
230 'price' => $config['selForPrice'],
231 'message' => $config['message'],
232 'message_below_limit' => $config['messageBelowLimit'],
233 'min_amount' => $method['minAmount'] ?? 0,
234 'max_amount' => $method['maxAmount'] ?? null,
235 ),
236 $atts,
237 'sequra_cart_widget'
238 );
239
240 ob_start();
241 \wc_get_template( 'front/mini_widget.php', $atts, '', $this->templates_path );
242 return ob_get_clean();
243 }
244 } catch ( \Throwable $e ) {
245 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
246 }
247 return '';
248 }
249
250 /**
251 * Handle the product listing widget shortcode callback
252 *
253 * @param array<string, string> $atts The shortcode attributes
254 * @return string
255 */
256 public function do_product_listing_widget_shortcode( $atts ) {
257 $this->logger->log_debug( 'Shortcode called', __FUNCTION__, __CLASS__ );
258 $atts = (array) $atts;
259
260 $current_country = $this->i18n->get_current_country();
261
262 if ( ! $this->configuration->is_product_listing_widget_enabled( $current_country ) ) {
263 $this->logger->log_info( 'Product listing Widget is disabled', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
264 return '';
265 }
266 if ( ! $this->product_service->can_display_mini_widgets() ) {
267 $this->logger->log_info( 'SeQura payment gateway is disabled', __FUNCTION__, __CLASS__ );
268 return '';
269 }
270
271 try {
272 $store_id = $this->configuration->get_store_id();
273 $merchant = $this->payment_service->get_merchant_id();
274 $methods = $this->payment_method_service->get_all_mini_widget_compatible_payment_methods( $store_id, $merchant );
275
276 $config = $this->configuration->get_product_listing_widget_config( $current_country );
277 if ( ! $config ) {
278 $this->logger->log_info( 'Product listing Widget configuration not found', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
279 return '';
280 }
281
282 foreach ( $methods as $method ) {
283 if ( $method['product'] !== $config['product'] ) {
284 continue;
285 }
286
287 $atts = \shortcode_atts(
288 array(
289 'product' => $method['product'] ?? '',
290 'campaign' => $method['campaign'] ?? '',
291 'dest' => $config['selForLocation'],
292 'price' => $config['selForPrice'],
293 'message' => $config['message'],
294 'message_below_limit' => $config['messageBelowLimit'],
295 'min_amount' => $method['minAmount'] ?? 0,
296 'max_amount' => $method['maxAmount'] ?? null,
297 ),
298 $atts,
299 'sequra_product_listing_widget'
300 );
301
302 ob_start();
303 \wc_get_template( 'front/mini_widget.php', $atts, '', $this->templates_path );
304 return ob_get_clean();
305 }
306 } catch ( \Throwable $e ) {
307 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
308 }
309 return '';
310 }
311
312 /**
313 * Add [sequra_widget] to product page automatically
314 */
315 private function add_widget_shortcode_to_product_page(): void {
316 if ( ! \is_product() ) {
317 return;
318 }
319 /**
320 * The current product.
321 *
322 * @var WC_Product $product
323 */
324 global $product;
325
326 $methods = array();
327 try {
328 $store_id = $this->configuration->get_store_id();
329 $merchant = $this->payment_service->get_merchant_id();
330 $methods = $this->payment_method_service->get_all_widget_compatible_payment_methods( $store_id, $merchant );
331 } catch ( \Throwable $e ) {
332 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
333 return;
334 }
335
336 foreach ( $methods as $method ) {
337 if ( ! $this->product_service->can_display_widget_for_method( $product, $method ) ) {
338 continue;
339 }
340
341 $atts = array(
342 'product' => $method['product'] ?? '',
343 'campaign' => $method['campaign'] ?? '',
344 'product_id' => $product->get_id(),
345 );
346
347 $atts_str = '';
348 foreach ( $atts as $key => $value ) {
349 $atts_str .= " $key=\"$value\"";
350 }
351 echo \do_shortcode( "[sequra_widget $atts_str]" );
352 }
353 }
354
355 /**
356 * Add [sequra_cart_widget] to product page automatically
357 */
358 private function add_widget_shortcode_to_cart_page(): void {
359 if ( ! \is_cart() ) {
360 return;
361 }
362 echo \do_shortcode( '[sequra_cart_widget]' );
363 }
364
365 /**
366 * Add [sequra_product_listing_widget] to product archive automatically
367 */
368 private function add_widget_shortcode_to_product_listing_page(): void {
369 if ( ! \is_product_category() && ! \is_product_tag() && ! \is_shop() ) {
370 return;
371 }
372 echo \do_shortcode( '[sequra_product_listing_widget]' );
373 }
374
375 /**
376 * Add [sequra_widget] to product page automatically
377 * Add [sequra_cart_widget] to cart page automatically
378 *
379 * @return void
380 */
381 public function add_widget_shortcode_to_page() {
382 $this->logger->log_debug( 'Hook executed', __FUNCTION__, __CLASS__ );
383 if ( \did_action( 'before_woocommerce_sequra_add_widget_to_page' ) ) {
384 return;
385 }
386
387 /**
388 * Fires before the SeQura widget is added to the page to prevent multiple executions.
389 *
390 * @since 3.0.0
391 */
392 \do_action( 'before_woocommerce_sequra_add_widget_to_page' );
393
394 $this->add_widget_shortcode_to_product_page();
395 $this->add_widget_shortcode_to_cart_page();
396 $this->add_widget_shortcode_to_product_listing_page();
397 }
398
399 /**
400 * Add meta boxes to the product edit page
401 *
402 * @return void
403 */
404 public function add_meta_boxes() {
405 \add_meta_box( 'sequra_settings', esc_html__( 'seQura settings', 'sequra' ), array( $this, 'render_meta_boxes' ), 'product', 'side', 'default' );
406 }
407
408 /**
409 * Render the meta boxes
410 *
411 * @param WP_Post $post The post object
412 * @return void
413 */
414 public function render_meta_boxes( $post ) {
415 $args = array(
416 'is_banned' => $this->product_service->get_is_banned( $post->ID ),
417 'is_banned_field_name' => self::FIELD_NAME_IS_BANNED,
418 'enabled_for_services' => $this->configuration->is_enabled_for_services(),
419 'is_service' => $this->product_service->is_service( $post->ID ),
420 'is_service_field_name' => self::FIELD_NAME_IS_SERVICE,
421 'service_end_date_default' => $this->configuration->get_default_services_end_date(),
422 'service_end_date' => $this->product_service->get_service_end_date( $post->ID, true ),
423 'service_end_date_field_name' => self::FIELD_NAME_SERVICE_END_DATE,
424 'allow_payment_delay' => $this->configuration->allow_first_service_payment_delay(),
425 'service_desired_first_charge_date' => $this->product_service->get_desired_first_charge_date( $post->ID, true ) ?? '',
426 'service_desired_first_charge_date_field_name' => self::FIELD_NAME_SERVICE_DESIRED_FIRST_CHARGE_DATE,
427 'date_or_duration_regex' => $this->regex->date_or_duration( false ),
428 'allow_registration_items' => $this->configuration->allow_service_reg_items(),
429 'service_registration_amount' => $this->product_service->get_registration_amount( $post->ID ),
430 'service_registration_amount_field_name' => self::FIELD_NAME_REGISTRATION_AMOUNT,
431 'nonce_name' => self::NONCE_SEQURA_PRODUCT,
432 );
433 \wc_get_template( 'admin/product_metabox.php', $args, '', $this->templates_path );
434 }
435
436 /**
437 * Save product meta
438 *
439 * @param int $post_id The post ID
440 * @return void
441 */
442 public function save_product_meta( $post_id ) {
443 if ( ! isset( $_POST[ self::NONCE_SEQURA_PRODUCT ] )
444 || ! \wp_verify_nonce( $_POST[ self::NONCE_SEQURA_PRODUCT ], -1 ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
445 || ! \current_user_can( 'edit_post', $post_id )
446 || \wp_doing_ajax() ) {
447 return;
448 }
449
450 $this->product_service->set_is_banned( $post_id, isset( $_POST[ self::FIELD_NAME_IS_BANNED ] ) ? \sanitize_text_field( \wp_unslash( $_POST[ self::FIELD_NAME_IS_BANNED ] ) ) : null );
451 $this->product_service->set_is_service( $post_id, isset( $_POST[ self::FIELD_NAME_IS_SERVICE ] ) ? \sanitize_text_field( \wp_unslash( $_POST[ self::FIELD_NAME_IS_SERVICE ] ) ) : null );
452 $this->product_service->set_service_end_date( $post_id, ! empty( $_POST[ self::FIELD_NAME_SERVICE_END_DATE ] ) ? \sanitize_text_field( \wp_unslash( $_POST[ self::FIELD_NAME_SERVICE_END_DATE ] ) ) : null );
453 $this->product_service->set_desired_first_charge_date( $post_id, ! empty( $_POST[ self::FIELD_NAME_SERVICE_DESIRED_FIRST_CHARGE_DATE ] ) ? \sanitize_text_field( \wp_unslash( $_POST[ self::FIELD_NAME_SERVICE_DESIRED_FIRST_CHARGE_DATE ] ) ) : null );
454 $this->product_service->set_registration_amount( $post_id, ! empty( $_POST[ self::FIELD_NAME_REGISTRATION_AMOUNT ] ) ? \sanitize_text_field( \wp_unslash( $_POST[ self::FIELD_NAME_REGISTRATION_AMOUNT ] ) ) : null );
455 }
456 }
457