PluginProbe ʕ •ᴥ•ʔ
WPML Multilingual & Multicurrency for WooCommerce / 5.5.7
WPML Multilingual & Multicurrency for WooCommerce v5.5.7
5.5.7 5.3.8 5.3.9 5.4.3 5.4.4 5.4.5 5.5.1 5.5.1.1 5.5.2.2 5.5.2.3 5.5.3 5.5.3.1 5.5.4 5.5.5 5.5.6 trunk 0.9 1.0 1.1 1.2 1.3 1.4 1.5 2.0 2.2 2.3 2.3.1 2.3.2 3.0 3.0.1 3.1 3.2 3.2.1 3.2.2 3.3 3.3.1 3.3.2 3.3.3 3.3.4 3.4 3.4.1 3.4.2 3.4.3 3.5 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.6 3.6.1 3.6.10 3.6.11 3.6.2 3.6.3 3.6.4 3.6.5 3.6.5.1 3.6.6 3.6.7 3.6.8 3.6.9 3.7 3.7.1 3.7.10 3.7.11 3.7.12 3.7.13 3.7.14 3.7.15 3.7.16 3.7.2 3.7.3 3.7.4 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.9.0 3.9.1 3.9.1.1 3.9.2 3.9.3 3.9.4 3.9.5 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.10.0 4.10.1 4.10.2 4.10.3 4.10.4 4.11.2 4.11.3.2 4.11.5 4.11.6 4.12.1 4.12.4 4.12.5 4.12.6 4.2.0 4.2.0.1 4.2.1 4.2.1.1 4.2.10 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.7.1 4.2.8 4.2.8.1 4.2.9 4.3.0 4.3.1 4.3.2 4.3.2.1 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.4.0 4.4.1 4.4.2 4.4.2.1 4.5.0 4.6.0 4.6.1 4.6.2 4.6.2.1 4.6.3 4.6.5 4.6.6 4.6.7 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 4.7.9 4.8.0 4.9.0 4.9.1 5.0.1 5.0.2 5.1.1 5.1.2 5.1.3 5.2.0 5.2.1 5.3.2 5.3.3.1 5.3.4 5.3.5 5.3.6 5.3.7
woocommerce-multilingual / inc / currencies / class-wcml-custom-prices.php
woocommerce-multilingual / inc / currencies Last commit date
currency-switcher 4 weeks ago class-wcml-custom-prices.php 4 weeks ago class-wcml-multi-currency-configuration.php 4 weeks ago class-wcml-multi-currency-coupons.php 1 year ago class-wcml-multi-currency-install.php 4 weeks ago class-wcml-multi-currency-orders.php 4 weeks ago class-wcml-multi-currency-prices.php 4 weeks ago class-wcml-multi-currency-reports.php 4 weeks ago class-wcml-multi-currency-resources.php 4 weeks ago class-wcml-multi-currency-shipping.php 4 weeks ago class-wcml-multi-currency-table-rate-shipping.php 4 weeks ago class-wcml-multi-currency.php 4 weeks ago class-wcml-price-filter.php 4 weeks ago class-wcml-w3tc-multi-currency.php 4 weeks ago
class-wcml-custom-prices.php
652 lines
1 <?php
2
3 use WCML\Utilities\DB;
4 use WPML\FP\Fns;
5 use WPML\FP\Maybe;
6 use WPML\FP\Obj;
7 use function WPML\FP\invoke;
8
9 class WCML_Custom_Prices {
10
11 private $woocommerce_wpml;
12 private $wpdb;
13
14 public function __construct( woocommerce_wpml $woocommerce_wpml, wpdb $wpdb ) {
15 $this->woocommerce_wpml = $woocommerce_wpml;
16 $this->wpdb = $wpdb;
17 }
18
19 public function add_hooks() {
20 add_filter( 'init', [ $this, 'custom_prices_init' ] );
21 }
22
23 public function custom_prices_init() {
24
25 if ( is_admin() ) {
26 add_action( 'woocommerce_ajax_save_product_variations', [ $this, 'sync_product_variations_custom_prices_on_ajax' ] );
27
28 add_action( 'save_post_product', [ $this, 'save_custom_prices' ] );
29 add_action( 'save_post_product_variation', [ $this, 'sync_product_variations_custom_prices' ] );
30 add_action( 'woocommerce_variation_options', [ $this, 'add_individual_variation_nonce' ], 10, 3 );
31
32 add_action( 'woocommerce_product_options_pricing', [ $this, 'woocommerce_product_options_custom_pricing' ] );
33 add_action( 'woocommerce_product_after_variable_attributes', [ $this, 'woocommerce_product_after_variable_attributes_custom_pricing' ], 10, 3 );
34
35 } else {
36 add_filter( 'woocommerce_product_is_on_sale', [ $this, 'filter_product_is_on_sale' ], 10, 2 );
37 add_filter( 'woocommerce_variation_prices', [ $this, 'exclude_hidden_variation_prices_from_prices' ], 10, 2 );
38 }
39
40 add_action( 'woocommerce_variation_is_visible', [ $this, 'filter_product_variations_with_custom_prices' ], 10, 2 );
41
42 add_filter( 'loop_shop_post_in', [ $this, 'filter_products_with_custom_prices' ], 100 );
43 add_filter( 'woocommerce_is_purchasable', [ $this, 'check_product_with_custom_prices' ], 10, 2 );
44
45 add_action( 'wc_after_products_starting_sales', [ $this, 'maybe_set_sale_prices' ] );
46 add_action( 'wc_after_products_ending_sales', [ $this, 'maybe_remove_sale_prices' ] );
47 }
48
49 public function add_individual_variation_nonce( $loop, $variation_data, $variation ) {
50
51 wp_nonce_field( 'wcml_save_custom_prices_variation_' . $variation->ID, '_wcml_custom_prices_variation_' . $variation->ID . '_nonce' );
52
53 }
54
55 public function get_product_custom_prices( $product_id, $currency = false ) {
56 if ( empty( $currency ) ) {
57 $currency = $this->woocommerce_wpml->multi_currency->get_client_currency();
58 }
59
60 if ( wcml_get_woocommerce_currency_option() === $currency ) {
61 return false;
62 }
63
64 $cache_key = $product_id . '_' . $currency;
65 $cache_group = 'product_custom_prices';
66 $cache_found = false;
67 $cache_custom_prices = wp_cache_get( $cache_key, $cache_group, false, $cache_found );
68 if ( $cache_found ) {
69 return $cache_custom_prices;
70 }
71
72 $product_meta = get_post_custom( $this->woocommerce_wpml->products->get_original_product_id( $product_id ) );
73 $custom_prices = [];
74
75 if ( ! empty( $product_meta['_wcml_custom_prices_status'][0] ) ) {
76 foreach ( wcml_price_custom_fields( $product_id ) as $key ) {
77 if ( isset( $product_meta[ $key . '_' . $currency ][0] ) ) {
78 $custom_prices[ $key ] = $product_meta[ $key . '_' . $currency ][0];
79 }
80 }
81 }
82
83 if ( ! isset( $custom_prices['_price'] ) ) {
84 return false;
85 }
86
87 $current__price_value = $custom_prices['_price'];
88
89 if ( $this->is_date_range_set( $product_meta, $currency ) && ! $this->is_on_sale_date_range( $product_meta, $currency ) ) {
90 $custom_prices['_sale_price'] = '';
91 $custom_prices['_price'] = $custom_prices['_regular_price'];
92 }
93
94 if ( $custom_prices['_price'] !== $current__price_value ) {
95 update_post_meta( $product_id, '_price_' . $currency, $custom_prices['_price'] );
96 }
97
98 if ( ! empty( $product_meta['_min_variation_price'] ) && is_array( $product_meta['_min_variation_price'] ) && $product_meta['_min_variation_price'] !== [ '' ] ) {
99
100 static $product_min_max_prices = [];
101
102 if ( ! isset( $product_min_max_prices[ $product_id ] ) && 'product' === get_post_type( $product_id ) ) {
103
104 $product_min_max_prices[ $product_id ] = [];
105
106 $variation_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT ID FROM {$this->wpdb->posts} WHERE post_parent = %d", $product_id ) );
107
108 $rows = $this->wpdb->get_results(
109 "SELECT post_id, meta_key, meta_value FROM {$this->wpdb->postmeta}
110 WHERE meta_key IN ('_price', '_regular_price', '_sale_price', '_price_$currency', '_regular_price_$currency', '_sale_price_$currency')
111 AND post_id IN (" . DB::prepareIn( $variation_ids, '%d' ) . ')'
112 );
113
114 $extractPricesByType = function( $rows, $key ) use ( $currency, $variation_ids ) {
115 $prices = wp_list_pluck( wp_list_filter( $rows, [ 'meta_key' => $key . '_' . $currency ] ), 'meta_value', 'post_id' );
116 $defaults = wp_list_pluck( wp_list_filter( $rows, [ 'meta_key' => $key ] ), 'meta_value', 'post_id' );
117
118 foreach ( $variation_ids as $id ) {
119 if ( empty( $prices[ $id ] ) && isset( $defaults[ $id ] ) ) {
120 $prices[ $id ] = apply_filters( 'wcml_raw_price_amount', $defaults[ $id ] );
121 }
122 }
123
124 return $prices;
125 };
126
127 $regular_prices = $extractPricesByType( $rows, '_regular_price' );
128 $sale_prices = $extractPricesByType( $rows, '_sale_price' );
129 $prices = $extractPricesByType( $rows, '_price' );
130
131 if ( ! empty( $regular_prices ) ) {
132 $product_min_max_prices[ $product_id ]['_min_variation_regular_price'] = min( $regular_prices );
133 $product_min_max_prices[ $product_id ]['_max_variation_regular_price'] = max( $regular_prices );
134 }
135
136 if ( ! empty( $sale_prices ) ) {
137 $product_min_max_prices[ $product_id ]['_min_variation_sale_price'] = min( $sale_prices );
138 $product_min_max_prices[ $product_id ]['_max_variation_sale_price'] = max( $sale_prices );
139 }
140
141 if ( ! empty( $prices ) ) {
142 $product_min_max_prices[ $product_id ]['_min_variation_price'] = min( $prices );
143 $product_min_max_prices[ $product_id ]['_max_variation_price'] = max( $prices );
144 }
145 }
146
147 if ( isset( $product_min_max_prices[ $product_id ]['_min_variation_regular_price'] ) ) {
148 $custom_prices['_min_variation_regular_price'] = $product_min_max_prices[ $product_id ]['_min_variation_regular_price'];
149 }
150
151 if ( isset( $product_min_max_prices[ $product_id ]['_max_variation_regular_price'] ) ) {
152 $custom_prices['_max_variation_regular_price'] = $product_min_max_prices[ $product_id ]['_max_variation_regular_price'];
153 }
154
155 if ( isset( $product_min_max_prices[ $product_id ]['_min_variation_sale_price'] ) ) {
156 $custom_prices['_min_variation_sale_price'] = $product_min_max_prices[ $product_id ]['_min_variation_sale_price'];
157 }
158 if ( isset( $product_min_max_prices[ $product_id ]['_max_variation_sale_price'] ) ) {
159 $custom_prices['_max_variation_sale_price'] = $product_min_max_prices[ $product_id ]['_max_variation_sale_price'];
160 }
161
162 if ( isset( $product_min_max_prices[ $product_id ]['_min_variation_price'] ) ) {
163 $custom_prices['_min_variation_price'] = $product_min_max_prices[ $product_id ]['_min_variation_price'];
164 }
165 if ( isset( $product_min_max_prices[ $product_id ]['_max_variation_price'] ) ) {
166 $custom_prices['_max_variation_price'] = $product_min_max_prices[ $product_id ]['_max_variation_price'];
167 }
168 }
169
170 $custom_prices = apply_filters( 'wcml_product_custom_prices', $custom_prices, $product_id, $currency );
171
172 wp_cache_set( $cache_key, $custom_prices, $cache_group );
173
174 return $custom_prices;
175 }
176
177 private function is_date_range_set( $product_meta, $currency ) {
178
179 $get_currency_schedule = function ( $suffix ) use ( $product_meta ) {
180 return Obj::path( [
181 '_sale_price_dates_from' . $suffix,
182 0
183 ], $product_meta ) || Obj::path( [ '_sale_price_dates_to' . $suffix, 0 ], $product_meta );
184 };
185
186 return $get_currency_schedule( '' ) || $get_currency_schedule( "_$currency" );
187 }
188
189 private function is_on_sale_date_range( $product_meta, $currency ) {
190
191 if (
192 isset( $product_meta[ '_sale_price_dates_from_' . $currency ] ) &&
193 isset( $product_meta[ '_sale_price_dates_to_' . $currency ] ) &&
194 $product_meta[ '_sale_price_dates_from_' . $currency ][0] &&
195 $product_meta[ '_sale_price_dates_to_' . $currency ][0]
196 ) {
197
198 if (
199 current_time( 'timestamp' ) > $product_meta[ '_sale_price_dates_from_' . $currency ][0] &&
200 current_time( 'timestamp' ) < $product_meta[ '_sale_price_dates_to_' . $currency ][0]
201 ) {
202 return true;
203 }
204 } elseif (
205 isset( $product_meta['_sale_price_dates_from'] ) &&
206 isset( $product_meta['_sale_price_dates_to'] ) &&
207 current_time( 'timestamp' ) > $product_meta['_sale_price_dates_from'][0] &&
208 current_time( 'timestamp' ) < $product_meta['_sale_price_dates_to'][0]
209 ) {
210 return true;
211 }
212
213 return false;
214 }
215
216 public function woocommerce_product_options_custom_pricing() {
217 global $pagenow;
218
219 $this->load_custom_prices_js_css();
220
221 if ( ( isset( $_GET['post'] ) && ( get_post_type( $_GET['post'] ) !== 'product' || ! $this->woocommerce_wpml->products->is_original_product( $_GET['post'] ) ) ) ||
222 ( isset( $_GET['post_type'] ) && $_GET['post_type'] === 'product' && isset( $_GET['source_lang'] ) ) ) {
223 return;
224 }
225
226 $product_id = 'new';
227
228 if ( $pagenow === 'post.php' && isset( $_GET['post'] ) && get_post_type( $_GET['post'] ) == 'product' ) {
229 $product_id = $_GET['post'];
230 }
231
232 $this->custom_pricing_output( $product_id );
233
234 do_action( 'wcml_after_custom_prices_block', $product_id );
235
236 wp_nonce_field( 'wcml_save_custom_prices', '_wcml_custom_prices_nonce' );
237
238 }
239
240 public function woocommerce_product_after_variable_attributes_custom_pricing( $loop, $variation_data, $variation ) {
241
242 if ( $this->woocommerce_wpml->products->is_original_product( $variation->post_parent ) ) {
243
244 echo '<tr><td>';
245 $this->custom_pricing_output( $variation->ID );
246 echo '</td></tr>';
247
248 }
249
250 }
251
252 private function load_custom_prices_js_css() {
253 wp_register_style( 'wpml-wcml-prices', WCML_PLUGIN_URL . '/res/css/wcml-prices.css', null, WCML_VERSION );
254 wp_register_script( 'wcml-tm-scripts-prices', WCML_PLUGIN_URL . '/res/js/prices' . WCML_JS_MIN . '.js', [ 'jquery' ], WCML_VERSION, true );
255
256 wp_enqueue_style( 'wpml-wcml-prices' );
257 wp_enqueue_script( 'wcml-tm-scripts-prices' );
258 }
259
260 private function custom_pricing_output( $post_id = false ) {
261
262 $custom_prices_ui = new WCML_Custom_Prices_UI( $this->woocommerce_wpml, $post_id );
263 $custom_prices_ui->show();
264
265 }
266
267 public function check_product_with_custom_prices( $is_purchasable, WC_Product $product ) {
268
269 if ( $this->is_filtering_products_with_custom_prices_enabled() && is_product() ) {
270
271 $original_id = $this->woocommerce_wpml->products->get_original_product_id( $product->get_id() );
272
273 if ( ! $this->is_custom_prices_set_for_product( $original_id ) ) {
274 return false;
275 }
276 }
277
278 return $is_purchasable;
279 }
280
281 public function filter_product_variations_with_custom_prices( $is_visible, $variation_id ) {
282 $is_product = is_product() || 'product_variation' === get_post_type( $variation_id );
283
284 if ( $this->is_filtering_products_with_custom_prices_enabled() && $is_product ) {
285
286 $orig_child_id = $this->woocommerce_wpml->products->get_original_product_id( $variation_id );
287
288 if ( ! $this->is_custom_prices_set_for_product( $orig_child_id ) ) {
289 return false;
290 }
291 }
292
293 return $is_visible;
294 }
295
296 public function filter_products_with_custom_prices( $filtered_posts ) {
297
298 if ( $this->is_filtering_products_with_custom_prices_enabled() ) {
299
300 $matched_products = [];
301 $matched_products_query = $this->wpdb->get_results(
302 "
303 SELECT DISTINCT ID, post_parent, post_type FROM {$this->wpdb->posts}
304 INNER JOIN {$this->wpdb->postmeta} ON ID = post_id
305 WHERE post_type IN ( 'product', 'product_variation' ) AND post_status = 'publish' AND meta_key = '_wcml_custom_prices_status' AND meta_value = 1
306 ",
307 OBJECT_K
308 );
309
310 if ( $matched_products_query ) {
311
312 $client_currency = $this->woocommerce_wpml->multi_currency->get_client_currency();
313
314 remove_filter( 'get_post_metadata', [ $this->woocommerce_wpml->multi_currency->prices, 'product_price_filter' ], 10 );
315 foreach ( $matched_products_query as $product ) {
316 if ( ! get_post_meta( $product->ID, '_price_' . $client_currency, true ) ) {
317 continue;
318 }
319 if ( $product->post_type == 'product' ) {
320 $matched_products[] = apply_filters( 'wpml_object_id', $product->ID, 'product', true );
321 }
322 if ( $product->post_parent > 0 && ! in_array( $product->post_parent, $matched_products ) ) {
323 $matched_products[] = apply_filters( 'wpml_object_id', $product->post_parent, get_post_type( $product->post_parent ), true );
324 }
325 }
326 $groupedProducts = [];
327
328 $groupedProductRawData = $this->wpdb->get_results(
329 "SELECT post_id, meta_value
330 FROM {$this->wpdb->postmeta}
331 INNER JOIN {$this->wpdb->posts} ON {$this->wpdb->postmeta}.post_id = {$this->wpdb->posts}.ID
332 WHERE meta_key = '_children' AND {$this->wpdb->posts}.post_type = 'product' AND {$this->wpdb->posts}.post_status = 'publish'"
333 );
334
335 foreach ( $groupedProductRawData as $rawData ) {
336 $ProductChildIdsRaw = maybe_unserialize( $rawData->meta_value );
337
338 if ( ! is_array( $ProductChildIdsRaw ) || empty( $ProductChildIdsRaw ) ) {
339 continue;
340 }
341
342 $AllProductChildrenHaveCustomPrices = true;
343
344 foreach ( $ProductChildIdsRaw as $ProductChildId ) {
345 if ( ! in_array( $ProductChildId, $matched_products ) ) {
346 $AllProductChildrenHaveCustomPrices = false;
347 break;
348 }
349 }
350
351 if ( $AllProductChildrenHaveCustomPrices ) {
352 $groupedProducts[] = $rawData->post_id;
353 }
354 }
355
356 $matched_products = array_merge( $matched_products, $groupedProducts );
357
358 add_filter( 'get_post_metadata', [ $this->woocommerce_wpml->multi_currency->prices, 'product_price_filter' ], 10, 4 );
359 }
360
361 if ( sizeof( $filtered_posts ) == 0 ) {
362 $filtered_posts = $matched_products;
363 $filtered_posts[] = 0;
364 } else {
365 $filtered_posts = array_intersect( $filtered_posts, $matched_products );
366 $filtered_posts[] = 0;
367 }
368 }
369
370 return $filtered_posts;
371 }
372
373 public function exclude_hidden_variation_prices_from_prices( $prices_array, $product ) {
374 if ( ! $this->is_filtering_products_with_custom_prices_enabled() ) {
375 return $prices_array;
376 }
377
378 foreach ( $prices_array['price'] as $variation_id => $price ) {
379 if ( ! apply_filters( 'woocommerce_variation_is_visible', true, $variation_id, $product->get_id() ) ) {
380 unset( $prices_array['price'][ $variation_id ] );
381 unset( $prices_array['regular_price'][ $variation_id ] );
382 unset( $prices_array['sale_price'][ $variation_id ] );
383 }
384 }
385
386 return $prices_array;
387 }
388
389 private function is_filtering_products_with_custom_prices_enabled() {
390
391 return wcml_is_multi_currency_on() &&
392 isset( $this->woocommerce_wpml->settings['display_custom_prices'] ) &&
393 $this->woocommerce_wpml->settings['display_custom_prices'] &&
394 $this->woocommerce_wpml->multi_currency->get_client_currency() !== wcml_get_woocommerce_currency_option();
395 }
396
397 public function save_custom_prices( $post_id ) {
398 if ( WCML_MULTI_CURRENCIES_INDEPENDENT !== $this->woocommerce_wpml->settings['enable_multi_currency'] ) {
399 return;
400 }
401
402 $nonce = filter_input( INPUT_POST, '_wcml_custom_prices_nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
403 if ( isset( $_POST['_wcml_custom_prices'] ) && isset( $nonce ) && wp_verify_nonce( $nonce, 'wcml_save_custom_prices' ) ) {
404 $original_product_id = $this->woocommerce_wpml->products->get_original_product_id( $post_id );
405 if ( empty( $original_product_id ) ) {
406 return;
407 }
408
409 $this->save_custom_prices_without_post_form( $original_product_id, $_POST );
410 }
411 }
412
413 public function save_custom_prices_on_rest( $post_id, $wpRestRequest ) {
414 if ( WCML_MULTI_CURRENCIES_INDEPENDENT !== $this->woocommerce_wpml->settings['enable_multi_currency'] ) {
415 return;
416 }
417
418 $original_product_id = $this->woocommerce_wpml->products->get_original_product_id( $post_id );
419 if ( empty( $original_product_id ) ) {
420 return;
421 }
422
423 $rest2post = [];
424
425 $rest2post['_wcml_custom_prices'] = [
426 $original_product_id => null,
427 'new' => null
428 ];
429
430 $rest2post['_custom_sale_price'] = [];
431 $rest2post['_custom_regular_price'] = [];
432 $rest2post['_wcml_schedule'] = [];
433 $rest2post['_custom_sale_price_dates_from'] = [];
434 $rest2post['_custom_sale_price_dates_to'] = [];
435 if( isset( $wpRestRequest['custom_prices'] ) && is_array( $wpRestRequest['custom_prices'] ) ) {
436 foreach ( $wpRestRequest['custom_prices'] as $code => $currency ) {
437 $rest2post['_wcml_custom_prices']['new'] = (int) true;
438
439 $rest2post['_custom_regular_price'][ $code ] = $currency['regular_price'] ?? null;
440 $rest2post['_custom_sale_price'][ $code ] = $currency['sale_price'] ?? null;
441 $rest2post['_wcml_schedule'][ $code ] = null;
442 $rest2post['_custom_sale_price_dates_from'][ $code ] = null;
443 $rest2post['_custom_sale_price_dates_to'][ $code ] = null;
444 }
445 }
446
447 $this->save_custom_prices_without_post_form( $original_product_id, $rest2post );
448 }
449
450 private function save_custom_prices_without_post_form( $post_id, array $formPostData ) {
451 if ( WCML_MULTI_CURRENCIES_INDEPENDENT !== $this->woocommerce_wpml->settings['enable_multi_currency'] ) {
452 return;
453 }
454
455 if ( ! $this->woocommerce_wpml->products->is_variable_product( $post_id ) ) {
456 if ( isset( $formPostData['_wcml_custom_prices'][ $post_id ] ) || isset( $formPostData['_wcml_custom_prices']['new'] ) ) {
457 $wcml_custom_prices_option = $formPostData['_wcml_custom_prices'][ $post_id ] ?? $formPostData['_wcml_custom_prices']['new'];
458 } else {
459 $current_option = get_post_meta( $post_id, '_wcml_custom_prices_status', true );
460 $wcml_custom_prices_option = $current_option ?: 0;
461 }
462 update_post_meta( $post_id, '_wcml_custom_prices_status', $wcml_custom_prices_option );
463
464 if ( $wcml_custom_prices_option == 1 ) {
465 $currencies = $this->woocommerce_wpml->multi_currency->get_currencies();
466 foreach ( $currencies as $code => $currency ) {
467 if( !isset( $formPostData['_custom_regular_price'][ $code ] ) ) {
468 continue;
469 }
470
471 $sale_price = wc_format_decimal( $formPostData['_custom_sale_price'][ $code ] ?? null );
472 $regular_price = wc_format_decimal( $formPostData['_custom_regular_price'][ $code ] );
473 $schedule = $formPostData['_wcml_schedule'][ $code ];
474 $date_from = $schedule && isset( $formPostData['_custom_sale_price_dates_from'][ $code ] ) ? strtotime( $formPostData['_custom_sale_price_dates_from'][ $code ] ) : '';
475 $date_to = $schedule && isset( $formPostData['_custom_sale_price_dates_to'][ $code ] ) ? strtotime( $formPostData['_custom_sale_price_dates_to'][ $code ] ) : '';
476
477 $custom_prices = apply_filters(
478 'wcml_update_custom_prices_values',
479 [
480 '_regular_price' => $regular_price,
481 '_sale_price' => $sale_price,
482 '_wcml_schedule' => $schedule,
483 '_sale_price_dates_from' => $date_from,
484 '_sale_price_dates_to' => $date_to,
485 ],
486 $code,
487 $post_id
488 );
489 $product_price = $this->update_custom_prices( $post_id, $custom_prices, $code );
490
491 do_action( 'wcml_after_save_custom_prices', $post_id, $product_price, $custom_prices, $code );
492 }
493 }
494 }
495 }
496
497 public function update_custom_prices( $post_id, $custom_prices, $code ) {
498 $defaults = [
499 '_sale_price_dates_to' => '',
500 '_sale_price_dates_from' => '',
501 '_sale_price' => '',
502 ];
503
504 $custom_prices = array_merge( $defaults, $custom_prices );
505
506 foreach ( $custom_prices as $custom_price_key => $custom_price_value ) {
507 update_post_meta( $post_id, $custom_price_key . '_' . $code, $custom_price_value );
508 }
509
510 if ( $this->is_sale_price_valid( $custom_prices ) ) {
511 $price = stripslashes( $custom_prices['_sale_price'] );
512 } else {
513 $price = stripslashes( $custom_prices['_regular_price'] );
514 $this->validate_and_update_sale_price_dates( $post_id, $code, $custom_prices );
515 }
516
517 $price = $price ?: null;
518 update_post_meta( $post_id, '_price_' . $code, $price );
519 do_action( 'wcml_after_save_custom_prices', $post_id, $price, $custom_prices, $code );
520
521 return $price;
522 }
523
524 private function validate_and_update_sale_price_dates( $post_id, $code, array $custom_prices ) {
525 $date_from = $custom_prices['_sale_price_dates_from'];
526 $date_to = $custom_prices['_sale_price_dates_to'];
527 if ( $date_to ) {
528 if ( $date_to < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
529 update_post_meta( $post_id, '_sale_price_dates_from_' . $code, '' );
530 update_post_meta( $post_id, '_sale_price_dates_to_' . $code, '' );
531 } elseif ( ! $date_from ) {
532 update_post_meta(
533 $post_id,
534 '_sale_price_dates_from_' . $code,
535 strtotime( 'NOW', current_time( 'timestamp' ) )
536 );
537 }
538 }
539 }
540
541 protected function is_sale_price_valid( array $custom_prices ) {
542 $sale_price_dates_from = $custom_prices['_sale_price_dates_from'];
543 $sale_price_dates_to = $custom_prices['_sale_price_dates_to'];
544 $not_depend_on_date = $sale_price_dates_to === '' && $sale_price_dates_from === '';
545 $valid_sale_date = $sale_price_dates_from < strtotime( 'NOW', current_time( 'timestamp' ) );
546
547 return $custom_prices['_sale_price'] !== '' && ( $not_depend_on_date || $valid_sale_date );
548 }
549
550 public function sync_product_variations_custom_prices_on_ajax( $product_id ) {
551 Maybe::fromNullable( wc_get_product( $product_id ) )
552 ->map( invoke( 'get_children' ) )
553 ->map( Fns::map( [ $this, 'sync_product_variations_custom_prices' ] ) );
554 }
555
556 public function sync_product_variations_custom_prices( $product_id ) {
557
558 if ( isset( $_POST['_wcml_custom_prices'][ $product_id ] ) ) {
559
560 $nonce = filter_input( INPUT_POST, '_wcml_custom_prices_variation_' . $product_id . '_nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
561 if ( isset( $_POST['_wcml_custom_prices'][ $product_id ] ) && isset( $nonce ) && wp_verify_nonce( $nonce, 'wcml_save_custom_prices_variation_' . $product_id ) ) {
562 update_post_meta( $product_id, '_wcml_custom_prices_status', $_POST['_wcml_custom_prices'][ $product_id ] );
563 $currencies = $this->woocommerce_wpml->multi_currency->get_currencies();
564
565 if ( $_POST['_wcml_custom_prices'][ $product_id ] == 1 ) {
566 foreach ( $currencies as $code => $currency ) {
567 $sale_price = wc_format_decimal( $_POST['_custom_variation_sale_price'][ $code ][ $product_id ] );
568 $regular_price = wc_format_decimal( $_POST['_custom_variation_regular_price'][ $code ][ $product_id ] );
569 $date_from = strtotime( $_POST['_custom_variation_sale_price_dates_from'][ $code ][ $product_id ] );
570 $date_to = strtotime( $_POST['_custom_variation_sale_price_dates_to'][ $code ][ $product_id ] );
571 $schedule = $_POST['_wcml_schedule'][ $code ][ $product_id ];
572
573 $custom_prices = apply_filters(
574 'wcml_update_custom_prices_values',
575 [
576 '_regular_price' => $regular_price,
577 '_sale_price' => $sale_price,
578 '_wcml_schedule' => $schedule,
579 '_sale_price_dates_from' => $date_from,
580 '_sale_price_dates_to' => $date_to,
581 ],
582 $code,
583 $product_id
584 );
585
586 $this->update_custom_prices( $product_id, $custom_prices, $code );
587 }
588 }
589 }
590 }
591 }
592
593 private function is_on_sale( $product_object ) {
594 $custom_prices = $this->get_product_custom_prices( $product_object->get_id() );
595
596 return $custom_prices
597 && array_key_exists( '_sale_price', $custom_prices )
598 && array_key_exists( '_regular_price', $custom_prices )
599 && '' !== $custom_prices['_sale_price']
600 && $custom_prices['_sale_price'] !== $custom_prices['_regular_price'];
601 }
602
603 public function filter_product_is_on_sale( $on_sale, $product_object ) {
604 if (
605 ! $on_sale &&
606 WCML_MULTI_CURRENCIES_INDEPENDENT === $this->woocommerce_wpml->settings['enable_multi_currency'] &&
607 $this->is_custom_prices_set_for_product( $product_object->get_id() )
608 ) {
609 $on_sale = $this->is_on_sale( $product_object );
610 }
611
612 return $on_sale;
613 }
614
615 private function is_custom_prices_set_for_product( $product_id ) {
616 return (bool) apply_filters( 'wcml_product_has_custom_prices', (bool) get_post_meta( $product_id, '_wcml_custom_prices_status', true ), $product_id );
617 }
618
619 public function maybe_set_sale_prices( $product_ids ) {
620 foreach ( $product_ids as $product_id ) {
621 if ( $this->is_custom_prices_set_for_product( $product_id ) ) {
622 foreach ( $this->woocommerce_wpml->multi_currency->get_currencies() as $code => $currency ) {
623 update_post_meta( $product_id, '_price_' . $code, get_post_meta( $product_id, '_sale_price_' . $code, true ) );
624 }
625 }
626 }
627 }
628
629 public function maybe_remove_sale_prices( $product_ids ) {
630
631 foreach ( $product_ids as $product_id ) {
632
633 $product = wc_get_product( $product_id );
634 $is_product_on_sale = $product && $product->get_sale_price();
635
636 if ( $is_product_on_sale || ! $this->is_custom_prices_set_for_product( $product_id ) ) {
637 continue;
638 }
639
640 foreach ( $this->woocommerce_wpml->multi_currency->get_currencies() as $code => $currency ) {
641
642 $is_auto_schedule_set = ! get_post_meta( $product_id, '_wcml_schedule_' . $code, true );
643
644 if ( $is_auto_schedule_set ) {
645 update_post_meta( $product_id, '_price_' . $code, get_post_meta( $product_id, '_regular_price_' . $code, true ) );
646 update_post_meta( $product_id, '_sale_price_' . $code, '' );
647 }
648 }
649 }
650 }
651 }
652