PluginProbe
seQura / 3.0.2
seQura v3.0.2
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.0.2, at src/Controllers/Hooks/Product/class-product-controller.php

437 lines 15.6 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 */
116 public function do_widget_shortcode( array $atts ): string {
117 $this->logger->log_info( 'Shortcode called', __FUNCTION__, __CLASS__ );
118
119 // Check for required attributes.
120 foreach ( array( 'product', 'product_id' ) as $required ) {
121 if ( ! isset( $atts[ $required ] ) ) {
122 $this->logger->log_error( "\"$required\" attribute is required", __FUNCTION__, __CLASS__ );
123 return '';
124 }
125 }
126
127 $current_country = $this->i18n->get_current_country();
128 $campaign = isset( $atts['campaign'] ) && '' !== $atts['campaign'] ? $atts['campaign'] : null;
129
130 if ( ! $this->configuration->is_widget_enabled( $atts['product'], $campaign, $current_country ) ) {
131 $this->logger->log_info( 'Widget is disabled', __FUNCTION__, __CLASS__ );
132 return '';
133 }
134
135 // Replace old attribute names introduced in 2.0.0 with the new ones.
136 $atts_replacements = array(
137 'variation_price' => 'alt_price',
138 'is_variable' => 'is_alt_price',
139 );
140
141 foreach ( $atts_replacements as $old => $new ) {
142 if ( isset( $atts[ $old ] ) ) {
143 $atts[ $new ] = $atts[ $old ];
144 unset( $atts[ $old ] );
145 }
146 }
147
148 $atts = shortcode_atts(
149 array(
150 'product' => '',
151 'campaign' => '',
152 'product_id' => '',
153 'theme' => $this->configuration->get_widget_theme( $atts['product'], $campaign, $current_country ),
154 'reverse' => 0,
155 'reg_amount' => $this->product_service->get_registration_amount( (int) $atts['product_id'], true ),
156 'dest' => $this->configuration->get_widget_dest_css_sel( $atts['product'], $campaign, $current_country ),
157 'price' => $this->configuration->get_widget_price_css_sel(),
158 'alt_price' => $this->configuration->get_widget_alt_price_css_sel(),
159 'is_alt_price' => $this->configuration->get_widget_is_alt_price_css_sel(),
160 ),
161 $atts,
162 'sequra_widget'
163 );
164
165 if ( ! $this->product_service->can_display_widget_for_method( (int) $atts['product_id'], $atts['product'] ) ) {
166 $this->logger->log_info(
167 'Widget cannot be displayed for product',
168 __FUNCTION__,
169 __CLASS__,
170 array(
171 new LogContextData( 'payment_method', $atts['product'] ),
172 new LogContextData( 'campaign', $atts['campaign'] ),
173 new LogContextData( 'product_id', $atts['product_id'] ),
174 )
175 );
176 return '';
177 }
178
179 ob_start();
180 wc_get_template( 'front/widget.php', $atts, '', $this->templates_path );
181 return ob_get_clean();
182 }
183
184 /**
185 * Handle the cart widget shortcode callback
186 */
187 public function do_cart_widget_shortcode( array $atts ): string {
188 $this->logger->log_info( 'Shortcode called', __FUNCTION__, __CLASS__ );
189
190 $current_country = $this->i18n->get_current_country();
191
192 if ( ! $this->configuration->is_cart_widget_enabled( $current_country ) ) {
193 $this->logger->log_info( 'Cart Widget is disabled', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
194 return '';
195 }
196 if ( ! $this->product_service->can_display_mini_widgets() ) {
197 $this->logger->log_info( 'SeQura payment gateway is disabled', __FUNCTION__, __CLASS__ );
198 return '';
199 }
200
201 try {
202 $store_id = $this->configuration->get_store_id();
203 $merchant = $this->payment_service->get_merchant_id();
204 $methods = $this->payment_method_service->get_all_mini_widget_compatible_payment_methods( $store_id, $merchant );
205
206 $config = $this->configuration->get_cart_widget_config( $current_country );
207 if ( ! $config ) {
208 $this->logger->log_info( 'Cart Widget configuration not found', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
209 return '';
210 }
211
212
213
214 foreach ( $methods as $method ) {
215 if ( $method['product'] !== $config['product'] ) {
216 continue;
217 }
218
219 $atts = shortcode_atts(
220 array(
221 'product' => $method['product'] ?? '',
222 'campaign' => $method['campaign'] ?? '',
223 'dest' => $config['selForLocation'],
224 'price' => $config['selForPrice'],
225 'message' => $config['message'],
226 'message_below_limit' => $config['messageBelowLimit'],
227 'min_amount' => $method['minAmount'] ?? 0,
228 'max_amount' => $method['maxAmount'] ?? null,
229 ),
230 $atts,
231 'sequra_cart_widget'
232 );
233
234 ob_start();
235 wc_get_template( 'front/mini_widget.php', $atts, '', $this->templates_path );
236 return ob_get_clean();
237 }
238 } catch ( \Throwable $e ) {
239 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
240 }
241 return '';
242 }
243
244 /**
245 * Handle the product listing widget shortcode callback
246 */
247 public function do_product_listing_widget_shortcode( array $atts ): string {
248 $this->logger->log_info( 'Shortcode called', __FUNCTION__, __CLASS__ );
249
250 $current_country = $this->i18n->get_current_country();
251
252 if ( ! $this->configuration->is_product_listing_widget_enabled( $current_country ) ) {
253 $this->logger->log_info( 'Product listing Widget is disabled', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
254 return '';
255 }
256 if ( ! $this->product_service->can_display_mini_widgets() ) {
257 $this->logger->log_info( 'SeQura payment gateway is disabled', __FUNCTION__, __CLASS__ );
258 return '';
259 }
260
261 try {
262 $store_id = $this->configuration->get_store_id();
263 $merchant = $this->payment_service->get_merchant_id();
264 $methods = $this->payment_method_service->get_all_mini_widget_compatible_payment_methods( $store_id, $merchant );
265
266 $config = $this->configuration->get_product_listing_widget_config( $current_country );
267 if ( ! $config ) {
268 $this->logger->log_info( 'Product listing Widget configuration not found', __FUNCTION__, __CLASS__, array( new LogContextData( 'country', $current_country ) ) );
269 return '';
270 }
271
272 foreach ( $methods as $method ) {
273 if ( $method['product'] !== $config['product'] ) {
274 continue;
275 }
276
277 $atts = shortcode_atts(
278 array(
279 'product' => $method['product'] ?? '',
280 'campaign' => $method['campaign'] ?? '',
281 'dest' => $config['selForLocation'],
282 'price' => $config['selForPrice'],
283 'message' => $config['message'],
284 'message_below_limit' => $config['messageBelowLimit'],
285 'min_amount' => $method['minAmount'] ?? 0,
286 'max_amount' => $method['maxAmount'] ?? null,
287 ),
288 $atts,
289 'sequra_product_listing_widget'
290 );
291
292 ob_start();
293 wc_get_template( 'front/mini_widget.php', $atts, '', $this->templates_path );
294 return ob_get_clean();
295 }
296 } catch ( \Throwable $e ) {
297 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
298 }
299 return '';
300 }
301
302 /**
303 * Add [sequra_widget] to product page automatically
304 */
305 private function add_widget_shortcode_to_product_page(): void {
306 if ( ! is_product() ) {
307 return;
308 }
309 /**
310 * The current product.
311 *
312 * @var WC_Product $product
313 */
314 global $product;
315
316 $methods = array();
317 try {
318 $store_id = $this->configuration->get_store_id();
319 $merchant = $this->payment_service->get_merchant_id();
320 $methods = $this->payment_method_service->get_all_widget_compatible_payment_methods( $store_id, $merchant );
321 } catch ( \Throwable $e ) {
322 $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
323 return;
324 }
325
326 foreach ( $methods as $method ) {
327 if ( ! $this->product_service->can_display_widget_for_method( $product, $method ) ) {
328 continue;
329 }
330
331 $atts = array(
332 'product' => $method['product'] ?? '',
333 'campaign' => $method['campaign'] ?? '',
334 'product_id' => $product->get_id(),
335 );
336
337 $atts_str = '';
338 foreach ( $atts as $key => $value ) {
339 $atts_str .= " $key=\"$value\"";
340 }
341 echo do_shortcode( "[sequra_widget $atts_str]" );
342 }
343 }
344
345 /**
346 * Add [sequra_cart_widget] to product page automatically
347 */
348 private function add_widget_shortcode_to_cart_page(): void {
349 if ( ! is_cart() ) {
350 return;
351 }
352 echo do_shortcode( '[sequra_cart_widget]' );
353 }
354
355 /**
356 * Add [sequra_product_listing_widget] to product archive automatically
357 */
358 private function add_widget_shortcode_to_product_listing_page(): void {
359 if ( ! is_product_category() && ! is_product_tag() && ! is_shop() ) {
360 return;
361 }
362 echo do_shortcode( '[sequra_product_listing_widget]' );
363 }
364
365 /**
366 * Add [sequra_widget] to product page automatically
367 * Add [sequra_cart_widget] to cart page automatically
368 */
369 public function add_widget_shortcode_to_page(): void {
370 $this->logger->log_info( 'Hook executed', __FUNCTION__, __CLASS__ );
371 if ( did_action( 'before_woocommerce_sequra_add_widget_to_page' ) ) {
372 return;
373 }
374
375 /**
376 * Fires before the SeQura widget is added to the page to prevent multiple executions.
377 *
378 * @since 3.0.0
379 */
380 do_action( 'before_woocommerce_sequra_add_widget_to_page' );
381
382 $this->add_widget_shortcode_to_product_page();
383 $this->add_widget_shortcode_to_cart_page();
384 $this->add_widget_shortcode_to_product_listing_page();
385 }
386
387 /**
388 * Add meta boxes to the product edit page
389 */
390 public function add_meta_boxes(): void {
391 add_meta_box( 'sequra_settings', esc_html__( 'seQura settings', 'sequra' ), array( $this, 'render_meta_boxes' ), 'product', 'side', 'default' );
392 }
393
394 /**
395 * Render the meta boxes
396 */
397 public function render_meta_boxes( WP_Post $post ): void {
398 $args = array(
399 'is_banned' => $this->product_service->is_banned( $post->ID ),
400 'is_banned_field_name' => self::FIELD_NAME_IS_BANNED,
401 'enabled_for_services' => $this->configuration->is_enabled_for_services(),
402 'is_service' => $this->product_service->is_service( $post->ID ),
403 'is_service_field_name' => self::FIELD_NAME_IS_SERVICE,
404 'service_end_date_default' => $this->configuration->get_default_services_end_date(),
405 'service_end_date' => $this->product_service->get_service_end_date( $post->ID, true ),
406 'service_end_date_field_name' => self::FIELD_NAME_SERVICE_END_DATE,
407 'allow_payment_delay' => $this->configuration->allow_first_service_payment_delay(),
408 'service_desired_first_charge_date' => $this->product_service->get_desired_first_charge_date( $post->ID, true ) ?? '',
409 'service_desired_first_charge_date_field_name' => self::FIELD_NAME_SERVICE_DESIRED_FIRST_CHARGE_DATE,
410 'date_or_duration_regex' => $this->regex->date_or_duration( false ),
411 'allow_registration_items' => $this->configuration->allow_service_reg_items(),
412 'service_registration_amount' => $this->product_service->get_registration_amount( $post->ID ),
413 'service_registration_amount_field_name' => self::FIELD_NAME_REGISTRATION_AMOUNT,
414 'nonce_name' => self::NONCE_SEQURA_PRODUCT,
415 );
416 wc_get_template( 'admin/product_metabox.php', $args, '', $this->templates_path );
417 }
418
419 /**
420 * Save product meta
421 */
422 public function save_product_meta( int $post_id ): void {
423 if ( ! isset( $_POST[ self::NONCE_SEQURA_PRODUCT ] )
424 || ! wp_verify_nonce( $_POST[ self::NONCE_SEQURA_PRODUCT ], -1 ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
425 || ! current_user_can( 'edit_post', $post_id )
426 || wp_doing_ajax() ) {
427 return;
428 }
429
430 $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 );
431 $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 );
432 $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 );
433 $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 );
434 $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 );
435 }
436 }
437