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 / class-wcml-attributes.php
woocommerce-multilingual / inc Last commit date
admin-menus 1 week ago currencies 1 week ago template-classes 1 week ago translation-editor 1 week ago class-wcml-ajax-setup.php 1 week ago class-wcml-attributes.php 1 week ago class-wcml-capabilities.php 1 week ago class-wcml-cart-removed-items-widget.php 1 year ago class-wcml-cart-switch-lang-functions.php 1 week ago class-wcml-cart-sync-warnings.php 1 week ago class-wcml-cart.php 1 week ago class-wcml-comments.php 1 week ago class-wcml-compatibility.php 1 week ago class-wcml-coupons.php 1 week ago class-wcml-dependencies.php 1 week ago class-wcml-emails.php 1 week ago class-wcml-endpoints.php 1 week ago class-wcml-install.php 1 week ago class-wcml-languages-upgrader.php 1 week ago class-wcml-locale.php 1 week ago class-wcml-orders.php 1 week ago class-wcml-products-screen-options.php 1 week ago class-wcml-products.php 1 week ago class-wcml-reports.php 1 week ago class-wcml-requests.php 1 week ago class-wcml-resources.php 1 week ago class-wcml-store-pages.php 1 week ago class-wcml-terms.php 1 week ago class-wcml-tp-support.php 1 week ago class-wcml-troubleshooting.php 1 week ago class-wcml-upgrade.php 1 week ago class-wcml-url-translation.php 1 week ago class-wcml-wc-gateways.php 1 week ago class-wcml-wc-shipping.php 1 week ago class-wcml-wc-strings.php 1 week ago class-wcml-widgets.php 1 week ago constants.php 1 week ago functions-pure.php 1 week ago functions.php 1 week ago installer-loader.php 1 week ago missing-php-functions.php 1 week ago wcml-core-functions.php 1 week ago wcml-switch-lang-request.php 1 week ago
class-wcml-attributes.php
899 lines
1 <?php
2
3 use WCML\Utilities\AdminPages;
4 use WPML\FP\Obj;
5 use WCML\Utilities\WCTaxonomies;
6 use WPML\FP\Str;
7
8 class WCML_Attributes {
9
10 const PRIORITY_AFTER_WC_INIT = 100;
11 const CACHE_GROUP_VARIATION = 'wpml-all-meta-product-variation';
12 const PRODUCT_ATTRIBUTE_PREFIX = WCML_WC_Strings::TAXONOMY_GENERAL_VALUE_PREFIX;
13
14 private $woocommerce_wpml;
15 private $sitepress;
16 private $post_translations;
17 private $term_translations;
18 private $wpdb;
19
20 public function __construct( woocommerce_wpml $woocommerce_wpml, \WPML\Core\ISitePress $sitepress, WPML_Post_Translation $post_translations, WPML_Term_Translation $term_translations, wpdb $wpdb ) {
21 $this->woocommerce_wpml = $woocommerce_wpml;
22 $this->sitepress = $sitepress;
23 $this->post_translations = $post_translations;
24 $this->term_translations = $term_translations;
25 $this->wpdb = $wpdb;
26 wp_cache_add_non_persistent_groups( self::CACHE_GROUP_VARIATION );
27 }
28
29 private function get_wcml_terms_instance() {
30 return $this->woocommerce_wpml->terms;
31 }
32
33 private function get_wcml_products_instance() {
34 return $this->woocommerce_wpml->products;
35 }
36
37 public function add_hooks() {
38
39 add_action( 'init', [ $this, 'init' ] );
40
41 add_filter(
42 'wpml_tm_job_field_is_translatable',
43 [
44 $this,
45 'set_custom_product_attributes_as_translatable_for_tm_job',
46 ],
47 10,
48 2
49 );
50 add_filter(
51 'woocommerce_dropdown_variation_attribute_options_args',
52 [
53 $this,
54 'filter_dropdown_variation_attribute_options_args',
55 ]
56 );
57
58 $this->wpml_ajax_custom_tax_sync_options();
59
60 add_filter(
61 'woocommerce_product_get_attributes',
62 [
63 $this,
64 'filter_adding_to_cart_product_attributes_names',
65 ]
66 );
67
68 if ( $this->get_wcml_products_instance()->is_product_display_as_translated_post_type() ) {
69 add_filter(
70 'woocommerce_available_variation',
71 [
72 $this,
73 'filter_available_variation_attribute_values_in_current_language',
74 ]
75 );
76 add_filter(
77 'get_post_metadata',
78 [
79 $this,
80 'filter_product_variation_post_meta_attribute_values_in_current_language',
81 ],
82 10,
83 4
84 );
85 add_action( 'added_post_meta', [ $this, 'invalidateVariationMetaCache' ], 10, 3 );
86 add_filter(
87 'woocommerce_product_get_default_attributes',
88 [
89 $this,
90 'filter_product_variation_default_attributes',
91 ]
92 );
93 }
94 add_action( 'update_post_meta', [ $this, 'set_translation_status_as_needs_update' ], 10, 3 );
95 add_action( 'wc_ajax_get_variation', [ $this, 'maybe_filter_get_variation' ], 9 );
96
97 add_filter( 'wpml_taxonomy_strings_source_language', [ $this, 'wcml_taxonomy_strings_source_language' ], self::PRIORITY_AFTER_WC_INIT, 3 );
98 add_filter( 'wpml_st_register_slug_set_source_language', [ $this, 'wcml_taxonomy_slug_set_source_language' ], self::PRIORITY_AFTER_WC_INIT, 3 );
99 }
100
101 public function init() {
102 $is_attr_page = apply_filters( 'wcml_is_attributes_page', AdminPages::isWcProductAttributesPage() );
103
104 if ( $is_attr_page ) {
105 add_action( 'admin_init', [ $this, 'not_translatable_html' ] );
106 add_action(
107 'woocommerce_attribute_added',
108 [
109 $this,
110 'set_attribute_readonly_config_form_action',
111 ],
112 self::PRIORITY_AFTER_WC_INIT,
113 2
114 );
115 add_action(
116 'woocommerce_attribute_updated',
117 [
118 $this,
119 'set_attribute_readonly_config_form_action',
120 ],
121 self::PRIORITY_AFTER_WC_INIT,
122 3
123 );
124 }
125
126 if ( wpml_is_rest_request() ) {
127 add_action( 'woocommerce_attribute_added', [ $this, 'set_attribute_readonly_config_rest_action' ], self::PRIORITY_AFTER_WC_INIT, 3 );
128 add_action( 'woocommerce_attribute_updated', [ $this, 'set_attribute_readonly_config_rest_action' ], self::PRIORITY_AFTER_WC_INIT, 3 );
129 }
130 }
131
132 public function not_translatable_html() {
133 $attr_id = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : false;
134
135 $attr_is_tnaslt = new WCML_Not_Translatable_Attributes( $attr_id, $this->woocommerce_wpml );
136 $attr_is_tnaslt->show();
137 }
138
139 public function get_attribute_terms( $attribute ) {
140
141 return $this->wpdb->get_results(
142 $this->wpdb->prepare(
143 "
144 SELECT * FROM {$this->wpdb->term_taxonomy} x JOIN {$this->wpdb->terms} t ON x.term_id = t.term_id WHERE x.taxonomy = %s",
145 $attribute
146 )
147 );
148
149 }
150
151 public function set_attribute_readonly_config_rest_action( $id, $attribute, $old_slug = false ) {
152 $attribute['wcml-is-translatable-attr'] = 1;
153
154 $this->set_attribute_readonly_config( $id, $attribute, $old_slug );
155 }
156
157 public function set_attribute_readonly_config_form_action( $id, $attribute, $old_slug = false ) {
158 $attribute['wcml-is-translatable-attr'] = isset( $_POST['wcml-is-translatable-attr'] ) ? 1 : 0;
159
160 if ( isset( $_POST['save_attribute'] ) || isset( $_POST['add_new_attribute'] ) ) {
161 $this->set_attribute_readonly_config( $id, $attribute, $old_slug );
162 }
163 }
164
165 public function set_attribute_readonly_config( $id, $attribute, $old_slug = false ) {
166
167 $is_translatable = (int) $attribute['wcml-is-translatable-attr'];
168
169 if ( $attribute['attribute_name'] ) {
170 $attribute_name = wc_sanitize_taxonomy_name( wp_unslash( $attribute['attribute_name'] ) );
171 } else {
172 $attribute_name = wc_sanitize_taxonomy_name( wc_clean( wp_unslash( $attribute['attribute_label'] ) ) );
173 }
174
175 $attribute_name = wc_attribute_taxonomy_name( $attribute_name );
176
177 if ( 0 === $is_translatable ) {
178 $this->delete_translated_attribute_terms( $attribute_name );
179 $this->set_variations_to_use_original_attributes( $attribute_name );
180 $this->set_original_attributes_for_products( $attribute_name );
181 }
182
183 if ( $old_slug !== $attribute['attribute_name'] ) {
184 $this->fix_attribute_slug_in_translations_table( wc_attribute_taxonomy_name( $old_slug ), $attribute_name );
185 }
186
187 $this->set_attribute_config_in_settings( $attribute_name, $is_translatable );
188 }
189
190 private function fix_attribute_slug_in_translations_table( $old_attribute_name, $new_attribute_name ) {
191 $this->wpdb->update(
192 $this->wpdb->prefix . 'icl_translations',
193 [ 'element_type' => 'tax_' . $new_attribute_name ],
194 [ 'element_type' => 'tax_' . $old_attribute_name ]
195 );
196 }
197
198 public function set_attribute_config_in_settings( $attribute_name, $is_translatable ) {
199 $this->set_attribute_config_in_wcml_settings( $attribute_name, $is_translatable );
200 $this->set_attribute_config_in_wpml_settings( $attribute_name, $is_translatable );
201
202 $this->get_wcml_terms_instance()->update_terms_translated_status( $attribute_name );
203 }
204
205 public function set_attribute_config_in_wcml_settings( $attribute_name, $is_translatable ) {
206 $wcml_settings = $this->woocommerce_wpml->get_settings();
207 $wcml_settings['attributes_settings'][ $attribute_name ] = $is_translatable;
208 $this->woocommerce_wpml->update_settings( $wcml_settings );
209 }
210
211 public function set_attribute_config_in_wpml_settings( $attribute_name, $is_translatable ) {
212
213 $sync_settings = $this->sitepress->get_setting( 'taxonomies_sync_option', [] );
214 $sync_settings[ $attribute_name ] = $is_translatable;
215 $this->sitepress->set_setting( 'taxonomies_sync_option', $sync_settings, true );
216 $this->sitepress->verify_taxonomy_translations( $attribute_name );
217 }
218
219 public function delete_translated_attribute_terms( $attribute ) {
220 $terms = $this->get_attribute_terms( $attribute );
221
222 foreach ( $terms as $term ) {
223 if ( $this->term_translations->get_source_lang_code( $term->term_id ) ) {
224 wp_delete_term( $term->term_id, $attribute );
225 }
226 }
227
228 }
229
230 public function set_variations_to_use_original_attributes( $attribute ) {
231 $terms = $this->get_attribute_terms( $attribute );
232
233 foreach ( $terms as $term ) {
234 if ( is_null( $this->term_translations->get_source_lang_code( $term->term_id ) ) ) {
235 $variations = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT post_id FROM {$this->wpdb->postmeta} WHERE meta_key=%s AND meta_value = %s", 'attribute_' . $attribute, $term->slug ) );
236
237 foreach ( $variations as $variation ) {
238 foreach ( $this->sitepress->get_active_languages() as $language ) {
239
240 $trnsl_variation_id = apply_filters( 'wpml_object_id', $variation->post_id, 'product_variation', false, $language['code'] );
241 if ( ! is_null( $trnsl_variation_id ) ) {
242 update_post_meta( $trnsl_variation_id, 'attribute_' . $attribute, $term->slug );
243 }
244 }
245 }
246 }
247 }
248 }
249
250 public function set_original_attributes_for_products( $attribute ) {
251
252 $terms = $this->get_attribute_terms( $attribute );
253 foreach ( $terms as $term ) {
254 if ( is_null( $this->term_translations->get_source_lang_code( $term->term_id ) ) ) {
255 $args = [
256 'tax_query' => [
257 [
258 'taxonomy' => $attribute,
259 'field' => 'slug',
260 'terms' => $term->slug,
261 ],
262 ],
263 ];
264
265 $products = get_posts( $args );
266
267 foreach ( $products as $product ) {
268
269 foreach ( $this->sitepress->get_active_languages() as $language ) {
270
271 $trnsl_product_id = apply_filters( 'wpml_object_id', $product->ID, 'product', false, $language['code'] );
272
273 if ( ! is_null( $trnsl_product_id ) ) {
274 if ( ! in_array( $trnsl_product_id, $trnsl_product_id ) ) {
275 wp_delete_object_term_relationships( $trnsl_product_id, $attribute );
276 }
277 wp_set_object_terms( $trnsl_product_id, $term->slug, $attribute, true );
278 }
279 }
280 }
281 }
282 }
283 }
284
285
286 public function is_translatable_attribute( $attr_name ) {
287
288 if ( ! isset( $this->woocommerce_wpml->settings['attributes_settings'][ $attr_name ] ) ) {
289 $this->set_attribute_config_in_settings( $attr_name, 1 );
290 }
291
292 return $this->woocommerce_wpml->settings['attributes_settings'][ $attr_name ] ?? 1;
293 }
294
295 public function get_translatable_attributes() {
296 $attributes = wc_get_attribute_taxonomies();
297
298 $translatable_attributes = [];
299 foreach ( $attributes as $attribute ) {
300 if ( $this->is_translatable_attribute( wc_attribute_taxonomy_name( $attribute->attribute_name ) ) ) {
301 $translatable_attributes[] = $attribute;
302 }
303 }
304
305 return $translatable_attributes;
306 }
307
308 public function set_translatable_attributes( $attributes ) {
309
310 foreach ( $attributes as $name => $is_translatable ) {
311
312 $attribute_name = wc_attribute_taxonomy_name( $name );
313 $this->set_attribute_config_in_settings( $attribute_name, $is_translatable );
314
315 }
316 }
317
318 public function sync_product_attr( $original_product_id, $tr_product_id, $language = false, $data = false ) {
319 $orig_product_attrs = $this->get_product_attributes( $original_product_id );
320 $translated_labels = $data ? $this->get_attr_label_translations( $tr_product_id ) : [];
321 $trnsl_product_attrs = $data ? [] : $this->get_product_attributes( $tr_product_id );
322
323 $update_translated_labels = false;
324
325 foreach ( $orig_product_attrs as $key => $orig_product_attr ) {
326 $key_to_save = $key;
327 $sanitized_key = $this->filter_attribute_name( $orig_product_attr['name'], $original_product_id, true );
328
329 if ( $sanitized_key !== $key ) {
330 $orig_product_attrs_buff = $orig_product_attrs[ $key ];
331 unset( $orig_product_attrs[ $key ] );
332 $orig_product_attrs[ $sanitized_key ] = $orig_product_attrs_buff;
333 $key_to_save = $sanitized_key;
334 }
335
336 if ( $data ) {
337 $translated_attribute_in_data = Obj::prop( md5( $key ), $data );
338 if (
339 null !== $translated_attribute_in_data &&
340 ! is_array( $translated_attribute_in_data ) &&
341 Obj::pathOr( null, [ $key_to_save, 'value' ], $orig_product_attrs ) !== $translated_attribute_in_data
342 ) {
343 $orig_product_attrs[ $key_to_save ]['value'] = $translated_attribute_in_data;
344 }
345
346 $translated_labels_in_data = Obj::prop( md5( $key . '_name' ), $data );
347 if (
348 $language &&
349 null !== $translated_labels_in_data &&
350 ! is_array( $translated_labels_in_data ) &&
351 Obj::pathOr( null, [ $language, $key_to_save ], $translated_labels ) !== stripslashes( $translated_labels_in_data )
352 ) {
353 $update_translated_labels = true;
354 $translated_labels[ $language ][ $key_to_save ] = stripslashes( $translated_labels_in_data );
355 }
356 } elseif ( ! $orig_product_attr['is_taxonomy'] ) {
357 $duplicate_of = get_post_meta( $tr_product_id, '_icl_lang_duplicate_of', true );
358 if ( ! $duplicate_of ) {
359 if ( isset( $trnsl_product_attrs[ $key ] ) ) {
360 $orig_product_attrs[ $key_to_save ]['value'] = $trnsl_product_attrs[ $key ]['value'];
361 } elseif ( ! empty( $trnsl_product_attrs ) ) {
362 unset( $orig_product_attrs[ $key_to_save ] );
363 }
364 }
365 }
366 }
367
368 if ( $update_translated_labels ) {
369 update_post_meta( $tr_product_id, 'attr_label_translations', $translated_labels );
370 }
371 update_post_meta( $tr_product_id, '_product_attributes', $orig_product_attrs );
372 }
373
374 public function getAttributeNameToSave( $attributeKey, $attributeData, $productId ) {
375 $sanitizedKey = $this->filter_attribute_name( $attributeData['name'], $productId, true );
376 if ( $sanitizedKey === $attributeKey ) {
377 return $attributeKey;
378 }
379 return $sanitizedKey;
380 }
381
382 public function get_product_attributes( $product_id ) {
383 $attributes = get_post_meta( $product_id, '_product_attributes', true );
384 if ( ! is_array( $attributes ) ) {
385 $attributes = [];
386 }
387
388 return $attributes;
389 }
390
391 public function get_attr_label_translations( $product_id, $lang = false ) {
392 $trnsl_labels = get_post_meta( $product_id, 'attr_label_translations', true );
393
394 $remove_empty_values = function ( $values ) {
395 return \wpml_collect( $values )->filter()->toArray();
396 };
397
398 if ( ! $lang && is_array( $trnsl_labels ) ) {
399 return \wpml_collect( $trnsl_labels )
400 ->map( $remove_empty_values )
401 ->toArray();
402 }
403
404 if ( isset( $trnsl_labels[ $lang ] ) ) {
405 return $remove_empty_values( $trnsl_labels[ $lang ] );
406 }
407
408 return [];
409 }
410
411 public function sync_default_product_attr( $orig_post_id, $transl_post_id, $lang ) {
412 $original_default_attributes = get_post_meta( $orig_post_id, '_default_attributes', true );
413
414 if ( ! empty( $original_default_attributes ) ) {
415 $unserialized_default_attributes = [];
416 foreach ( maybe_unserialize( $original_default_attributes ) as $attribute => $default_term_slug ) {
417 if ( WCTaxonomies::isProductAttribute( $attribute ) ) {
418 if ( $this->is_translatable_attribute( $attribute ) ) {
419 $sanitized_attribute_name = wc_sanitize_taxonomy_name( $attribute );
420 $default_term_id = $this->get_wcml_terms_instance()->wcml_get_term_id_by_slug( $sanitized_attribute_name, $default_term_slug );
421 $tr_id = apply_filters( 'wpml_object_id', $default_term_id, $sanitized_attribute_name, false, $lang );
422
423 if ( $tr_id ) {
424 $translated_term = $this->get_wcml_terms_instance()->wcml_get_term_by_id( $tr_id, $sanitized_attribute_name );
425 $unserialized_default_attributes[ $attribute ] = $translated_term->slug;
426 }
427 } else {
428 $unserialized_default_attributes[ $attribute ] = $default_term_slug;
429 }
430 } else {
431 $orig_product_attributes = get_post_meta( $orig_post_id, '_product_attributes', true );
432 $unserialized_orig_product_attributes = maybe_unserialize( $orig_product_attributes );
433
434 if ( isset( $unserialized_orig_product_attributes[ $attribute ] ) ) {
435 $orig_attr_values = explode( '|', $unserialized_orig_product_attributes[ $attribute ]['value'] );
436 $orig_attr_values = array_map( 'trim', $orig_attr_values );
437
438 foreach ( $orig_attr_values as $key => $orig_attr_value ) {
439 $orig_attr_value_sanitized = strtolower( sanitize_title( $orig_attr_value ) );
440
441 if ( $orig_attr_value_sanitized == $default_term_slug || trim( $orig_attr_value ) == trim( $default_term_slug ) ) {
442 $tnsl_product_attributes = get_post_meta( $transl_post_id, '_product_attributes', true );
443 $unserialized_tnsl_product_attributes = maybe_unserialize( $tnsl_product_attributes );
444
445 if ( isset( $unserialized_tnsl_product_attributes[ $attribute ] ) ) {
446 $trnsl_attr_values = explode( '|', $unserialized_tnsl_product_attributes[ $attribute ]['value'] );
447
448 if ( $orig_attr_value_sanitized == $default_term_slug ) {
449 $trnsl_attr_value = strtolower( sanitize_title( trim( $trnsl_attr_values[ $key ] ) ) );
450 } else {
451 $trnsl_attr_value = trim( $trnsl_attr_values[ $key ] );
452 }
453 $unserialized_default_attributes[ $attribute ] = $trnsl_attr_value;
454 }
455 }
456 }
457 }
458 }
459 }
460
461 $data = [ 'meta_value' => maybe_serialize( $unserialized_default_attributes ) ];
462 } else {
463 $data = [ 'meta_value' => maybe_serialize( [] ) ];
464 }
465
466 $where = [
467 'post_id' => $transl_post_id,
468 'meta_key' => '_default_attributes',
469 ];
470
471 $translated_product_meta = get_post_meta( $transl_post_id );
472 if ( isset( $translated_product_meta['_default_attributes'] ) ) {
473 $this->wpdb->update( $this->wpdb->postmeta, $data, $where );
474 } else {
475 $this->wpdb->insert( $this->wpdb->postmeta, array_merge( $data, $where ) );
476 }
477
478 }
479
480 public function get_custom_attribute_translation( $product_id, $attribute_key, $attribute, $lang_code ) {
481 $tr_post_id = apply_filters( 'wpml_object_id', $product_id, 'product', false, $lang_code );
482 $transl = [];
483 if ( $tr_post_id ) {
484 if ( ! $attribute['is_taxonomy'] ) {
485 $tr_attrs = get_post_meta( $tr_post_id, '_product_attributes', true );
486 if ( $tr_attrs ) {
487 foreach ( $tr_attrs as $key => $tr_attr ) {
488 if ( $attribute_key == $key ) {
489 $transl['value'] = $tr_attr['value'];
490 $trnsl_labels = $this->get_attr_label_translations( $tr_post_id );
491
492 if ( isset( $trnsl_labels[ $lang_code ][ $attribute_key ] ) ) {
493 $transl['name'] = $trnsl_labels[ $lang_code ][ $attribute_key ];
494 } else {
495 $transl['name'] = $tr_attr['name'];
496 }
497
498 return $transl;
499 }
500 }
501 }
502
503 return false;
504 }
505 }
506
507 return false;
508 }
509
510 public function get_custom_attr_translation( $product_id, $tr_product_id, $taxonomy, $attribute ) {
511 $orig_product_attributes = get_post_meta( $product_id, '_product_attributes', true );
512 $unserialized_orig_product_attributes = maybe_unserialize( $orig_product_attributes );
513
514 foreach ( $unserialized_orig_product_attributes as $orig_attr_key => $orig_product_attribute ) {
515 $orig_attr_key = urldecode( $orig_attr_key );
516 if ( strtolower( $taxonomy ) == $orig_attr_key ) {
517 $values = explode( '|', $orig_product_attribute['value'] );
518
519 foreach ( $values as $key_id => $value ) {
520 if ( trim( $value, ' ' ) == $attribute ) {
521 $attr_key_id = $key_id;
522 }
523 }
524 }
525 }
526
527 $trnsl_product_attributes = get_post_meta( $tr_product_id, '_product_attributes', true );
528 $unserialized_trnsl_product_attributes = maybe_unserialize( $trnsl_product_attributes );
529 $taxonomy = sanitize_title( $taxonomy );
530 $trnsl_attr_values = explode( '|', $unserialized_trnsl_product_attributes[ $taxonomy ]['value'] );
531
532 if ( isset( $attr_key_id ) && isset( $trnsl_attr_values[ $attr_key_id ] ) ) {
533 return trim( $trnsl_attr_values[ $attr_key_id ] );
534 }
535
536 return $attribute;
537 }
538
539 public function set_custom_product_attributes_as_translatable_for_tm_job( $translatable, $job_translate ) {
540
541 if ( 'wc_attribute' === substr( $job_translate['field_type'], 0, 12 ) ) {
542 return true;
543 }
544
545 return $translatable;
546 }
547
548 public function is_attributes_fully_translated() {
549
550 $product_attributes = $this->get_translatable_attributes();
551
552 $fully_translated = true;
553
554 if ( $product_attributes ) {
555 foreach ( $product_attributes as $attribute ) {
556 $is_fully_translated = $this->get_wcml_terms_instance()->is_fully_translated( WCTaxonomies::TAXONOMY_PREFIX_ATTRIBUTE . $attribute->attribute_name );
557 if ( ! $is_fully_translated ) {
558 $fully_translated = false;
559 break;
560 }
561 }
562 }
563
564 return $fully_translated;
565 }
566
567 public function get_translated_variation_attribute_post_meta( $meta_value, $meta_key, $original_variation_id, $variation_id, $lang ) {
568
569 $original_product_attr = get_post_meta( wp_get_post_parent_id( $original_variation_id ), '_product_attributes', true );
570 $tr_product_attr = get_post_meta( wp_get_post_parent_id( $variation_id ), '_product_attributes', true );
571
572 $tax = wc_sanitize_taxonomy_name( substr( $meta_key, 10 ) );
573 if ( taxonomy_exists( $tax ) ) {
574 $attid = $this->get_wcml_terms_instance()->wcml_get_term_id_by_slug( $tax, $meta_value );
575 if ( $this->is_translatable_attribute( $tax ) && $attid ) {
576
577 $term_obj = $this->get_wcml_terms_instance()->wcml_get_term_by_id( $attid, $tax );
578 $trnsl_term_id = apply_filters( 'wpml_object_id', $term_obj->term_id, $tax, false, $lang );
579
580 if ( $trnsl_term_id ) {
581 $trnsl_term_obj = $this->get_wcml_terms_instance()->wcml_get_term_by_id( $trnsl_term_id, $tax );
582 $meta_value = $trnsl_term_obj->slug;
583 }
584 }
585 } else {
586 if ( ! isset( $original_product_attr[ $tax ] ) ) {
587 $tax = sanitize_title( $tax );
588 }
589
590 if ( isset( $original_product_attr[ $tax ] ) ) {
591 if ( isset( $tr_product_attr[ $tax ] ) ) {
592 $values_arrs = array_map( 'trim', explode( '|', $original_product_attr[ $tax ]['value'] ) );
593 $values_arrs_tr = array_map( 'trim', explode( '|', $tr_product_attr[ $tax ]['value'] ) );
594
595 $position = array_search( $meta_value, $values_arrs, true );
596 if ( ( false !== $position ) && isset( $values_arrs_tr[ $position ] )) {
597 $meta_value = $values_arrs_tr[ $position ];
598 }
599 }
600 }
601 $meta_key = 'attribute_' . $tax;
602 }
603
604 return [
605 'meta_value' => $meta_value,
606 'meta_key' => $meta_key,
607 ];
608 }
609
610 public function filter_dropdown_variation_attribute_options_args( $args ) {
611
612 if ( isset( $args['attribute'] ) && isset( $args['product'] ) ) {
613 $args['attribute'] = $this->filter_attribute_name( $args['attribute'], $args['product']->get_id() );
614
615 if ( $this->get_wcml_products_instance()->is_product_display_as_translated_post_type() ) {
616 foreach ( $args['options'] as $key => $attribute_value ) {
617 $args['options'][ $key ] = $this->get_attribute_term_translation_in_current_language( $args['attribute'], $attribute_value );
618 }
619 }
620 }
621
622 return $args;
623 }
624
625 public function filter_attribute_name( $attribute_name, $product_id, $return_sanitized = false ) {
626
627 $special_symbols_languages = [ 'de', 'da' ];
628 $sanitize_in_origin = false;
629 $current_language = $this->sitepress->get_current_language();
630
631 if ( $product_id ) {
632 $orig_lang = $this->get_wcml_products_instance()->get_original_product_language( $product_id );
633
634 if ( in_array( $orig_lang, $special_symbols_languages, true ) && $current_language !== $orig_lang ) {
635 $attribute_name = $this->sitepress->locale_utils->filter_sanitize_title( remove_accents( $attribute_name ), $attribute_name );
636 $this->remove_wpml_locale_sanitize_title_filter();
637 }
638
639 $sanitize_in_origin = in_array( $current_language, $special_symbols_languages, true ) && $current_language !== $orig_lang;
640 if ( $sanitize_in_origin && $return_sanitized ) {
641 $this->remove_wpml_locale_sanitize_title_filter();
642 $this->sitepress->switch_lang( $orig_lang );
643 }
644 }
645
646 if ( $return_sanitized ) {
647 $attribute_name = sanitize_title( $attribute_name );
648
649 if ( $sanitize_in_origin ) {
650
651 $this->sitepress->switch_lang( $current_language );
652 }
653 }
654
655 return $attribute_name;
656 }
657
658 private function remove_wpml_locale_sanitize_title_filter() {
659 remove_filter( 'sanitize_title', [ $this->sitepress->locale_utils, 'filter_sanitize_title' ], 10 );
660 }
661
662 public function filter_adding_to_cart_product_attributes_names( $attributes ) {
663
664 if (
665 ( isset( $_REQUEST['add-to-cart'] ) ) ||
666 ( isset( $_REQUEST['wc-ajax'] ) && 'get_variation' === $_REQUEST['wc-ajax'] && isset( $_REQUEST['product_id'] ) )
667 ) {
668
669 $product_id = $_REQUEST['add-to-cart'] ?? $_REQUEST['product_id'];
670
671 foreach ( $attributes as $key => $attribute ) {
672 $attributes[ $key ]['name'] = $this->filter_attribute_name( $attributes[ $key ]['name'], $product_id );
673 }
674 }
675
676 return $attributes;
677 }
678
679 public function is_a_taxonomy( $attribute ) {
680
681 if (
682 (
683 $attribute instanceof WC_Product_Attribute &&
684 $attribute->is_taxonomy()
685 ) ||
686 (
687 is_array( $attribute ) &&
688 $attribute['is_taxonomy']
689 )
690 ) {
691 return true;
692 }
693
694 return false;
695 }
696
697 public function filter_available_variation_attribute_values_in_current_language( $args ) {
698
699 foreach ( $args['attributes'] as $attribute_key => $attribute_value ) {
700
701 $args['attributes'][ $attribute_key ] = $this->get_attribute_term_translation_in_current_language( substr( $attribute_key, 10 ), $attribute_value );
702 }
703
704 return $args;
705 }
706
707 public function filter_product_variation_post_meta_attribute_values_in_current_language( $value, $object_id, $meta_key, $single ) {
708
709 if ( '' === $meta_key && 'product_variation' === get_post_type( $object_id ) ) {
710 remove_filter(
711 'get_post_metadata',
712 [
713 $this,
714 'filter_product_variation_post_meta_attribute_values_in_current_language',
715 ],
716 10
717 );
718
719 $all_meta = get_post_meta( $object_id );
720
721 add_filter(
722 'get_post_metadata',
723 [
724 $this,
725 'filter_product_variation_post_meta_attribute_values_in_current_language',
726 ],
727 10,
728 4
729 );
730
731 if ( $all_meta ) {
732 $wcml_meta = $this->get_product_variation_post_meta_attribute_translated_list( $object_id, $all_meta );
733
734 return array_merge( $all_meta, $wcml_meta );
735 }
736 }
737
738 return $value;
739 }
740
741 private function get_product_variation_post_meta_attribute_translated_list( $object_id, $all_meta ): array {
742 $cache_key = $this->getCacheWCMLAttributeMetaKey( $object_id );
743
744 $cached_value = wp_cache_get( $cache_key, self::CACHE_GROUP_VARIATION );
745
746 if ( ! empty( $cached_value ) && is_array( $cached_value ) ) {
747 return $cached_value;
748 }
749
750 $wcml_meta = [];
751 foreach ( $all_meta as $meta_key => $meta_value ) {
752 if ( self::isAttributeMeta( $meta_key ) ) {
753 foreach ( $meta_value as $key => $value ) {
754 $wcml_meta[ $meta_key ][ $key ] = $this->get_attribute_term_translation_in_current_language( substr( $meta_key, 10 ), $value );
755 }
756 }
757 }
758
759 wp_cache_add( $cache_key, $wcml_meta, self::CACHE_GROUP_VARIATION );
760
761 return $wcml_meta;
762 }
763
764 public function invalidateVariationMetaCache( $mid, $objectId, $key ) {
765 if ( self::isAttributeMeta( $key ) ) {
766 wp_cache_delete( $this->getCacheWCMLAttributeMetaKey( $objectId ), self::CACHE_GROUP_VARIATION );
767 }
768 }
769
770 private static function isAttributeMeta( $key ) {
771 return 'attribute_' === substr( $key, 0, 10 );
772 }
773
774 private function getCacheWCMLAttributeMetaKey( $variationId ) {
775 return sprintf(
776 'product_variation_%d_post_meta_attribute_translated_%s',
777 $variationId,
778 $this->sitepress->get_current_language()
779 );
780 }
781
782 public function filter_product_variation_default_attributes( $default_attributes ) {
783
784 if ( $default_attributes ) {
785
786 foreach ( $default_attributes as $attribute_key => $attribute_value ) {
787
788 $default_attributes[ $attribute_key ] = $this->get_attribute_term_translation_in_current_language( $attribute_key, $attribute_value );
789
790 }
791 }
792
793 return $default_attributes;
794 }
795
796 private function get_attribute_term_translation_in_current_language( $attribute_taxonomy, $attribute_value ) {
797
798 if ( taxonomy_exists( $attribute_taxonomy ) ) {
799 $term = get_term_by( 'slug', $attribute_value, $attribute_taxonomy );
800 if ( $term ) {
801 $attribute_value = $term->slug;
802 }
803 }
804
805 return $attribute_value;
806 }
807
808 public function set_translation_status_as_needs_update( $meta_id, $object_id, $meta_key ) {
809 if ( $meta_key === '_product_attributes' ) {
810
811 if ( null === $this->post_translations->get_source_lang_code( $object_id ) ) {
812 $status_helper = wpml_get_post_status_helper();
813
814 foreach ( $this->post_translations->get_element_translations( $object_id ) as $translation ) {
815 if ( null !== $this->post_translations->get_source_lang_code( $translation ) ) {
816 $status_helper->set_update_status( $translation, 1 );
817 }
818 }
819 }
820 }
821 }
822
823 public function maybe_filter_get_variation() {
824
825 if (
826 isset( $_POST['product_id'] ) &&
827 $this->woocommerce_wpml->products->is_product_display_as_translated_post_type() &&
828 is_null( $this->post_translations->element_id_in( $_POST['product_id'], $this->sitepress->get_current_language() ) )
829 ) {
830 foreach ( wp_unslash( $_POST ) as $key => $value ) {
831 if ( substr( $key, 0, 13 ) == 'attribute_pa_' ) {
832 $taxonomy = substr( $key, 10 );
833 $term_id = $this->get_wcml_terms_instance()->wcml_get_term_id_by_slug( $taxonomy, $value );
834 $translated_term = $this->get_wcml_terms_instance()->wcml_get_translated_term( $term_id, $taxonomy, $this->get_wcml_products_instance()->get_original_product_language( $_POST['product_id'] ) );
835 $_POST[ $key ] = $translated_term->slug;
836 }
837 }
838 }
839 }
840
841 private function wpml_ajax_custom_tax_sync_options() {
842 if ( ! isset( $_POST['icl_ajx_action'] ) || $_POST['icl_ajx_action'] !== 'icl_custom_tax_sync_options' ) {
843 return;
844 }
845
846 if ( ! isset( $_POST['icl_sync_tax'] ) || ! is_array( $_POST['icl_sync_tax'] ) ) {
847 return;
848 }
849
850 if ( isset( $_POST['_icl_nonce'] ) && wp_verify_nonce( $_POST['_icl_nonce'], 'icl_custom_tax_sync_options_nonce' ) ) {
851
852 foreach ( $_POST['icl_sync_tax'] as $taxonomyName => $translationPreference ) {
853 $taxonomyName = wc_sanitize_taxonomy_name( wp_unslash( $taxonomyName ) );
854 $translationPreference = (int) $translationPreference;
855
856 if ( WCTaxonomies::isProductAttribute( $taxonomyName ) ) {
857 $this->set_attribute_config_in_wcml_settings( $taxonomyName, $translationPreference );
858 }
859 }
860 }
861 }
862
863 public function wcml_taxonomy_strings_source_language( $source_lang, $attr_value, $attr_name ) {
864 $product_attr = $this->extract_product_attribute_taxonomy( $attr_value, $attr_name );
865
866 if ( $product_attr ) {
867 $attribute_taxonomies = wp_list_pluck( wc_get_attribute_taxonomies(), 'attribute_label' );
868
869 if ( in_array( $product_attr, $attribute_taxonomies, true ) ) {
870 return $this->sitepress->get_default_language();
871 }
872 }
873
874 return $source_lang;
875 }
876
877 private function extract_product_attribute_taxonomy( $attr_value, $attr_name ) {
878 if ( Str::startsWith( WCML_WC_Strings::TAXONOMY_GENERAL_NAME_PREFIX, $attr_name ) ) {
879 return substr( $attr_value, strlen( WCML_WC_Strings::TAXONOMY_GENERAL_VALUE_PREFIX ) );
880 }
881
882 if ( Str::startsWith( WCML_WC_Strings::TAXONOMY_SINGULAR_NAME_PREFIX, $attr_name ) ) {
883 return $attr_value;
884 }
885
886 return null;
887 }
888
889 public function wcml_taxonomy_slug_set_source_language( $source_lang, $type, $slug ) {
890 if ( WCTaxonomies::isProductAttribute( $slug ) ) {
891 if ( taxonomy_exists( $slug ) ) {
892 return $this->sitepress->get_default_language();
893 }
894 }
895
896 return $source_lang;
897 }
898 }
899