PluginProbe ʕ •ᴥ•ʔ
WPML Multilingual & Multicurrency for WooCommerce / trunk
WPML Multilingual & Multicurrency for WooCommerce vtrunk
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 / Component / Variations.php
woocommerce-multilingual / classes / Synchronization / Component Last commit date
Attachments.php 1 year ago Attributes.php 4 months ago DownloadableFiles.php 4 months ago Linked.php 10 months ago Meta.php 1 year ago Post.php 3 months ago Stock.php 1 year ago Synchronizer.php 1 year ago SynchronizerForMeta.php 9 months ago Taxonomies.php 4 months ago VariationAttachments.php 1 year ago VariationMeta.php 8 months ago VariationTaxonomies.php 8 months ago Variations.php 1 month ago
Variations.php
258 lines
1 <?php
2
3 namespace WCML\Synchronization\Component;
4
5 use WCML\Synchronization\Hooks;
6 use WCML\Utilities\DB;
7 use WCML\Utilities\SyncHash;
8 use WPML\FP\Obj;
9
10 class Variations extends SynchronizerForMeta {
11
12 /**
13 * @param \WP_Post $product
14 * @param int[] $translationsIds
15 * @param array<int,string> $translationsLanguages
16 */
17 public function run( $product, $translationsIds, $translationsLanguages ) {
18 $isVariableProduct = $this->woocommerceWpml->products->is_variable_product( $product->ID );
19 if ( ! $isVariableProduct ) {
20 return;
21 }
22
23 $productsIds = array_merge( [ $product->ID ], $translationsIds );
24 $storedVariations = [];
25 $storedRawVariations = $this->wpdb->get_results(
26 $this->wpdb->prepare(
27 "
28 SELECT * FROM {$this->wpdb->posts}
29 WHERE post_status IN ('publish','private')
30 AND post_type = %s
31 AND post_parent IN (" . DB::prepareIn( $productsIds ) . ")
32 ",
33 'product_variation'
34 )
35 );
36 foreach ( $storedRawVariations as $rawVariation ) {
37 $storedVariations[ $rawVariation->post_parent ][ $rawVariation->ID ] = $rawVariation;
38 }
39
40 $productVariations = $storedVariations[ $product->ID ] ?? null;
41 if ( ! $productVariations ) {
42 return;
43 }
44
45 // Editor-scoped sync mode: narrow the iteration to the variations the editor
46 // actually saved during this request. In Complete sync mode (default) this
47 // filter passes through unchanged. See classes/EditorScopedSync/SyncGate.php.
48 $editorScopedIds = apply_filters( 'wcml_editor_scoped_variation_ids', null, $product->ID );
49 if ( is_array( $editorScopedIds ) ) {
50 if ( empty( $editorScopedIds ) ) {
51 return;
52 }
53
54 $productVariations = array_intersect_key( $productVariations, array_flip( $editorScopedIds ) );
55 }
56
57 $variationsTranslations = [];
58 $preparedVariationsToSetAsDuplication = [];
59
60 remove_action( 'save_post', [ $this->elementTranslations, 'save_post_actions' ], 100 );
61
62 foreach ( $productVariations as $productVariation ) {
63 foreach ( $translationsLanguages as $translationId => $language ) {
64 $variationTranslationId = $this->elementTranslations->element_id_in( $productVariation->ID, $language, false );
65 if ( $variationTranslationId ) {
66 $this->wpdb->update(
67 $this->wpdb->posts,
68 [
69 'post_status' => $productVariation->post_status,
70 'post_modified' => $productVariation->post_modified,
71 'post_modified_gmt' => $productVariation->post_modified_gmt,
72 'post_parent' => $translationId,// This should be already set! We can try and see... and update all other post_* and menu_order at once.
73 'menu_order' => $productVariation->menu_order,
74 ],
75 [ 'ID' => $variationTranslationId ]
76 );
77 unset( $storedVariations[ $translationId ][ $variationTranslationId ] );
78 $this->syncHashManager->initialize( $variationTranslationId, SyncHash::SOURCE_META );
79 } else {
80 $translationGui = str_replace( (string) $product->ID, (string) $translationId, $productVariation->guid );
81 $translationSlug = str_replace( (string) $product->ID, (string) $translationId, $productVariation->post_name );
82 $variationTranslationId = wp_insert_post(
83 [
84 'post_author' => $productVariation->post_author,
85 'post_date_gmt' => $productVariation->post_date_gmt,
86 'post_content' => $productVariation->post_content,
87 'post_title' => $productVariation->post_title,
88 'post_excerpt' => $productVariation->post_excerpt,
89 'post_status' => $productVariation->post_status,
90 'comment_status' => $productVariation->comment_status,
91 'ping_status' => $productVariation->ping_status,
92 'post_password' => $productVariation->post_password,
93 'post_name' => $translationSlug,
94 'to_ping' => $productVariation->to_ping,
95 'pinged' => $productVariation->pinged,
96 'post_modified' => $productVariation->post_modified,
97 'post_modified_gmt' => $productVariation->post_modified_gmt,
98 'post_content_filtered' => $productVariation->post_content_filtered,
99 'post_parent' => $translationId,
100 'guid' => $translationGui,
101 'menu_order' => $productVariation->menu_order,
102 'post_type' => $productVariation->post_type,
103 'post_mime_type' => $productVariation->post_mime_type,
104 'comment_count' => $productVariation->comment_count,
105 ]
106 );
107 // Set language details and connection for the new variation translation.
108 $trid = $this->sitepress->get_element_trid( $productVariation->ID, 'post_product_variation' );
109 $this->sitepress->set_element_language_details( $variationTranslationId, 'post_product_variation', $trid, $language );
110 // Declare that the new variation translation is a duplicate of the product variation.
111 $preparedVariationsToSetAsDuplication[] = $this->wpdb->prepare( "(%d,%s,%s)", $variationTranslationId, '_wcml_duplicate_of_variation', $productVariation->ID );
112 $this->syncHashManager->initialize( $variationTranslationId, SyncHash::SOURCE_EMPTY, true );
113 }
114 $variationsTranslations[ $productVariation->ID ][ $variationTranslationId ] = $language;
115 }
116 }
117
118 if ( ! empty( $preparedVariationsToSetAsDuplication ) ) {
119 $this->wpdb->query(
120 "INSERT INTO {$this->wpdb->postmeta}
121 (`post_id`,`meta_key`,`meta_value`)
122 VALUES " . implode( ',', $preparedVariationsToSetAsDuplication )
123 );
124 }
125
126 if ( ! $editorScopedIds ) {
127 foreach ( $translationsIds as $translationId ) {
128 $orphanedTranslationVariations = $storedVariations[ $translationId ] ?? [];
129 foreach ( $orphanedTranslationVariations as $orphanedTranslationVariationId => $orphanedTranslationVariation ) {
130 wp_delete_post( $orphanedTranslationVariationId, true );
131 }
132 }
133 }
134
135 $this->removeOrphanedVariationAttributes( $product->ID, array_keys( $productVariations ) );
136 $this->synchronizeMinMaxPrices( $product->ID, $translationsLanguages, $variationsTranslations );
137
138 $wcmlProductDataStore = wcml_product_data_store_cpt();
139 foreach ( $variationsTranslations as $variationId => $variationTranslationsLanguages ) {
140 $variationTranslations = array_keys( $variationTranslationsLanguages );
141 do_action( Hooks::HOOK_SYNCHRONIZE_PRODUCT_VARIATION_TRANSLATIONS, $productVariations[ $variationId ], $variationTranslations, $variationTranslationsLanguages );
142 foreach ( $variationTranslations as $variationTranslationId ) {
143 // NOTE This is still potentially expensive.
144 //$wcmlProductDataStore->update_lookup_table_data( $variationTranslationId );
145 $this->syncHashManager->saveHash( $variationTranslationId, true );
146 }
147 }
148
149 add_action( 'save_post', [ $this->elementTranslations, 'save_post_actions' ], 100, 2 );
150 }
151
152 /**
153 * @param int $productId
154 * @param int[] $productVariations
155 */
156 private function removeOrphanedVariationAttributes( $productId, $productVariations ) {
157 $productAttributes = get_post_meta( $productId, '_product_attributes', true );
158 $variationsAttributes = $this->wpdb->get_results(
159 $this->wpdb->prepare(
160 "
161 SELECT meta_id, meta_key
162 FROM {$this->wpdb->postmeta}
163 WHERE meta_key LIKE %s
164 AND post_id IN (" . DB::prepareIn( $productVariations ) . ")
165 ",
166 'attribute_%%'
167 ),
168 OBJECT_K
169 );
170
171 $metaIdsToDelete = [];
172 foreach ( $variationsAttributes as $variationsAttribute ) {
173 $attributeName = substr( $variationsAttribute->meta_key, 10 );
174 if ( ! isset( $productAttributes[ $attributeName ] ) ) {
175 $metaIdsToDelete[] = $variationsAttribute->meta_id;
176 }
177 }
178
179 if ( empty( $metaIdsToDelete ) ) {
180 return;
181 }
182
183 $this->deleteMetaByIds( $metaIdsToDelete );
184 }
185
186 /**
187 * @param int $productId
188 * @param array<int,string> $translationsLanguages
189 * @param array<int,array> $variationsTranslations
190 */
191 private function synchronizeMinMaxPrices( $productId, $translationsLanguages, $variationsTranslations ) {
192 $productsIds = array_merge( [ $productId ], array_keys( $translationsLanguages ) );
193
194 $storedRawData = $this->wpdb->get_results(
195 $this->wpdb->prepare(
196 "
197 SELECT post_id, meta_key, meta_value
198 FROM {$this->wpdb->postmeta}
199 WHERE meta_key IN (
200 '_min_price_variation_id',
201 '_max_price_variation_id',
202 '_min_regular_price_variation_id',
203 '_max_regular_price_variation_id',
204 '_min_sale_price_variation_id',
205 '_max_sale_price_variation_id'
206 )
207 AND post_id IN (" . DB::prepareIn( $productsIds ) . ")
208 LIMIT %d
209 ",
210 count( $productsIds ) * 6
211 )
212 );
213
214 $storedData = [];
215 foreach ( $storedRawData as $rawData ) {
216 $storedData[ $rawData->post_id ][ $rawData->meta_key ] = $rawData->meta_value;
217 }
218
219 $productMinMaxVariationsData = $storedData[ $productId ] ?? null;
220
221 if ( null === $productMinMaxVariationsData ) {
222 return;
223 }
224
225 $metaToInsert = [];
226 $metaToUpdate = [];
227 foreach ( $translationsLanguages as $translationId => $language ) {
228 $translationMinMaxVariationsData = $storedData[ $translationId ] ?? [];
229 foreach ( $productMinMaxVariationsData as $minMaxKey => $minMaxVariationId ) {
230 $minMaxVariationTranslations = $variationsTranslations[ $minMaxVariationId ] ?? [];
231 $minMaxVariationTranslationsByLanguage = array_flip( $minMaxVariationTranslations );
232 if ( ! Obj::prop( $language, $minMaxVariationTranslationsByLanguage ) ) {
233 continue;
234 }
235 $translationMinMaxVariationId = $translationMinMaxVariationsData[ $minMaxKey ] ?? null;
236 if ( null === $translationMinMaxVariationId ) {
237 $metaToInsert[ $minMaxKey ][ $translationId ] = Obj::prop( $language, $minMaxVariationTranslationsByLanguage );
238 continue;
239 }
240
241 if ( $language === Obj::prop( $translationMinMaxVariationId, $minMaxVariationTranslations ) ) {
242 continue;
243 }
244 $metaToUpdate[ $minMaxKey ][ $translationId ] = Obj::prop( $language, $minMaxVariationTranslationsByLanguage );
245 }
246 }
247
248 foreach ( $metaToInsert as $metaKey => $metaData ) {
249 $this->insertMeta( $metaKey, $metaData );
250 }
251
252 foreach ( $metaToUpdate as $metaKey => $metaData ) {
253 $this->updateMeta( $metaKey, $metaData );
254 }
255 }
256
257 }
258