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 / compatibility / WcProductAddons / MulticurrencyHooks.php
woocommerce-multilingual / compatibility / WcProductAddons Last commit date
Factory.php 2 weeks ago JobHooks.php 2 weeks ago MulticurrencyHooks.php 2 weeks ago SharedHooks.php 2 weeks ago class-wcml-product-addons.php 2 weeks ago
MulticurrencyHooks.php
294 lines
1 <?php
2
3 namespace WCML\Compatibility\WcProductAddons;
4
5 use WC_Product_Booking;
6 use WCML_Product_Addons;
7 use woocommerce_wpml;
8 use WPML_Twig_Template_Loader;
9
10 class MulticurrencyHooks implements \IWPML_Action {
11 const ADDON_FIELD_TYPE_INPUT_MULTIPLIER = 'input_multiplier';
12 const ADDON_PRICE_TYPE_QUANTITY_BASED = 'quantity_based';
13 const ADDON_PRICE_TYPE_FLAT_FEE = 'flat_fee';
14
15 const CONVERTABLE_ADDON_PRICE_TYPE_LIST = [
16 self::ADDON_PRICE_TYPE_QUANTITY_BASED,
17 self::ADDON_PRICE_TYPE_FLAT_FEE,
18 ];
19
20 const TEMPLATE_FOLDER = '/templates/compatibility/';
21 const DIALOG_TEMPLATE = 'product-addons-prices-dialog.twig';
22 const SETTINGS_TEMPLATE = 'product-addons-prices-settings.twig';
23 const PRICE_OPTION_KEY = '_product_addon_prices';
24
25 private $woocommerce_wpml;
26
27 public function __construct( woocommerce_wpml $woocommerce_wpml ) {
28 $this->woocommerce_wpml = $woocommerce_wpml;
29 }
30
31 public function add_hooks() {
32 add_filter( 'get_product_addons_fields', [ $this, 'product_addons_price_filter' ], 10, 2 );
33 add_filter( 'wcml_cart_contents_not_changed', [ $this, 'filter_booking_addon_product_in_cart_contents' ], 20 );
34 add_filter( 'wcml_product_addons_global_updated', [ $this, 'onGlobalAddonsUpdated' ], 10, 2 );
35
36 if ( wp_doing_ajax() ) {
37 add_action( 'wcml_switch_currency', [ $this, 'convertAddonPriceSavedInSession' ], 10, 2 );
38 }
39
40 if ( is_admin() ) {
41 add_action( 'woocommerce_product_addons_panel_start', [ $this, 'load_dialog_resources' ] );
42 add_action( 'woocommerce_product_addons_panel_option_row', [ $this, 'dialog_button_after_option_row' ], 10, 4 );
43 add_action( 'woocommerce_product_addons_panel_before_options', [ $this, 'dialog_button_before_options' ], 10, 3 );
44 add_action( 'wcml_before_sync_product', [ $this, 'update_custom_prices_values' ] );
45 add_action( 'save_post', [ $this, 'maybeUpdateCustomPricesValues' ], 10, 2 );
46 add_action( 'woocommerce_product_addons_global_edit_objects', [ $this, 'custom_prices_settings_block' ] );
47 }
48 }
49
50 public function convertAddonPriceSavedInSession( $toCurrency, $fromCurrency ) {
51
52 $cart = WC()->session->get( 'cart', null );
53
54 if ( is_array( $cart ) ) {
55 $save = false;
56 foreach ( $cart as $itemId => $item ) {
57 if ( isset( $item['addons'] ) && is_array( $item['addons'] ) ) {
58 foreach ( $item['addons'] as $addonId => $addon ) {
59 $addonFieldType = $addon['field_type'] ?? null;
60 $addonPriceType = $addon['price_type'] ?? null;
61 $addonPrice = $addon['price'] ?? null;
62
63 if ( self::ADDON_FIELD_TYPE_INPUT_MULTIPLIER === $addonFieldType ) {
64 if ( in_array( $addonPriceType, self::CONVERTABLE_ADDON_PRICE_TYPE_LIST, true ) ) {
65 $orgPrice = $this->woocommerce_wpml->multi_currency->prices->unconvert_price_amount( $addonPrice, $fromCurrency );
66 $newPrice = $this->woocommerce_wpml->multi_currency->prices->convert_price_amount( $orgPrice, $toCurrency );
67
68 $cart[ $itemId ]['addons'][ $addonId ]['price'] = $newPrice;
69
70 $save = true;
71 }
72 }
73 }
74 }
75 }
76 if ( $save ) {
77 WC()->session->set( 'cart', $cart );
78 }
79 }
80 }
81
82 public function maybeUpdateCustomPricesValues( $productId, $arg ) {
83 if ( 'product' === get_post_type( $productId ) ) {
84 $this->update_custom_prices_values( $productId );
85 }
86 }
87
88 public function product_addons_price_filter( $addons, $postId ) {
89 foreach ( $addons as $addonId => $addon ) {
90
91 $addon_data = wpml_collect( $addon );
92
93 if ( $addon_data->offsetExists( 'price' ) && $addon_data->get( 'price' ) ) {
94 $addons[ $addonId ]['price'] = $this->converted_addon_price( $addon, $postId );
95 }
96
97 if ( $addon_data->offsetExists( 'options' ) ) {
98 foreach ( $addon_data->get( 'options' ) as $key => $option ) {
99 $addons[ $addonId ]['options'][ $key ]['price'] = $this->converted_addon_price( $option, $postId );
100 }
101 }
102 }
103
104 return $addons;
105 }
106
107 public function filter_booking_addon_product_in_cart_contents( $cartItem ) {
108 $isBookingProductWithAddons = $cartItem['data'] instanceof WC_Product_Booking && isset( $cartItem['addons'] );
109
110 if ( $isBookingProductWithAddons ) {
111 $cost = $cartItem['data']->get_price();
112
113 foreach ( $cartItem['addons'] as $addon ) {
114 $cost += $addon['price'];
115 }
116
117 $cartItem['data']->set_price( $cost );
118 }
119
120 return $cartItem;
121 }
122
123 private function converted_addon_price( $addon, $postId ) {
124 $addonData = wpml_collect( $addon );
125
126 $isCustomPricesOn = $this->isProductCustomPricesOn( $postId );
127 $field = 'price_' . $this->woocommerce_wpml->multi_currency->get_client_currency();
128
129 if (
130 $isCustomPricesOn &&
131 $addonData->get( $field )
132 ) {
133 return $addonData->get( $field );
134 }
135
136 if ( wpml_collect( self::CONVERTABLE_ADDON_PRICE_TYPE_LIST )->contains( $addonData->get( 'price_type' ) ) ) {
137 return apply_filters( 'wcml_raw_price_amount', $addonData->get( 'price' ) );
138 }
139
140 return $addonData->get( 'price' );
141 }
142
143 private function isProductCustomPricesOn( $productId ) {
144 if ( $productId ) {
145 return get_post_meta( $productId, '_wcml_custom_prices_status', true );
146 }
147
148 if ( SharedHooks::isGlobalAddonEditPage() ) {
149 return $this->getGlobalAddonPricesStatus();
150 }
151
152 return false;
153 }
154
155 private function getGlobalAddonPricesStatus() {
156 if ( isset( $_GET['edit'] ) ) {
157 return get_post_meta( $_GET['edit'], '_wcml_custom_prices_status', true );
158 } elseif ( isset( $_POST['_wcml_custom_prices'] ) ) {
159 return $_POST['_wcml_custom_prices'];
160 }
161
162 return false;
163 }
164
165 public function load_dialog_resources() {
166 wp_enqueue_script( 'wcml-dialogs', WCML_PLUGIN_URL . '/res/js/dialogs' . WCML_JS_MIN . '.js', [ 'jquery-ui-dialog', 'underscore' ], WCML_VERSION );
167 }
168
169 public function dialog_button_after_option_row( $product, $productAddons, $loop, $option ) {
170 if ( $option ) {
171 $this->renderEditPriceElement( $this->getPricesDialogModel( $productAddons, $option, $loop, $this->isProductCustomPricesOn( $product ? $product->ID : false ) ) );
172 }
173 }
174
175 public function dialog_button_before_options( $product, $productAddons, $loop ) {
176 $this->renderEditPriceElement( $this->getPricesDialogModel( [], $productAddons, $loop, $this->isProductCustomPricesOn( $product ? $product->ID : false ) ) );
177 }
178
179 public function onGlobalAddonsUpdated( $metaId, $id ) {
180 $this->update_custom_prices_values( $id );
181 }
182
183 public function update_custom_prices_values( $productId ) {
184 $this->saveGlobalAddonPricesSetting( $productId );
185 $productAddons = SharedHooks::getProductAddons( $productId );
186
187 if ( $productAddons ) {
188 $activeCurrencies = $this->woocommerce_wpml->multi_currency->get_currencies();
189
190 foreach ( $productAddons as $addonKey => $productAddon ) {
191
192 foreach ( $activeCurrencies as $code => $currency ) {
193 $priceOptionKey = self::PRICE_OPTION_KEY;
194
195 if ( in_array( $productAddon['type'], self::getOnePriceTypes(), true ) ) {
196 $productAddons = $this->updateSingleOptionPrices( $productAddons, $priceOptionKey, $addonKey, $code );
197 } else {
198 $productAddons = $this->updateMultipleOptionsPrices( $productAddons, $priceOptionKey, $addonKey, $code );
199 }
200 }
201 }
202
203 update_post_meta( $productId, WCML_Product_Addons::ADDONS_OPTION_KEY, $productAddons );
204 }
205 }
206
207 private function updateSingleOptionPrices( $productAddons, $priceOptionKey, $addonKey, $code ) {
208 if ( isset( $_POST[ $priceOptionKey ][ $addonKey ][ 'price_' . $code ][0] ) ) {
209 $productAddons[ $addonKey ][ 'price_' . $code ] = wc_format_decimal( $_POST[ $priceOptionKey ][ $addonKey ][ 'price_' . $code ][0] );
210 }
211
212 return $productAddons;
213 }
214
215 private function updateMultipleOptionsPrices( $productAddons, $priceOptionKey, $addonKey, $code ) {
216 $addon_data = wpml_collect( $productAddons[ $addonKey ] );
217
218 if ( $addon_data->offsetExists( 'options' ) ) {
219 foreach ( $addon_data->get( 'options' ) as $option_key => $option ) {
220 if ( isset( $_POST[ $priceOptionKey ][ $addonKey ][ 'price_' . $code ][ $option_key ] ) ) {
221 $productAddons[ $addonKey ]['options'][ $option_key ][ 'price_' . $code ] = wc_format_decimal( $_POST[ $priceOptionKey ][ $addonKey ][ 'price_' . $code ][ $option_key ] );
222 }
223 }
224 }
225
226 return $productAddons;
227 }
228
229 public function custom_prices_settings_block() {
230 echo $this->getTwigLoader()->get_template()->show( $this->getCustomPricesSettingsModel(), self::SETTINGS_TEMPLATE );
231 }
232
233 private function renderEditPriceElement( $model ) {
234 echo $this->getTwigLoader()->get_template()->show( $model, self::DIALOG_TEMPLATE );
235 }
236
237 private function getTwigLoader() {
238 return new WPML_Twig_Template_Loader( [ WCML_PLUGIN_PATH . SharedHooks::TEMPLATE_FOLDER ] );
239 }
240
241 private function getCustomPricesSettingsModel() {
242 return [
243 'strings' => [
244 'label' => __( 'Multi-currency settings', 'woocommerce-multilingual' ),
245 'auto' => __( 'Calculate prices in other currencies automatically', 'woocommerce-multilingual' ),
246 'manually' => __( 'Set prices in other currencies manually', 'woocommerce-multilingual' ),
247 ],
248 'custom_prices_on' => $this->getGlobalAddonPricesStatus(),
249 'nonce' => wp_create_nonce( 'wcml_save_custom_prices' ),
250 ];
251 }
252
253 private function getPricesDialogModel( $productAddons, $option, $loop, $customPricesOn ) {
254
255 $label = isset( $option['label'] ) ? $option['label'] : $option['name'];
256
257 return [
258 'strings' => [
259 'dialog_title' => __( 'Multi-currency settings', 'woocommerce-multilingual' ),
260 /* translators: %s is an option label */
261 'description' => sprintf( __( 'Here you can set different prices for the %s in multiple currencies:', 'woocommerce-multilingual' ), '<strong>' . $label . '</strong>' ),
262 'apply' => __( 'Apply', 'woocommerce-multilingual' ),
263 'cancel' => __( 'Cancel', 'woocommerce-multilingual' ),
264 ],
265 'custom_prices_on' => $customPricesOn,
266 'dialog_id' => '_product_addon_option_' . md5( uniqid( $loop . $label ) ),
267 'option_id' => isset( $productAddons[ $loop ]['options'] ) ? array_search( $option, $productAddons[ $loop ]['options'] ) : '',
268 'addon_id' => $loop,
269 'option_details' => $option,
270 'default_currency' => wcml_get_woocommerce_currency_option(),
271 'active_currencies' => $this->woocommerce_wpml->multi_currency->get_currencies(),
272 ];
273 }
274
275 private function saveGlobalAddonPricesSetting( $productId ) {
276 if ( SharedHooks::isGlobalAddon( $productId ) ) {
277 $nonce = filter_var( isset( $_POST['_wcml_custom_prices_nonce'] ) ? $_POST['_wcml_custom_prices_nonce'] : '', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
278
279 if ( isset( $_POST['_wcml_custom_prices'] ) && isset( $nonce ) && wp_verify_nonce( $nonce, 'wcml_save_custom_prices' ) ) {
280 update_post_meta( $productId, '_wcml_custom_prices_status', $_POST['_wcml_custom_prices'] );
281 }
282 }
283 }
284
285 private static function getOnePriceTypes() {
286 return [
287 'custom_text',
288 'custom_textarea',
289 'file_upload',
290 self::ADDON_FIELD_TYPE_INPUT_MULTIPLIER,
291 ];
292 }
293 }
294