PluginProbe
seQura / 4.1.0
seQura v4.1.0
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
← All changes | src/Controllers/Hooks/Product/class-product-controller.php +171 -194 3.1.14.1.0 View file →
@@ -8,21 +8,20 @@
8 8
9 9 namespace SeQura\WC\Controllers\Hooks\Product;
10 10
11 11 use SeQura\Core\Infrastructure\Logger\LogContextData;
12 +use SeQura\Core\Infrastructure\Utility\RegexProvider;
12 13 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;
14 +use SeQura\WC\Services\Log\Interface_Logger_Service;
18 15 use SeQura\WC\Services\Product\Interface_Product_Service;
19 -use SeQura\WC\Services\Regex\Regex;
16 +use SeQura\WC\Services\Widgets\Interface_Widgets_Service;
20 17 use WC_Product;
21 18 use WP_Post;
22 19
23 20 /**
24 21 * Product Controller implementation
22 + *
23 + * @phpstan-import-type WidgetDataArray from Interface_Widgets_Service
25 24 */
26 25 class Product_Controller extends Controller implements Interface_Product_Controller {
27 26
28 27 private const FIELD_NAME_IS_BANNED = 'is_sequra_banned';
@@ -32,22 +31,8 @@
32 31 private const FIELD_NAME_REGISTRATION_AMOUNT = 'sequra_registration_amount';
33 32 private const NONCE_SEQURA_PRODUCT = '_sequra_product_nonce';
34 33
35 34 /**
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 35 * Product service
51 36 *
52 37 * @var Interface_Product_Service
53 38 */
@@ -53,27 +38,27 @@
53 38 */
54 39 private $product_service;
55 40
56 41 /**
57 - * Payment service
42 + * RegEx service
58 43 *
59 - * @var Interface_Payment_Service
44 + * @var RegexProvider
60 45 */
61 - private $payment_service;
46 + private $regex;
62 47
63 48 /**
64 - * Payment method service
65 - *
66 - * @var Interface_Payment_Method_Service
49 + * Cached available widgets for product page
50 + *
51 + * @var array<WidgetDataArray[]>
67 52 */
68 - private $payment_method_service;
53 + private $available_widgets_for_product_page;
69 54
70 55 /**
71 - * RegEx service
72 - *
73 - * @var Regex
56 + * Widgets service
57 + *
58 + * @var Interface_Widgets_Service
74 59 */
75 - private $regex;
60 + private $widgets_service;
76 61
77 62 /**
78 63 * Constructor
79 64 */
@@ -78,24 +63,19 @@
78 63 * Constructor
79 64 */
80 65 public function __construct(
81 66 Interface_Logger_Service $logger,
82 - string $templates_path,
83 - Configuration $configuration,
67 + string $templates_path,
84 68 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
69 + RegexProvider $regex,
70 + Interface_Widgets_Service $widgets_service
89 71 ) {
90 72 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;
73 + $this->logger = $logger;
74 + $this->product_service = $product_service;
75 + $this->regex = $regex;
76 + $this->widgets_service = $widgets_service;
77 + $this->available_widgets_for_product_page = array();
98 78 }
99 79
100 80 /**
101 81 * Handle the widget shortcode callback
@@ -102,15 +82,17 @@
102 82 * [sequra_widget]
103 83 * Expected attributes:
104 84 * - product: The seQura product identifier. Required.
105 85 * - 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.
86 + * - dest: A CSS selector to place the widget. Optional. If not provided, the widget will be placed in the same container as the shortcode.
87 + * - product_id: The WooCommerce product identifier. Optional.
88 + * - price: A CSS selector to get the product price or the price itself as a string. Optional.
109 89 * - alt_price: An alternative CSS selector to retrieve the product price for special product page layouts. Optional.
110 90 * - is_alt_price: A CSS selector to determine if the product has an alternative price. Optional.
111 91 * - reg_amount: The registration amount. Optional.
112 92 * - theme: The theme to use. Accepted values are: L, R, legacy, legacyL, legacyR, minimal, minimalL, minimalR or JSON formatted string. Optional.
93 + * - min_amount: The minimum amount to display the widget. Optional.
94 + * - max_amount: The maximum amount to display the widget. Optional.
113 95 *
114 96 * @param array<string, string> $atts The shortcode attributes
115 97 * @return string
116 98 */
@@ -118,9 +100,9 @@
118 100 $this->logger->log_debug( 'Shortcode called', __FUNCTION__, __CLASS__ );
119 101 $atts = (array) $atts;
120 102
121 103 // Check for required attributes.
122 - foreach ( array( 'product', 'product_id' ) as $required ) {
104 + foreach ( array( 'product' ) as $required ) {
123 105 if ( ! isset( $atts[ $required ] ) ) {
124 106 $this->logger->log_error( "\"$required\" attribute is required", __FUNCTION__, __CLASS__ );
125 107 return '';
126 108 }
@@ -125,16 +107,40 @@
125 107 return '';
126 108 }
127 109 }
128 110
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__ );
111 + $product_id = intval( $atts['product_id'] ?? 0 );
112 + $product = $atts['product'];
113 + $campaign = isset( $atts['campaign'] ) && '' !== $atts['campaign'] ? $atts['campaign'] : '';
114 + $widget = $this->get_widget_for_product( $product_id, $product, $campaign );
115 + if ( ! $widget ) {
116 + $this->logger->log_info(
117 + 'Widget is disabled',
118 + __FUNCTION__,
119 + __CLASS__,
120 + array(
121 + new LogContextData( 'payment_method', $product ),
122 + new LogContextData( 'campaign', $campaign ),
123 + new LogContextData( 'product_id', $product_id ),
124 + )
125 + );
134 126 return '';
135 127 }
136 128
129 + if ( ! $this->product_service->can_display_widget_for_method( $product_id, $product ) ) {
130 + $this->logger->log_info(
131 + 'Widget cannot be displayed for product',
132 + __FUNCTION__,
133 + __CLASS__,
134 + array(
135 + new LogContextData( 'payment_method', $product ),
136 + new LogContextData( 'campaign', $campaign ),
137 + new LogContextData( 'product_id', $product_id ),
138 + )
139 + );
140 + return '';
141 + }
142 +
137 143 // Replace old attribute names introduced in 2.0.0 with the new ones.
138 144 $atts_replacements = array(
139 145 'variation_price' => 'alt_price',
140 146 'is_variable' => 'is_alt_price',
@@ -148,37 +154,24 @@
148 154 }
149 155
150 156 $atts = \shortcode_atts(
151 157 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(),
158 + 'product' => $widget['product'] ?? '',
159 + 'campaign' => $widget['campaign'] ?? '',
160 + 'theme' => $widget['theme'] ?? '',
161 + 'reverse' => $widget['reverse'] ?? '0',
162 + 'reg_amount' => $this->product_service->get_registration_amount( $product_id, true ),
163 + 'dest' => '',
164 + 'price' => $widget['priceSel'] ?? '',
165 + 'alt_price' => $widget['altPriceSel'] ?? '',
166 + 'is_alt_price' => $widget['altTriggerSelector'] ?? '',
167 + 'min_amount' => $widget['minAmount'] ?? 0,
168 + 'max_amount' => $widget['maxAmount'] ?? null,
162 169 ),
163 170 $atts,
164 171 'sequra_widget'
165 172 );
166 173
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 174 ob_start();
182 175 \wc_get_template( 'front/widget.php', $atts, '', $this->templates_path );
183 176 return ob_get_clean();
184 177 }
@@ -190,62 +183,37 @@
190 183 * @return string
191 184 */
192 185 public function do_cart_widget_shortcode( $atts ) {
193 186 $this->logger->log_debug( 'Shortcode called', __FUNCTION__, __CLASS__ );
194 - $atts = (array) $atts;
195 187
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 ) ) );
188 + $widget = $this->widgets_service->get_widget_for_cart_page();
189 + if ( ! $widget ) {
200 190 return '';
201 191 }
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 192
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 );
193 + $atts = \shortcode_atts(
194 + array(
195 + 'product' => $widget['product'] ?? '',
196 + 'campaign' => $widget['campaign'] ?? '',
197 + 'dest' => $widget['dest'] ?? '',
198 + 'theme' => $widget['theme'] ?? '',
199 + 'price' => $widget['priceSel'] ?? '',
200 + 'alt_price' => $widget['altPriceSel'] ?? '',
201 + 'is_alt_price' => $widget['altTriggerSelector'] ?? '',
202 + 'message' => $widget['miniWidgetMessage'],
203 + 'message_below_limit' => $widget['miniWidgetBelowLimitMessage'],
204 + 'min_amount' => $widget['minAmount'] ?? 0,
205 + 'max_amount' => $widget['maxAmount'] ?? null,
206 + 'reg_amount' => 0,
207 + 'reverse' => 0,
208 + ),
209 + (array) $atts,
210 + 'sequra_cart_widget'
211 + );
211 212
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 '';
213 + ob_start();
214 + \wc_get_template( 'front/widget.php', $atts, '', $this->templates_path );
215 + return ob_get_clean();
248 216 }
249 217
250 218 /**
251 219 * Handle the product listing widget shortcode callback
@@ -254,60 +222,76 @@
254 222 * @return string
255 223 */
256 224 public function do_product_listing_widget_shortcode( $atts ) {
257 225 $this->logger->log_debug( 'Shortcode called', __FUNCTION__, __CLASS__ );
258 - $atts = (array) $atts;
226 + $widget = $this->widgets_service->get_widget_for_product_listing_page();
227 + if ( ! $widget ) {
228 + return '';
229 + }
259 230
260 - $current_country = $this->i18n->get_current_country();
231 + $atts = \shortcode_atts(
232 + array(
233 + 'product' => $widget['product'] ?? '',
234 + 'campaign' => $widget['campaign'] ?? '',
235 + 'dest' => $widget['dest'],
236 + 'price' => $widget['priceSel'],
237 + 'message' => $widget['miniWidgetMessage'],
238 + 'message_below_limit' => $widget['miniWidgetBelowLimitMessage'],
239 + 'min_amount' => $widget['minAmount'] ?? 0,
240 + 'max_amount' => $widget['maxAmount'] ?? null,
241 + ),
242 + (array) $atts,
243 + 'sequra_product_listing_widget'
244 + );
245 +
246 + ob_start();
247 + \wc_get_template( 'front/mini_widget.php', $atts, '', $this->templates_path );
248 + return ob_get_clean();
249 + }
250 +
251 + /**
252 + * Get available widgets for a product page from in-memory cache or CheckoutAPI
253 + *
254 + * @param int|string $product_id The product ID
255 + * @return WidgetDataArray[] The available widgets
256 + */
257 + private function get_available_widgets_for_product_page( $product_id ): array {
261 258
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 '';
259 + $product_id = (string) $product_id;
260 + if ( isset( $this->available_widgets_for_product_page[ $product_id ] ) ) {
261 + return $this->available_widgets_for_product_page[ $product_id ];
265 262 }
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 263
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 );
264 + $widgets = $this->widgets_service->get_widgets_for_product_page( $product_id );
275 265
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 - }
266 + if ( ! $widgets ) {
267 + return array();
268 + }
281 269
282 - foreach ( $methods as $method ) {
283 - if ( $method['product'] !== $config['product'] ) {
284 - continue;
285 - }
270 + $this->available_widgets_for_product_page[ $product_id ] = $widgets;
271 + return $widgets;
272 + }
286 273
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();
274 + /**
275 + * Get the widget for a product if enabled
276 + *
277 + * @param string|int $product_id The WooCommerce product ID. Pass and empty value to ignore it.
278 + * @param string $product The seQura product identifier
279 + * @param string $campaign The seQura campaign name. Use an empty string to ignore the campaign.
280 + *
281 + * @return WidgetDataArray|null The widget data if enabled, null otherwise
282 + */
283 + private function get_widget_for_product( $product_id, string $product, string $campaign = '' ): ?array {
284 + /**
285 + * Promotional widget data
286 + *
287 + * @var array<string, mixed> $widget */
288 + foreach ( $this->get_available_widgets_for_product_page( $product_id ) as $widget ) {
289 + if ( $widget['product'] === $product && $widget['campaign'] === $campaign ) {
290 + return $widget;
305 291 }
306 - } catch ( \Throwable $e ) {
307 - $this->logger->log_throwable( $e, __FUNCTION__, __CLASS__ );
308 292 }
309 - return '';
293 + return null;
310 294 }
311 295
312 296 /**
313 297 * Add [sequra_widget] to product page automatically
@@ -321,33 +305,26 @@
321 305 *
322 306 * @var WC_Product $product
323 307 */
324 308 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 309
336 - foreach ( $methods as $method ) {
337 - if ( ! $this->product_service->can_display_widget_for_method( $product, $method ) ) {
338 - continue;
339 - }
340 -
310 + foreach ( $this->get_available_widgets_for_product_page( $product->get_id() ) as $widget ) {
341 311 $atts = array(
342 - 'product' => $method['product'] ?? '',
343 - 'campaign' => $method['campaign'] ?? '',
344 - 'product_id' => $product->get_id(),
312 + 'product' => $widget['product'] ?? '',
313 + 'campaign' => $widget['campaign'] ?? '',
314 + 'product_id' => $product->get_id(),
315 + 'theme' => $widget['theme'] ?? '',
316 + 'dest' => $widget['dest'] ?? '',
317 + 'price' => $widget['priceSel'] ?? '',
318 + 'alt_price' => $widget['altPriceSel'] ?? '',
319 + 'is_alt_price' => $widget['altTriggerSelector'] ?? '',
320 + 'min_amount' => $widget['minAmount'] ?? 0,
321 + 'max_amount' => $widget['maxAmount'] ?? null,
322 +
345 323 );
346 -
347 324 $atts_str = '';
348 325 foreach ( $atts as $key => $value ) {
349 - $atts_str .= " $key=\"$value\"";
326 + $atts_str .= " $key='$value'";
350 327 }
351 328 echo \do_shortcode( "[sequra_widget $atts_str]" );
352 329 }
353 330 }
@@ -414,19 +391,19 @@
414 391 public function render_meta_boxes( $post ) {
415 392 $args = array(
416 393 'is_banned' => $this->product_service->get_is_banned( $post->ID ),
417 394 'is_banned_field_name' => self::FIELD_NAME_IS_BANNED,
418 - 'enabled_for_services' => $this->configuration->is_enabled_for_services(),
395 + 'enabled_for_services' => $this->product_service->is_enabled_for_services(),
419 396 'is_service' => $this->product_service->is_service( $post->ID ),
420 397 'is_service_field_name' => self::FIELD_NAME_IS_SERVICE,
421 - 'service_end_date_default' => $this->configuration->get_default_services_end_date(),
398 + 'service_end_date_default' => $this->product_service->get_default_services_end_date(),
422 399 'service_end_date' => $this->product_service->get_service_end_date( $post->ID, true ),
423 400 'service_end_date_field_name' => self::FIELD_NAME_SERVICE_END_DATE,
424 - 'allow_payment_delay' => $this->configuration->allow_first_service_payment_delay(),
401 + 'allow_payment_delay' => $this->product_service->is_allow_first_service_payment_delay(),
425 402 'service_desired_first_charge_date' => $this->product_service->get_desired_first_charge_date( $post->ID, true ) ?? '',
426 403 '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(),
404 + 'date_or_duration_regex' => $this->regex->getDateOrDurationRegex( false ),
405 + 'allow_registration_items' => $this->product_service->is_allow_service_registration_items(),
429 406 'service_registration_amount' => $this->product_service->get_registration_amount( $post->ID ),
430 407 'service_registration_amount_field_name' => self::FIELD_NAME_REGISTRATION_AMOUNT,
431 408 'nonce_name' => self::NONCE_SEQURA_PRODUCT,
432 409 );