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-url-translation.php
woocommerce-multilingual / inc Last commit date
admin-menus 4 weeks ago currencies 4 weeks ago template-classes 4 weeks ago translation-editor 4 weeks ago class-wcml-ajax-setup.php 4 weeks ago class-wcml-attributes.php 4 weeks ago class-wcml-capabilities.php 4 weeks ago class-wcml-cart-removed-items-widget.php 1 year ago class-wcml-cart-switch-lang-functions.php 4 weeks ago class-wcml-cart-sync-warnings.php 4 weeks ago class-wcml-cart.php 4 weeks ago class-wcml-comments.php 4 weeks ago class-wcml-compatibility.php 4 weeks ago class-wcml-coupons.php 4 weeks ago class-wcml-dependencies.php 4 weeks ago class-wcml-emails.php 4 weeks ago class-wcml-endpoints.php 4 weeks ago class-wcml-install.php 4 weeks ago class-wcml-languages-upgrader.php 4 weeks ago class-wcml-locale.php 4 weeks ago class-wcml-orders.php 4 weeks ago class-wcml-products-screen-options.php 4 weeks ago class-wcml-products.php 4 weeks ago class-wcml-reports.php 4 weeks ago class-wcml-requests.php 4 weeks ago class-wcml-resources.php 4 weeks ago class-wcml-store-pages.php 4 weeks ago class-wcml-terms.php 4 weeks ago class-wcml-tp-support.php 4 weeks ago class-wcml-troubleshooting.php 4 weeks ago class-wcml-upgrade.php 4 weeks ago class-wcml-url-translation.php 4 weeks ago class-wcml-wc-gateways.php 4 weeks ago class-wcml-wc-shipping.php 4 weeks ago class-wcml-wc-strings.php 4 weeks ago class-wcml-widgets.php 4 weeks ago constants.php 4 weeks ago functions-pure.php 4 weeks ago functions.php 4 weeks ago installer-loader.php 4 weeks ago missing-php-functions.php 4 weeks ago wcml-core-functions.php 4 weeks ago wcml-switch-lang-request.php 4 weeks ago
class-wcml-url-translation.php
968 lines
1 <?php
2
3 use WCML\Permalinks\Strings;
4 use WCML\Utilities\WCTaxonomies;
5
6 class WCML_Url_Translation {
7 const WC_STRING_CONTEXT = 'WooCommerce Endpoints';
8
9
10 private $woocommerce_wpml;
11 private $sitepress;
12 private $wpdb;
13
14 public $default_product_base;
15 public $default_product_category_base;
16 public $default_product_category_gettext_base;
17 public $default_product_tag_base;
18 public $default_product_tag_gettext_base;
19 public $wc_permalinks;
20
21 public function __construct( woocommerce_wpml $woocommerce_wpml, \WPML\Core\ISitePress $sitepress, wpdb $wpdb ) {
22 $this->woocommerce_wpml = $woocommerce_wpml;
23 $this->sitepress = $sitepress;
24 $this->wpdb = $wpdb;
25
26 $this->default_product_base = Strings::DEFAULT_PRODUCT_BASE;
27 $this->default_product_category_base = Strings::DEFAULT_PRODUCT_CATEGORY_BASE;
28 $this->default_product_tag_base = Strings::DEFAULT_PRODUCT_TAG_BASE;
29 $this->default_product_category_gettext_base = _x( Strings::DEFAULT_PRODUCT_CATEGORY_BASE, 'slug', 'woocommerce' );
30 $this->default_product_tag_gettext_base = _x( Strings::DEFAULT_PRODUCT_TAG_BASE, 'slug', 'woocommerce' );
31 }
32
33 public function set_up() {
34
35 $this->wc_permalinks = get_option( 'woocommerce_permalinks' );
36
37 if ( ! is_admin() ) {
38 add_filter(
39 'option_woocommerce_permalinks',
40 [
41 $this,
42 'use_untranslated_default_url_bases',
43 ],
44 1,
45 1
46 );
47 add_action(
48 'init',
49 [
50 $this,
51 'fix_post_object_rewrite_slug',
52 ],
53 6
54 );
55 }
56
57 add_filter(
58 'pre_update_option_rewrite_rules',
59 [
60 $this,
61 'force_bases_in_strings_languages',
62 ],
63 1,
64 1
65 );
66 add_filter( 'option_rewrite_rules', [ $this, 'translate_bases_in_rewrite_rules' ], 0, 1 );
67 add_filter( 'term_link', [ $this, 'translate_taxonomy_base' ], 0, 3 );
68 add_filter( 'woocommerce_taxonomy_archive_description_raw', [ $this, 'process_taxonomy_description_links' ], 10, 2 );
69
70 add_action( 'wp_ajax_wcml_update_base_translation', [ $this, 'wcml_update_base_translation' ] );
71 add_filter( 'redirect_canonical', [ $this, 'check_wc_tax_url_on_redirect' ], 10, 2 );
72 add_filter( 'query_vars', [ $this, 'translate_query_var_for_product' ] );
73 add_filter( 'wp_redirect', [ $this, 'encode_shop_slug' ] );
74 add_action( 'switch_blog', [ $this, 'maybe_remove_query_vars_filter' ] );
75
76 add_filter( 'post_type_link', [ $this, 'translate_product_post_type_link' ], 10, 2 );
77
78 if ( empty( $this->woocommerce_wpml->settings['url_translation_set_up'] ) ) {
79
80 $this->clean_up_product_and_taxonomy_bases();
81
82 $this->translate_product_base();
83
84 $this->register_product_and_taxonomy_bases();
85
86 $this->woocommerce_wpml->settings['url_translation_set_up'] = 1;
87 $this->woocommerce_wpml->update_settings();
88 }
89
90 add_action( 'init', [ $this, 'add_hooks_after_init' ], PHP_INT_MAX );
91 }
92
93 public function add_hooks_after_init() {
94 if ( ! is_admin() ) {
95 if ( $this->isProductDisplayAsTranslatedDocument() ) {
96 add_filter( 'wpml_st_post_type_link_filter_language_details', [ $this, 'translate_product_slug_when_product_is_display_as_translated_document' ] );
97 }
98 }
99
100 if ( $this->woocommerce_wpml->products->is_product_display_as_translated_post_type() ) {
101 if ( false !== strpos( $this->get_woocommerce_product_base(), '%product_cat%' ) ) {
102 add_filter( 'wc_product_post_type_link_product_cat', [ $this, 'translate_product_post_type_link_product_cat_when_display_as_translated' ], 10, 3 );
103 }
104 }
105
106 add_filter( 'wpml_absolute_links_permalink_query_vars', [ $this, 'adjustQueryVarsOfShopAbsoluteLink' ], 10, 3 );
107 }
108
109 public function adjustQueryVarsOfShopAbsoluteLink( $permalink_query_vars, $query, $language ) {
110 if ( 'post_type=product' === $query ) {
111 $default_language = $this->sitepress->get_default_language();
112
113 $pagename = 'shop';
114 if ( 'en' !== $default_language && $default_language === $language ) {
115 $shop_id = apply_filters( 'wpml_object_id', wc_get_page_id( 'shop' ), 'page', false, $language );
116 $pagename = get_page_uri( $shop_id );
117 }
118
119 return [
120 'pagename' => $pagename,
121 'page' => '',
122 ];
123 }
124
125 return $permalink_query_vars;
126 }
127
128 public function translate_product_slug_when_product_is_display_as_translated_document( $elementLanguageDetails ) {
129 if ( $elementLanguageDetails->source_language_code ) {
130 return $elementLanguageDetails;
131 }
132
133 if ( 'product' !== get_post_type( $elementLanguageDetails->element_id ) ) {
134 return $elementLanguageDetails;
135 }
136
137 $elementLanguageDetails->source_language_code = $elementLanguageDetails->language_code;
138 $elementLanguageDetails->language_code = $this->sitepress->get_current_language();
139
140 return $elementLanguageDetails;
141 }
142
143 private function isProductDisplayAsTranslatedDocument(): bool {
144 return in_array( 'product', \WPML\API\PostTypes::getDisplayAsTranslated(), true );
145 }
146
147 public function flushWcSettings() {
148 $this->wc_permalinks = get_option( 'woocommerce_permalinks' );
149 }
150
151 public function clean_up_product_and_taxonomy_bases() {
152
153 $base = $this->get_woocommerce_product_base();
154
155 $this->wpdb->query(
156 $this->wpdb->prepare(
157 "DELETE FROM {$this->wpdb->prefix}icl_strings WHERE context = %s AND value != %s AND name LIKE 'URL slug:%' ",
158 Strings::TRANSLATION_DOMAIN,
159 trim( $base, '/' )
160 )
161 );
162
163 $this->wpdb->update(
164 $this->wpdb->prefix . 'icl_strings',
165 [
166 'context' => Strings::TRANSLATION_DOMAIN,
167 'name' => 'URL slug: product',
168 ],
169 [
170 'context' => Strings::TRANSLATION_DOMAIN,
171 'name' => sprintf( 'Url slug: %s', trim( $base, '/' ) ),
172 ]
173 );
174
175 $woocommerce_permalinks = maybe_unserialize( get_option( 'woocommerce_permalinks' ) );
176
177 foreach ( (array) $woocommerce_permalinks as $base_key => $base ) {
178
179 $base_key = trim( $base_key, '/' );
180
181 $taxonomy = false;
182
183 switch ( $base_key ) {
184 case 'category_base':
185 $taxonomy = 'product_cat';
186 break;
187 case 'tag_base':
188 $taxonomy = 'product_tag';
189 break;
190 case 'attribute_base':
191 $taxonomy = 'attribute';
192 break;
193 }
194
195 if ( $taxonomy ) {
196 $this->wpdb->query( "DELETE FROM {$this->wpdb->prefix}icl_strings WHERE context LIKE '" . sprintf( 'URL %s slugs - ', $taxonomy ) . "%'" );
197 }
198 }
199
200 }
201
202 public function fix_post_object_rewrite_slug() {
203 global $wp_post_types, $wp_rewrite;
204
205 if ( empty( $this->wc_permalinks['product_base'] ) ) {
206 $wp_post_types['product']->rewrite['slug'] = 'product';
207 if ( empty( $wp_rewrite->extra_permastructs['product']['struct'] ) ) {
208 $wp_rewrite->extra_permastructs['product']['struct'] = '/product/%product%';
209 }
210 }
211 }
212
213 public function url_strings_context() {
214 return Strings::TRANSLATION_DOMAIN;
215 }
216
217 public function url_string_name( $type, $value = '' ) {
218 return Strings::getStringName( $type, $value );
219 }
220
221 public function translate_product_base() {
222 if ( ! defined( 'WOOCOMMERCE_VERSION' ) || ( ! isset( $GLOBALS['ICL_Pro_Translation'] ) ) ) {
223 return;
224 }
225
226 $slug = $this->get_woocommerce_product_base();
227 do_action( 'wpml_activate_slug_translation', 'product', $slug );
228 }
229
230 public function get_woocommerce_product_base() {
231
232 if ( isset( $this->wc_permalinks['product_base'] ) && ! empty( $this->wc_permalinks['product_base'] ) ) {
233 return trim( $this->wc_permalinks['product_base'], '/' );
234 } elseif ( get_option( 'woocommerce_product_slug' ) != false ) {
235 return trim( get_option( 'woocommerce_product_slug' ), '/' );
236 } else {
237 return $this->default_product_base;
238 }
239
240 }
241
242 public function register_product_and_taxonomy_bases( $wcPermalinks = false, $deprecated = false ) {
243
244 if ( empty( $wcPermalinks ) ) {
245 $permalink_options = $this->wc_permalinks;
246 } else {
247 $permalink_options = $wcPermalinks;
248 }
249
250 $product_base = ! empty( $permalink_options['product_base'] ) ? trim( $permalink_options['product_base'], '/' ) : $this->default_product_base;
251 $name = Strings::getStringName( 'product' );
252
253 $string_language = $this->woocommerce_wpml->strings->get_string_language( $product_base, Strings::TRANSLATION_DOMAIN, $name );
254 if ( is_null( $string_language ) ) {
255 $string_language = '';
256 }
257 do_action( 'wpml_register_single_string', Strings::TRANSLATION_DOMAIN, $name, $product_base, false, $string_language );
258
259 if ( isset( $_POST['product_base_language'] ) ) {
260 $this->woocommerce_wpml->strings->set_string_language( $product_base, Strings::TRANSLATION_DOMAIN, $name, $_POST['product_base_language'] );
261 }
262
263 if ( $product_base == $this->default_product_base ) {
264 $this->add_default_slug_translations( $product_base, $name );
265 }
266
267 $category_base = ! empty( $permalink_options['category_base'] ) ? $permalink_options['category_base'] : $this->default_product_category_base;
268 $name = Strings::getStringName( 'product_cat' );
269
270 $string_language = $this->woocommerce_wpml->strings->get_string_language( $category_base, Strings::TRANSLATION_DOMAIN, $name );
271 if ( is_null( $string_language ) ) {
272 $string_language = '';
273 }
274 do_action( 'wpml_register_single_string', Strings::TRANSLATION_DOMAIN, $name, $category_base, false, $string_language );
275
276 if ( isset( $_POST['category_base_language'] ) ) {
277 $this->woocommerce_wpml->strings->set_string_language( $category_base, Strings::TRANSLATION_DOMAIN, $name, $_POST['category_base_language'] );
278 }
279
280 if ( $category_base == $this->default_product_category_base ) {
281 $this->add_default_slug_translations( $category_base, $name );
282 }
283
284 $tag_base = ! empty( $permalink_options['tag_base'] ) ? $permalink_options['tag_base'] : $this->default_product_tag_base;
285 $name = Strings::getStringName( 'product_tag' );
286
287 $string_language = $this->woocommerce_wpml->strings->get_string_language( $tag_base, Strings::TRANSLATION_DOMAIN, $name );
288 if ( is_null( $string_language ) ) {
289 $string_language = '';
290 }
291 do_action( 'wpml_register_single_string', Strings::TRANSLATION_DOMAIN, $name, $tag_base, false, $string_language );
292
293 if ( isset( $_POST['tag_base_language'] ) ) {
294 $this->woocommerce_wpml->strings->set_string_language( $tag_base, Strings::TRANSLATION_DOMAIN, $name, $_POST['tag_base_language'] );
295 }
296
297 if ( $tag_base == $this->default_product_tag_base ) {
298 $this->add_default_slug_translations( $tag_base, $name );
299 }
300
301 if ( isset( $permalink_options['attribute_base'] ) && $permalink_options['attribute_base'] ) {
302 $attr_base = trim( $permalink_options['attribute_base'], '/' );
303 $attr_string_name = Strings::getStringName( 'attribute' );
304
305 $string_language = $this->woocommerce_wpml->strings->get_string_language( $attr_base, Strings::TRANSLATION_DOMAIN, $attr_string_name );
306 if ( is_null( $string_language ) ) {
307 $string_language = '';
308 }
309 do_action( 'wpml_register_single_string', Strings::TRANSLATION_DOMAIN, $attr_string_name, $attr_base, false, $string_language );
310
311 if ( isset( $_POST['attribute_base_language'] ) ) {
312 $this->woocommerce_wpml->strings->set_string_language( $attr_base, Strings::TRANSLATION_DOMAIN, $attr_string_name, $_POST['attribute_base_language'] );
313 }
314 }
315
316 return $wcPermalinks;
317 }
318
319 public function use_untranslated_default_url_bases( $permalinks ) {
320
321 if ( preg_match( '#^/?index\.php/#', get_option( 'permalink_structure' ) ) ) {
322 return $permalinks;
323 }
324
325 if ( empty( $permalinks['product_base'] ) ) {
326 $permalinks['product_base'] = $this->default_product_base;
327 }
328 if ( empty( $permalinks['category_base'] ) ) {
329 $permalinks['category_base'] = $this->default_product_category_base;
330 }
331 if ( empty( $permalinks['tag_base'] ) ) {
332 $permalinks['tag_base'] = $this->default_product_tag_base;
333 }
334
335 return $permalinks;
336 }
337
338 public function add_default_slug_translations( $slug, $name ) {
339
340 $string_id = icl_get_string_id( $slug, Strings::TRANSLATION_DOMAIN, $name );
341 $string_language = $this->woocommerce_wpml->strings->get_string_language( $slug, Strings::TRANSLATION_DOMAIN, $name );
342
343 $string_object = new WPML_ST_String( $string_id, $this->wpdb );
344 $string_translation_statuses = $string_object->get_translation_statuses();
345
346 foreach ( $string_translation_statuses as $s ) {
347 $string_translations[ $s->language ] = $s->status;
348 }
349
350 $languages = $this->sitepress->get_active_languages();
351
352 foreach ( $languages as $language => $language_info ) {
353
354 if ( $language != $string_language ) {
355
356 if ( ! isset( $string_translations[ $language ] ) ) {
357
358 $slug_translation = $this->woocommerce_wpml->strings->get_translation_from_woocommerce_mo_file( $slug, $language, false );
359
360 if ( $slug_translation ) {
361 icl_add_string_translation( $string_id, $language, $slug_translation, ICL_STRING_TRANSLATION_COMPLETE );
362 }
363 }
364 }
365 }
366
367 }
368
369 public function force_bases_in_strings_languages( $value ) {
370
371 if ( $value && $this->sitepress->get_current_language() !== 'en' ) {
372
373 remove_filter(
374 'gettext_with_context',
375 [
376 $this->woocommerce_wpml->strings,
377 'category_base_in_strings_language',
378 ],
379 99
380 );
381 $taxonomies = [
382 'product_cat' => [
383 'base' => 'category_base',
384 'base_translated' => apply_filters(
385 'wpml_translate_single_string',
386 'product-category',
387 Strings::TRANSLATION_DOMAIN,
388 Strings::getStringName( 'product_cat' )
389 ),
390 'default' => $this->default_product_category_base,
391 ],
392 'product_tag' => [
393 'base' => 'tag_base',
394 'base_translated' => apply_filters(
395 'wpml_translate_single_string',
396 'product-tag',
397 Strings::TRANSLATION_DOMAIN,
398 Strings::getStringName( 'product_tag' )
399 ),
400 'default' => $this->default_product_tag_base,
401 ],
402 ];
403 add_filter(
404 'gettext_with_context',
405 [
406 $this->woocommerce_wpml->strings,
407 'category_base_in_strings_language',
408 ],
409 99,
410 3
411 );
412 foreach ( $taxonomies as $taxonomy_details ) {
413
414 if ( empty( $this->wc_permalinks[ $taxonomy_details['base'] ] ) && $value ) {
415
416 $new_value = [];
417 foreach ( $value as $k => $v ) {
418 $k = preg_replace( '#' . $taxonomy_details['base_translated'] . '/#', $taxonomy_details['default'] . '/', $k );
419 $new_value[ $k ] = $v;
420 }
421 $value = $new_value;
422 unset( $new_value );
423
424 }
425 }
426 }
427
428 return $value;
429
430 }
431
432 public function translate_bases_in_rewrite_rules( $value ) {
433
434 if ( ! empty( $value ) ) {
435 $value = $this->translate_wc_default_taxonomies_bases_in_rewrite_rules( $value );
436 $value = $this->translate_attributes_bases_in_rewrite_rules( $value );
437 $value = $this->translate_shop_page_base_in_rewrite_rules( $value );
438 }
439
440 return $value;
441 }
442
443 public function translate_wc_default_taxonomies_bases_in_rewrite_rules( $value ) {
444 $taxonomies = [ WCTaxonomies::TAXONOMY_PRODUCT_CATEGORY, WCTaxonomies::TAXONOMY_PRODUCT_TAG ];
445
446 foreach ( $taxonomies as $taxonomy ) {
447 $slug_details = $this->get_translated_tax_slug( $taxonomy );
448
449 $string_language = $this->woocommerce_wpml->strings->get_string_language( $slug_details['slug'], Strings::TRANSLATION_DOMAIN, Strings::getStringName( $taxonomy ) );
450 if ( $this->sitepress->get_current_language() == $string_language ) {
451 continue;
452 }
453
454 if ( $slug_details ) {
455
456 $slug_match = addslashes( ltrim( $slug_details['slug'], '/' ) );
457 $slug_translation_match = ltrim( $slug_details['translated_slug'], '/' );
458
459 $buff_value = [];
460
461 foreach ( (array) $value as $k => $v ) {
462
463 if ( $slug_details['slug'] != $slug_details['translated_slug'] && preg_match( '#(^|^/)' . $slug_match . '/#', $k ) ) {
464 $k = preg_replace( '#(^|^/)' . $slug_match . '/#', '$1' . $slug_translation_match . '/', $k );
465 }
466
467 $buff_value[ $k ] = $v;
468 }
469 $value = $buff_value;
470 unset( $buff_value );
471 }
472 }
473
474 return $value;
475 }
476
477 public function translate_attributes_bases_in_rewrite_rules( $value ) {
478
479 $wc_taxonomies = wc_get_attribute_taxonomies();
480 $wc_taxonomies_wc_format = [];
481 foreach ( $wc_taxonomies as $v ) {
482 $wc_taxonomies_wc_format[] = WCTaxonomies::TAXONOMY_PREFIX_ATTRIBUTE . $v->attribute_name;
483 }
484
485 foreach ( $wc_taxonomies_wc_format as $taxonomy ) {
486 $taxonomy_obj = get_taxonomy( $taxonomy );
487
488 if ( isset( $taxonomy_obj->rewrite['slug'] ) ) {
489 $exp = explode( '/', trim( $taxonomy_obj->rewrite['slug'], '/' ) );
490 $slug = join( '/', array_slice( $exp, 0, count( $exp ) - 1 ) );
491 $attribute_slug = preg_replace( "#^$slug/#", '', $taxonomy_obj->rewrite['slug'] );
492
493 $current_language = $this->sitepress->get_current_language();
494 $slug_language = $this->woocommerce_wpml->strings->get_string_language( $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute' ) );
495 $attribute_slug_language = $this->woocommerce_wpml->strings->get_string_language( $attribute_slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute_slug', $attribute_slug ) );
496
497 if ( $current_language !== $attribute_slug_language || $current_language !== $slug_language ) {
498
499 $slug_translation = apply_filters( 'wpml_translate_single_string', $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute' ) );
500
501 $attribute_slug_translation = apply_filters( 'wpml_translate_single_string', $attribute_slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute_slug', $attribute_slug ) );
502
503 if ( $slug && $slug_translation && $slug !== $slug_translation ) {
504
505 $slug_match = addslashes( ltrim( $slug, '/' ) );
506 $slug_translation_match = ltrim( $slug_translation, '/' );
507
508 $pattern = '#^' . $slug_match . '/(.*)#';
509 $replacement_pattern = '#^' . $slug_match . '/(' . $attribute_slug . ')/(.*)#';
510 $replacement = $slug_translation_match . '/' . $attribute_slug_translation . '/$2';
511
512 $value = $this->replace_bases_in_rewrite_rules( $value, $pattern, $replacement_pattern, $replacement );
513
514 } elseif ( $attribute_slug_translation && $attribute_slug !== $attribute_slug_translation ) {
515
516 $slug_match = addslashes( ltrim( $attribute_slug, '/' ) );
517 $pattern = '#(^|\/)' . $slug_match . '/(.*)#';
518 $replacement = '$1' . $attribute_slug_translation . '/$2';
519
520 $value = $this->replace_bases_in_rewrite_rules( $value, $pattern, $pattern, $replacement );
521
522 }
523 }
524 }
525 }
526
527 return $value;
528
529 }
530
531 public function translate_shop_page_base_in_rewrite_rules( $value ) {
532 $current_shop_id = wc_get_page_id( 'shop' );
533 $default_shop_id = apply_filters( 'wpml_object_id', $current_shop_id, 'page', true, $this->sitepress->get_default_language() );
534
535 if ( is_null( get_post( $current_shop_id ) ) || is_null( get_post( $default_shop_id ) ) ) {
536 return $value;
537 }
538
539 $current_slug = urldecode( get_page_uri( $current_shop_id ) );
540 $default_slug = urldecode( get_page_uri( $default_shop_id ) );
541
542 if ( $current_slug != $default_slug ) {
543 $buff_value = [];
544 foreach ( (array) $value as $k => $v ) {
545
546 if ( preg_match( '#^' . $default_slug . '/\?\$$#', $k ) ||
547 preg_match( '#^' . $default_slug . '/\(?feed#', $k ) ||
548 preg_match( '#^' . $default_slug . '/page#', $k )
549 ) {
550
551 $k = preg_replace( '#^' . $default_slug . '/#', $current_slug . '/', $k );
552 }
553
554 $buff_value[ $k ] = $v;
555 }
556
557 $value = $buff_value;
558 unset( $buff_value );
559 }
560
561 return $value;
562 }
563
564 public function replace_bases_in_rewrite_rules( $value, $pattern, $replacement_pattern, $replacement ) {
565
566 $buff_value = [];
567 foreach ( (array) $value as $k => $v ) {
568 if ( preg_match( $pattern, $k ) ) {
569 $k = preg_replace( $replacement_pattern, $replacement, $k );
570 }
571 $buff_value[ $k ] = $v;
572 }
573
574 $value = $buff_value;
575 unset( $buff_value );
576
577 return $value;
578 }
579
580 public function translate_taxonomy_base( $termlink, $term, $taxonomy ) {
581 global $wp_rewrite, $wpml_term_translations;
582 static $no_recursion_flag;
583
584 $wc_taxonomies = wc_get_attribute_taxonomies();
585 foreach ( $wc_taxonomies as $v ) {
586 $wc_taxonomies_wc_format[] = WCTaxonomies::TAXONOMY_PREFIX_ATTRIBUTE . $v->attribute_name;
587 }
588
589 if ( ( WCTaxonomies::isProductAttribute( $taxonomy ) || WCTaxonomies::isProductTag( $taxonomy ) || ( ! empty( $wc_taxonomies_wc_format ) && in_array( $taxonomy, $wc_taxonomies_wc_format ) ) ) && ! $no_recursion_flag ) {
590
591 $cache_key = 'termlink#' . $taxonomy . '#' . $term->term_id;
592 if ( $link = wp_cache_get( $cache_key, 'terms' ) ) {
593 $termlink = $link;
594 } else {
595
596 $no_recursion_flag = false;
597
598 if ( ! is_null( $wpml_term_translations ) ) {
599 $term_language = $term->term_id ? $wpml_term_translations->get_element_lang_code( $term->term_taxonomy_id ) : false;
600 } else {
601 $term_language = $term->term_id ? $this->sitepress->get_language_for_element( $term->term_taxonomy_id, 'tax_' . $taxonomy ) : false;
602 }
603
604 if ( $term_language ) {
605
606 $slug_details = $this->get_translated_tax_slug( $taxonomy, $term_language );
607
608 $base = $slug_details['slug'];
609 $base_translated = $slug_details['translated_slug'];
610
611 if ( isset( $wp_rewrite->extra_permastructs[ $taxonomy ] ) ) {
612
613 $buff = $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'];
614
615 if ( $base_translated !== $base ) {
616 $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'] = preg_replace( '#^' . $base . '/(.*)#', $base_translated . '/$1', $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'] );
617 }
618
619 $attribute_slug = preg_replace( '#^' . $base . '/([^/]+)/.+$#', '$1', $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'] );
620 $attribute_slug_default = preg_replace( '#^pa_#', '', $taxonomy );
621 $attribute_slug_translation = apply_filters(
622 'wpml_translate_single_string',
623 $attribute_slug,
624 Strings::TRANSLATION_DOMAIN,
625 Strings::getStringName( 'attribute_slug', $attribute_slug_default ),
626 $term_language
627 );
628
629 if ( $attribute_slug_translation != $attribute_slug ) {
630
631 if ( $base ) {
632 $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'] = preg_replace(
633 '#^' . $base_translated . '/([^/]+)/(.+)$#',
634 $base_translated . '/' . $attribute_slug_translation . '/$2',
635 $wp_rewrite->extra_permastructs[ $taxonomy ]['struct']
636 );
637 } else {
638 $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'] = preg_replace(
639 '#' . $attribute_slug_default . '/(.+)$#',
640 $attribute_slug_translation . '/$1',
641 $wp_rewrite->extra_permastructs[ $taxonomy ]['struct']
642 );
643 }
644 }
645
646 $no_recursion_flag = true;
647 $termlink = get_term_link( $term, $taxonomy );
648
649 $wp_rewrite->extra_permastructs[ $taxonomy ]['struct'] = $buff;
650
651 }
652 }
653
654 $no_recursion_flag = false;
655
656 wp_cache_add( $cache_key, $termlink, 'terms', 0 );
657 }
658 }
659
660 return $termlink;
661 }
662
663 public function process_taxonomy_description_links( $term_description, $term ) {
664 if ( ! is_string( $term_description ) ) {
665 return $term_description;
666 }
667
668 return (string) apply_filters( 'wpml_translate_link_targets', $term_description );
669 }
670
671 public function get_translated_tax_slug( $taxonomy, $language = false ) {
672
673 switch ( $taxonomy ) {
674 case 'product_tag':
675 if ( ! empty( $this->wc_permalinks['tag_base'] ) ) {
676 $slug = trim( $this->wc_permalinks['tag_base'], '/' );
677 } else {
678 $slug = 'product-tag';
679 }
680
681 $string_language = $this->woocommerce_wpml->strings->get_string_language( $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $taxonomy ) );
682
683 break;
684
685 case 'product_cat':
686 if ( ! empty( $this->wc_permalinks['category_base'] ) ) {
687 $slug = trim( $this->wc_permalinks['category_base'], '/' );
688 } else {
689 $slug = 'product-category';
690 }
691
692 $string_language = $this->woocommerce_wpml->strings->get_string_language( $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $taxonomy ) );
693
694 break;
695
696 default:
697 $slug = trim( $this->wc_permalinks['attribute_base'], '/' );
698
699 $string_language = $this->woocommerce_wpml->strings->get_string_language( $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute' ) );
700
701 $taxonomy = 'attribute';
702
703 break;
704 }
705
706 if ( ! $language ) {
707 $language = $this->sitepress->get_current_language();
708 }
709
710 if ( $slug && $language !== 'all' && $language !== $string_language ) {
711
712 $slug_translation = apply_filters( 'wpml_translate_single_string', $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $taxonomy ), $language, false );
713
714 return [
715 'slug' => $slug,
716 'translated_slug' => $slug_translation,
717 ];
718 }
719
720 return [
721 'slug' => $slug,
722 'translated_slug' => $slug,
723 ];
724
725 }
726
727 public function get_base_translation( $base, $language ) {
728 $original_base = $base;
729
730 if ( strpos( $base, 'attribute_slug-' ) === 0 ) {
731 $base = 'attribute_slug';
732 }
733
734 switch ( $base ) {
735 case 'product':
736 $slug = $this->get_woocommerce_product_base();
737 $return['name'] = __( 'Product Base', 'woocommerce-multilingual' );
738 break;
739
740 case 'product_tag':
741 $slug = ! empty( $this->wc_permalinks['tag_base'] ) ? trim( $this->wc_permalinks['tag_base'], '/' ) : 'product-tag';
742 $return['name'] = __( 'Product Tag Base', 'woocommerce-multilingual' );
743 break;
744
745 case 'product_cat':
746 $slug = ! empty( $this->wc_permalinks['category_base'] ) ? trim( $this->wc_permalinks['category_base'], '/' ) : 'product-category';
747 $return['name'] = __( 'Product Category Base', 'woocommerce-multilingual' );
748 break;
749
750 case 'attribute':
751 $slug = trim( $this->wc_permalinks['attribute_base'], '/' );
752 $return['name'] = __( 'Product Attribute Base', 'woocommerce-multilingual' );
753 break;
754
755 case 'attribute_slug':
756 $slug = preg_replace( '#^attribute_slug-#', '', $original_base );
757 $return['name'] = __( 'Attribute Slug', 'woocommerce-multilingual' );
758 $string_id = icl_get_string_id( $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $base, $slug ) );
759 break;
760
761 default:
762 $endpoints = WC()->query->get_query_vars();
763 $slug = isset( $endpoints[ $base ] ) ? $endpoints[ $base ] : $base;
764
765 /* translators: %s is a slug */
766 $return['name'] = sprintf( __( 'Endpoint: %s', 'woocommerce-multilingual' ), $base );
767 $string_id = icl_get_string_id( $slug, $this->get_endpoint_string_context(), $base );
768 break;
769 }
770
771 $return['original_value'] = $slug;
772 if ( ! isset( $string_id ) ) {
773 $string_id = icl_get_string_id( $slug, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $base ) );
774 }
775 $base_translations = icl_get_string_translations_by_id( $string_id );
776
777 $return['translated_base'] = '';
778 if ( isset( $base_translations[ $language ] ) ) {
779 if ( $base_translations[ $language ]['status'] == ICL_TM_COMPLETE ) {
780 $return['translated_base'] = $base_translations[ $language ]['value'];
781 } elseif ( $base_translations[ $language ]['status'] == ICL_TM_NEEDS_UPDATE ) {
782 $return['translated_base'] = $base_translations[ $language ]['value'];
783 $return['needs_update '] = true;
784 }
785 }
786
787 return $return;
788
789 }
790
791 private function get_endpoint_string_context() {
792 return self::get_endpoints_string_context();
793 }
794
795 public static function get_endpoints_string_context(): string {
796 return class_exists( WPML_Endpoints_Support::class ) ? WPML_Endpoints_Support::STRING_CONTEXT : self::WC_STRING_CONTEXT;
797 }
798
799 public function get_source_slug_language( $base ) {
800
801 if ( $base == 'shop' ) {
802 $source_language = $this->sitepress->get_language_for_element( wc_get_page_id( 'shop' ), 'post_page' );
803 } elseif ( in_array( $base, [ 'product', WCTaxonomies::TAXONOMY_PRODUCT_CATEGORY, WCTaxonomies::TAXONOMY_PRODUCT_TAG, 'attribute' ] ) ) {
804 $source_language = $this->woocommerce_wpml->strings->get_string_language( $base, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $base ) );
805 } elseif ( strpos( $base, 'attribute_slug-' ) === 0 ) {
806 $slug = preg_replace( '#^attribute_slug-#', '', $base );
807 $source_language = $this->woocommerce_wpml->strings->get_string_language( $base, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute_slug', $slug ) );
808 } else {
809 $source_language = $this->woocommerce_wpml->strings->get_string_language( $base, $this->get_endpoint_string_context(), $base );
810 }
811
812 return $source_language;
813 }
814
815 public function wcml_update_base_translation() {
816
817 $nonce = filter_input( INPUT_POST, 'wcml_nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
818 if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wcml_update_base_translation' ) ) {
819 die( 'Invalid nonce' );
820 }
821
822 $original_base = $_POST['base'];
823 $original_base_value = $_POST['base_value'];
824 $base_translation = wc_sanitize_permalink( trim( $_POST['base_translation'], '/' ) );
825 $language = $_POST['language'];
826
827 if ( $original_base == 'shop' ) {
828 $original_shop_id = wc_get_page_id( 'shop' );
829 $translated_shop_id = apply_filters( 'wpml_object_id', $original_shop_id, 'page', false, $language );
830
831 if ( ! is_null( $translated_shop_id ) ) {
832
833 $trnsl_shop_obj = get_post( $translated_shop_id );
834 $new_slug = wp_unique_post_slug( $base_translation, $translated_shop_id, $trnsl_shop_obj->post_status, $trnsl_shop_obj->post_type, $trnsl_shop_obj->post_parent );
835 $this->wpdb->update( $this->wpdb->posts, [ 'post_name' => $new_slug ], [ 'ID' => $translated_shop_id ] );
836
837 }
838 } else {
839 if ( in_array( $original_base, [ 'product', WCTaxonomies::TAXONOMY_PRODUCT_CATEGORY, WCTaxonomies::TAXONOMY_PRODUCT_TAG, 'attribute' ] ) ) {
840 $string_id = icl_get_string_id( $original_base_value, Strings::TRANSLATION_DOMAIN, Strings::getStringName( $original_base ) );
841 } elseif ( strpos( $original_base, 'attribute_slug-' ) === 0 ) {
842 $slug = preg_replace( '#^attribute_slug-#', '', $original_base );
843 do_action( 'wpml_register_single_string', Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute_slug', $slug ), $slug );
844 $string_id = icl_get_string_id( $original_base_value, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'attribute_slug', $slug ) );
845 } else {
846 $string_id = icl_get_string_id( $original_base_value, $this->get_endpoint_string_context(), $original_base );
847 if ( ! $string_id && function_exists( 'icl_register_string' ) ) {
848 $string_id = icl_register_string( $this->get_endpoint_string_context(), $original_base, $original_base_value );
849 }
850 }
851
852 icl_add_string_translation( $string_id, $language, $base_translation, ICL_STRING_TRANSLATION_COMPLETE );
853 }
854
855 if ( in_array( $original_base, [ WCTaxonomies::TAXONOMY_PRODUCT_CATEGORY, WCTaxonomies::TAXONOMY_PRODUCT_TAG ], true ) ) {
856 do_action( 'wpml_activate_slug_translation', $original_base, $original_base_value, WPML_Slug_Translation_Factory::TAX );
857 }
858
859 $edit_base = new WCML_Store_URLs_Edit_Base_UI( $original_base, $language, $this->woocommerce_wpml, $this->sitepress );
860 $html = $edit_base->get_view();
861
862 echo json_encode( $html );
863 die();
864
865 }
866
867 public function check_wc_tax_url_on_redirect( $redirect_url, $requested_url ) {
868 global $wp_query;
869
870 if ( is_tax() ) {
871 $original = @parse_url( $requested_url );
872 if ( isset( $original['query'] ) ) {
873 parse_str( $original['query'], $query_args );
874 if ( ( isset( $query_args['product_cat'] ) || isset( $query_args['product_tag'] ) ) && isset( $query_args['lang'] ) ) {
875 $obj = $wp_query->get_queried_object();
876 $tax_url = get_term_link( (int) $obj->term_id, $obj->taxonomy );
877
878 return $tax_url;
879 }
880 }
881 }
882
883 return $redirect_url;
884 }
885
886 public function translate_query_var_for_product( $public_query_vars ) {
887
888 $product_permalink = $this->woocommerce_wpml->strings->product_permalink_slug();
889 $string_language = $this->woocommerce_wpml->strings->get_string_language( $product_permalink, Strings::TRANSLATION_DOMAIN, Strings::getStringName( 'product' ) );
890
891 if ( $this->sitepress->get_current_language() != $string_language ) {
892 $translated_slug = $this->get_translated_product_base_by_lang( false, $product_permalink );
893
894 if ( isset( $_GET[ $translated_slug ] ) ) {
895 $buff = $_GET[ $translated_slug ];
896 unset( $_GET[ $translated_slug ] );
897 $_GET[ $product_permalink ] = $buff;
898 }
899 }
900
901 return $public_query_vars;
902 }
903
904 public function maybe_remove_query_vars_filter() {
905 if ( ! is_plugin_active( basename( $this->sitepress->get_wp_api()->constant( 'WPML_ST_PATH' ) ) . '/plugin.php' ) ) {
906 remove_filter( 'query_vars', [ $this, 'translate_query_var_for_product' ] );
907 }
908 }
909
910 public function get_translated_product_base_by_lang( $language = false, $product_slug = false ) {
911
912 if ( ! $language ) {
913 $language = $this->sitepress->get_current_language();
914 }
915
916 if ( ! $product_slug ) {
917 $product_slug = $this->woocommerce_wpml->strings->product_permalink_slug();
918 }
919
920 $translated_slug = apply_filters( 'wpml_get_translated_slug', $product_slug, 'product', $language );
921
922 return $translated_slug;
923 }
924
925 public function encode_shop_slug( $location ) {
926 if ( get_post_type( get_query_var( 'p' ) ) == 'product' ) {
927 $language = $this->sitepress->get_language_for_element( get_query_var( 'p' ), 'post_product' );
928 $base_slug = $this->get_translated_product_base_by_lang( $language );
929
930 $location = str_replace( $base_slug, implode( '/', array_map( 'rawurlencode', explode( '/', $base_slug ) ) ), $location );
931 }
932
933 return $location;
934 }
935
936 public function translate_product_post_type_link( $permalink, $post ) {
937
938 if ( 'product' !== $post->post_type || false === strpos( $permalink, '/uncategorized/' ) ) {
939 return $permalink;
940 }
941
942 $permalinks = wc_get_permalink_structure();
943
944 if ( preg_match( '`/(.+)(/%product_cat%)`', $permalinks['product_rewrite_slug'] ) ) {
945 $find = 'uncategorized';
946 $element_language = $this->sitepress->get_language_for_element( $post->ID, 'post_product' );
947 $replace = $this->woocommerce_wpml->strings->get_translation_from_woocommerce_mo_file( 'slug' . chr( 4 ) . $find, $element_language, false );
948
949 if ( ! is_null( $replace ) ) {
950 $permalink = str_replace( $find, $replace, $permalink );
951 }
952 }
953
954 return $permalink;
955 }
956
957 public function translate_product_post_type_link_product_cat_when_display_as_translated( $primary_term, $terms, $post ) {
958 if ( 'product' === $post->post_type && WCTaxonomies::isProductCategory( $primary_term->taxonomy ) ) {
959 $translated_term_id = apply_filters( 'wpml_object_id', $primary_term->term_id, $primary_term->taxonomy, true );
960
961 return get_term( $translated_term_id, $primary_term->taxonomy );
962 }
963
964 return $primary_term;
965 }
966
967 }
968