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 / classes / Synchronization / Hooks.php
woocommerce-multilingual / classes / Synchronization Last commit date
Component 1 week ago Hooks.php 1 week ago Manager.php 1 week ago Store.php 1 week ago
Hooks.php
379 lines
1 <?php
2
3 namespace WCML\Synchronization;
4
5 use WCML\Utilities\SyncHash;
6 use WPML\FP\Obj;
7 use function WCML\functions\isCli;
8
9 class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action {
10
11 const HOOK_SYNCHRONIZE_PRODUCT_TRANSLATIONS = 'wcml_synchronize_product_translations';
12 const HOOK_SYNCHRONIZE_PRODUCT_VARIATION_TRANSLATIONS = 'wcml_synchronize_product_variation_translations';
13 const HOOK_SYNCHRONIZE_PRODUCT_COMPONENT = 'wcml_synchronize_product_component';
14
15 const PRIORITY_BEFORE_STOCK_EMAIL_TRIGGER = 9;
16
17 protected $woocommerceWpml;
18
19 protected $sitepress;
20
21 private $manager;
22
23 public function __construct(
24 \woocommerce_wpml $woocommerceWpml,
25 \SitePress $sitepress,
26 \wpdb $wpdb,
27 SyncHash $syncHashManager
28 ) {
29 $this->woocommerceWpml = $woocommerceWpml;
30 $this->sitepress = $sitepress;
31 $this->manager = new Manager(
32 new Store(
33 $woocommerceWpml,
34 $sitepress,
35 $wpdb,
36 $syncHashManager
37 )
38 );
39 }
40
41 public function add_hooks() {
42 if ( is_admin() || isCli() ) {
43 add_action( 'save_post', [ $this, 'synchronizeProductTranslationsOnSave' ], PHP_INT_MAX, 2 );
44 add_action( 'icl_make_duplicate', [ $this, 'synchronizeProductDuplication' ], 110, 4 );
45
46 add_action( 'woocommerce_product_quick_edit_save', [ $this, 'synchronizeOnEditSave' ] );
47 add_action( 'woocommerce_product_bulk_edit_save', [ $this, 'synchronizeOnEditSave' ] );
48
49 add_action( 'wpml_translation_update', [ $this, 'synchronizeConnectedTranslations' ] );
50 }
51
52 if ( is_admin() || wpml_is_rest_request() ) {
53 add_action( 'wpml_pro_translation_completed', [ $this, 'synchronizeProductTranslation' ] );
54 }
55
56 add_action( self::HOOK_SYNCHRONIZE_PRODUCT_TRANSLATIONS, [ $this, 'synchronizeProductTranslations' ], 10, 3 );
57 add_action( self::HOOK_SYNCHRONIZE_PRODUCT_VARIATION_TRANSLATIONS, [ $this, 'synchronizeProductVariationTranslations' ], 10, 3 );
58 add_action( self::HOOK_SYNCHRONIZE_PRODUCT_COMPONENT, [ $this, 'synchronizeProductComponent' ], 10, 4 );
59
60 add_action( 'woocommerce_ajax_save_product_variations', [ $this, 'synchronizeProductVariationsOnAjax' ], 11 );
61 add_action( 'woocommerce_bulk_edit_variations', [ $this, 'synchronizeProductVariationsOnBulkEdit' ], 10, 3 );
62
63 add_action( 'woocommerce_product_set_stock', [ $this, 'syncProductStock' ], self::PRIORITY_BEFORE_STOCK_EMAIL_TRIGGER );
64 add_action( 'woocommerce_variation_set_stock', [ $this, 'syncProductStock' ], self::PRIORITY_BEFORE_STOCK_EMAIL_TRIGGER );
65 }
66
67 private function canRunProductSynchronization( $post ) {
68 if ( 'product' !== $post->post_type ) {
69 return false;
70 }
71
72 if ( 'auto-draft' === $post->post_status ) {
73 return false;
74 }
75
76 if ( isset( $_POST['autosave'] ) ) {
77 return false;
78 }
79
80 if ( isset( $_GET['action'] ) && 'trash' === $_GET['action'] ) {
81 return false;
82 }
83
84 return true;
85 }
86
87 private function isProductSynchronizationValidContext() {
88 global $pagenow, $wp;
89 $isDuplicating = ( ! empty( $_POST['icl_ajx_action'] ) && 'make_duplicates' === $_POST['icl_ajx_action'] );
90 $isApiRequest = ! empty( $wp->query_vars['wc-api-version'] );
91 $isValidContext = isCli()
92 || $isDuplicating
93 || $isApiRequest
94 || in_array( $pagenow, [ 'post.php', 'post-new.php', 'admin.php' ], true );
95
96 return apply_filters( 'wcml_product_synchronization_on_save_is_valid_context', $isValidContext );
97 }
98
99 public function synchronizeProductTranslationsOnSave( $postId, $post ) {
100 if ( 'product_variation' === $post->post_type ) {
101 $this->setVariationLanguageDetails( $postId, $post );
102 return;
103 }
104
105 if ( ! $this->canRunProductSynchronization( $post ) ) {
106 return;
107 }
108 if ( ! $this->isProductSynchronizationValidContext() ) {
109 return;
110 }
111
112 $originalProduct = $this->manager->getOriginalProduct( $post );
113 if ( $this->woocommerceWpml->is_wpml_prior_4_2() ) {
114 $is_using_native_editor = ! $this->woocommerceWpml->settings['trnsl_interface'];
115 } else {
116 $is_using_native_editor = ! \WPML_TM_Post_Edit_TM_Editor_Mode::is_using_tm_editor( $this->sitepress, $originalProduct->ID );
117 }
118
119 if ( $is_using_native_editor ) {
120 $this->synchronizeProductTranslationsOnSaveInNativeEditor( $postId, $post );
121 return;
122 }
123
124 $this->manager->setContext( $this->getContext() );
125 $this->manager->run( $post );
126 $this->manager->setContext( null );
127 }
128
129 private function getContext() {
130 $isUpdatingProductFromEditScreen = isset( $_POST['action'] ) && 'editpost' === sanitize_key( wp_unslash( $_POST['action'] ) );
131
132 if ( $isUpdatingProductFromEditScreen ) {
133 return Manager::CONTEXT_PRODUCT_EDIT_SCREEN_UPDATE;
134 }
135
136 return null;
137 }
138
139 private function setVariationLanguageDetails( $variationId, $variation ) {
140 $productId = (int) $variation->post_parent;
141 $productLanguage = $this->sitepress->get_language_for_element( $productId, 'post_product' );
142 if ( ! $productLanguage ) {
143 return;
144 }
145 $productInOriginalLanguage = $this->woocommerceWpml->products->is_original_product( $productId );
146 if ( ! $productInOriginalLanguage ) {
147 return;
148 }
149 $variationLanguageDetails = $this->sitepress->get_element_language_details( $variationId, 'post_product_variation' );
150 if ( ! is_object( $variationLanguageDetails ) ) {
151 $this->sitepress->set_element_language_details( $variationId, 'post_product_variation', null, $productLanguage );
152 }
153 }
154
155 private function synchronizeProductTranslationsOnSaveInNativeEditor( $postId, $post ) {
156 $originalProduct = $this->manager->getOriginalProduct( $post );
157 if ( $originalProduct->ID === $postId ) {
158 $this->manager->run( $post );
159 return;
160 }
161
162 $originalLanguage = $this->manager->getElementLanguage( $originalProduct->ID );
163 $currentLanguage = $this->sitepress->get_current_language();
164
165 if ( $originalLanguage === $currentLanguage ) {
166 $this->manager->run( $post );
167 return;
168 }
169
170 if ( ! empty( $_POST['wp-preview'] ) ) {
171 return;
172 }
173
174 $postId = apply_filters( 'wpml_object_id', $postId, 'product', false, $currentLanguage );
175
176 $this->manager->run( $originalProduct, [ $postId ], [ $postId => $currentLanguage ] );
177 }
178
179 public function synchronizeProductDuplication( $productId, $language, $duplicatedPostData, $duplicatedProductId ) {
180 if ( 'product' !== $duplicatedPostData['post_type'] ) {
181 return;
182 }
183
184 global $iclTranslationManagement;
185 $customFieldSettings = $iclTranslationManagement->settings['custom_fields_translation'];
186 $variationDescriptionFieldSetting = Obj::prop( '_variation_description', $customFieldSettings );
187
188 $iclTranslationManagement->settings['custom_fields_translation']['_variation_description'] = WPML_COPY_CUSTOM_FIELD;
189
190 $product = get_post( $productId );
191 $originalProduct = $this->manager->getOriginalProduct( $product );
192
193 $this->manager->runProductComponents( $originalProduct, [ $duplicatedProductId ], [ $duplicatedProductId => $language ] );
194
195 if ( null === $variationDescriptionFieldSetting ) {
196 unset( $iclTranslationManagement->settings['custom_fields_translation']['_variation_description'] );
197 } else {
198 $iclTranslationManagement->settings['custom_fields_translation']['_variation_description'] = $variationDescriptionFieldSetting;
199 }
200 }
201
202 public function synchronizeProductTranslation( $translatedProductId ) {
203 if ( 'product' !== get_post_type( $translatedProductId ) ) {
204 return;
205 }
206
207 $translatedProduct = get_post( $translatedProductId );
208 $originalProduct = $this->manager->getOriginalProduct( $translatedProduct );
209
210 if ( $originalProduct ) {
211 $translationsLanguages = [
212 $translatedProductId => $this->manager->getElementLanguage( $translatedProductId ),
213 ];
214 $this->manager->runProductComponents( $originalProduct, [ $translatedProductId ], $translationsLanguages );
215 }
216 }
217
218 public function synchronizeConnectedTranslations() {
219 if ( 'connect_translations' !== Obj::prop( 'icl_ajx_action', $_POST ) ) {
220 return;
221 }
222
223 $postType = Obj::prop( 'post_type', $_POST );
224 if ( 'product' !== $postType ) {
225 return;
226 }
227
228 $newTrid = Obj::prop( 'new_trid', $_POST );
229 $translations = $this->sitepress->get_element_translations( $newTrid, 'post_' . $postType );
230 if ( ! $translations ) {
231 return;
232 }
233
234 $postId = Obj::prop( 'post_id', $_POST );
235 $setAsSource = Obj::prop( 'set_as_source', $_POST );
236
237 remove_action( 'wpml_translation_update', [ $this, 'synchronizeConnectedTranslations' ] );
238 foreach ( $translations as $translation ) {
239 if ( $setAsSource && ! $translation->original ) {
240 $productId = $postId;
241 $translatedProductId = $translation->element_id;
242 $language = $translation->language_code;
243 break;
244 } elseif ( ! $setAsSource && $translation->original ) {
245 $productId = $translation->element_id;
246 $translatedProductId = $postId;
247 $language = $this->sitepress->get_current_language();
248 break;
249 }
250 }
251
252 if ( isset( $productId, $translatedProductId, $language ) ) {
253 $product = get_post( $productId );
254 $this->manager->runProductComponents( $product, [ $translatedProductId ], [ $translatedProductId => $language ] );
255 $this->sitepress->copy_custom_fields( $productId, $translatedProductId );
256 $this->woocommerceWpml->translation_editor->create_product_translation_package( $productId, $newTrid, $language, ICL_TM_COMPLETE );
257 }
258 add_action( 'wpml_translation_update', [ $this, 'synchronizeConnectedTranslations' ] );
259 }
260
261 public function synchronizeOnEditSave( $productObject ) {
262 $productId = $productObject->get_id();
263 $product = get_post( $productId );
264 $isOriginal = $this->manager->isOriginalProduct( $product );
265 $originalProduct = $this->manager->getOriginalProduct( $product );
266 $translations = $this->manager->getElementTranslations( $productId );
267
268 if ( ! $translations ) {
269 return;
270 }
271
272 $this->manager->setContext( Manager::CONTEXT_PRODUCT_BULK_OR_QUICK_EDIT );
273
274 if ( ! $isOriginal ) {
275 $language = $this->manager->getElementLanguage( $productId );
276 $this->manager->runProductComponents( $originalProduct, [ $productId ], [ $productId => $language ] );
277 } else {
278 $translationsLanguages = [];
279 foreach ( $translations as $index => $translation ) {
280 if ( $productId === (int) $translation ) {
281 unset( $translations[ $index ] );
282 } else {
283 $translationsLanguages[ $translation ] = $this->manager->getElementLanguage( $translation );
284 }
285 }
286
287 if ( ! empty( $translations ) ) {
288 $this->manager->runProductComponents( $originalProduct, $translations, $translationsLanguages );
289 }
290 }
291
292 $this->manager->setContext( null );
293 }
294
295 public function synchronizeProductTranslations( $product, $translationsIds = [], $translationsLanguages = [] ) {
296 if ( ! $this->canRunProductSynchronization( $product ) ) {
297 return;
298 }
299 $this->manager->run( $product, $translationsIds, $translationsLanguages );
300 }
301
302 public function synchronizeProductVariationTranslations( $variation, $variationTranslations, $variationTranslationsLanguages ) {
303 $this->manager->runProductVariationComponents( $variation, $variationTranslations, $variationTranslationsLanguages );
304 }
305
306 public function synchronizeProductComponent( $product, $translationsIds, $translationsLanguages, $componentName ) {
307 if ( ! $this->canRunProductSynchronization( $product ) ) {
308 return;
309 }
310 $this->manager->runComponent( $product, $translationsIds, $translationsLanguages, $componentName );
311 }
312
313 public function synchronizeProductVariationsOnAjax( $productId ) {
314 $product = get_post( $productId );
315 $isOriginal = $this->manager->isOriginalProduct( $product );
316
317 if ( ! $isOriginal ) {
318 return;
319 }
320
321 $translations = $this->manager->getElementTranslations( $productId );
322 if ( empty( $translations ) ) {
323 return;
324 }
325
326 $translationsLanguages = [];
327 foreach ( $translations as $index => $translation ) {
328 if ( $productId === (int) $translation ) {
329 unset( $translations[ $index ] );
330 } else {
331 $translationsLanguages[ $translation ] = $this->manager->getElementLanguage( $translation );
332 }
333 }
334
335 $this->manager->runComponent( $product, $translations, $translationsLanguages, Store::COMPONENT_VARIATIONS );
336 $this->manager->runComponent( $product, $translations, $translationsLanguages, Store::COMPONENT_ATTRIBUTES );
337 }
338
339 public function synchronizeProductVariationsOnBulkEdit( $bulkAction, $data, $productId ) {
340 $this->synchronizeProductVariationsOnAjax( $productId );
341 }
342
343 public function syncProductStock( $product ) {
344 $productId = $product->get_id();
345 $translations = $this->manager->getElementTranslations( $productId, false, false );
346 if ( empty( $translations ) ) {
347 return;
348 }
349
350 $translationsLanguages = [];
351 foreach ( $translations as $index => $translation ) {
352 if ( $productId === (int) $translation ) {
353 unset( $translations[ $index ] );
354 } else {
355 $translationsLanguages[ $translation ] = $this->manager->getElementLanguage( $translation );
356 }
357 }
358
359 $this->manager->runComponent( get_post( $productId ), $translations, $translationsLanguages, Store::COMPONENT_STOCK );
360
361 $wcml_data_store = wcml_product_data_store_cpt();
362 wp_cache_delete( $productId, 'post_meta' );
363 wp_cache_delete( 'product-' . $productId, 'products' );
364 delete_transient( 'wc_product_children_' . $productId );
365 $wcml_data_store->update_lookup_table_data( $productId );
366
367 foreach( $translations as $translation ) {
368 wp_cache_delete( $translation, 'post_meta' );
369 wp_cache_delete( 'product-' . $translation, 'products' );
370 delete_transient( 'wc_product_children_' . $translation );
371 $wcml_data_store->update_lookup_table_data( $translation );
372 }
373
374 delete_transient( 'wc_low_stock_count' );
375 delete_transient( 'wc_outofstock_count' );
376 }
377
378 }
379