admin-menus
2 weeks ago
currencies
2 weeks ago
template-classes
2 weeks ago
translation-editor
2 weeks ago
class-wcml-ajax-setup.php
2 weeks ago
class-wcml-attributes.php
2 weeks ago
class-wcml-capabilities.php
2 weeks ago
class-wcml-cart-removed-items-widget.php
1 year ago
class-wcml-cart-switch-lang-functions.php
2 weeks ago
class-wcml-cart-sync-warnings.php
2 weeks ago
class-wcml-cart.php
2 weeks ago
class-wcml-comments.php
2 weeks ago
class-wcml-compatibility.php
2 weeks ago
class-wcml-coupons.php
2 weeks ago
class-wcml-dependencies.php
2 weeks ago
class-wcml-emails.php
2 weeks ago
class-wcml-endpoints.php
2 weeks ago
class-wcml-install.php
2 weeks ago
class-wcml-languages-upgrader.php
2 weeks ago
class-wcml-locale.php
2 weeks ago
class-wcml-orders.php
2 weeks ago
class-wcml-products-screen-options.php
2 weeks ago
class-wcml-products.php
2 weeks ago
class-wcml-reports.php
2 weeks ago
class-wcml-requests.php
2 weeks ago
class-wcml-resources.php
2 weeks ago
class-wcml-store-pages.php
2 weeks ago
class-wcml-terms.php
2 weeks ago
class-wcml-tp-support.php
2 weeks ago
class-wcml-troubleshooting.php
2 weeks ago
class-wcml-upgrade.php
2 weeks ago
class-wcml-url-translation.php
2 weeks ago
class-wcml-wc-gateways.php
2 weeks ago
class-wcml-wc-shipping.php
2 weeks ago
class-wcml-wc-strings.php
2 weeks ago
class-wcml-widgets.php
2 weeks ago
constants.php
2 weeks ago
functions-pure.php
2 weeks ago
functions.php
2 weeks ago
installer-loader.php
2 weeks ago
missing-php-functions.php
2 weeks ago
wcml-core-functions.php
2 weeks ago
wcml-switch-lang-request.php
2 weeks ago
class-wcml-tp-support.php
440 lines
| 1 | <?php |
| 2 | |
| 3 | use WCML\TranslationJob\Hooks; |
| 4 | use function WCML\functions\flushProductCachePrefixById; |
| 5 | |
| 6 | class WCML_TP_Support { |
| 7 | |
| 8 | const CUSTOM_FIELD_NAME = 'wc_variation_field:'; |
| 9 | |
| 10 | const DOWNLOADABLE_FILES_TRANSLATABLE_FIELDS = [ 'name', 'file' ]; |
| 11 | |
| 12 | const PRIORITY_SAVE_VARIATION_CUSTOM_FIELDS_TRANSLATION = 20; |
| 13 | |
| 14 | const PACKAGE_IMAGE_KEY_PREFIX = 'image-id-'; |
| 15 | |
| 16 | private $woocommerce_wpml; |
| 17 | private $wpdb; |
| 18 | private $tp; |
| 19 | private $tm_settings; |
| 20 | |
| 21 | public function __construct( woocommerce_wpml $woocommerce_wpml, wpdb $wpdb, WPML_Element_Translation_Package $tp, array $tm_settings ) { |
| 22 | $this->woocommerce_wpml = $woocommerce_wpml; |
| 23 | $this->wpdb = $wpdb; |
| 24 | $this->tp = $tp; |
| 25 | $this->tm_settings = $tm_settings; |
| 26 | } |
| 27 | |
| 28 | public function add_hooks() { |
| 29 | add_filter( 'wpml_tm_translation_job_data', [ |
| 30 | $this, |
| 31 | 'append_custom_attributes_to_translation_package' |
| 32 | ], 10, 2 ); |
| 33 | add_action( 'wpml_translation_job_saved', [ $this, 'save_custom_attribute_translations' ], 10, 3 ); |
| 34 | |
| 35 | add_filter( 'wpml_tm_translation_job_data', [ |
| 36 | $this, |
| 37 | 'append_variation_custom_fields_to_translation_package' |
| 38 | ], 10, 2 ); |
| 39 | |
| 40 | add_filter( 'wpml_tm_translation_job_data', [ |
| 41 | $this, |
| 42 | 'append_variation_downloadable_fields_to_translation_package' |
| 43 | ], 10, 2 ); |
| 44 | |
| 45 | add_filter( 'wpml_tm_translation_job_data', [ |
| 46 | $this, |
| 47 | 'append_simple_downloadable_fields_to_translation_package' |
| 48 | ], 10, 2 ); |
| 49 | |
| 50 | add_action( 'wpml_pro_translation_completed', [ |
| 51 | $this, |
| 52 | 'save_variation_custom_fields_translations' |
| 53 | ], self::PRIORITY_SAVE_VARIATION_CUSTOM_FIELDS_TRANSLATION, 3 ); |
| 54 | |
| 55 | add_action( |
| 56 | 'wpml_pro_translation_completed', |
| 57 | [ |
| 58 | $this, |
| 59 | 'flush_variable_product_cache_prefix', |
| 60 | ], |
| 61 | self::PRIORITY_SAVE_VARIATION_CUSTOM_FIELDS_TRANSLATION + 10, |
| 62 | 3 |
| 63 | ); |
| 64 | |
| 65 | add_filter( 'wpml_tm_translation_job_data', [ $this, 'append_images_to_translation_package' ], 10, 2 ); |
| 66 | add_action( 'wpml_translation_job_saved', [ $this, 'save_images_translations' ], 10, 3 ); |
| 67 | |
| 68 | add_filter( 'wpml_custom_field_settings_override_lock_render', [ $this, 'set_wpml_term_custom_field_thumbnail_id_as_read_only' ], 10, 2 ); |
| 69 | } |
| 70 | |
| 71 | public function set_wpml_term_custom_field_thumbnail_id_as_read_only( $override, $setting ) { |
| 72 | if ( 'thumbnail_id' === $setting->get_index() && ( $setting instanceof WPML_Term_Custom_Field_Setting ) ) { |
| 73 | if( WPML_COPY_CUSTOM_FIELD === $setting->status() ) { |
| 74 | $setting->make_read_only(); |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | return $override; |
| 79 | } |
| 80 | |
| 81 | public function append_custom_attributes_to_translation_package( $package, $post ) { |
| 82 | if ( $this->isWpPostAWcProduct( $post ) ) { |
| 83 | |
| 84 | $product = wc_get_product( $post->ID ); |
| 85 | |
| 86 | if ( ! empty( $product ) ) { |
| 87 | |
| 88 | $attributes = $product->get_attributes(); |
| 89 | |
| 90 | foreach ( $attributes as $attribute_key => $attribute ) { |
| 91 | |
| 92 | if ( $this->woocommerce_wpml->attributes->is_a_taxonomy( $attribute ) ) { |
| 93 | continue; |
| 94 | } |
| 95 | |
| 96 | $package['contents'][ 'wc_attribute_name:' . $attribute_key ] = [ |
| 97 | 'translate' => 1, |
| 98 | 'data' => $this->tp->encode_field_data( $attribute['name'] ), |
| 99 | 'format' => 'base64' |
| 100 | ]; |
| 101 | $values = explode( '|', $attribute['value'] ); |
| 102 | $values = array_map( 'trim', $values ); |
| 103 | |
| 104 | foreach ( $values as $value_key => $value ) { |
| 105 | $package['contents'][ 'wc_attribute_value:' . $value_key . ':' . $attribute_key ] = [ |
| 106 | 'translate' => 1, |
| 107 | 'data' => $this->tp->encode_field_data( $value ), |
| 108 | 'format' => 'base64' |
| 109 | ]; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | return $package; |
| 116 | } |
| 117 | |
| 118 | public function save_custom_attribute_translations( $post_id, $data, $job ) { |
| 119 | |
| 120 | $translated_attributes = []; |
| 121 | $translated_labels = $this->woocommerce_wpml->attributes->get_attr_label_translations( $post_id ); |
| 122 | |
| 123 | foreach ( $data as $value ) { |
| 124 | |
| 125 | if ( $value['finished'] && isset( $value['field_type'] ) && strpos( $value['field_type'], 'wc_attribute_' ) === 0 ) { |
| 126 | |
| 127 | if ( strpos( $value['field_type'], 'wc_attribute_name:' ) === 0 ) { |
| 128 | |
| 129 | $exp = explode( ':', $value['field_type'], 2 ); |
| 130 | $attribute_key = $exp[1]; |
| 131 | |
| 132 | $translated_attributes[ $attribute_key ]['name'] = $value['data']; |
| 133 | |
| 134 | } else if ( strpos( $value['field_type'], 'wc_attribute_value:' ) === 0 ) { |
| 135 | |
| 136 | $exp = explode( ':', $value['field_type'], 3 ); |
| 137 | $value_key = $exp[1]; |
| 138 | $attribute_key = $exp[2]; |
| 139 | |
| 140 | $translated_attributes[ $attribute_key ]['values'][ $value_key ] = $value['data']; |
| 141 | |
| 142 | } |
| 143 | |
| 144 | } |
| 145 | |
| 146 | } |
| 147 | |
| 148 | if ( $translated_attributes ) { |
| 149 | |
| 150 | $product_attributes = get_post_meta( $post_id, '_product_attributes', true ) ?: []; |
| 151 | |
| 152 | if( isset( $job->original_doc_id ) ){ |
| 153 | $original_post_id = $job->original_doc_id; |
| 154 | }else{ |
| 155 | $original_post_id = $this->woocommerce_wpml->products->get_original_product_id( $post_id ); |
| 156 | } |
| 157 | |
| 158 | $original_attributes = get_post_meta( $original_post_id, '_product_attributes', true ) ?: []; |
| 159 | |
| 160 | foreach ( $translated_attributes as $attribute_key => $attribute ) { |
| 161 | if( isset( $original_attributes[ $attribute_key ] ) ){ |
| 162 | $product_attributes[ $attribute_key ] = $original_attributes[ $attribute_key ]; |
| 163 | $product_attributes[ $attribute_key ]['name'] = $attribute['name']; |
| 164 | $product_attributes[ $attribute_key ]['value'] = isset( $attribute['values'] ) |
| 165 | ? join( ' | ', $attribute['values'] ) |
| 166 | : $product_attributes[ $attribute_key ]['value']; |
| 167 | |
| 168 | $translated_labels[ $job->language_code ][ $attribute_key ] = $attribute['name']; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | update_post_meta( $post_id, '_product_attributes', $product_attributes ); |
| 173 | update_post_meta( $post_id, 'attr_label_translations', $translated_labels ); |
| 174 | } |
| 175 | |
| 176 | } |
| 177 | |
| 178 | private function get_variation_custom_fields_to_translate( $variation_id ) { |
| 179 | $is_field_translatable = function ( $meta_key ) { |
| 180 | return isset( $this->tm_settings['custom_fields_translation'][ $meta_key ] ) |
| 181 | && (int) $this->tm_settings['custom_fields_translation'][ $meta_key ] === WPML_TRANSLATE_CUSTOM_FIELD; |
| 182 | }; |
| 183 | |
| 184 | return wpml_collect( (array) get_post_custom_keys( $variation_id ) ) |
| 185 | ->filter( $is_field_translatable ) |
| 186 | ->toArray(); |
| 187 | } |
| 188 | |
| 189 | public function append_variation_custom_fields_to_translation_package( $package, $post ) { |
| 190 | if ( $this->isWpPostAWcProduct( $post ) ) { |
| 191 | |
| 192 | $product = wc_get_product( $post->ID ); |
| 193 | |
| 194 | $allowed_variations_types = apply_filters( 'wcml_xliff_allowed_variations_types', [ 'variable' ] ); |
| 195 | |
| 196 | if ( $product instanceof WC_Product && in_array( $product->get_type(), $allowed_variations_types, true ) ) { |
| 197 | |
| 198 | $variations = $this->woocommerce_wpml->sync_variations_data->get_product_variations( $post->ID ); |
| 199 | |
| 200 | foreach ( $variations as $variation ) { |
| 201 | |
| 202 | $meta_keys_to_translate = $this->get_variation_custom_fields_to_translate( $variation->ID ); |
| 203 | |
| 204 | foreach ( $meta_keys_to_translate as $meta_key ){ |
| 205 | $meta_value = get_post_meta( $variation->ID, $meta_key, true ); |
| 206 | |
| 207 | if ( $meta_value && !is_array( $meta_value ) ) { |
| 208 | $package['contents'][ self::CUSTOM_FIELD_NAME.$meta_key.':' . $variation->ID ] = [ |
| 209 | 'translate' => 1, |
| 210 | 'data' => $this->tp->encode_field_data( $meta_value ), |
| 211 | 'format' => 'base64' |
| 212 | ]; |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | } |
| 218 | |
| 219 | } |
| 220 | |
| 221 | return $package; |
| 222 | |
| 223 | } |
| 224 | |
| 225 | public function append_variation_downloadable_fields_to_translation_package( $package, $post ) { |
| 226 | if ( $this->isWpPostAWcProduct( $post ) ) { |
| 227 | |
| 228 | $product = wc_get_product( $post->ID ); |
| 229 | |
| 230 | $allowed_variations_types = apply_filters( 'wcml_xliff_allowed_variations_types', [ 'variable' ] ); |
| 231 | |
| 232 | if ( $product instanceof WC_Product && in_array( $product->get_type(), $allowed_variations_types, true ) ) { |
| 233 | |
| 234 | $variations = $this->woocommerce_wpml->sync_variations_data->get_product_variations( $post->ID ); |
| 235 | |
| 236 | foreach ( $variations as $variation ) { |
| 237 | |
| 238 | $meta_keys_to_translate = $this->get_variation_custom_fields_to_translate( $variation->ID ); |
| 239 | |
| 240 | if ( ! in_array( WCML_Downloadable_Products::DOWNLOADABLE_FILES_META, $meta_keys_to_translate ) ) { |
| 241 | continue; |
| 242 | } |
| 243 | |
| 244 | $meta_value = get_post_meta( $variation->ID, WCML_Downloadable_Products::DOWNLOADABLE_FILES_META, true ); |
| 245 | |
| 246 | $fileNo = 0; |
| 247 | foreach ( $meta_value as $downloadableId => $downloadableFile ) { |
| 248 | foreach ( $downloadableFile as $name => $value ) { |
| 249 | if ( ! in_array( $name, self::DOWNLOADABLE_FILES_TRANSLATABLE_FIELDS ) ) { |
| 250 | continue; |
| 251 | } |
| 252 | |
| 253 | $productKey = WCML_Downloadable_Products::buildDownloadableFileField( $fileNo, $downloadableId, $name ); |
| 254 | $productVariantKey = sprintf( '%s:%d', $productKey, $variation->ID ); |
| 255 | |
| 256 | $package['contents'][ $productVariantKey ] = [ |
| 257 | 'translate' => 1, |
| 258 | 'data' => $this->tp->encode_field_data( $value ), |
| 259 | 'format' => 'base64' |
| 260 | ]; |
| 261 | } |
| 262 | $fileNo ++; |
| 263 | } |
| 264 | } |
| 265 | } |
| 266 | } |
| 267 | return $package; |
| 268 | } |
| 269 | |
| 270 | public function append_simple_downloadable_fields_to_translation_package( $package, $post ) { |
| 271 | if ( $this->isWpPostAWcProduct( $post ) ) { |
| 272 | if ( WCML_Downloadable_Products::isDownloadableFilesSetToUseSame( $post->ID ) ) { |
| 273 | return $package; |
| 274 | } |
| 275 | |
| 276 | $product = wc_get_product( $post->ID ); |
| 277 | |
| 278 | $allowed_types = [ 'simple' ]; |
| 279 | |
| 280 | if ( $product instanceof WC_Product && in_array( $product->get_type(), $allowed_types, true ) ) { |
| 281 | |
| 282 | $meta_value = get_post_meta( $post->ID, WCML_Downloadable_Products::DOWNLOADABLE_FILES_META, true ); |
| 283 | |
| 284 | if ( ! is_array( $meta_value ) ) { |
| 285 | return $package; |
| 286 | } |
| 287 | |
| 288 | $fileNo = 0; |
| 289 | foreach ( $meta_value as $downloadableId => $downloadableFile ) { |
| 290 | foreach ( $downloadableFile as $name => $value ) { |
| 291 | if ( ! in_array( $name, self::DOWNLOADABLE_FILES_TRANSLATABLE_FIELDS ) ) { |
| 292 | continue; |
| 293 | } |
| 294 | |
| 295 | $productKey = WCML_Downloadable_Products::buildDownloadableFileField( $fileNo, $downloadableId, $name ); |
| 296 | |
| 297 | $package['contents'][ $productKey ] = [ |
| 298 | 'translate' => 1, |
| 299 | 'data' => $this->tp->encode_field_data( $value ), |
| 300 | 'format' => 'base64' |
| 301 | ]; |
| 302 | } |
| 303 | $fileNo ++; |
| 304 | } |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | return $package; |
| 309 | } |
| 310 | |
| 311 | public function save_variation_custom_fields_translations( $post_id, $data, $job ) { |
| 312 | if ( ! Hooks::isProduct( $job ) ) { |
| 313 | return null; |
| 314 | } |
| 315 | |
| 316 | $language = $job->language_code; |
| 317 | |
| 318 | foreach ( $data as $value ) { |
| 319 | |
| 320 | if ( $value['finished'] && isset( $value['field_type'] ) && strpos( $value['field_type'], self::CUSTOM_FIELD_NAME ) === 0 ) { |
| 321 | |
| 322 | $exp = explode( ':', $value['field_type'], 3 ); |
| 323 | $meta_key = $exp[1]; |
| 324 | $variation_id = $exp[2]; |
| 325 | |
| 326 | if ( is_post_type_translated( 'product_variation' ) ) { |
| 327 | $translated_variation_id = apply_filters( 'wpml_object_id', $variation_id, 'product_variation', false, $language ); |
| 328 | } else { |
| 329 | global $wpml_post_translations; |
| 330 | $translations = $wpml_post_translations->get_element_translations( $variation_id ); |
| 331 | $translated_variation_id = $translations[ $language ] ?? false; |
| 332 | } |
| 333 | |
| 334 | if ( $translated_variation_id ) { |
| 335 | update_post_meta( $translated_variation_id, $meta_key, $value['data'] ); |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | } |
| 341 | |
| 342 | public function append_images_to_translation_package( $package, $post ) { |
| 343 | if ( $this->isWpPostAWcProduct( $post ) ) { |
| 344 | |
| 345 | $product_images = $this->woocommerce_wpml->media->product_images_ids( $post->ID ); |
| 346 | foreach ( $product_images as $image_id ) { |
| 347 | $attachment_data = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT post_title,post_excerpt,post_content FROM {$this->wpdb->posts} WHERE ID = %d", $image_id ) ); |
| 348 | if ( ! is_object( $attachment_data ) ) { |
| 349 | continue; |
| 350 | } |
| 351 | $alt_text = get_post_meta( $image_id, '_wp_attachment_image_alt', true ); |
| 352 | $alt_text = $alt_text ?: ''; |
| 353 | $this->add_to_package( $package, self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-title', $attachment_data->post_title ); |
| 354 | $this->add_to_package( $package, self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-caption', $attachment_data->post_excerpt ); |
| 355 | $this->add_to_package( $package, self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-description', $attachment_data->post_content ); |
| 356 | $this->add_to_package( $package, self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-alt-text', $alt_text ); |
| 357 | |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | return $package; |
| 362 | } |
| 363 | |
| 364 | public function save_images_translations( $post_id, $data, $job ) { |
| 365 | |
| 366 | $language = $job->language_code; |
| 367 | |
| 368 | $product_images = $this->woocommerce_wpml->media->product_images_ids( $job->original_doc_id ); |
| 369 | foreach ( $product_images as $image_id ) { |
| 370 | $translated_prod_image = apply_filters( 'wpml_object_id', $image_id, 'attachment', false, $language ); |
| 371 | $image_data = $this->get_image_data( $image_id, $data ); |
| 372 | if ( ! empty( $image_data ) ) { |
| 373 | |
| 374 | $translation = []; |
| 375 | if ( isset( $image_data['title'] ) ) { |
| 376 | $translation['post_title'] = $image_data['title']; |
| 377 | } |
| 378 | if ( isset( $image_data['description'] ) ) { |
| 379 | $translation['post_content'] = $image_data['description']; |
| 380 | } |
| 381 | if ( isset( $image_data['caption'] ) ) { |
| 382 | $translation['post_excerpt'] = $image_data['caption']; |
| 383 | } |
| 384 | |
| 385 | if ( $translation ) { |
| 386 | $this->wpdb->update( $this->wpdb->posts, $translation, [ 'id' => $translated_prod_image ] ); |
| 387 | } |
| 388 | |
| 389 | if ( isset( $image_data['alt-text'] ) ) { |
| 390 | update_post_meta( $translated_prod_image, '_wp_attachment_image_alt', $image_data['alt-text'] ); |
| 391 | } |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | private function isWpPostAWcProduct( $post ): bool { |
| 397 | return 'product' === $post->post_type; |
| 398 | } |
| 399 | |
| 400 | private function get_image_data( $image_id, $data ) { |
| 401 | $image_data = []; |
| 402 | |
| 403 | foreach ( $data as $value ) { |
| 404 | if ( $value['finished'] && isset( $value['field_type'] ) ) { |
| 405 | if ( strpos( $value['field_type'], self::PACKAGE_IMAGE_KEY_PREFIX . $image_id ) === 0 ) { |
| 406 | if ( $value['field_type'] === self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-title' ) { |
| 407 | $image_data['title'] = $value['data']; |
| 408 | } |
| 409 | if ( $value['field_type'] === self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-caption' ) { |
| 410 | $image_data['caption'] = $value['data']; |
| 411 | } |
| 412 | if ( $value['field_type'] === self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-description' ) { |
| 413 | $image_data['description'] = $value['data']; |
| 414 | } |
| 415 | if ( $value['field_type'] === self::PACKAGE_IMAGE_KEY_PREFIX . $image_id . '-alt-text' ) { |
| 416 | $image_data['alt-text'] = $value['data']; |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | return $image_data; |
| 423 | } |
| 424 | |
| 425 | private function add_to_package( &$package, $key, $data ) { |
| 426 | $package['contents'][ $key ] = [ |
| 427 | 'translate' => 1, |
| 428 | 'data' => $this->tp->encode_field_data( $data ), |
| 429 | 'format' => 'base64' |
| 430 | ]; |
| 431 | |
| 432 | } |
| 433 | |
| 434 | public function flush_variable_product_cache_prefix( $post_id, $data, $job ) { |
| 435 | if ( Hooks::isProduct( $job ) ) { |
| 436 | flushProductCachePrefixById( $post_id ); |
| 437 | } |
| 438 | } |
| 439 | } |
| 440 |