'array', ]; /** * The type of product. * * @var \Webkul\Product\Type\AbstractType */ protected $typeInstance; /** * Loaded attribute values. * * @var array */ public static $loadedAttributeValues = []; /** * The `booted` method of the model. * * @return void */ protected static function booted(): void { parent::boot(); static::deleting(function ($product) { foreach ($product->product_flats as $productFlat) { $productFlat->unsearchable(); } foreach ($product->variants as $variant) { foreach ($variant->product_flats as $productFlat) { $productFlat->unsearchable(); } } }); } /** * Get the product flat entries that are associated with product. * May be one for each locale and each channel. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function product_flats(): HasMany { return $this->hasMany(ProductFlatProxy::modelClass(), 'product_id'); } /** * Get the product that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function parent(): BelongsTo { return $this->belongsTo(static::class, 'parent_id'); } /** * Get the product attribute family that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function attribute_family(): BelongsTo { return $this->belongsTo(AttributeFamilyProxy::modelClass()); } /** * The super attributes that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function super_attributes(): BelongsToMany { return $this->belongsToMany(AttributeProxy::modelClass(), 'product_super_attributes'); } /** * Get the product attribute values that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function attribute_values(): HasMany { return $this->hasMany(ProductAttributeValueProxy::modelClass()); } /** * Get the product customer group prices that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function customer_group_prices(): HasMany { return $this->hasMany(ProductCustomerGroupPriceProxy::modelClass()); } /** * The categories that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function categories(): BelongsToMany { return $this->belongsToMany(CategoryProxy::modelClass(), 'product_categories'); } /** * The images that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function images(): HasMany { return $this->hasMany(ProductImageProxy::modelClass(), 'product_id') ->orderBy('position'); } /** * The videos that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function videos(): HasMany { return $this->hasMany(ProductVideoProxy::modelClass(), 'product_id') ->orderBy('position'); } /** * Get the product reviews that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function reviews(): HasMany { return $this->hasMany(ProductReviewProxy::modelClass()); } /** * The inventory sources that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function inventory_sources(): BelongsToMany { return $this->belongsToMany(InventorySourceProxy::modelClass(), 'product_inventories') ->withPivot('id', 'qty'); } /** * Get inventory source quantity. * * @param $inventorySourceId * @return bool */ public function inventory_source_qty($inventorySourceId) { return $this->inventories() ->where('inventory_source_id', $inventorySourceId) ->sum('qty'); } /** * The inventories that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function inventories(): HasMany { return $this->hasMany(ProductInventoryProxy::modelClass(), 'product_id'); } /** * The ordered inventories that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function ordered_inventories(): HasMany { return $this->hasMany(ProductOrderedInventoryProxy::modelClass(), 'product_id'); } /** * Get the product variants that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function variants(): HasMany { return $this->hasMany(static::class, 'parent_id'); } /** * Get the grouped products that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function grouped_products(): HasMany { return $this->hasMany(ProductGroupedProductProxy::modelClass()); } /** * The images that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function downloadable_samples(): HasMany { return $this->hasMany(ProductDownloadableSampleProxy::modelClass()); } /** * The images that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function downloadable_links(): HasMany { return $this->hasMany(ProductDownloadableLinkProxy::modelClass()); } /** * Get the bundle options that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function bundle_options(): HasMany { return $this->hasMany(ProductBundleOptionProxy::modelClass()); } /** * Get the booking that owns the product. * * @return \Illuminate\Database\Eloquent\Relations\HasOne */ public function booking_product() { return $this->hasOne(BookingProductProxy::modelClass()); } /** * The related products that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function related_products(): BelongsToMany { return $this->belongsToMany(static::class, 'product_relations', 'parent_id', 'child_id') ->limit(4); } /** * The up sells that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function up_sells(): BelongsToMany { return $this->belongsToMany(static::class, 'product_up_sells', 'parent_id', 'child_id') ->limit(4); } /** * The cross sells that belong to the product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function cross_sells(): BelongsToMany { return $this->belongsToMany(static::class, 'product_cross_sells', 'parent_id', 'child_id') ->limit(4); } /** * Is saleable. * * @param string $key * @return bool * * @throws \Exception */ public function isSaleable(): bool { return $this->getTypeInstance() ->isSaleable(); } /** * Is stockable. * * @return bool * * @throws \Exception */ public function isStockable(): bool { return $this->getTypeInstance() ->isStockable(); } /** * Total quantity. * * @return integer * * @throws \Exception */ public function totalQuantity(): int { return $this->getTypeInstance() ->totalQuantity(); } /** * Have sufficient quantity. * * @param int $qty * @return bool * * @throws \Exception */ public function haveSufficientQuantity(int $qty): bool { return $this->getTypeInstance() ->haveSufficientQuantity($qty); } /** * Get type instance. * * @return AbstractType * * @throws \Exception */ public function getTypeInstance(): AbstractType { if ($this->typeInstance) { return $this->typeInstance; } $this->typeInstance = app(config('product_types.' . $this->type . '.class')); if (! $this->typeInstance instanceof AbstractType) { throw new Exception("Please ensure the product type '{$this->type}' is configured in your application."); } $this->typeInstance->setProduct($this); return $this->typeInstance; } /** * Return the product id attribute. * * @return int */ public function getProductIdAttribute() { return $this->id; } /** * Return the product attribute. * * @return self */ public function getProductAttribute() { return $this; } /** * The images that belong to the product. * * @return string */ public function getBaseImageUrlAttribute() { $image = $this->images() ->first(); return $image->url ?? null; } /** * Get an attribute from the model. * * @param string $key * @return mixed */ public function getAttribute($key) { if (! method_exists(static::class, $key) && ! in_array($key, [ 'pivot', 'parent_id', 'attribute_family_id', ]) && ! isset($this->attributes[$key])) { if (isset($this->id)) { $this->attributes[$key] = ''; $attribute = core() ->getSingletonInstance(AttributeRepository::class) ->getAttributeByCode($key); $this->attributes[$key] = $this->getCustomAttributeValue($attribute); return $this->getAttributeValue($key); } } return parent::getAttribute($key); } /** * Retrieve product attributes. * * @param Group $group * @param bool $skipSuperAttribute * @return \Illuminate\Support\Collection * * @throws \Exception */ public function getEditableAttributes($group = null, $skipSuperAttribute = true): Collection { return $this->getTypeInstance() ->getEditableAttributes($group, $skipSuperAttribute); } /** * Get an product attribute value. * * @return mixed */ public function getCustomAttributeValue($attribute) { if (! $attribute) { return; } $locale = core()->checkRequestedLocaleCodeInRequestedChannel(); $channel = core()->getRequestedChannelCode(); if (array_key_exists($this->id, self::$loadedAttributeValues) && array_key_exists($attribute->id, self::$loadedAttributeValues[$this->id])) { return self::$loadedAttributeValues[$this->id][$attribute->id]; } if ($attribute->value_per_channel) { if ($attribute->value_per_locale) { $attributeValue = $this->attribute_values() ->where('channel', $channel) ->where('locale', $locale) ->where('attribute_id', $attribute->id) ->first(); } else { $attributeValue = $this->attribute_values() ->where('channel', $channel) ->where('attribute_id', $attribute->id) ->first(); } } else { if ($attribute->value_per_locale) { $attributeValue = $this->attribute_values() ->where('locale', $locale) ->where('attribute_id', $attribute->id) ->first(); } else { $attributeValue = $this->attribute_values() ->where('attribute_id', $attribute->id) ->first(); } } return self::$loadedAttributeValues[$this->id][$attribute->id] = $attributeValue[ProductAttributeValue::$attributeTypeFields[$attribute->type]] ?? null; } /** * Attributes to array. * * @return array */ public function attributesToArray(): array { $attributes = parent::attributesToArray(); $hiddenAttributes = $this->getHidden(); if (isset($this->id)) { $familyAttributes = $this->checkInLoadedFamilyAttributes(); foreach ($familyAttributes as $attribute) { if (in_array($attribute->code, $hiddenAttributes)) { continue; } $attributes[$attribute->code] = $this->getCustomAttributeValue($attribute); } } return $attributes; } /** * Check in loaded family attributes. * * @return object */ public function checkInLoadedFamilyAttributes(): object { static $loadedFamilyAttributes = []; if (array_key_exists($this->attribute_family_id, $loadedFamilyAttributes)) { return $loadedFamilyAttributes[$this->attribute_family_id]; } return $loadedFamilyAttributes[$this->attribute_family_id] = core() ->getSingletonInstance(AttributeRepository::class) ->getFamilyAttributes($this->attribute_family); } /** * Overrides the default Eloquent query builder. * * @param \Illuminate\Database\Query\Builder $query * @return \Webkul\Product\Database\Eloquent\Builder */ public function newEloquentBuilder($query) { return new Builder($query); } /** * Refresh the loaded attribute values. * * @return void */ public function refreshloadedAttributeValues(): void { self::$loadedAttributeValues = []; } /** * Create a new factory instance for the model. * * @return \Illuminate\Database\Eloquent\Factories\Factory */ protected static function newFactory(): Factory { return ProductFactory::new (); } }