PluginProbe
WowAddons – Product Addons and Product Options With Custom Fields / trunk
WowAddons – Product Addons and Product Options With Custom Fields vtrunk
1.7.2 1.7.1 1.7.0 1.6.21 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.5.10 1.5.11 1.5.2 1.5.3 1.5.4 1.5.5 All 57 releases
product-addons / includes / common / class-hooks.php

class-hooks.php in WowAddons – Product Addons and Product Options With Custom Fields trunk, at includes/common/class-hooks.php

622 lines 20.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore
2 /**
3 * Class Hooks
4 *
5 * @package WowAddons
6 */
7
8 namespace PRAD\Includes\Common;
9
10 use PRAD\Includes\Compatibility\BaseCurrency;
11 use PRAD\Includes\Xpo;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Hooks class.
19 */
20 class Hooks {
21
22 /**
23 * Stores the current product ID.
24 *
25 * @var int|string
26 */
27 private $p_id = '';
28 /**
29 * Stores the current product object.
30 *
31 * @var WC_Product|null
32 */
33 private $prad_product;
34 /**
35 * Stores the tax display setting for the shop.
36 *
37 * @var mixed
38 */
39 private $tax_display_shop;
40
41 /**
42 * Constructor
43 */
44 public function __construct() {
45 add_filter( 'prad_blocks_price_both_show', array( $this, 'handle_prad_blocks_price_both_show' ), 10, 4 );
46 add_action( 'prad_delete_option_product_meta', array( $this, 'delete_option_product_meta_callback' ), 10, 1 );
47
48 add_action( 'prad_enqueue_block_css', array( $this, 'enqueue_block_css_callback' ), 10 );
49 add_action( 'prad_enqueue_block_js', array( $this, 'enqueue_block_js_callback' ), 10 );
50
51 add_filter( 'prad_allowed_html_tags', array( $this, 'handle_prad_allowed_html_tags' ), 10, 1 );
52
53 add_filter( 'prad_raw_tax_currency_compitable_price', array( $this, 'handle_prad_raw_tax_currency_compitable_price' ), 10, 1 );
54 add_filter( 'prad_raw_tax_compitable_price', array( $this, 'prad_get_price_including_tax' ), 10, 1 );
55
56 add_action( 'prad_load_script_on_ajax', array( $this, 'handle_prad_load_script_on_ajax' ), 99 );
57 }
58
59 /**
60 * Loads required scripts and styles via AJAX for the frontend.
61 *
62 * @since 1.0.0
63 *
64 * @return void
65 */
66 public function handle_prad_load_script_on_ajax() {
67
68 // Load Needed JS.
69 $prad_option_front = $this->get_prad_option_front_data();
70
71 echo '<script id="prad-option-front-data" type="text/javascript">';
72 echo 'var prad_option_front = ' . wp_json_encode( $prad_option_front ) . ';';
73 echo '</script>';
74
75 $front_asset = product_addons()->get_script_asset( 'assets/js/frontend-script.js' );
76 $front_script = add_query_arg( 'ver', $front_asset['version'], PRAD_URL . 'assets/js/frontend-script.js' );
77 echo '<script src="' . esc_url( $front_script ) . '" id="prad-front-script-js-2"></script>'; // phpcs:ignore
78
79 $date_asset = product_addons()->get_script_asset( 'assets/js/wowdate-min.js' );
80 $data_script = add_query_arg( 'ver', $date_asset['version'], PRAD_URL . 'assets/js/wowdate-min.js' );
81 echo '<script src="' . esc_url( $data_script ) . '" id="prad-front-date-js-2"></script>'; // phpcs:ignore
82
83 $flag_asset = product_addons()->get_script_asset( 'assets/js/wowflag.js' );
84 $flag_script = add_query_arg( 'ver', $flag_asset['version'], PRAD_URL . 'assets/js/wowflag.js' );
85 echo '<script src="' . esc_url( $flag_script ) . '" id="prad-flag-script-js-2"></script>'; // phpcs:ignore
86
87 // Load Needed CSS.
88 $frontend_css = file_get_contents( product_addons()->get_style_path( 'wowaddons-frontend' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
89 echo '<style id="prad-frontend-css-inline">' . wp_strip_all_tags( $frontend_css ) . '</style>'; // phpcs:ignore
90
91 $block_css = file_get_contents( product_addons()->get_style_path( 'wowaddons-blocks' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
92 echo '<style id="prad-blocks-css-inline">' . wp_strip_all_tags( $block_css ) . '</style>'; // phpcs:ignore
93
94 $global_css = get_option( 'prad_global_style_css', '' );
95 echo '<style id="prad-global-css-inline">' . wp_strip_all_tags( $global_css ) . '</style>'; // phpcs:ignore
96 }
97
98 /**
99 * Enqueues the front-end script for PRAD and localizes necessary data.
100 *
101 * @since 1.0.0
102 *
103 * @return void
104 */
105 public function enqueue_block_js_callback() {
106 $front_asset = product_addons()->get_script_asset( 'assets/js/frontend-script.js', array( 'wp-api-fetch', 'jquery', 'wp-i18n' ) );
107 wp_enqueue_script( 'prad-front-script', PRAD_URL . 'assets/js/frontend-script.js', $front_asset['dependencies'], $front_asset['version'], true );
108
109 $date_asset = product_addons()->get_script_asset( 'assets/js/wowdate-min.js', array( 'jquery' ) );
110 wp_enqueue_script( 'prad-date-script', PRAD_URL . 'assets/js/wowdate-min.js', $date_asset['dependencies'], $date_asset['version'], true );
111
112 $flag_asset = product_addons()->get_script_asset( 'assets/js/wowflag.js', array( 'jquery' ) );
113 wp_enqueue_script( 'prad-flag-script', PRAD_URL . 'assets/js/wowflag.js', $flag_asset['dependencies'], $flag_asset['version'], true );
114 wp_localize_script(
115 'prad-front-script',
116 'prad_option_front',
117 $this->get_prad_option_front_data()
118 );
119 wp_set_script_translations( 'prad-front-script', 'product-addons', PRAD_PATH . 'languages/' );
120 }
121 /**
122 * Enqueues the front-end styles.
123 *
124 * @since 1.0.0
125 *
126 * @return void
127 */
128 public function enqueue_block_css_callback() {
129
130 product_addons()->enqueue_style( 'prad-frontend-css', 'wowaddons-frontend' );
131 product_addons()->enqueue_style( 'prad-blocks-css', 'wowaddons-blocks' );
132
133 $css = get_option( 'prad_global_style_css', '' );
134 $new_css = get_option( 'prad_global_style_thematic_css', '' );
135
136 if ( $new_css ) {
137 wp_register_style( 'prad-global-css', false ); // phpcs:ignore
138 wp_enqueue_style( 'prad-global-css' );
139 wp_add_inline_style( 'prad-global-css', $new_css );
140 } elseif ( $css ) {
141 wp_register_style( 'prad-global-css', false ); // phpcs:ignore
142 wp_enqueue_style( 'prad-global-css' );
143 wp_add_inline_style( 'prad-global-css', $css );
144 }
145 }
146
147 /**
148 * Render Sale price.
149 *
150 * @param float $sale The sale price of the product.
151 *
152 * @return bool True if both prices should be shown, false otherwise.
153 */
154 public function handle_prad_sale_price_return( $sale ) {
155 if ( ! product_addons()->is_pro_feature_available() ) {
156 $sale = null;
157 }
158 return $sale ? floatval( $sale ) : null;
159 }
160
161 /**
162 * Determines whether to display both regular and sale prices for PRAD blocks.
163 *
164 * @param string $type The type of price display logic.
165 * @param float $regular The regular price of the product.
166 * @param float $sale The sale price of the product.
167 * @param int $product_id The ID of the product.
168 *
169 * @return string price html.
170 */
171 public function handle_prad_blocks_price_html_return( $type, $regular, $sale, $product_id ) {
172 $regular = floatval( $regular );
173 $sale = $this->handle_prad_sale_price_return( $sale );
174 $type = $type ? $type : 'fixed';
175
176 switch ( $type ) {
177 case 'percentage':
178 $price_product = apply_filters(
179 'prad_percentage_based_price_raw',
180 $product_id,
181 'revert'
182 );
183 $regular_c = $regular ? ( ( $price_product * $regular ) / 100 ) : null;
184 $sale_c = $sale ? ( ( $price_product * $sale ) / 100 ) : null;
185 return $this->get_price_html( $regular_c, $sale_c, $product_id );
186 case 'per_char':
187 case 'per_char_no_space':
188 return $this->get_per_unit_price_html( $regular, $sale, $product_id, Xpo::get_prad_settings_item( 'characterText', 'Character' ) );
189 case 'per_word':
190 return $this->get_per_unit_price_html( $regular, $sale, $product_id, Xpo::get_prad_settings_item( 'wordText', 'Word' ) );
191 case 'per_unit':
192 return $this->get_price_html( $regular, $sale, $product_id );
193 case 'no_cost':
194 return '<span class="pricex prad-d-none">10</span>';
195 default:
196 return $this->get_price_html( $regular, $sale, $product_id );
197 }
198 }
199
200 /**
201 * Determines whether to display both regular and sale prices for PRAD blocks.
202 *
203 * @param string $type The type of price display logic.
204 * @param float $regular The regular price of the product.
205 * @param float $sale The sale price of the product.
206 * @param int $product_id The ID of the product.
207 *
208 * @return bool True if both prices should be shown, false otherwise.
209 */
210 public function handle_prad_blocks_price_both_show( $type, $regular, $sale, $product_id ) {
211 $type = $type ? $type : 'fixed';
212 return array(
213 'type' => $type,
214 'price' => $this->handle_prad_blocks_price_return( $type, $regular, $sale, $product_id ),
215 'html' => $this->handle_prad_blocks_price_html_return( $type, $regular, $sale, $product_id ),
216 );
217 }
218
219 /**
220 * Handles the price and sale calculations for PRAD blocks.
221 *
222 * @param string $type The type of price calculation.
223 * @param float $regular The regular price of the product.
224 * @param float $sale The sale price of the product.
225 *
226 * @return float The calculated price based on the type.
227 */
228 public function handle_prad_blocks_price_return( $type, $regular, $sale, $product_id ) {
229 $sale = $this->handle_prad_sale_price_return( $sale );
230 $type = $type ? $type : 'fixed';
231 $to_return = 0;
232 switch ( $type ) {
233 case 'no_cost':
234 $to_return = 0;
235 break;
236 default:
237 $regular = floatval( $regular );
238 $sale = $sale ? $sale : null;
239 $to_return = $sale ?? $regular;
240 }
241 return 'percentage' === $type ? $to_return : $this->handle_prad_raw_tax_currency_compitable_price(
242 array(
243 'price' => $to_return,
244 'product_id' => $product_id,
245 'source' => 'product_page',
246 )
247 );
248 }
249
250 /**
251 * Generate HTML for Pricing
252 *
253 * @param float $regular Regular price.
254 * @param float|null $sale Sale price (optional).
255 * @param int $product_id Product ID.
256 *
257 * @return string HTML representation of the pricing.
258 */
259 private function get_price_html( $regular, $sale, $product_id ) {
260 $regular = $regular ? $this->handle_prad_raw_tax_currency_compitable_price(
261 array(
262 'price' => $regular,
263 'product_id' => $product_id,
264 'source' => 'product_page',
265 )
266 ) : 0;
267 $html = '';
268 // $html = '<span class="pricex">' . $regular . $currency_symbol . '</span>';
269 $html = '<span class="pricex">' . wc_price( $regular ) . '</span>';
270 if ( $sale ) {
271 $sale = $this->handle_prad_raw_tax_currency_compitable_price(
272 array(
273 'price' => $sale,
274 'product_id' => $product_id,
275 'source' => 'product_page',
276 )
277 );
278 if ( $regular ) {
279 $html = '<span class="pricex"><del>' . wc_price( $regular ) . '</del> <ins>' . wc_price( $sale ) . '</ins></span>';
280 } else {
281 $html = '<span class="pricex">' . wc_price( $sale ) . '</span>';
282 }
283 }
284
285 return wp_kses(
286 $html,
287 $this->handle_prad_allowed_html_tags()
288 );
289 }
290
291 /**
292 * Generate HTML for a count-based price type (per_char/per_word), rendered
293 * as "rate/unit * count = total". Count starts at 0 on initial page load;
294 * the frontend script updates this live as the customer types.
295 *
296 * @param float $regular Regular price (rate).
297 * @param float|null $sale Sale price (rate), if any.
298 * @param int $product_id Product ID.
299 * @param string $unit Unit label (e.g. 'Character', 'Word').
300 *
301 * @return string HTML representation of the pricing breakdown.
302 */
303 private function get_per_unit_price_html( $regular, $sale, $product_id, $unit ) {
304 $regular = $regular ? $this->handle_prad_raw_tax_currency_compitable_price(
305 array(
306 'price' => $regular,
307 'product_id' => $product_id,
308 'source' => 'product_page',
309 )
310 ) : 0;
311
312 $rate = $regular;
313
314 if ( $sale ) {
315 $sale = $this->handle_prad_raw_tax_currency_compitable_price(
316 array(
317 'price' => $sale,
318 'product_id' => $product_id,
319 'source' => 'product_page',
320 )
321 );
322 $rate = $sale;
323 }
324
325 $count = 0;
326 $total = $rate * $count;
327
328 $rate_html = $sale
329 ? '<del>' . wc_price( $regular ) . '</del> <ins>' . wc_price( $sale ) . '</ins>'
330 : wc_price( $regular );
331
332 $breakdown_hidden = $count > 0 ? '' : ' prad-d-none';
333
334 $html = '<span class="pricex prad-per-unit-price" data-unit-rate="' . esc_attr( $rate ) . '">'
335 . $rate_html . '/' . esc_html( $unit )
336 . '<span class="prad-per-unit-breakdown' . esc_attr( $breakdown_hidden ) . '"> * <span class="prad-per-unit-count">' . esc_html( $count ) . '</span> = <span class="prad-per-unit-total">' . wc_price( $total ) . '</span></span>'
337 . '</span>';
338
339 return wp_kses(
340 $html,
341 $this->handle_prad_allowed_html_tags()
342 );
343 }
344
345 /**
346 * Set ALlowed Html
347 *
348 * @since 1.0.0
349 *
350 * @param array $extras Allowed htmls.
351 *
352 * @return array
353 */
354 public function handle_prad_allowed_html_tags( $extras = array() ) {
355 $allowed = array(
356 'del' => array(),
357 'ins' => array(),
358 'select' => array(
359 'multiple' => true,
360 'data-*' => true,
361 ),
362 'option' => array(
363 'value' => true,
364 'data-*' => true,
365 ),
366 'strong' => array(),
367 'b' => array(),
368 'input' => array(
369 'data-*' => true,
370 'type' => true,
371 'value' => true,
372 'placeholder' => true,
373 'name' => true,
374 'id' => true,
375 'min' => true,
376 'max' => true,
377 'format' => true,
378 'class' => true,
379 'step' => true,
380 'disabled' => true,
381 'readonly' => true,
382 'required' => true,
383 'maxlength' => true,
384 'minlength' => true,
385 'pattern' => true,
386 'autocomplete' => true,
387 'accept' => true,
388 ),
389 'textarea' => array(
390 'data-*' => true,
391 'type' => true,
392 'value' => true,
393 'placeholder' => true,
394 'name' => true,
395 'id' => true,
396 'min' => true,
397 'max' => true,
398 'rows' => true,
399 'format' => true,
400 'class' => true,
401 'disabled' => true,
402 'readonly' => true,
403 'required' => true,
404 'maxlength' => true,
405 'minlength' => true,
406 'pattern' => true,
407 'autocomplete' => true,
408 'accept' => true,
409 ),
410 'svg' => array(
411 'xmlns' => true,
412 'width' => true,
413 'height' => true,
414 'viewbox' => true,
415 'fill' => true,
416 'stroke' => true,
417 'stroke-width' => true,
418 ),
419 'g' => array(
420 'fill' => true,
421 'stroke' => true,
422 'opacity' => true,
423 'stroke-linecap' => true,
424 'stroke-linejoin' => true,
425 'stroke-width' => true,
426 'clip-path' => true,
427 ),
428 'path' => array(
429 'd' => true,
430 'fill' => true,
431 'stroke' => true,
432 'stroke-linecap' => true,
433 'stroke-linejoin' => true,
434 'stroke-width' => true,
435 'clip-rule' => true,
436 ),
437 'rect' => array(
438 'rx' => true,
439 'width' => true,
440 'height' => true,
441 'fill' => true,
442 'stroke' => true,
443 'stroke-width' => true,
444 ),
445 'defs' => array(),
446 'clipPath' => array(
447 'id' => true,
448 ),
449 'style' => array(
450 'id' => true,
451 'type' => true,
452 'media' => true,
453 'title' => true,
454 'scoped' => true,
455 'data-*' => true,
456 ),
457 );
458
459 return array_merge( wp_kses_allowed_html( 'post' ), $allowed, $extras );
460 }
461
462 /**
463 * Delete Product Meta
464 *
465 * @since 1.0.0
466 *
467 * @param string $option_id Option id.
468 *
469 * @return void
470 */
471 public function delete_option_product_meta_callback( $option_id ) {
472 if ( ! $option_id ) {
473 return;
474 }
475 $assigned_data = json_decode( product_addons()->safe_stripslashes( get_post_meta( $option_id, 'prad_base_assigned_data', true ) ), true );
476 if ( $assigned_data ) {
477 if ( 'all_product' === $assigned_data['aType'] ) { /* Remove options for All Products */
478 $option_settings = json_decode( product_addons()->safe_stripslashes( get_option( 'prad_option_assign_all', '[]' ) ), true );
479
480 if ( is_array( $option_settings ) ) {
481 if ( in_array( $option_id, $option_settings, false ) ) { //phpcs:ignore
482 $option_settings = array_values( array_diff( $option_settings, array( $option_id ) ) );
483 }
484 } else {
485 $option_settings = array();
486 }
487 update_option( 'prad_option_assign_all', wp_json_encode( $option_settings ) );
488
489 if ( ! empty( $assigned_data['excludeCategories'] ) && is_array( $assigned_data['excludeCategories'] ) ) {
490 foreach ( $assigned_data['excludeCategories'] as $key => $exclude_cat ) {
491 $meta_exc = json_decode( product_addons()->safe_stripslashes( get_term_meta( $exclude_cat, 'prad_term_assigned_meta_exc', true ) ), true );
492 if ( is_array( $meta_exc ) ) {
493 if ( in_array( $option_id, $meta_exc, false ) ) { //phpcs:ignore
494 $meta_exc = array_values( array_diff( $meta_exc, array( $option_id ) ) );
495 }
496 } else {
497 $meta_exc = array();
498 }
499 update_term_meta( $exclude_cat, 'prad_term_assigned_meta_exc', wp_json_encode( $meta_exc ) );
500 }
501 }
502 } elseif ( is_array( $assigned_data['includes'] ) && count( $assigned_data['includes'] ) > 0 ) {
503 foreach ( $assigned_data['includes'] as $key => $include ) {
504 $meta_inc = array();
505 if ( 'specific_product' === $assigned_data['aType'] ) {
506 $meta_inc = json_decode( product_addons()->safe_stripslashes( get_post_meta( $include, 'prad_product_assigned_meta_inc', true ) ), true );
507 } elseif ( 'specific_category' === $assigned_data['aType'] || 'specific_tag' === $assigned_data['aType'] || 'specific_brand' === $assigned_data['aType'] ) {
508 $meta_inc = json_decode( product_addons()->safe_stripslashes( get_term_meta( $include, 'prad_term_assigned_meta_inc', true ) ), true );
509 }
510 if ( is_array( $meta_inc ) ) {
511 if ( in_array( $option_id, $meta_inc, false ) ) { //phpcs:ignore
512 $meta_inc = array_values( array_diff( $meta_inc, array( $option_id ) ) );
513 }
514 } else {
515 $meta_inc = array();
516 }
517 if ( 'specific_product' === $assigned_data['aType'] ) {
518 update_post_meta( $include, 'prad_product_assigned_meta_inc', wp_json_encode( $meta_inc ) );
519 } elseif ( 'specific_category' === $assigned_data['aType'] || 'specific_tag' === $assigned_data['aType'] || 'specific_brand' === $assigned_data['aType'] ) {
520 update_term_meta( $include, 'prad_term_assigned_meta_inc', wp_json_encode( $meta_inc ) );
521 }
522 }
523 }
524 /* Handle excludes */
525 if ( is_array( $assigned_data['excludes'] ) && count( $assigned_data['excludes'] ) > 0 ) {
526 foreach ( $assigned_data['excludes'] as $key => $exclude ) {
527 $meta_exc = json_decode( product_addons()->safe_stripslashes( get_post_meta( $exclude, 'prad_product_assigned_meta_exc', true ) ), true );
528 if ( is_array( $meta_exc ) ) {
529 if ( in_array( $option_id, $meta_exc, false ) ) { //phpcs:ignore
530 $meta_exc = array_values( array_diff( $meta_exc, array( $option_id ) ) );
531 }
532 } else {
533 $meta_exc = array();
534 }
535 update_post_meta( $exclude, 'prad_product_assigned_meta_exc', wp_json_encode( $meta_exc ) );
536 }
537 }
538 }
539 }
540
541 /**
542 * Converts the price to a compatible value with tax and currency.
543 *
544 * @since 1.0.0
545 *
546 * @param array $args Arguments for price conversion.
547 *
548 * @return float Converted price.
549 */
550 public function handle_prad_raw_tax_currency_compitable_price( $args ) {
551 $price = $this->prad_get_price_including_tax( $args );
552 return $price ? BaseCurrency::convert( floatval( $price ) ) : 0;
553 }
554
555 /**
556 * Get price including tax.
557 *
558 * @since 1.0.0
559 *
560 * @param array $args Arguments for price calculation.
561 *
562 * @return float Price including tax.
563 */
564 public function prad_get_price_including_tax( $args ) {
565 $price = isset( $args['price'] ) ? $args['price'] : 0;
566 $product_id = isset( $args['product_id'] ) ? $args['product_id'] : 0;
567 $source = isset( $args['source'] ) ? $args['source'] : '';
568
569 if ( ! $price ) {
570 return $price;
571 }
572
573 if ( ! $product_id ) {
574 return $price ? $price : 0;
575 }
576
577 if ( (int) $this->p_id !== (int) $product_id ) {
578 $this->prad_product = wc_get_product( $product_id );
579 $this->p_id = $product_id;
580 }
581
582 if ( ! $this->prad_product ) {
583 return $price ? $price : 0;
584 }
585
586 return wc_get_price_to_display(
587 $this->prad_product,
588 array(
589 'qty' => 1,
590 'price' => $price,
591 'display_context' => $source,
592 )
593 );
594 }
595
596 /**
597 * Retrieves front-end option data for PRAD scripts.
598 *
599 * @since 1.0.0
600 *
601 * @return array Front-end option data.
602 */
603 public function get_prad_option_front_data() {
604 return array_merge(
605 array(
606 'url' => PRAD_URL,
607 'nonce' => wp_create_nonce( 'prad-nonce' ),
608 'isActive' => product_addons()->is_lc_active(),
609 'thousand_sep' => get_option( 'woocommerce_price_thousand_sep', ',' ),
610 'decimal_sep' => get_option( 'woocommerce_price_decimal_sep', '.' ),
611 'num_decimals' => get_option( 'woocommerce_price_num_decimals', '2' ),
612 'currency_pos' => get_option( 'woocommerce_currency_pos', 'left' ),
613 'currencySymbol' => function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '$',
614 'characterText' => \PRAD\Includes\Xpo::get_prad_settings_item( 'characterText', 'Character' ),
615 'wordText' => \PRAD\Includes\Xpo::get_prad_settings_item( 'wordText', 'Word' ),
616 'date_format' => get_option( 'date_format' ),
617 ),
618 product_addons()->get_currency_converted_data(),
619 );
620 }
621 }
622