| 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 |
|