diff --git a/packages/Webkul/Product/src/Http/Controllers/ProductController.php b/packages/Webkul/Product/src/Http/Controllers/ProductController.php index a11ac38a4..e70b4564d 100755 --- a/packages/Webkul/Product/src/Http/Controllers/ProductController.php +++ b/packages/Webkul/Product/src/Http/Controllers/ProductController.php @@ -4,8 +4,6 @@ namespace Webkul\Product\Http\Controllers; use Exception; use Illuminate\Support\Facades\Event; -use Webkul\Attribute\Models\Attribute; -use Webkul\Product\Models\ProductFlat; use Webkul\Product\Helpers\ProductType; use Illuminate\Support\Facades\Storage; use Webkul\Core\Contracts\Validations\Slug; @@ -249,77 +247,7 @@ class ProductController extends Controller */ public function copy(int $productId) { - $originalProduct = $this->productRepository - ->findOrFail($productId) - ->load('attribute_family') - ->load('categories') - ->load('customer_group_prices') - ->load('inventories') - ->load('inventory_sources'); - - $copiedProduct = $originalProduct - ->replicate() - ->fill([ - // the sku and url_key needs to be unique and should be entered again newly by the admin: - 'sku' => 'temporary-sku-' . substr(md5(microtime()), 0, 6), - ]); - - $copiedProduct->save(); - - $attributeName = Attribute::query()->where(['code' => 'name'])->firstOrFail(); - $attributeSku = Attribute::query()->where(['code' => 'sku'])->firstOrFail(); - $attributeStatus = Attribute::query()->where(['code' => 'status'])->firstOrFail(); - $attributeUrlKey = Attribute::query()->where(['code' => 'url_key'])->firstOrFail(); - - $newProductFlat = new ProductFlat(); - - // only obey copied locale and channel: - if (isset($originalProduct->product_flats[0])) { - $newProductFlat = $originalProduct->product_flats[0]->replicate(); - } - - $newProductFlat->product_id = $copiedProduct->id; - - foreach ($originalProduct->attribute_values as $oldValue) { - $newValue = $oldValue->replicate(); - - if ($oldValue->attribute_id === $attributeName->id) { - $newValue->text_value = __('admin::app.copy-of') . ' ' . $originalProduct->name; - $newProductFlat->name = __('admin::app.copy-of') . ' ' . $originalProduct->name; - } - - if ($oldValue->attribute_id === $attributeUrlKey->id) { - $newValue->text_value = __('admin::app.copy-of-slug') . '-' . $originalProduct->url_key; - $newProductFlat->url_key = __('admin::app.copy-of-slug') . '-' . $originalProduct->url_key; - } - - if ($oldValue->attribute_id === $attributeSku->id) { - $newValue->text_value = $copiedProduct->sku; - $newProductFlat->sku = $copiedProduct->sku; - } - - if ($oldValue->attribute_id === $attributeStatus->id) { - $newValue->boolean_value = 0; - $newProductFlat->status = 0; - } - - $copiedProduct->attribute_values()->save($newValue); - - } - - $newProductFlat->save(); - - foreach ($originalProduct->categories as $category) { - $copiedProduct->categories()->save($category); - } - - foreach ($originalProduct->inventories as $inventory) { - $copiedProduct->inventories()->save($inventory); - } - - foreach ($originalProduct->customer_group_prices as $customer_group_price) { - $copiedProduct->customer_group_prices()->save($customer_group_price); - } + $copiedProduct = $this->productRepository->copy($productId); return redirect()->to(route('admin.catalog.products.edit', ['id' => $copiedProduct->id])); } diff --git a/packages/Webkul/Product/src/Repositories/ProductRepository.php b/packages/Webkul/Product/src/Repositories/ProductRepository.php index 56225f1e7..131d5f883 100755 --- a/packages/Webkul/Product/src/Repositories/ProductRepository.php +++ b/packages/Webkul/Product/src/Repositories/ProductRepository.php @@ -2,14 +2,18 @@ namespace Webkul\Product\Repositories; -use Illuminate\Pagination\Paginator; +use Webkul\Product\Models\Product; use Illuminate\Support\Facades\DB; -use Illuminate\Container\Container as App; -use Illuminate\Support\Facades\Event; -use Illuminate\Database\Eloquent\ModelNotFoundException; -use Webkul\Attribute\Repositories\AttributeRepository; +use Illuminate\Pagination\Paginator; use Webkul\Core\Eloquent\Repository; +use Illuminate\Support\Facades\Event; +use Webkul\Attribute\Models\Attribute; +use Webkul\Product\Models\ProductFlat; +use Illuminate\Container\Container as App; +use Illuminate\Pagination\LengthAwarePaginator; use Webkul\Product\Models\ProductAttributeValueProxy; +use Webkul\Attribute\Repositories\AttributeRepository; +use Illuminate\Database\Eloquent\ModelNotFoundException; class ProductRepository extends Repository { @@ -23,8 +27,9 @@ class ProductRepository extends Repository /** * Create a new repository instance. * - * @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository - * @param \Illuminate\Container\Container $app + * @param \Webkul\Attribute\Repositories\AttributeRepository $attributeRepository + * @param \Illuminate\Container\Container $app + * * @return void */ public function __construct( @@ -48,7 +53,8 @@ class ProductRepository extends Repository } /** - * @param array $data + * @param array $data + * * @return \Webkul\Product\Contracts\Product */ public function create(array $data) @@ -65,9 +71,10 @@ class ProductRepository extends Repository } /** - * @param array $data - * @param int $id - * @param string $attribute + * @param array $data + * @param int $id + * @param string $attribute + * * @return \Webkul\Product\Contracts\Product */ public function update(array $data, $id, $attribute = "id") @@ -88,7 +95,8 @@ class ProductRepository extends Repository } /** - * @param int $id + * @param int $id + * * @return void */ public function delete($id) @@ -101,7 +109,8 @@ class ProductRepository extends Repository } /** - * @param int $categoryId + * @param int $categoryId + * * @return \Illuminate\Support\Collection */ public function getAll($categoryId = null) @@ -111,21 +120,21 @@ class ProductRepository extends Repository if (core()->getConfigData('catalog.products.storefront.products_per_page')) { $pages = explode(',', core()->getConfigData('catalog.products.storefront.products_per_page')); - $perPage = isset($params['limit']) ? (!empty($params['limit']) ? $params['limit'] : 9) : current($pages); + $perPage = isset($params['limit']) ? (! empty($params['limit']) ? $params['limit'] : 9) : current($pages); } else { - $perPage = isset($params['limit']) && !empty($params['limit']) ? $params['limit'] : 9; + $perPage = isset($params['limit']) && ! empty($params['limit']) ? $params['limit'] : 9; } $page = Paginator::resolveCurrentPage('page'); - $repository = app(ProductFlatRepository::class)->scopeQuery(function($query) use($params, $categoryId) { + $repository = app(ProductFlatRepository::class)->scopeQuery(function ($query) use ($params, $categoryId) { $channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode()); $locale = request()->get('locale') ?: app()->getLocale(); $qb = $query->distinct() ->select('product_flat.*') - ->join('product_flat as variants', 'product_flat.id', '=', DB::raw('COALESCE('.DB::getTablePrefix().'variants.parent_id, '.DB::getTablePrefix().'variants.id)')) + ->join('product_flat as variants', 'product_flat.id', '=', DB::raw('COALESCE(' . DB::getTablePrefix() . 'variants.parent_id, ' . DB::getTablePrefix() . 'variants.id)')) ->leftJoin('product_categories', 'product_categories.product_id', '=', 'product_flat.product_id') ->leftJoin('product_attribute_values', 'product_attribute_values.product_id', '=', 'variants.product_id') ->where('product_flat.channel', $channel) @@ -149,25 +158,25 @@ class ProductRepository extends Repository # sort direction $orderDirection = 'asc'; - if( isset($params['order']) && in_array($params['order'], ['desc', 'asc']) ){ + if (isset($params['order']) && in_array($params['order'], ['desc', 'asc'])) { $orderDirection = $params['order']; } else { $sortOptions = $this->getDefaultSortByOption(); - $orderDirection = !empty($sortOptions) ? $sortOptions[1] : 'asc'; + $orderDirection = ! empty($sortOptions) ? $sortOptions[1] : 'asc'; } if (isset($params['sort'])) { $this->checkSortAttributeAndGenerateQuery($qb, $params['sort'], $orderDirection); } else { $sortOptions = $this->getDefaultSortByOption(); - if (!empty($sortOptions)) { + if (! empty($sortOptions)) { $this->checkSortAttributeAndGenerateQuery($qb, $sortOptions[0], $orderDirection); } } - if ( $priceFilter = request('price') ){ + if ($priceFilter = request('price')) { $priceRange = explode(',', $priceFilter); - if( count($priceRange) > 0 ) { + if (count($priceRange) > 0) { $qb->where('variants.min_price', '>=', core()->convertToBasePrice($priceRange[0])); $qb->where('variants.min_price', '<=', core()->convertToBasePrice(end($priceRange))); } @@ -178,8 +187,8 @@ class ProductRepository extends Repository request()->except(['price']) )); - if ( count($attributeFilters) > 0 ) { - $qb->where(function ($filterQuery) use($attributeFilters){ + if (count($attributeFilters) > 0) { + $qb->where(function ($filterQuery) use ($attributeFilters) { foreach ($attributeFilters as $attribute) { $filterQuery->orWhere(function ($attributeQuery) use ($attribute) { @@ -196,7 +205,7 @@ class ProductRepository extends Repository $attributeQuery->where(function ($attributeValueQuery) use ($column, $filterInputValues) { foreach ($filterInputValues as $filterValue) { - if (!is_numeric($filterValue)) { + if (! is_numeric($filterValue)) { continue; } $attributeValueQuery->orWhereRaw("find_in_set(?, {$column})", [$filterValue]); @@ -239,9 +248,9 @@ class ProductRepository extends Repository $items = []; } - $results = new \Illuminate\Pagination\LengthAwarePaginator($items, $count, $perPage, $page, [ + $results = new LengthAwarePaginator($items, $count, $perPage, $page, [ 'path' => request()->url(), - 'query' => request()->query() + 'query' => request()->query(), ]); return $results; @@ -250,8 +259,9 @@ class ProductRepository extends Repository /** * Retrive product from slug * - * @param string $slug - * @param string $columns + * @param string $slug + * @param string $columns + * * @return \Webkul\Product\Contracts\Product */ public function findBySlugOrFail($slug, $columns = null) @@ -274,7 +284,8 @@ class ProductRepository extends Repository /** * Retrieve product from slug without throwing an exception (might return null) * - * @param string $slug + * @param string $slug + * * @return \Webkul\Product\Contracts\ProductFlat */ public function findBySlug($slug) @@ -293,19 +304,19 @@ class ProductRepository extends Repository */ public function getNewProducts() { - $results = app(ProductFlatRepository::class)->scopeQuery(function($query) { + $results = app(ProductFlatRepository::class)->scopeQuery(function ($query) { $channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode()); $locale = request()->get('locale') ?: app()->getLocale(); return $query->distinct() - ->addSelect('product_flat.*') - ->where('product_flat.status', 1) - ->where('product_flat.visible_individually', 1) - ->where('product_flat.new', 1) - ->where('product_flat.channel', $channel) - ->where('product_flat.locale', $locale) - ->inRandomOrder(); + ->addSelect('product_flat.*') + ->where('product_flat.status', 1) + ->where('product_flat.visible_individually', 1) + ->where('product_flat.new', 1) + ->where('product_flat.channel', $channel) + ->where('product_flat.locale', $locale) + ->inRandomOrder(); })->paginate(4); return $results; @@ -318,19 +329,19 @@ class ProductRepository extends Repository */ public function getFeaturedProducts() { - $results = app(ProductFlatRepository::class)->scopeQuery(function($query) { + $results = app(ProductFlatRepository::class)->scopeQuery(function ($query) { $channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode()); $locale = request()->get('locale') ?: app()->getLocale(); return $query->distinct() - ->addSelect('product_flat.*') - ->where('product_flat.status', 1) - ->where('product_flat.visible_individually', 1) - ->where('product_flat.featured', 1) - ->where('product_flat.channel', $channel) - ->where('product_flat.locale', $locale) - ->inRandomOrder(); + ->addSelect('product_flat.*') + ->where('product_flat.status', 1) + ->where('product_flat.visible_individually', 1) + ->where('product_flat.featured', 1) + ->where('product_flat.channel', $channel) + ->where('product_flat.locale', $locale) + ->inRandomOrder(); })->paginate(4); return $results; @@ -339,7 +350,8 @@ class ProductRepository extends Repository /** * Search Product by Attribute * - * @param string $term + * @param string $term + * * @return \Illuminate\Support\Collection */ public function searchProductByAttribute($term) @@ -349,27 +361,27 @@ class ProductRepository extends Repository $locale = request()->get('locale') ?: app()->getLocale(); if (config('scout.driver') == 'algolia') { - $results = app(ProductFlatRepository::class)->getModel()::search('query', function ($searchDriver, string $query, array $options) use($term, $channel, $locale) { + $results = app(ProductFlatRepository::class)->getModel()::search('query', function ($searchDriver, string $query, array $options) use ($term, $channel, $locale) { $queries = explode('_', $term); $options['similarQuery'] = array_map('trim', $queries); $searchDriver->setSettings([ 'attributesForFaceting' => [ - "searchable(locale)", - "searchable(channel)" - ] + "searchable(locale)", + "searchable(channel)", + ], ]); - $options['facetFilters'] = ['locale:' . $locale, 'channel:' . $channel]; + $options['facetFilters'] = ['locale:' . $locale, 'channel:' . $channel]; return $searchDriver->search($query, $options); }) - ->where('status', 1) - ->where('visible_individually', 1) - ->orderBy('product_id', 'desc') - ->paginate(16); - } else if(config('scout.driver') == 'elastic') { + ->where('status', 1) + ->where('visible_individually', 1) + ->orderBy('product_id', 'desc') + ->paginate(16); + } else if (config('scout.driver') == 'elastic') { $queries = explode('_', $term); $results = app(ProductFlatRepository::class)->getModel()::search(implode(' OR ', $queries)) @@ -380,23 +392,23 @@ class ProductRepository extends Repository ->orderBy('product_id', 'desc') ->paginate(16); } else { - $results = app(ProductFlatRepository::class)->scopeQuery(function($query) use($term, $channel, $locale) { + $results = app(ProductFlatRepository::class)->scopeQuery(function ($query) use ($term, $channel, $locale) { return $query->distinct() - ->addSelect('product_flat.*') - ->where('product_flat.status', 1) - ->where('product_flat.visible_individually', 1) - ->where('product_flat.channel', $channel) - ->where('product_flat.locale', $locale) - ->whereNotNull('product_flat.url_key') - ->where(function($subQuery) use ($term) { - $queries = explode('_', $term); + ->addSelect('product_flat.*') + ->where('product_flat.status', 1) + ->where('product_flat.visible_individually', 1) + ->where('product_flat.channel', $channel) + ->where('product_flat.locale', $locale) + ->whereNotNull('product_flat.url_key') + ->where(function ($subQuery) use ($term) { + $queries = explode('_', $term); - foreach (array_map('trim', $queries) as $value) { - $subQuery->orWhere('product_flat.name', 'like', '%' . urldecode($value) . '%') - ->orWhere('product_flat.short_description', 'like', '%' . urldecode($value) . '%'); - } - }) - ->orderBy('product_id', 'desc'); + foreach (array_map('trim', $queries) as $value) { + $subQuery->orWhere('product_flat.name', 'like', '%' . urldecode($value) . '%') + ->orWhere('product_flat.short_description', 'like', '%' . urldecode($value) . '%'); + } + }) + ->orderBy('product_id', 'desc'); })->paginate(16); } @@ -406,7 +418,8 @@ class ProductRepository extends Repository /** * Returns product's super attribute with options * - * @param \Webkul\Product\Contracts\Product $product + * @param \Webkul\Product\Contracts\Product $product + * * @return \Illuminate\Support\Collection */ public function getSuperAttributes($product) @@ -432,35 +445,54 @@ class ProductRepository extends Repository /** * Search simple products for grouped product association * - * @param string $term + * @param string $term + * * @return \Illuminate\Support\Collection */ public function searchSimpleProducts($term) { - return app(ProductFlatRepository::class)->scopeQuery(function($query) use($term) { + return app(ProductFlatRepository::class)->scopeQuery(function ($query) use ($term) { $channel = request()->get('channel') ?: (core()->getCurrentChannelCode() ?: core()->getDefaultChannelCode()); $locale = request()->get('locale') ?: app()->getLocale(); return $query->distinct() - ->addSelect('product_flat.*') - ->addSelect('product_flat.product_id as id') - ->leftJoin('products', 'product_flat.product_id', '=', 'products.id') - ->where('products.type', 'simple') - ->where('product_flat.channel', $channel) - ->where('product_flat.locale', $locale) - ->where('product_flat.name', 'like', '%' . urldecode($term) . '%') - ->orderBy('product_id', 'desc'); + ->addSelect('product_flat.*') + ->addSelect('product_flat.product_id as id') + ->leftJoin('products', 'product_flat.product_id', '=', 'products.id') + ->where('products.type', 'simple') + ->where('product_flat.channel', $channel) + ->where('product_flat.locale', $locale) + ->where('product_flat.name', 'like', '%' . urldecode($term) . '%') + ->orderBy('product_id', 'desc'); })->get(); } + /** + * Copy a product. Is usually called by the copy() function of the ProductController. + * + * @param int $sourceProductId the id of the product that should be copied + */ + public function copy(int $sourceProductId): Product + { + $originalProduct = $this->loadOriginalProduct($sourceProductId); + + $copiedProduct = $this->persistCopiedProduct($originalProduct); + + $this->persistAttributeValues($originalProduct, $copiedProduct); + + $this->persistRelations($originalProduct, $copiedProduct); + + return $copiedProduct; + } + /** * Get default sort by option * * @return array */ private function getDefaultSortByOption() - { + { $value = core()->getConfigData('catalog.products.storefront.sort_by'); $config = $value ? $value : 'name-desc'; @@ -471,9 +503,10 @@ class ProductRepository extends Repository /** * Check sort attribute and generate query * - * @param object $query - * @param string $sort - * @param string $direction + * @param object $query + * @param string $sort + * @param string $direction + * * @return object */ private function checkSortAttributeAndGenerateQuery($query, $sort, $direction) @@ -490,4 +523,118 @@ class ProductRepository extends Repository return $query; } + + /** + * @param int $sourceProductId + * + * @return mixed + */ + private function loadOriginalProduct(int $sourceProductId) + { + $originalProduct = $this + ->findOrFail($sourceProductId) + ->load('attribute_family') + ->load('categories') + ->load('customer_group_prices') + ->load('inventories') + ->load('inventory_sources'); + return $originalProduct; + } + + /** + * @param $originalProduct + * + * @return mixed + */ + private function persistCopiedProduct($originalProduct) + { + $copiedProduct = $originalProduct + ->replicate() + ->fill([ + // the sku and url_key needs to be unique and should be entered again newly by the admin: + 'sku' => 'temporary-sku-' . substr(md5(microtime()), 0, 6), + ]); + + $copiedProduct->save(); + + return $copiedProduct; + } + + + /** + * Gather the ids of the necessary product attributes. + * Throw an Exception if one of these 'basic' attributes are missing for some reason. + */ + private function gatherAttributeIds(): array + { + $ids = []; + + foreach (['name', 'sku', 'status', 'url_key'] as $code) { + $ids[$code] = Attribute::query()->where(['code' => $code])->firstOrFail()->id; + } + + return $ids; + } + + private function persistAttributeValues(Product $originalProduct, Product $copiedProduct): void + { + $attributeIds = $this->gatherAttributeIds(); + + $newProductFlat = new ProductFlat(); + + // only obey copied locale and channel: + if (isset($originalProduct->product_flats[0])) { + $newProductFlat = $originalProduct->product_flats[0]->replicate(); + } + + $newProductFlat->product_id = $copiedProduct->id; + + foreach ($originalProduct->attribute_values as $oldValue) { + $newValue = $oldValue->replicate(); + + if ($oldValue->attribute_id === $attributeIds['name']) { + $newValue->text_value = __('admin::app.copy-of') . ' ' . $originalProduct->name; + $newProductFlat->name = __('admin::app.copy-of') . ' ' . $originalProduct->name; + } + + if ($oldValue->attribute_id === $attributeIds['url_key']) { + $newValue->text_value = __('admin::app.copy-of-slug') . '-' . $originalProduct->url_key; + $newProductFlat->url_key = __('admin::app.copy-of-slug') . '-' . $originalProduct->url_key; + } + + if ($oldValue->attribute_id === $attributeIds['sku']) { + $newValue->text_value = $copiedProduct->sku; + $newProductFlat->sku = $copiedProduct->sku; + } + + if ($oldValue->attribute_id === $attributeIds['status']) { + $newValue->boolean_value = 0; + $newProductFlat->status = 0; + } + + $copiedProduct->attribute_values()->save($newValue); + + } + + $newProductFlat->save(); + } + + /** + * @param $originalProduct + * @param $copiedProduct + */ + private function persistRelations($originalProduct, $copiedProduct): void + { + foreach ($originalProduct->categories as $category) { + $copiedProduct->categories()->save($category); + } + + foreach ($originalProduct->inventories as $inventory) { + $copiedProduct->inventories()->save($inventory); + } + + foreach ($originalProduct->customer_group_prices as $customer_group_price) { + $copiedProduct->customer_group_prices()->save($customer_group_price); + } + } } \ No newline at end of file