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
Attributes.php
240 lines
| 1 | <?php |
| 2 | |
| 3 | namespace WCML\Synchronization\Component; |
| 4 | |
| 5 | use WCML\Utilities\DB; |
| 6 | use WCML\Utilities\WCTaxonomies; |
| 7 | use WPML\FP\Lst; |
| 8 | use WPML\FP\Obj; |
| 9 | |
| 10 | class Attributes extends SynchronizerForMeta { |
| 11 | |
| 12 | const DEFAULT_ATTRIBUTES_META_KEY = '_default_attributes'; |
| 13 | const PRODUCT_ATTRIBUTES_META_KEY = '_product_attributes'; |
| 14 | |
| 15 | /** |
| 16 | * @param \WP_Post $product |
| 17 | * @param int[] $translationsIds |
| 18 | * @param array<int,string> $translationsLanguages |
| 19 | */ |
| 20 | public function run( $product, $translationsIds, $translationsLanguages ) { |
| 21 | $productsIds = array_merge( [ $product->ID ], $translationsIds ); |
| 22 | $storedAttributes = $this->getMeta( self::PRODUCT_ATTRIBUTES_META_KEY, $productsIds ); |
| 23 | |
| 24 | $this->runForAttributes( $product->ID, $translationsIds, $translationsLanguages, $storedAttributes ); |
| 25 | $this->runForDefaultAttributes( $product->ID, $translationsIds, $translationsLanguages, $storedAttributes ); |
| 26 | } |
| 27 | |
| 28 | private function runForAttributes( $productId, $translationsIds, $translationsLanguages, $storedAttributes ) { |
| 29 | $productAttributes = $storedAttributes[ $productId ] ?? null; |
| 30 | |
| 31 | if ( null === $productAttributes ) { |
| 32 | $translationsIdsToClear = array_intersect( $translationsIds, array_keys( $storedAttributes ) ); |
| 33 | $this->clearTranslationsValue( $translationsIdsToClear, self::PRODUCT_ATTRIBUTES_META_KEY ); |
| 34 | return; |
| 35 | } |
| 36 | |
| 37 | if ( empty( $productAttributes ) ) { |
| 38 | $this->spreadEmptyValue( $translationsIds, $storedAttributes, self::PRODUCT_ATTRIBUTES_META_KEY ); |
| 39 | return; |
| 40 | } |
| 41 | |
| 42 | $hasLocalAttributes = (bool) Lst::find( |
| 43 | function( $attributeData ) { |
| 44 | return (bool) Obj::prop( 'is_taxonomy', $attributeData ) === false; |
| 45 | }, |
| 46 | $productAttributes |
| 47 | ); |
| 48 | |
| 49 | $duplicationsIds = []; |
| 50 | if ( $hasLocalAttributes ) { |
| 51 | // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared |
| 52 | $duplicationsIds = $this->wpdb->get_col( |
| 53 | $this->wpdb->prepare( |
| 54 | " |
| 55 | SELECT post_id |
| 56 | FROM {$this->wpdb->postmeta} |
| 57 | WHERE meta_key = %s |
| 58 | AND post_id IN (" . DB::prepareIn( $translationsIds ) . ") |
| 59 | LIMIT %d |
| 60 | ", |
| 61 | '_icl_lang_duplicate_of', |
| 62 | count( $translationsIds ) |
| 63 | ) |
| 64 | ); |
| 65 | // phpcs:enable |
| 66 | } |
| 67 | |
| 68 | $sanitizedAttributeNames = []; |
| 69 | foreach ( $productAttributes as $attribute => $attributeData ) { |
| 70 | $sanitizedAttributeNames[ $attribute ] = $attribute; |
| 71 | $sanitizedAttribute = $this->woocommerceWpml->attributes->getAttributeNameToSave( $attribute, $attributeData, $productId ); |
| 72 | if ( $attribute !== $sanitizedAttribute ) { |
| 73 | $sanitizedAttributeNames[ $sanitizedAttribute ] = $attribute; |
| 74 | $productAttributes[ $sanitizedAttribute ] = $attributeData; |
| 75 | unset( $productAttributes[ $attribute ] ); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | $translationsIdsToInsert = []; |
| 80 | $translationsIdsToUpdate = []; |
| 81 | foreach ( $translationsIds as $translationId ) { |
| 82 | $translationAttributes = $productAttributes; |
| 83 | $storedTranslationAttributes = $storedAttributes[ $translationId ] ?? []; |
| 84 | foreach ( $translationAttributes as $attribute => $attributeData ) { |
| 85 | if ( $attributeData['is_taxonomy'] || in_array( $translationId, $duplicationsIds, true ) ) { |
| 86 | continue; |
| 87 | } |
| 88 | |
| 89 | $attributeToSave = $sanitizedAttributeNames[ $attribute ]; |
| 90 | if ( isset( $storedTranslationAttributes[ $attribute ] ) ) { |
| 91 | $translationAttributes[ $attributeToSave ]['value'] = $storedTranslationAttributes[ $attribute ]['value']; |
| 92 | } else if ( isset( $storedTranslationAttributes[ $attributeToSave ] ) ) { |
| 93 | $translationAttributes[ $attributeToSave ]['value'] = $storedTranslationAttributes[ $attributeToSave ]['value']; |
| 94 | } else if ( ! empty( $storedTranslationAttributes ) ) { |
| 95 | unset( $translationAttributes[ $attribute ] ); |
| 96 | } |
| 97 | } |
| 98 | if ( maybe_serialize( $translationAttributes ) === maybe_serialize( $storedTranslationAttributes ) ) { |
| 99 | continue; |
| 100 | } |
| 101 | if ( array_key_exists( $translationId, $storedAttributes ) ) { |
| 102 | $translationsIdsToUpdate[ $translationId ] = $translationAttributes; |
| 103 | } else { |
| 104 | $translationsIdsToInsert[ $translationId ] = $translationAttributes; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | $this->insertMeta( self::PRODUCT_ATTRIBUTES_META_KEY, $translationsIdsToInsert ); |
| 109 | $this->updateMeta( self::PRODUCT_ATTRIBUTES_META_KEY, $translationsIdsToUpdate ); |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * @param int $productId |
| 114 | * @param int[] $translationsIds |
| 115 | * @param array<int,string> $translationsLanguages |
| 116 | * @param array<int,array> $storedAttributes |
| 117 | */ |
| 118 | private function runForDefaultAttributes( $productId, $translationsIds, $translationsLanguages, $storedAttributes ) { |
| 119 | $productsIds = array_merge( [ $productId ], $translationsIds ); |
| 120 | $storedDefaultAttributes = $this->getMeta( self::DEFAULT_ATTRIBUTES_META_KEY, $productsIds ); |
| 121 | $defaultAttributes = $storedDefaultAttributes[ $productId ] ?? null; |
| 122 | |
| 123 | if ( null === $defaultAttributes ) { |
| 124 | $translationsIdsToClear = array_intersect( $translationsIds, array_keys( $storedDefaultAttributes ) ); |
| 125 | $this->clearTranslationsValue( $translationsIdsToClear, self::DEFAULT_ATTRIBUTES_META_KEY ); |
| 126 | return; |
| 127 | } |
| 128 | |
| 129 | if ( empty( $defaultAttributes ) ) { |
| 130 | $this->spreadEmptyValue( $translationsIds, $storedDefaultAttributes, self::DEFAULT_ATTRIBUTES_META_KEY ); |
| 131 | return; |
| 132 | } |
| 133 | |
| 134 | $defaultAttributesToUpdate = []; |
| 135 | foreach ( $defaultAttributes as $attribute => $defaultAttributeValue ) { |
| 136 | if ( WCTaxonomies::isProductAttribute( $attribute ) ) { |
| 137 | if ( $this->woocommerceWpml->attributes->is_translatable_attribute( $attribute ) ) { |
| 138 | $sanitizedAttributeName = wc_sanitize_taxonomy_name( $attribute ); |
| 139 | $defaultTerm = $this->woocommerceWpml->terms->wcml_get_term_by_slug( $defaultAttributeValue, $sanitizedAttributeName ); |
| 140 | $defaultTermTranslations = $defaultTerm |
| 141 | ? $this->elementTranslations->get_element_translations( $defaultTerm->term_taxonomy_id, false, false ) |
| 142 | : []; |
| 143 | |
| 144 | foreach ( $translationsLanguages as $translationId => $language ) { |
| 145 | $translatedDefaultAttributes = $storedDefaultAttributes[ $translationId ] ?? []; |
| 146 | $translatedDefaultTermTaxonomyId = $defaultTermTranslations[ $language ] ?? null; |
| 147 | $translatedDefaultTerm = $translatedDefaultTermTaxonomyId |
| 148 | ? $this->woocommerceWpml->terms->wcml_get_term_by_taxonomy_id( $translatedDefaultTermTaxonomyId, $sanitizedAttributeName ) |
| 149 | : null; |
| 150 | $translatedDefaultAttributeValue = $translatedDefaultTerm |
| 151 | ? $translatedDefaultTerm->slug |
| 152 | : 0; |
| 153 | if ( |
| 154 | ! array_key_exists( $attribute, $translatedDefaultAttributes ) |
| 155 | || $translatedDefaultAttributes[ $attribute ] !== $translatedDefaultAttributeValue |
| 156 | ) { |
| 157 | $defaultAttributesToUpdate[ $translationId ][ $attribute ] = $translatedDefaultAttributeValue; |
| 158 | } |
| 159 | } |
| 160 | } else { |
| 161 | foreach ( $translationsIds as $translationId ) { |
| 162 | $translatedDefaultAttributes = $storedDefaultAttributes[ $translationId ] ?? []; |
| 163 | if ( |
| 164 | ! array_key_exists( $attribute, $translatedDefaultAttributes ) |
| 165 | || $translatedDefaultAttributes[ $attribute ] !== $defaultAttributeValue |
| 166 | ) { |
| 167 | $defaultAttributesToUpdate[ $translationId ][ $attribute ] = $defaultAttributeValue; |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | unset( $defaultAttributes[ $attribute ] ); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | if ( ! empty( $defaultAttributes ) ) { |
| 176 | $productAttributes = $storedAttributes[ $productId ] ?? []; |
| 177 | |
| 178 | foreach ( $defaultAttributes as $attribute => $defaultAttributeValue ) { |
| 179 | if ( ! array_key_exists( $attribute, $productAttributes ) ) { |
| 180 | continue; |
| 181 | } |
| 182 | |
| 183 | $productAttributeValues = explode( '|', $productAttributes[ $attribute ]['value'] ); |
| 184 | $productAttributeValues = array_map( 'trim', $productAttributeValues ); |
| 185 | |
| 186 | foreach ( $productAttributeValues as $attributeIndex => $attributeValue ) { |
| 187 | $attributeValueSanitized = strtolower( sanitize_title( $attributeValue ) ); |
| 188 | if ( |
| 189 | $attributeValueSanitized !== $defaultAttributeValue |
| 190 | && trim( $attributeValue ) !== trim( $defaultAttributeValue ) |
| 191 | ) { |
| 192 | continue; |
| 193 | } |
| 194 | foreach ( $translationsIds as $translationId ) { |
| 195 | $translatedStoredAttributes = $storedAttributes[ $translationId ] ?? []; |
| 196 | if ( ! array_key_exists( $attribute, $translatedStoredAttributes ) ) { |
| 197 | continue; |
| 198 | } |
| 199 | $translatedAttributeValues = explode( '|', $translatedStoredAttributes[ $attribute ]['value'] ); |
| 200 | if ( ! isset( $translatedAttributeValues[ $attributeIndex ] ) ) { |
| 201 | $defaultAttributesToUpdate[ $translationId ][ $attribute ] = ''; |
| 202 | continue; |
| 203 | } |
| 204 | if ( $attributeValueSanitized === $defaultAttributeValue ) { |
| 205 | $translatedAttributeValue = strtolower( sanitize_title( trim( $translatedAttributeValues[ $attributeIndex ] ) ) ); |
| 206 | } else { |
| 207 | $translatedAttributeValue = trim( $translatedAttributeValues[ $attributeIndex ] ); |
| 208 | } |
| 209 | if ( $translatedAttributeValue !== Obj::path( [ $translationId, $attribute ], $storedDefaultAttributes ) ) { |
| 210 | $defaultAttributesToUpdate[ $translationId ][ $attribute ] = $translatedAttributeValue; |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | if ( ! empty( $defaultAttributesToUpdate ) ) { |
| 218 | $metaToInsert = []; |
| 219 | $metaToUpdate = []; |
| 220 | foreach ( $defaultAttributesToUpdate as $translationId => $translationAttributes ) { |
| 221 | if ( ! array_key_exists( $translationId, $storedDefaultAttributes ) ) { |
| 222 | $metaToInsert[ $translationId ] = $translationAttributes; |
| 223 | continue; |
| 224 | } |
| 225 | $translationDefaultAttributes = $storedDefaultAttributes[ $translationId ]; |
| 226 | if ( ! empty( $translationDefaultAttributes ) ) { |
| 227 | $metaToUpdate[ $translationId ] = $storedDefaultAttributes[ $translationId ]; |
| 228 | foreach ( $translationAttributes as $attribute => $attributeValue ) { |
| 229 | $metaToUpdate[ $translationId ][ $attribute ] = $attributeValue; |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | $this->insertMeta( self::DEFAULT_ATTRIBUTES_META_KEY, $metaToInsert ); |
| 235 | $this->updateMeta( self::DEFAULT_ATTRIBUTES_META_KEY, $metaToUpdate ); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | } |
| 240 |