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-functions.php

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

1,438 lines 49.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore
2 namespace PRAD\Includes\Common;
3
4 use PRAD\Includes\Compatibility\BaseCurrency;
5 use WC_Data_Store;
6
7 defined( 'ABSPATH' ) || exit;
8
9 /**
10 * Functions class.
11 */
12 class Functions {
13
14 /**
15 * Setup class.
16 *
17 * @since v.1.0.0
18 */
19 public function __construct() {
20 }
21
22 /**
23 * Load style path
24 *
25 * @param string $style_name style name.
26 * @return string
27 */
28 public function get_style_path( $style_name ) {
29 return PRAD_URL . 'assets/css/' . $style_name . ( is_rtl() ? '-rtl' : '' ) . '.css';
30 }
31
32 /**
33 * Enqueue style
34 *
35 * @param string $handle handle.
36 * @param string $style_name style name.
37 * @param array $deps dependencies.
38 * @return void
39 */
40 public function enqueue_style( $handle, $style_name, $deps = array() ) {
41 wp_enqueue_style(
42 $handle,
43 $this->get_style_path( $style_name ),
44 $deps,
45 PRAD_VER
46 );
47 }
48
49 /**
50 * Enqueue style
51 *
52 * @param string $handle handle.
53 * @param string $script_name script name.
54 * @param array $args args.
55 * @return void
56 */
57 public function enqueue_script( $handle, $script_name, $args = true ) {
58 $script_path = PRAD_URL . 'assets/js/' . $script_name . '.js';
59 $assets = $this->get_script_asset( $script_path );
60 wp_enqueue_script( $handle, $script_path, $assets['dependencies'], $assets['version'], $args );
61 }
62
63 /**
64 * Read a wp-scripts generated asset file for a built script.
65 *
66 * WP Scripts (DependencyExtractionWebpackPlugin) generates a sibling
67 * `{entry}.asset.php` file containing `dependencies` and `version`.
68 *
69 * @since 1.5.8
70 *
71 * @param string $relative_js_path Relative JS path inside the plugin, e.g. 'assets/js/frontend-script.js'.
72 * @param array $fallback_deps Dependencies to use if the asset file does not exist.
73 *
74 * @return array { dependencies: string[], version: string }
75 */
76 public function get_script_asset( $relative_js_path, $fallback_deps = array() ) {
77 $relative_js_path = ltrim( (string) $relative_js_path, '/\\' );
78 $asset_path = preg_replace( '/\\.js$/', '.asset.php', $relative_js_path );
79 $asset_file = trailingslashit( PRAD_PATH ) . str_replace( array( '\\', '//' ), '/', $asset_path );
80
81 $asset = null;
82 if ( $asset_file && file_exists( $asset_file ) ) {
83 $asset = include $asset_file;
84 }
85
86 if ( is_array( $asset ) && isset( $asset['dependencies'], $asset['version'] ) ) {
87 return array(
88 'dependencies' => is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(),
89 'version' => (string) $asset['version'],
90 );
91 }
92
93 return array(
94 'dependencies' => is_array( $fallback_deps ) ? $fallback_deps : array(),
95 'version' => defined( 'PRAD_VER' ) ? PRAD_VER : false,
96 );
97 }
98
99 /**
100 * Checks if the Product Addons Pro plugin is active and its license is valid.
101 *
102 * Verifies whether the plugin located at 'product-addons-pro/product-addons-pro.php'
103 * is currently active. If active, it then checks the license status stored in the
104 * 'edd_prad_license_data' option to ensure it is marked as 'valid'.
105 *
106 * @return bool True if the plugin is active and the license is valid, false otherwise.
107 */
108 public function is_lc_active() {
109 if ( defined( 'PRAD_PRO_VER' ) ) {
110 $license_data = get_option( 'edd_prad_license_data', array() );
111 return isset( $license_data['license'] ) && 'valid' === $license_data['license'] ? true : false;
112 }
113 return false;
114 }
115
116 /**
117 * Checks if the license has expired.
118 *
119 * This method checks the stored license data in the WordPress options table
120 * and determines if the license status is set to 'expired'. It returns `true`
121 * if the license is expired, otherwise `false`.
122 *
123 * @return bool True if the license is expired, otherwise false.
124 */
125 public function is_lc_expired() {
126 $license_data = get_option( 'edd_prad_license_data', array() );
127 return isset( $license_data['license'] ) && 'expired' === $license_data['license'] ? true : false;
128 }
129
130 /**
131 * Checks if the license has expired.
132 *
133 * This method checks the stored license data in the WordPress options table
134 * and determines if the license status is set to 'expired'. It returns `true`
135 * if the license is expired, otherwise `false`.
136 *
137 * @return bool True if the license is expired, otherwise false.
138 */
139 public function handle_all_pro_block() {
140 if ( defined( 'PRAD_PRO_VER' ) ) {
141 $license_data = get_option( 'edd_prad_license_data', array() );
142 return isset( $license_data['license'] ) && ( 'valid' === $license_data['license'] || 'expired' === $license_data['license'] ) ? true : false;
143 }
144 return false;
145 }
146
147 /**
148 * Checks if the pro feature is available based on plugin activation and license status.
149 *
150 * Returns true if the Product Addons Pro plugin is active and the license is either valid or expired.
151 *
152 * @return bool True if pro features are available, false otherwise.
153 */
154 public function is_pro_feature_available() {
155 if ( defined( 'PRAD_PRO_VER' ) ) {
156 $license_data = get_option( 'edd_prad_license_data', array() );
157 return isset( $license_data['license'] ) && ( 'valid' === $license_data['license'] || 'expired' === $license_data['license'] ) ? true : false;
158 }
159 return false;
160 }
161
162 /**
163 * Retrieves the assigned product data for a given option.
164 *
165 * @since 1.0.0
166 *
167 * @param int $option_id The ID of the option to retrieve assigned data for.
168 *
169 * @return object An object containing the assigned data, including 'aType', 'includes', and 'excludes'.
170 */
171 public function get_assigned_product_data( $option_id ) {
172 $assigned_data = json_decode( product_addons()->safe_stripslashes( get_post_meta( $option_id, 'prad_base_assigned_data', true ) ), true );
173 if ( empty( $assigned_data ) ) {
174 return (object) array(
175 'aType' => 'specific_product',
176 'includes' => array(),
177 'excludes' => array(),
178 'excludeCategories' => array(),
179 );
180 } else {
181 if ( isset( $assigned_data['includes'] ) && count( $assigned_data['includes'] ) > 0 ) {
182 if ( 'specific_product' === $assigned_data['aType'] ) {
183 $includes = $this->get_searched_products( '', false, '', $assigned_data['includes'] );
184 } elseif ( 'specific_category' === $assigned_data['aType'] || 'specific_tag' === $assigned_data['aType'] || 'specific_brand' === $assigned_data['aType'] ) {
185 $term_type = 'specific_category' === $assigned_data['aType'] ? 'cat' : ( 'specific_tag' === $assigned_data['aType'] ? 'tag' : 'brand' );
186 $includes = $this->get_searched_categories(
187 array(
188 'term' => '',
189 'limit' => '',
190 'includes' => $assigned_data['includes'],
191 'trigger_type' => $term_type,
192 )
193 );
194 } else {
195 $includes = array();
196 }
197 } else {
198 $includes = array();
199 }
200
201 return (object) array(
202 'aType' => $assigned_data['aType'],
203 'includes' => $includes,
204 'excludes' => isset( $assigned_data['excludes'] ) && count( $assigned_data['excludes'] ) > 0
205 ? $this->get_searched_products( '', false, '', $assigned_data['excludes'] )
206 : array(),
207 'excludeCategories' => isset( $assigned_data['excludeCategories'] ) && count( $assigned_data['excludeCategories'] ) > 0
208 ? $this->get_searched_categories(
209 array(
210 'term' => '',
211 'limit' => '',
212 'includes' => $assigned_data['excludeCategories'],
213 'trigger_type' => 'cat',
214 )
215 )
216 : array(),
217 );
218 }
219 }
220
221 /**
222 * Retrieves a list of searched products based on the provided term, including optional product variations.
223 *
224 * @since 1.0.0
225 *
226 * @param string $term The search term.
227 * @param bool $include_variations Whether to include product variations in the search. Default is false.
228 * @param int $limit The number of products to return. Defaults to all.
229 * @param array $include_ids Array of product IDs to include in the search.
230 * @param array $tax_filter Optional. Restrict results to products in the given taxonomy terms,
231 * e.g. array( 'taxonomy' => 'product_cat', 'term_ids' => array( 1, 2 ) ).
232 *
233 * @return array An array of product details, including item ID, URL, name, and thumbnail URL.
234 */
235 public function get_searched_products( $term, $include_variations = false, $limit = '', $include_ids = array(), $tax_filter = array() ) {
236 $taxonomy = isset( $tax_filter['taxonomy'] ) ? $tax_filter['taxonomy'] : '';
237 $term_ids = isset( $tax_filter['term_ids'] ) && is_array( $tax_filter['term_ids'] ) ? array_map( 'absint', $tax_filter['term_ids'] ) : array();
238
239 if ( $taxonomy && $term_ids ) {
240 $ids = $this->get_searched_product_ids_by_taxonomy( $term, $taxonomy, $term_ids, $limit );
241 } else {
242 // Load the product data store.
243 $data_store = WC_Data_Store::load( 'product' );
244 $exclude_ids = array();
245 $ids = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit, $include_ids, $exclude_ids );
246 }
247
248 $products = array();
249
250 foreach ( $ids as $product_id ) {
251 $product = wc_get_product( $product_id );
252
253 if ( $product ) {
254 $products[] = array(
255 'item_id' => $product_id,
256 'url' => get_permalink( $product_id ),
257 'item_name' => rawurldecode( wp_strip_all_tags( $product->get_name() ) ),
258 'thumbnail' => wp_get_attachment_url( $product->get_image_id() ),
259 'regular' => $product->get_regular_price(),
260 'sale' => $product->get_sale_price(),
261 );
262 }
263 }
264
265 return $products;
266 }
267
268 /**
269 * Finds product IDs matching a keyword and belonging to the given taxonomy terms.
270 *
271 * WC_Data_Store::search_products() has no taxonomy support, so a scoped
272 * product-picker search (e.g. excluding products from a chosen category)
273 * is run through WP_Query with a tax_query instead.
274 *
275 * @since 1.6.17
276 *
277 * @param string $term The search term.
278 * @param string $taxonomy The taxonomy to filter by (product_cat, product_tag, product_brand).
279 * @param array $term_ids Term IDs within that taxonomy to restrict results to.
280 * @param int $limit The number of products to return.
281 *
282 * @return array Product IDs.
283 */
284 private function get_searched_product_ids_by_taxonomy( $term, $taxonomy, $term_ids, $limit = '' ) {
285 $query_args = array(
286 'post_type' => 'product',
287 'post_status' => 'publish',
288 'fields' => 'ids',
289 'posts_per_page' => $limit ? (int) $limit : -1,
290 's' => $term,
291 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
292 array(
293 'taxonomy' => $taxonomy,
294 'field' => 'term_id',
295 'terms' => $term_ids,
296 ),
297 ),
298 );
299
300 $query = new \WP_Query( $query_args );
301
302 return $query->posts;
303 }
304
305 /**
306 * Retrieves a list of searched product categories based on the provided term.
307 *
308 * @since 1.0.0
309 *
310 * @param mixed $params The search parameters.
311 *
312 * @return array An array of category details, including item ID, name, URL, and thumbnail URL.
313 */
314 public function get_searched_categories( $params ) {
315 $term = isset( $params['term'] ) ? $params['term'] : '';
316 $limit = isset( $params['limit'] ) ? $params['limit'] : '';
317 $includes = isset( $params['includes'] ) && is_array( $params['includes'] ) ? $params['includes'] : array();
318 $trigger_type = isset( $params['trigger_type'] ) ? $params['trigger_type'] : 'cat';
319
320 $found_categories = array();
321 $args = array(
322 'taxonomy' => array( 'product_' . $trigger_type ),
323 'orderby' => 'id',
324 'number' => $limit,
325 'order' => 'ASC',
326 'hide_empty' => false,
327 'fields' => 'all',
328 'name__like' => $term,
329 'include' => $includes,
330 );
331
332 $categories = get_terms( $args );
333 foreach ( $categories as $category ) {
334 if ( ! is_wp_error( $category ) ) {
335 $found_categories[] = array(
336 'item_id' => $category->term_id,
337 'item_name' => $category->name,
338 'url' => get_term_link( $category ),
339 'thumbnail' => get_term_meta( $category->term_id, 'thumbnail_id', true )
340 ? wp_get_attachment_url( get_term_meta( $category->term_id, 'thumbnail_id', true ) )
341 : wc_placeholder_img_src(),
342 );
343 }
344 }
345
346 return $found_categories;
347 }
348
349 /**
350 * Renders and enqueues inline CSS for a specific addon post.
351 *
352 * Retrieves the custom CSS stored in the post meta with key 'prad_addons_css'
353 * for the given post ID. If CSS is found, it registers a dummy style handle,
354 * enqueues it, and adds the CSS inline using `wp_add_inline_style`.
355 *
356 * @param int $id The post ID from which to retrieve and render the addon CSS.
357 * @param mixed $type The context in which to render the CSS (e.g., 'print' for printing).
358 */
359 public function render_addon_css( $id, $type = '' ) {
360 $prad_addons_css = get_post_meta( $id, 'prad_addons_css', true );
361 if ( $prad_addons_css ) {
362 wp_register_style( 'prad-addons-css-'. $id, false ); // phpcs:ignore
363 wp_enqueue_style( 'prad-addons-css-' . $id );
364 wp_add_inline_style( 'prad-addons-css-' . $id, $prad_addons_css );
365 }
366 if ( 'print' === $type ) {
367 echo '<style id="prad-addons-css-' . esc_attr( $id ) . '-inline">' . esc_html( $prad_addons_css ) . '</style>';
368 }
369 }
370
371 /**
372 * Sanitize Params
373 *
374 * Recursively sanitizes the given parameters to ensure safe data handling.
375 *
376 * @param mixed $params The data to be sanitized. Can be an array, boolean, object, or string.
377 *
378 * @return mixed The sanitized data.
379 */
380 public function sanitize_rest_params( $params ) {
381 if ( is_array( $params ) ) {
382 return array_map( array( $this, 'sanitize_rest_params' ), $params );
383 } elseif ( is_bool( $params ) ) {
384 return rest_sanitize_boolean( $params );
385 } elseif ( is_object( $params ) ) {
386 return $params;
387 } elseif ( is_string( $params ) ) {
388 return $params;
389 } else {
390 return $params;
391 }
392 }
393
394 /**
395 * Returns a structured price object with numeric and formatted HTML price.
396 *
397 * If a sale price is provided, the returned price is the sale price, and the
398 * HTML includes both the regular and sale prices. Otherwise, it returns only
399 * the regular price.
400 *
401 * @since 1.0.3
402 *
403 * @param float|string $regular The regular price.
404 * @param float|string $sale The sale price. If empty or false, regular price is used.
405 *
406 * @return array {
407 * @type float $price The numeric value of the applicable price.
408 * @type string $html The formatted HTML price string.
409 * }
410 */
411 public function get_price_object( $regular, $sale ) {
412 return array(
413 'price' => $sale ? floatval( $sale ) : floatval( $regular ),
414 'html' => $sale ? '<span class="pricex"><del>' . wc_price( $regular ) . '</del> <ins>' . wc_price( $sale ) . '</ins></span>' : '<span class="pricex">' . wc_price( $regular ) . '</span>',
415 );
416 }
417
418 /**
419 * Converts the given price to the current currency based on active currency switchers.
420 *
421 * This function checks for various currency switcher plugins (e.g., WowStore Switcher,
422 * WooCommerce Currency Switcher, CURCY, Yay_Currency, etc.) and applies the appropriate
423 * conversion logic to the provided price. If no currency switcher is active, the original
424 * price is returned.
425 *
426 * @since 1.0.4
427 *
428 * @param float $price The original price to be converted.
429 *
430 * @return float The converted price based on the active currency switcher, or the original price if no switcher is active.
431 */
432 public function get_currency_converted_price( $price ) {
433 $price = floatval( $price );
434
435 // WowStore Switcher.
436 if ( defined( 'WOPB_VER' ) && defined( 'WOPB_PRO_VER' ) && class_exists( 'WOPB_PRO\Currency_Switcher_Action' ) ) {
437 $current_currency_code = wopb_function()->get_setting( 'wopb_current_currency' );
438 $default_currency = wopb_function()->get_setting( 'wopb_default_currency' );
439 $current_currency = \WOPB_PRO\Currency_Switcher_Action::get_currency( $current_currency_code );
440 if ( ! $current_currency ) {
441 $current_currency = $default_currency;
442 }
443
444 if ( $current_currency_code !== $default_currency ) {
445 $wopb_current_currency_rate = floatval( ( isset( $current_currency['wopb_currency_rate'] ) && $current_currency['wopb_currency_rate'] > 0 && ! ( '' === $current_currency['wopb_currency_rate'] ) ) ? $current_currency['wopb_currency_rate'] : 1 );
446 $wopb_current_exchange_fee = floatval( ( isset( $current_currency['wopb_currency_exchange_fee'] ) && $current_currency['wopb_currency_exchange_fee'] >= 0 && ! ( '' === $current_currency['wopb_currency_exchange_fee'] ) ) ? $current_currency['wopb_currency_exchange_fee'] : 0 );
447 $total_rate = ( $wopb_current_currency_rate + $wopb_current_exchange_fee );
448 return $price * $total_rate;
449 }
450 }
451
452 // WooCommerce Currency Switcher by WPExperts.
453 if ( defined( 'WCCS_VERSION' ) ) {
454 $price = apply_filters( 'woocommerce_product_addons_option_price_raw', $price, '' ); //phpcs:ignore
455 return $price;
456 }
457
458 if ( function_exists( 'wmc_get_price' ) ) {
459 if ( defined( 'WOOMULTI_CURRENCY_VERSION' ) && class_exists( 'WOOMULTI_CURRENCY_Data' ) ) {
460 $curcy = \WOOMULTI_CURRENCY_Data::get_ins();
461 } elseif ( defined( 'WOOMULTI_CURRENCY_F_VERSION' ) && class_exists( 'WOOMULTI_CURRENCY_F_Data' ) ) {
462 $curcy = \WOOMULTI_CURRENCY_F_Data::get_ins();
463 }
464
465 if ( isset( $curcy ) && $curcy->get_enable() ) {
466 $price = wmc_get_price( $price );
467 return $price;
468 }
469 }
470
471 // Yay_Currency Switcher.
472 if ( defined( 'YAY_CURRENCY_VERSION' ) ) {
473 $price = apply_filters( 'yay_currency_convert_price', $price, '' ); // phpcs:ignore
474 return $price;
475 }
476
477 // FOX - Currency Switcher.
478 if ( defined( 'WOOCS_VERSION' ) ) {
479 $price = apply_filters( 'woocs_convert_price', $price, '' );// phpcs:ignore
480 return $price;
481 }
482
483 // Currency Switcher for WooCommerce by wpwham.
484 if ( function_exists( 'alg_get_current_currency_code' ) && function_exists( 'alg_convert_price' ) ) {
485 $default_currency = get_option( 'woocommerce_currency' );
486 $current_currency_code = alg_get_current_currency_code();
487 if ( $current_currency_code !== $default_currency ) {
488 $price = alg_convert_price(
489 array(
490 'price' => $price,
491 'currency' => $current_currency_code,
492 'currency_from' => $default_currency,
493 'format_price' => 'no',
494 )
495 );
496 return $price;
497 }
498 }
499
500 // Yith Currency Switcher.
501 if ( function_exists( 'yith_wcmcs_convert_price' ) ) {
502 $price = apply_filters( 'yith_wcmcs_convert_price', $price, '' );// phpcs:ignore
503 return $price;
504 }
505
506 // Aelia Currency Switcher.
507 if ( class_exists( 'WC_Aelia_CurrencySwitcher' ) ) {
508 $base_currency = apply_filters( 'wc_aelia_cs_base_currency', '' );// phpcs:ignore
509 $active_currency = get_woocommerce_currency();
510 $price = apply_filters( 'wc_aelia_cs_convert', $price, $base_currency, $active_currency );// phpcs:ignore
511 return $price;
512 }
513
514 if ( class_exists( '\WCPay\MultiCurrency\MultiCurrency' ) ) {
515 $multi_currency = null;
516
517 if ( class_exists( 'WC_Payments' ) && method_exists( '\WC_Payments', 'get_gateway' ) ) {
518 $gateway = \WC_Payments::get_gateway();
519
520 if ( class_exists( '\WCPay\WC_Payments_Currency_Manager' ) ) {
521 $currency_manager = new \WCPay\WC_Payments_Currency_Manager( $gateway );
522
523 if ( method_exists( $currency_manager, 'get_multi_currency_instance' ) ) {
524 $multi_currency = $currency_manager->get_multi_currency_instance();
525 }
526 }
527 }
528
529 // Convert price if instance exists.
530 if ( $multi_currency instanceof \WCPay\MultiCurrency\MultiCurrency && method_exists( $multi_currency, 'get_price' ) ) {
531 $price = $multi_currency->get_price( $price, 'product' );
532 return $price;
533 }
534 }
535
536 return $price;
537 }
538
539 /**
540 * Revert the given price to the base price.
541 *
542 * Price is returned.
543 *
544 * @since 1.0.4
545 *
546 * @param float $price The original price to be reverted.
547 *
548 * @return float The reverted price.
549 */
550 public function get_currency_reverted_price( $price ) {
551 $price = floatval( $price );
552
553 // WowStore Switcher.
554 if ( defined( 'WOPB_VER' ) && defined( 'WOPB_PRO_VER' ) && class_exists( 'WOPB_PRO\Currency_Switcher_Action' ) ) {
555 $current_currency_code = wopb_function()->get_setting( 'wopb_current_currency' );
556 $default_currency = wopb_function()->get_setting( 'wopb_default_currency' );
557 $current_currency = \WOPB_PRO\Currency_Switcher_Action::get_currency( $current_currency_code );
558 if ( ! $current_currency ) {
559 $current_currency = $default_currency;
560 }
561
562 if ( $current_currency_code !== $default_currency ) {
563 $wopb_current_currency_rate = floatval( ( isset( $current_currency['wopb_currency_rate'] ) && $current_currency['wopb_currency_rate'] > 0 && ! ( '' === $current_currency['wopb_currency_rate'] ) ) ? $current_currency['wopb_currency_rate'] : 1 );
564 $wopb_current_exchange_fee = floatval( ( isset( $current_currency['wopb_currency_exchange_fee'] ) && $current_currency['wopb_currency_exchange_fee'] >= 0 && ! ( '' === $current_currency['wopb_currency_exchange_fee'] ) ) ? $current_currency['wopb_currency_exchange_fee'] : 0 );
565 $total_rate = ( $wopb_current_currency_rate + $wopb_current_exchange_fee );
566 return $price / $total_rate;
567 }
568 }
569
570 // WooCommerce Currency Switcher by WPExperts.
571 if ( defined( 'WCCS_VERSION' ) ) {
572 // Dont have any filter to revert the price.
573 $price = $this->manual_currency_reverted_price( $price );
574 return $price;
575 }
576
577 if ( function_exists( 'wmc_revert_price' ) ) {
578
579 if ( defined( 'WOOMULTI_CURRENCY_VERSION' ) && class_exists( 'WOOMULTI_CURRENCY_Data' ) ) {
580 $curcy = \WOOMULTI_CURRENCY_Data::get_ins();
581 } elseif ( defined( 'WOOMULTI_CURRENCY_F_VERSION' ) && class_exists( 'WOOMULTI_CURRENCY_F_Data' ) ) {
582 $curcy = \WOOMULTI_CURRENCY_F_Data::get_ins();
583 }
584
585 if ( isset( $curcy ) && $curcy->get_enable() ) {
586 $price = wmc_revert_price( $price );
587 return $price;
588 }
589 }
590
591 // Yay_Currency Switcher.
592 if ( defined( 'YAY_CURRENCY_VERSION' ) ) {
593 $price = apply_filters( 'yay_currency_revert_price', $price, '' );// phpcs:ignore
594 return $price;
595 }
596
597 // FOX - Currency Switcher.
598 if ( defined( 'WOOCS_VERSION' ) ) {
599 $price = apply_filters( 'woocs_back_convert_price', $price, '' );// phpcs:ignore
600 return $price;
601 }
602
603 // Currency Switcher for WooCommerce by wpwham.
604 if ( function_exists( 'alg_get_current_currency_code' ) && function_exists( 'alg_convert_price' ) ) {
605 // Dont have any filter to revert the price.
606 $price = $this->manual_currency_reverted_price( $price );
607 return $price;
608 }
609
610 // Yith Currency Switcher.
611 if ( function_exists( 'yith_wcmcs_convert_price' ) ) {
612 // Dont have any filter to revert the price.
613 $price = $this->manual_currency_reverted_price( $price );
614 return $price;
615 }
616
617 // Aelia Currency Switcher.
618 if ( class_exists( 'WC_Aelia_CurrencySwitcher' ) ) {
619 $base_currency = apply_filters( 'wc_aelia_cs_base_currency', '' );// phpcs:ignore
620 $active_currency = get_woocommerce_currency();
621 $price = apply_filters( 'wc_aelia_cs_convert', $price, $active_currency, $base_currency );// phpcs:ignore
622 return $price;
623 }
624
625 if ( class_exists( '\WCPay\MultiCurrency\MultiCurrency' ) ) {
626 $multi_currency = null;
627 $converted_price = $price;
628
629 if ( class_exists( 'WC_Payments' ) && method_exists( '\WC_Payments', 'get_gateway' ) ) {
630 $gateway = \WC_Payments::get_gateway();
631
632 if ( class_exists( '\WCPay\WC_Payments_Currency_Manager' ) ) {
633 $currency_manager = new \WCPay\WC_Payments_Currency_Manager( $gateway );
634
635 if ( method_exists( $currency_manager, 'get_multi_currency_instance' ) ) {
636 $multi_currency = $currency_manager->get_multi_currency_instance();
637 }
638 }
639 }
640
641 // Convert price if instance exists.
642 if ( $multi_currency instanceof \WCPay\MultiCurrency\MultiCurrency && method_exists( $multi_currency, 'get_price' ) ) {
643 $currency = $multi_currency->get_selected_currency();
644 $rate = $currency->get_rate();
645 if ( $currency->get_is_default() || $rate <= 0 ) {
646 return $converted_price;
647 }
648
649 // Reverse conversion.
650 $base_price = (float) $converted_price / $rate;
651
652 return (float) $base_price;
653
654 }
655 }
656
657 return $price;
658 }
659
660 /**
661 * Retrieves currency conversion data including rate and extra value.
662 *
663 * This method calculates the converted currency rate by comparing
664 * two values from `get_currency_converted_price()`. It returns an array
665 * with the active status, rate difference, and extra amount.
666 *
667 * @since 1.0.4
668 * @return array {
669 * @type bool $cr_active Whether currency conversion is active (rate ≠ 1).
670 * @type float $cr_rate The currency conversion rate difference.
671 * @type float $cr_extra The extra value included in the conversion.
672 * }
673 */
674 public function get_currency_converted_data() {
675 $custom_array = array();
676 $_extra = BaseCurrency::convert( 0 );
677 $_rate = BaseCurrency::convert( 1 ) - $_extra;
678 $custom_array['cr_active'] = floatval( 1 ) !== floatval( $_rate ) ? true : false;
679 $custom_array['cr_rate'] = $_rate;
680 $custom_array['cr_extra'] = $_extra;
681
682 return $custom_array;
683 }
684
685 /**
686 * Manually revert a converted currency price to its base value.
687 *
688 * This function uses the conversion rate and extra value from get_currency_converted_data()
689 * to calculate the original price before conversion.
690 *
691 * @since 1.0.4
692 * @param float $price The converted price to revert.
693 * @return float The reverted base price.
694 */
695 public function manual_currency_reverted_price( $price ) {
696 $currency_data = $this->get_currency_converted_data();
697 if ( $currency_data['cr_active'] ) {
698 $cr_rate = isset( $currency_data['cr_rate'] ) ? $currency_data['cr_rate'] : 1;
699 $cr_extra = isset( $currency_data['cr_extra'] ) ? $currency_data['cr_extra'] : 0;
700
701 if ( floatval( $cr_rate ) == floatval( 0 ) ) {
702 return 0;
703 }
704
705 return ( floatval( $price ) - floatval( $cr_extra ) ) / floatval( $cr_rate );
706 }
707
708 return floatval( $price );
709 }
710
711 /**
712 * Sanitizes the given content using wp_kses with allowed HTML tags.
713 *
714 * This method applies the 'prad_allowed_html_tags' filter to determine
715 * which HTML tags are permitted, and then sanitizes the $prad_blocks content
716 * accordingly using wp_kses.
717 *
718 * @since 1.0.5
719 *
720 * @param mixed $prad_blocks The content to be sanitized.
721 * @return mixed The sanitized content with only allowed HTML tags.
722 */
723 public function get_wp_kses_content( $prad_blocks ) {
724 return $prad_blocks;
725 }
726
727 /**
728 * Get Option Value bypassing cache
729 * Inspired By WordPress Core get_option
730 *
731 * @since v.1.0.7
732 * @param string $option Option Name.
733 * @param boolean $default_value option default value.
734 * @return mixed
735 */
736 public function get_option_without_cache( $option, $default_value = false ) {
737 global $wpdb;
738
739 if ( is_scalar( $option ) ) {
740 $option = trim( $option );
741 }
742
743 if ( empty( $option ) ) {
744 return false;
745 }
746
747 $value = $default_value;
748
749 $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
750
751 if ( is_object( $row ) ) {
752 $value = $row->option_value;
753 } else {
754 return apply_filters( "prad_default_option_{$option}", $default_value, $option );
755 }
756
757 return apply_filters( "prad_option_{$option}", maybe_unserialize( $value ), $option );
758 }
759
760 /**
761 * Add option without adding to the cache
762 * Inspired By WordPress Core set_transient
763 *
764 * @since v.1.0.7
765 * @param string $option option name.
766 * @param string $value option value.
767 * @param string $autoload whether to load WordPress startup.
768 * @return bool
769 */
770 public function add_option_without_cache( $option, $value = '', $autoload = 'yes' ) {
771 global $wpdb;
772
773 if ( is_scalar( $option ) ) {
774 $option = trim( $option );
775 }
776
777 if ( empty( $option ) ) {
778 return false;
779 }
780
781 wp_protect_special_option( $option );
782
783 if ( is_object( $value ) ) {
784 $value = clone $value;
785 }
786
787 $value = sanitize_option( $option, $value );
788
789 /*
790 * Make sure the option doesn't already exist.
791 */
792
793 if ( apply_filters( "prad_default_option_{$option}", false, $option, false ) !== $this->get_option_without_cache( $option ) ) {
794 return false;
795 }
796
797 $serialized_value = maybe_serialize( $value );
798 $autoload = ( 'no' === $autoload || false === $autoload ) ? 'no' : 'yes';
799
800 $result = $wpdb->query( $wpdb->prepare( "INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE `option_name` = VALUES(`option_name`), `option_value` = VALUES(`option_value`), `autoload` = VALUES(`autoload`)", $option, $serialized_value, $autoload ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
801 if ( ! $result ) {
802 return false;
803 }
804
805 return true;
806 }
807
808 /**
809 * Get Transient Value bypassing cache
810 * Inspired By WordPress Core get_transient
811 *
812 * @since v.1.0.7
813 * @param string $transient Transient Name.
814 * @return mixed
815 */
816 public function get_transient_without_cache( $transient ) {
817 $transient_option = '_transient_' . $transient;
818 $transient_timeout = '_transient_timeout_' . $transient;
819 $timeout = $this->get_option_without_cache( $transient_timeout );
820
821 if ( false !== $timeout && $timeout < time() ) {
822 delete_option( $transient_option );
823 delete_option( $transient_timeout );
824 $value = false;
825 }
826
827 if ( ! isset( $value ) ) {
828 $value = $this->get_option_without_cache( $transient_option );
829 }
830
831 return apply_filters( "prad_transient_{$transient}", $value, $transient );
832 }
833
834 /**
835 * Set transient without adding to the cache
836 * Inspired By WordPress Core set_transient
837 *
838 * @since v.1.0.7
839 * @param string $transient Transient Name.
840 * @param mixed $value Transient Value.
841 * @param integer $expiration Time until expiration in seconds.
842 * @return bool
843 */
844 public function set_transient_without_cache( $transient, $value, $expiration = 0 ) {
845 $expiration = (int) $expiration;
846
847 $transient_timeout = '_transient_timeout_' . $transient;
848 $transient_option = '_transient_' . $transient;
849
850 $result = false;
851
852 if ( false === $this->get_option_without_cache( $transient_option ) ) {
853 $autoload = 'yes';
854 if ( $expiration ) {
855 $autoload = 'no';
856 $this->add_option_without_cache( $transient_timeout, time() + $expiration, 'no' );
857 }
858 $result = $this->add_option_without_cache( $transient_option, $value, $autoload );
859 } else {
860 /*
861 * If expiration is requested, but the transient has no timeout option,
862 * delete, then re-create transient rather than update.
863 */
864 $update = true;
865
866 if ( $expiration ) {
867 if ( false === $this->get_option_without_cache( $transient_timeout ) ) {
868 delete_option( $transient_option );
869 $this->add_option_without_cache( $transient_timeout, time() + $expiration, 'no' );
870 $result = $this->add_option_without_cache( $transient_option, $value, 'no' );
871 $update = false;
872 } else {
873 update_option( $transient_timeout, time() + $expiration );
874 }
875 }
876
877 if ( $update ) {
878 $result = update_option( $transient_option, $value );
879 }
880 }
881
882 return $result;
883 }
884
885 /**
886 * Generates the HTML for the product block variation section.
887 *
888 * This function outputs the variation selection UI for a given product block item.
889 *
890 * @param array $args Arguments containing the product item.
891 * @return string The generated HTML for the variation section.
892 */
893 public function generate_products_block_variation_section_html( $args ) {
894 $item = $args['item'];
895 $allowed_html_tags = apply_filters( 'prad_allowed_html_tags', array() );
896 ob_start();
897 if ( isset( $item->variation ) && $item->variation ) {
898 $select_options = '';
899 $product = wc_get_product( $item->id );
900 $available_variations = $product->get_available_variations();
901 foreach ( $available_variations as $variation_data ) {
902 $variation_id = $variation_data['variation_id'];
903 $variation = wc_get_product( $variation_id );
904
905 if ( $variation && $variation->is_purchasable() && $variation->is_in_stock() ) {
906 $variation_attributes = $variation->get_attributes();
907 $option_label = '';
908 $valid_variation = true;
909 $i = 0;
910 $regular_price = $variation->get_regular_price( '' );
911 $sale_price = $variation->get_sale_price( '' );
912
913 $regular_price = apply_filters(
914 'prad_raw_tax_compitable_price',
915 array(
916 'product_id' => $variation_id,
917 'price' => $variation->get_regular_price(),
918 'source' => 'product_page',
919 )
920 );
921 $sale_price = apply_filters(
922 'prad_raw_tax_compitable_price',
923 array(
924 'product_id' => $variation_id,
925 'price' => $variation->get_sale_price(),
926 'source' => 'product_page',
927 )
928 );
929 $price_obj = product_addons()->get_price_object( $regular_price, $sale_price );
930 foreach ( $variation_attributes as $key => $value ) {
931 $label = str_replace( '_', ' ', str_replace( 'pa_', '', $key ) );
932 if ( ! empty( $value ) ) {
933 $option_label .= ( $i > 0 ? ' , ' : '' ) . ucfirst( $label ) . ' - ' . ucfirst( $value );
934 ++$i;
935 } else {
936 $valid_variation = false;
937 }
938 }
939 if ( $valid_variation ) {
940 $option_label = rawurldecode( wp_strip_all_tags( $option_label ) );
941 $select_options .= '<div class="prad-select-option" title="' . esc_attr( $option_label ) . '" value="' . esc_attr( $price_obj['price'] ) . '" data-variation-id="' . esc_attr( $variation_id ) . '" data-pricehtml="' . esc_attr( $price_obj['html'] ) . '">' . esc_html( $option_label ) . '</div>';
942 }
943 }
944 }
945
946 if ( $select_options ) {
947 ?>
948 <div class="prad-product-block-variation-select prad-mt-10">
949 <div class="prad-custom-select prad-w-full prad-product-variation-select-comp">
950 <div class="prad-select-box prad-block-input prad-block-content" readonly="readonly"><div style="max-width: 120px" class="prad-select-box-item prad-mr-12 prad-ellipsis"><?php esc_html_e( 'Select an option', 'product-addons' ); ?></div> <div class="prad-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="8" fill="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m1 1 6 6 6-6"></path></svg></div></div>
951 <div class="prad-select-options">
952 <?php echo wp_kses( $select_options, $allowed_html_tags ); ?>
953 </div>
954 </div>
955 </div>
956
957 <?php
958 }
959 }
960 return ob_get_clean();
961 }
962
963 /**
964 * Retrieves product block attributes for a given product ID.
965 *
966 * Returns an object containing product details such as ID, type, variation status,
967 * URL, name, image, regular and sale prices, stock status, and purchasable status.
968 *
969 * @param int $p_id Product ID.
970 * @param bool $var_p Whether the product is a variation.
971 * @return object|null Product attributes object or null if not found.
972 */
973 public function get_product_block_product_attr( $p_id, $var_p = false ) {
974 $product = wc_get_product( $p_id );
975 if ( $product ) {
976 $formatted_variation = wc_get_formatted_variation( $product, true, false, true );
977 $product_value = $product->get_name() . ( $formatted_variation ? ' - ' . $formatted_variation : '' );
978 $data = array(
979 'id' => $p_id,
980 'type' => 'per_unit',
981 'variation' => $var_p,
982 'url' => get_permalink( $p_id ),
983 'value' => rawurldecode( wp_strip_all_tags( $product_value ) ),
984 'img' => wp_get_attachment_url( $product->get_image_id() ),
985 'regular' => apply_filters(
986 'prad_raw_tax_compitable_price',
987 array(
988 'product_id' => $p_id,
989 'ppType' => 'reg',
990 'price' => $product->get_regular_price(),
991 'source' => 'product_page',
992 )
993 ),
994 'sale' => apply_filters(
995 'prad_raw_tax_compitable_price',
996 array(
997 'product_id' => $p_id,
998 'ppType' => 'sale',
999 'price' => $product->get_sale_price(),
1000 'source' => 'product_page',
1001 )
1002 ),
1003 'is_in_stock' => $product->is_in_stock(),
1004 'is_purchasable' => $product->is_purchasable(),
1005 );
1006 return (object) $data;
1007 }
1008
1009 return null;
1010 }
1011
1012 /**
1013 * Generates a UTM link with specified parameters and configuration.
1014 *
1015 * This method constructs a URL with UTM parameters for tracking purposes,
1016 * optionally including affiliate and hash values, and supports custom UTM configurations.
1017 *
1018 * @param array $params {
1019 * Array of parameters for generating the UTM link.
1020 * @type string $url Base URL to append UTM parameters to.
1021 * @type string $utmKey Key to select default UTM configuration.
1022 * @type string $affiliate Affiliate ID to append as 'ref'.
1023 * @type string $hash Hash fragment to append to the URL.
1024 * @type array $config Custom UTM configuration array.
1025 * }
1026 * @return string The generated URL with UTM parameters.
1027 */
1028 public function generate_utm_link( $params ) {
1029 // Default UTM configurations.
1030 $default_config = array(
1031 'example' => array(
1032 'source' => 'db-wowaddons-featurename',
1033 'medium' => 'block-feature',
1034 'campaign' => 'wowaddons-dashboard',
1035 ),
1036 'summer_db' => array(
1037 'source' => 'db-wowaddons-notice',
1038 'medium' => 'black-friday-sale',
1039 'campaign' => 'wowaddons-dashboard',
1040 ),
1041 'final_hour' => array(
1042 'source' => 'db-wowaddons-text',
1043 'medium' => 'final-hour-sale',
1044 'campaign' => 'wowaddons-dashboard',
1045 ),
1046 'massive_sale' => array(
1047 'source' => 'db-wowaddons-notice-logo',
1048 'medium' => 'massive-sale',
1049 'campaign' => 'wowaddons-dashboard',
1050 ),
1051 'flash_sale' => array(
1052 'source' => 'db-wowaddons-notice-text',
1053 'medium' => 'flash-sale',
1054 'campaign' => 'wowaddons-dashboard',
1055 ),
1056 'exclusive_deals' => array(
1057 'source' => 'db-wowaddons-notice-logo',
1058 'medium' => 'exclusive-deals',
1059 'campaign' => 'wowaddons-dashboard',
1060 ),
1061 );
1062
1063 // Step 1: Get parameters.
1064 $base_url = $params['url'] ?? 'https://www.wpxpo.com/product/wowaddons/';
1065 $utm_key = $params['utmKey'] ?? null;
1066 $affiliate = $params['affiliate'] ?? apply_filters( 'prad_affiliate_id', '' );
1067 $hash = $params['hash'] ?? '';
1068 $custom_config = $params['config'] ?? null;
1069
1070 $parsed_url = wp_parse_url( $base_url );
1071 $scheme = $parsed_url['scheme'] ?? 'https';
1072 $host = $parsed_url['host'] ?? '';
1073 $path = $parsed_url['path'] ?? '';
1074 $query = array();
1075
1076 // Step 3: Extract existing query params if present.
1077 if ( isset( $parsed_url['query'] ) ) {
1078 parse_str( $parsed_url['query'], $query );
1079 }
1080
1081 // Step 4: Determine config.
1082 $utm_config = $custom_config ?? ( $utm_key && isset( $default_config[ $utm_key ] ) ? $default_config[ $utm_key ] : array() );
1083
1084 // Step 5: Add UTM parameters.
1085 if ( ! empty( $utm_config ) ) {
1086 $query = array_merge(
1087 $query,
1088 array(
1089 'utm_source' => $utm_config['source'],
1090 'utm_medium' => $utm_config['medium'],
1091 'utm_campaign' => $utm_config['campaign'],
1092 )
1093 );
1094 }
1095
1096 // Step 6: Add affiliate if present.
1097 if ( $affiliate ) {
1098 $query['ref'] = $affiliate;
1099 }
1100
1101 // Step 7: Reconstruct URL.
1102 $final_url = $scheme . '://' . $host . $path;
1103
1104 if ( ! empty( $query ) ) {
1105 $final_url .= '?' . http_build_query( $query );
1106 }
1107
1108 if ( $hash ) {
1109 $final_url .= '#' . $hash;
1110 }
1111
1112 return $final_url;
1113 }
1114
1115 /**
1116 * Get WOW Products Details
1117 *
1118 * @return array
1119 */
1120 public function get_wow_products_details() {
1121 return array(
1122 'products' => array(
1123 'wow_shipping' => file_exists( WP_PLUGIN_DIR . '/wow-table-rate-shipping/wow-table-rate-shipping.php' ),
1124 'post_x' => file_exists( WP_PLUGIN_DIR . '/ultimate-post/ultimate-post.php' ),
1125 'wow_store' => file_exists( WP_PLUGIN_DIR . '/product-blocks/product-blocks.php' ),
1126 'wow_optin' => file_exists( WP_PLUGIN_DIR . '/optin/optin.php' ),
1127 'wow_revenue' => file_exists( WP_PLUGIN_DIR . '/revenue/revenue.php' ),
1128 'wholesale_x' => file_exists( WP_PLUGIN_DIR . '/wholesalex/wholesalex.php' ),
1129 ),
1130 'products_active' => array(
1131 'wow_shipping' => defined( 'WTRS_VER' ),
1132 'post_x' => defined( 'ULTP_VER' ),
1133 'wow_store' => defined( 'WOPB_VER' ),
1134 'wow_optin' => defined( 'OPTN_VERSION' ),
1135 'wow_revenue' => defined( 'REVENUE_VER' ),
1136 'wholesale_x' => defined( 'WHOLESALEX_VER' ),
1137 ),
1138 );
1139 }
1140
1141 /**
1142 * Move or copy files from temp folder to on_cart folder.
1143 *
1144 * @param array $src_files Absolute file paths (from temp folder).
1145 * @param string $_from Source folder name ('temp' or 'order_placed').
1146 * @param bool $delete Whether to delete original files (true = move, false = copy).
1147 *
1148 * @return array List of processed files with new paths & URLs.
1149 */
1150 public function prad_move_uploadblock_files( array $src_files, string $_from = 'temp', bool $delete = false ) {
1151 $upload_dir = wp_upload_dir();
1152
1153 $_to = 'order_placed';
1154 if ( 'order_placed' === $_from ) {
1155 $_to = 'order_completed';
1156 }
1157
1158 $from_dir = $upload_dir['basedir'] . '/prad_option_files/' . $_from;
1159 $to_dir = $upload_dir['basedir'] . '/prad_option_files/' . $_to;
1160
1161 // Make sure destination folder exists
1162 if ( ! file_exists( $to_dir ) ) {
1163 wp_mkdir_p( $to_dir );
1164 }
1165
1166 // Make sure temp folder exists
1167 if ( ! file_exists( $from_dir ) ) {
1168 wp_mkdir_p( $from_dir );
1169 }
1170
1171 $processed = array();
1172
1173 foreach ( $src_files as $src ) {
1174 // Extract filename with extension.
1175 $filename = basename( $src );
1176
1177 $source_path = $from_dir . '/' . $filename;
1178 $destination_path = $to_dir . '/' . $filename;
1179
1180 // Skip if source file doesn't exist.
1181 if ( ! file_exists( $source_path ) ) {
1182 continue;
1183 }
1184
1185 if ( 'on_cart' !== $_to && file_exists( $destination_path ) ) {
1186 $file_info = pathinfo( $filename );
1187 $name = $file_info['filename'];
1188 $ext = isset( $file_info['extension'] ) ? '.' . $file_info['extension'] : '';
1189 $counter = 1;
1190
1191 while ( file_exists( $destination_path ) ) {
1192 $destination_path = $to_dir . '/' . $name . '-' . $counter . $ext;
1193 ++$counter;
1194 }
1195 }
1196
1197 if ( copy( $source_path, $destination_path ) ) {
1198 if ( $delete ) {
1199 wp_delete_file( $source_path );
1200 }
1201
1202 $processed[] = array(
1203 'updated_src' => array(
1204 'prev_src' => $upload_dir['baseurl'] . '/prad_option_files/' . $_from . '/' . basename( $source_path ),
1205 'curr_src' => $upload_dir['baseurl'] . '/prad_option_files/' . $_to . '/' . basename( $destination_path ),
1206 'curr_name' => basename( $destination_path ),
1207 ),
1208 'file' => $destination_path,
1209 'url' => $upload_dir['baseurl'] . '/prad_option_files/' . $_to . '/' . basename( $destination_path ),
1210 );
1211 }
1212 }
1213
1214 return $processed;
1215 }
1216
1217 /**
1218 * Safe stripslashes that handles both strings and arrays.
1219 *
1220 * This function safely applies stripslashes to a value, whether it's a string or an array.
1221 * If the value is already an array, it returns the array as-is.
1222 * If the value is a string, it applies stripslashes and returns the result.
1223 *
1224 * @since 1.0.0
1225 * @param mixed $value The value to process (string or array).
1226 * @return mixed The processed value.
1227 */
1228 public function safe_stripslashes( $value ) {
1229 if ( is_array( $value ) ) {
1230 return $value;
1231 }
1232
1233 if ( is_string( $value ) ) {
1234 return stripslashes( $value );
1235 }
1236
1237 return $value;
1238 }
1239
1240 /**
1241 * Retrieves the list of allowed file types for uploads.
1242 *
1243 * This function returns an array of file types that are permitted to be uploaded
1244 * through the product addons functionality.
1245 *
1246 * @return array List of allowed file types for uploads.
1247 */
1248 public function prad_get_upload_allowed_file_types( $extras = array() ) {
1249
1250 $allowed_types = array(
1251 'png' => 'image/png',
1252 'jpg' => 'image/jpeg',
1253 'jpeg' => 'image/jpeg',
1254 'pdf' => 'application/pdf',
1255 'csv' => 'text/csv',
1256 'doc' => 'application/msword',
1257 'txt' => 'text/plain',
1258 'ppt' => 'application/vnd.ms-powerpoint',
1259 'heic' => 'image/heic',
1260 'svg' => 'image/svg+xml',
1261 'ai' => 'application/pdf',
1262 'psd' => 'image/vnd.adobe.photoshop',
1263 'eps' => 'application/postscript',
1264 'cdr' => 'application/vnd.corel-draw',
1265 'gpx' => 'text/xml',
1266 );
1267
1268 if ( ! empty( $extras ) && is_array( $extras ) ) {
1269 $allowed_types = array_merge( $allowed_types, $extras );
1270 }
1271
1272 return apply_filters( 'prad_upload_field_allowed_file_types', $allowed_types );
1273 }
1274
1275 /**
1276 * Retrieves global WooCommerce product attributes and their terms.
1277 *
1278 * This function fetches all global attributes defined in WooCommerce
1279 * and their associated terms, returning them in a structured array.
1280 *
1281 * @return array An associative array of attributes and their terms.
1282 */
1283 public function prad_get_attributes() {
1284 $global_attrs = wc_get_attribute_taxonomies();
1285
1286 $attrs = array();
1287 foreach ( $global_attrs as $attribute ) {
1288 $attr_name = $attribute->attribute_name;
1289 $terms = get_terms(
1290 array(
1291 'taxonomy' => wc_attribute_taxonomy_name( $attr_name ),
1292 'hide_empty' => false,
1293 )
1294 );
1295
1296 foreach ( $terms as $term ) {
1297 $attrs_ops[ $attr_name ][] = array(
1298 'value' => (int) $term->term_id,
1299 'slug' => $term->slug,
1300 'label' => $term->name,
1301 );
1302 }
1303
1304 $attrs[ 'pa_' . $attr_name ] = array(
1305 'label' => $attribute->attribute_label,
1306 'options' => isset( $attrs_ops[ $attr_name ] ) ? $attrs_ops[ $attr_name ] : array(),
1307 );
1308 }
1309
1310 return $attrs;
1311 }
1312
1313 /**
1314 * Retrieves product option IDs based on global, product-specific, and term assignments.
1315 *
1316 * This function merges options assigned globally, per product, and by product terms,
1317 * then excludes any options marked as excluded.
1318 *
1319 * @param int $product_id The product ID.
1320 *
1321 * @return array An array of option IDs assigned to the product.
1322 */
1323 public function get_product_option_ids( int $product_id ): array {
1324 $option_all = $this->get_json_option( 'prad_option_assign_all', array() );
1325
1326 // Get options assigned directly to this product.
1327 $option_product = $this->get_json_meta( $product_id, 'prad_product_assigned_meta_inc', array() );
1328
1329 // Get options excluded from this product.
1330 $option_exclude = $this->get_json_meta( $product_id, 'prad_product_assigned_meta_exc', array() );
1331
1332 // Get options from product terms (categories, tags, brands).
1333 $option_terms_inc = $this->get_product_term_options( $product_id, 'prad_term_assigned_meta_inc' );
1334 $option_terms_exc = $this->get_product_term_options( $product_id, 'prad_term_assigned_meta_exc' );
1335
1336 // Merge and filter.
1337 $merged = array_unique( array_merge( $option_all, $option_terms_inc, $option_product ) );
1338 $option_ids = array_diff( $merged, $option_exclude, $option_terms_exc );
1339
1340 // Sort for consistency.
1341 sort( $option_ids );
1342
1343 return apply_filters( 'prad_product_option_ids', $option_ids, $product_id );
1344 }
1345
1346 /**
1347 * Retrieves product options from terms (categories, tags, brands).
1348 *
1349 * @param int $product_id The product ID.
1350 * @param string $meta_key The meta key to retrieve ('prad_term_assigned_meta_inc' or 'prad_term_assigned_meta_exc').
1351 *
1352 * @return array An array of option IDs from product terms.
1353 */
1354 private function get_product_term_options( int $product_id, string $meta_key ) {
1355 $option_terms = array();
1356 if ( 'prad_term_assigned_meta_exc' === $meta_key && ! $this->is_lc_active() ) {
1357 return $option_terms;
1358 }
1359
1360 $taxonomies = array( 'product_cat', 'product_tag', 'product_brand' );
1361 if ( 'prad_term_assigned_meta_exc' === $meta_key ) {
1362 $taxonomies = array( 'product_cat' );
1363 }
1364
1365 foreach ( $taxonomies as $taxonomy ) {
1366 $terms = get_the_terms( $product_id, $taxonomy );
1367 if ( $terms && ! is_wp_error( $terms ) ) {
1368 foreach ( $terms as $term ) {
1369 $term_options = $this->get_json_term_meta( $term->term_id, $meta_key, array() );
1370
1371 if ( is_array( $term_options ) ) {
1372 $option_terms = array_unique( array_merge( $option_terms, $term_options ) );
1373 }
1374 }
1375 }
1376 }
1377
1378 return $option_terms;
1379 }
1380
1381 /**
1382 * Retrieves a JSON-encoded option and decodes it.
1383 *
1384 * @param string $option_name The option name.
1385 * @param array $def The default value if option is not found.
1386 *
1387 * @return array The decoded option value or default.
1388 */
1389 private function get_json_option( string $option_name, array $def = array() ): array {
1390 $value = get_option( $option_name, '[]' );
1391 $decoded = json_decode( product_addons()->safe_stripslashes( $value ), true );
1392
1393 return is_array( $decoded ) ? $decoded : $def;
1394 }
1395
1396 /**
1397 * Retrieves a JSON-encoded post meta and decodes it.
1398 *
1399 * @param int $post_id The post ID.
1400 * @param string $meta_key The meta key.
1401 * @param array $def The default value if meta is not found.
1402 *
1403 * @return array The decoded meta value or default.
1404 */
1405 private function get_json_meta( int $post_id, string $meta_key, array $def = array() ): array {
1406 $value = get_post_meta( $post_id, $meta_key, true );
1407
1408 if ( empty( $value ) ) {
1409 return $def;
1410 }
1411
1412 $decoded = json_decode( product_addons()->safe_stripslashes( $value ), true );
1413
1414 return is_array( $decoded ) ? $decoded : $def;
1415 }
1416
1417 /**
1418 * Retrieves a JSON-encoded term meta and decodes it.
1419 *
1420 * @param int $term_id The term ID.
1421 * @param string $meta_key The meta key.
1422 * @param array $def The default value if meta is not found.
1423 *
1424 * @return array The decoded meta value or default.
1425 */
1426 private function get_json_term_meta( int $term_id, string $meta_key, array $def = array() ): array {
1427 $value = get_term_meta( $term_id, $meta_key, true );
1428
1429 if ( empty( $value ) ) {
1430 return $def;
1431 }
1432
1433 $decoded = json_decode( product_addons()->safe_stripslashes( $value ), true );
1434
1435 return is_array( $decoded ) ? $decoded : $def;
1436 }
1437 }
1438