| @@ -176,14 +176,8 @@ | ||
| 176 | 176 | return get_the_terms($this->ID, 'product-categories'); |
| 177 | 177 | } |
| 178 | 178 | |
| 179 | 179 | |
| 180 | - public function getTags() | |
| 181 | - { | |
| 182 | - return get_the_terms($this->ID, 'product-tags'); | |
| 183 | - } | |
| 184 | - | |
| 185 | - | |
| 186 | 180 | public function getMediaUrl($size = 'thumbnail') |
| 187 | 181 | { |
| 188 | 182 | return get_the_post_thumbnail_url($this->ID, $size); |
| 189 | 183 | } |
| @@ -192,14 +186,8 @@ | ||
| 192 | 186 | /* |
| 193 | 187 | * Transforming old getters with accessor |
| 194 | 188 | * Todo check |
| 195 | 189 | */ |
| 196 | - public function getTagsAttribute($value) | |
| 197 | - { | |
| 198 | - return get_the_terms($this->ID, 'product-tags'); | |
| 199 | - } | |
| 200 | - | |
| 201 | - | |
| 202 | 190 | public function getCategoriesAttribute($value) |
| 203 | 191 | { |
| 204 | 192 | return get_the_terms($this->ID, 'product-categories'); |
| 205 | 193 | } |
| @@ -207,9 +195,9 @@ | ||
| 207 | 195 | |
| 208 | 196 | public function getThumbnailAttribute() |
| 209 | 197 | { |
| 210 | 198 | if (empty($this->detail) || empty($this->detail->featured_media)) { |
| 211 | - return Vite::getAssetUrl('images/placeholder.svg'); | |
| 199 | + return Helper::getProductPlaceholderUrl(); | |
| 212 | 200 | } |
| 213 | 201 | return Arr::get($this->detail->featured_media, 'url'); |
| 214 | 202 | } |
| 215 | 203 | |
| @@ -259,14 +247,8 @@ | ||
| 259 | 247 | return $this->getTermByType('product-categories'); |
| 260 | 248 | } |
| 261 | 249 | |
| 262 | 250 | |
| 263 | - public function tags() | |
| 264 | - { | |
| 265 | - return $this->getTermByType('product-tags'); | |
| 266 | - } | |
| 267 | - | |
| 268 | - | |
| 269 | 251 | /* |
| 270 | 252 | * Todo: Discuss on below relation |
| 271 | 253 | */ |
| 272 | 254 | public function thumbUrl(): HasOne |
| @@ -509,9 +491,9 @@ | ||
| 509 | 491 | |
| 510 | 492 | public function images(): array |
| 511 | 493 | { |
| 512 | 494 | $images = []; |
| 513 | - $thumbnailImage = $this->thumbnail ?? Vite::getAssetUrl('images/placeholder.svg'); | |
| 495 | + $thumbnailImage = $this->thumbnail ?? Helper::getProductPlaceholderUrl(); | |
| 514 | 496 | |
| 515 | 497 | $galleryImages = get_post_meta($this->ID, 'fluent-products-gallery-image', true); |
| 516 | 498 | |
| 517 | 499 | |
| @@ -697,17 +679,60 @@ | ||
| 697 | 679 | |
| 698 | 680 | $newVariant = ProductVariation::query()->create($variantData); |
| 699 | 681 | $variationIdMap[$originalVariant->id] = $newVariant->id; |
| 700 | 682 | |
| 683 | + // Copy the variant's thumbnail meta to the new variant. | |
| 684 | + // $originalVariant->media is hasOne on fct_product_meta | |
| 685 | + // where meta_key = 'product_thumbnail'; the old code | |
| 686 | + // foreach'd over it (treating a single Model as a | |
| 687 | + // collection — iterating its attribute scalars) and | |
| 688 | + // mis-routed the data through wp_set_object_terms (the | |
| 689 | + // WordPress taxonomy API, unrelated to fct_product_meta). | |
| 690 | + // Mirror the Pro AdvancedVariationService pattern: a | |
| 691 | + // fresh fct_product_meta row owned by the new variant id | |
| 692 | + // with the same meta_value payload. | |
| 701 | 693 | if ($originalVariant->media && $newVariant) { |
| 702 | - foreach ($originalVariant->media as $media) { | |
| 703 | - \wp_set_object_terms( | |
| 704 | - $newVariant->id, | |
| 705 | - $media->term_id ?? $media->id, | |
| 706 | - 'product_media' | |
| 707 | - ); | |
| 694 | + $media = $originalVariant->media; | |
| 695 | + ProductMeta::query()->create([ | |
| 696 | + 'object_id' => $newVariant->id, | |
| 697 | + 'object_type' => 'product_variant_info', | |
| 698 | + 'meta_key' => 'product_thumbnail', | |
| 699 | + 'meta_value' => $media->meta_value, | |
| 700 | + ]); | |
| 701 | + } | |
| 702 | + } | |
| 703 | + } | |
| 704 | + | |
| 705 | + // Mirror advanced-variation attribute relations onto the new | |
| 706 | + // variants. Without this, fct_atts_relations stays empty for | |
| 707 | + // the duplicated product so variant.attr_map is empty | |
| 708 | + // downstream — AdvancedVariationTable can't group by an | |
| 709 | + // attribute, the per-variant breadcrumb collapses to "—", and | |
| 710 | + // (with the Pro variation_title snapshot) future re-saves | |
| 711 | + // would compose blank titles. Re-key on the variationIdMap | |
| 712 | + // built above so each (old_variant_id, group_id, term_id) | |
| 713 | + // tuple becomes a (new_variant_id, group_id, term_id) tuple. | |
| 714 | + if (!empty($variationIdMap)) { | |
| 715 | + $originalVariantIds = array_keys($variationIdMap); | |
| 716 | + $relations = AttributeRelation::query() | |
| 717 | + ->whereIn('object_id', $originalVariantIds) | |
| 718 | + ->get(); | |
| 719 | + if ($relations->isNotEmpty()) { | |
| 720 | + $rows = []; | |
| 721 | + foreach ($relations as $rel) { | |
| 722 | + $newVariantId = Arr::get($variationIdMap, $rel->object_id); | |
| 723 | + if (!$newVariantId) { | |
| 724 | + continue; | |
| 708 | 725 | } |
| 726 | + $rows[] = [ | |
| 727 | + 'group_id' => (int) $rel->group_id, | |
| 728 | + 'term_id' => (int) $rel->term_id, | |
| 729 | + 'object_id' => (int) $newVariantId, | |
| 730 | + ]; | |
| 709 | 731 | } |
| 732 | + if (!empty($rows)) { | |
| 733 | + AttributeRelation::query()->insert($rows); | |
| 734 | + } | |
| 710 | 735 | } |
| 711 | 736 | } |
| 712 | 737 | |
| 713 | 738 | if ($importDownloadableFiles && $this->downloadable_files) { |
| @@ -778,6 +803,13 @@ | ||
| 778 | 803 | |
| 779 | 804 | public function integrations(): \FluentCart\Framework\Database\Orm\Relations\HasMany |
| 780 | 805 | { |
| 781 | 806 | return $this->hasMany(ProductMeta::class, 'object_id')->where('object_type', 'product_integration'); |
| 807 | + } | |
| 808 | + | |
| 809 | + public function reviews(): \FluentCart\Framework\Database\Orm\Relations\HasMany | |
| 810 | + { | |
| 811 | + return $this->hasMany(ProductReview::class, 'post_id', 'ID') | |
| 812 | + ->whereNull('parent_id') | |
| 813 | + ->where('status', Status::REVIEW_APPROVED); | |
| 782 | 814 | } |
| 783 | 815 | } |