Skip to content
This repository has been archived by the owner on Mar 9, 2023. It is now read-only.

Commit

Permalink
Add clone functionality for products and product-variants
Browse files Browse the repository at this point in the history
Add clone functionality for products and product-variants by PR #6.
  • Loading branch information
Ruben Beglaryan authored and Antonio Mansilla committed Feb 5, 2019
1 parent fb754a8 commit 2a36265
Show file tree
Hide file tree
Showing 17 changed files with 473 additions and 116 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@
## Functionalities ##

* Clone product model


# 1.1.0 #

## Functionalities ##

* Clone products and variation products

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This bundle is aimed to offer product clone functionality within Akeneo PIM.

## Functionalities ##
* Clone a product model
* Clone a product or a variant product


## Installation ##
Expand Down Expand Up @@ -61,7 +62,7 @@ yarn run webpack
```

## How to use it ##
Open a product from type **product model** and there open the **options dialog** at the **right corner**.
Open a product and there open the **options dialog** at the **right corner**.
You can see it here on the screen:
![Product Model Clone Screen](https://raw.githubusercontent.com/Flagbit/akeneo-product-cloner/master/screens/product_model_clone.png "Product Model Clone Screen")

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
}
],
"require": {
"akeneo/pim-community-dev": "^2.0"
"akeneo/pim-community-dev": "^2.0",
"ext-json": "*"
},
"require-dev": {
"phpspec/phpspec": "*",
Expand Down
35 changes: 35 additions & 0 deletions src/Controller/AbstractController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Flagbit\Bundle\ProductClonerBundle\Controller;

use Pim\Component\Catalog\Model\EntityWithFamilyVariantInterface;
use Pim\Component\Catalog\Repository\AttributeRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

abstract class AbstractController extends Controller
{

abstract protected function getNormalizer(): NormalizerInterface;
abstract protected function getAttributeRepository(): AttributeRepositoryInterface;

protected function normalizeProduct(EntityWithFamilyVariantInterface $product)
{
$normalizedProduct = $this->getNormalizer()->normalize($product, 'standard');

while ($parent = $product->getParent()) {
foreach ($parent->getValuesForVariation() as $value) {
//this workaround removes the attributes of all parent models, as the getValues() Method,
// which is called by the normalizer, returns all Values including the values of the parent Model
unset($normalizedProduct['values'][$value->getAttribute()->getCode()]);
}
$product = $parent;
};

foreach ($this->getAttributeRepository()->findUniqueAttributeCodes() as $attributeCode) {
unset($normalizedProduct['values'][$attributeCode]);
}
unset($normalizedProduct['identifier']);
return $normalizedProduct;
}
}
215 changes: 215 additions & 0 deletions src/Controller/ProductController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<?php

namespace Flagbit\Bundle\ProductClonerBundle\Controller;

use Akeneo\Component\StorageUtils\Saver\SaverInterface;
use Akeneo\Component\StorageUtils\Updater\ObjectUpdaterInterface;
use Oro\Bundle\SecurityBundle\Annotation\AclAncestor;
use Pim\Bundle\UserBundle\Context\UserContext;
use Pim\Component\Catalog\Builder\ProductBuilderInterface;
use Pim\Component\Catalog\Comparator\Filter\FilterInterface;
use Pim\Component\Catalog\Localization\Localizer\AttributeConverterInterface;
use Pim\Component\Catalog\Model\ProductInterface;
use Pim\Component\Catalog\Repository\AttributeRepositoryInterface;
use Pim\Component\Catalog\Repository\ProductRepositoryInterface;
use Pim\Component\Enrich\Converter\ConverterInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class ProductController extends AbstractController
{
/**
* @var ProductRepositoryInterface
*/
private $productRepository;
/**
* @var ObjectUpdaterInterface
*/
private $productUpdater;
/**
* @var SaverInterface
*/
private $productSaver;
/**
* @var NormalizerInterface
*/
private $normalizer;
/**
* @var ValidatorInterface
*/
private $validator;
/**
* @var UserContext
*/
private $userContext;
/**
* @var ProductBuilderInterface
*/
private $productBuilder;
/**
* @var AttributeConverterInterface
*/
private $localizedConverter;
/**
* @var FilterInterface
*/
private $emptyValuesFilter;
/**
* @var ConverterInterface
*/
private $productValueConverter;
/**
* @var NormalizerInterface
*/
private $constraintViolationNormalizer;
/**
* @var ProductBuilderInterface
*/
private $variantProductBuilder;
/**
* @var AttributeRepositoryInterface
*/
private $attributeRepository;

public function __construct(
ProductRepositoryInterface $productRepository,
AttributeRepositoryInterface $attributeRepository,
ObjectUpdaterInterface $productUpdater,
SaverInterface $productSaver,
NormalizerInterface $normalizer,
ValidatorInterface $validator,
UserContext $userContext,
ProductBuilderInterface $productBuilder,
AttributeConverterInterface $localizedConverter,
FilterInterface $emptyValuesFilter,
ConverterInterface $productValueConverter,
NormalizerInterface $constraintViolationNormalizer,
ProductBuilderInterface $variantProductBuilder
) {

$this->productRepository = $productRepository;
$this->productUpdater = $productUpdater;
$this->productSaver = $productSaver;
$this->normalizer = $normalizer;
$this->validator = $validator;
$this->userContext = $userContext;
$this->productBuilder = $productBuilder;
$this->localizedConverter = $localizedConverter;
$this->emptyValuesFilter = $emptyValuesFilter;
$this->productValueConverter = $productValueConverter;
$this->constraintViolationNormalizer = $constraintViolationNormalizer;
$this->variantProductBuilder = $variantProductBuilder;
$this->attributeRepository = $attributeRepository;
}
/**
* @param Request $request
*
* @AclAncestor("pim_enrich_product_model_create")
*
* @return JsonResponse
*/
public function cloneAction(Request $request) : JsonResponse
{
$data = json_decode($request->getContent(), true);

// check 'code_to_clone' is provided otherwise HTTP bad request
if (false === isset($data['code_to_clone'])) {
return new JsonResponse('Field "code_to_clone" is missing.', Response::HTTP_BAD_REQUEST);
}

// check whether product to be cloned is found otherwise not found HTTP
$product = $this->productRepository->findOneByIdentifier($data['code_to_clone']);
if (null === $product) {
return new JsonResponse(
sprintf('Product model with code %s could not be found.', $data['code_to_clone']),
Response::HTTP_NOT_FOUND
);
}
unset($data['code_to_clone']);
if (isset($data['parent'])) {
// TODO: remove this as soon as support of 2.1 is dropped
$cloneProduct = $this->variantProductBuilder->createProduct();
} else {
$cloneProduct = $this->productBuilder->createProduct(
$data['code']
);
unset($data['code']);
}

// clone product using Akeneo normalizer
$normalizedProduct = $this->normalizeProduct($product);

$normalizedProduct = $this->removeIdentifierAttributeValue($normalizedProduct);
$this->productUpdater->update($cloneProduct, $normalizedProduct);
if (!empty($data['values'])) {
$this->updateProduct($cloneProduct, $data);
}
// validate product model clone and return violations if found
$violations = $this->validator->validate($cloneProduct);
if (count($violations) > 0) {
$normalizedViolations = [];
foreach ($violations as $violation) {
$violation = $this->constraintViolationNormalizer->normalize(
$violation,
'internal_api',
['product' => $cloneProduct]
);
$normalizedViolations[] = $violation;
}

return new JsonResponse(['values' => $normalizedViolations], Response::HTTP_BAD_REQUEST);
}

$this->productSaver->save($cloneProduct);

return new JsonResponse();
}

private function removeIdentifierAttributeValue(array $data): array
{
unset($data['identifier']);
$identifierAttributeCode = $this->attributeRepository->getIdentifier()->getCode();

if (isset($data['values'][$identifierAttributeCode])) {
unset($data['values'][$identifierAttributeCode]);
}
return $data;
}
/**
* Updates product with the provided request data
*
* @param ProductInterface $product
* @param array $data
*/
private function updateProduct(ProductInterface $product, array $data)
{
$values = $this->productValueConverter->convert($data['values']);

$values = $this->localizedConverter->convertToDefaultFormats($values, [
'locale' => $this->userContext->getUiLocale()->getCode()
]);

$dataFiltered = $this->emptyValuesFilter->filter($product, ['values' => $values]);

if (!empty($dataFiltered)) {
$data = array_replace($data, $dataFiltered);
} else {
$data['values'] = [];
}

$this->productUpdater->update($product, $data);
}

protected function getNormalizer(): NormalizerInterface
{
return $this->normalizer;
}

protected function getAttributeRepository(): AttributeRepositoryInterface
{
return $this->attributeRepository;
}
}
42 changes: 29 additions & 13 deletions src/Controller/ProductModelController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
use Akeneo\Component\StorageUtils\Saver\SaverInterface;
use Akeneo\Component\StorageUtils\Updater\ObjectUpdaterInterface;
use Oro\Bundle\SecurityBundle\Annotation\AclAncestor;
use Pim\Component\Catalog\Repository\AttributeRepositoryInterface;
use Pim\Component\Catalog\Repository\ProductModelRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class ProductModelController extends Controller
class ProductModelController extends AbstractController
{
/**
* @var ProductModelRepositoryInterface
Expand Down Expand Up @@ -50,20 +50,26 @@ class ProductModelController extends Controller
* @var NormalizerInterface
*/
private $violationNormalizer;
/**
* @var AttributeRepositoryInterface
*/
private $attributeRepository;

/**
* DefaultController constructor.
*
* @param ProductModelRepositoryInterface $productModelRepository
* @param NormalizerInterface $normalizer
* @param SimpleFactoryInterface $productModelFactory
* @param ObjectUpdaterInterface $productModelUpdater
* @param SaverInterface $productModelSaver
* @param ValidatorInterface $validator
* @param NormalizerInterface $violiationNormalizer
* @param NormalizerInterface $normalizer
* @param SimpleFactoryInterface $productModelFactory
* @param ObjectUpdaterInterface $productModelUpdater
* @param SaverInterface $productModelSaver
* @param ValidatorInterface $validator
* @param NormalizerInterface $violiationNormalizer
* @param AttributeRepositoryInterface $attributeRepository
*/
public function __construct(
ProductModelRepositoryInterface $productModelRepository,
AttributeRepositoryInterface $attributeRepository,
NormalizerInterface $normalizer,
SimpleFactoryInterface $productModelFactory,
ObjectUpdaterInterface $productModelUpdater,
Expand All @@ -78,6 +84,7 @@ public function __construct(
$this->productModelSaver = $productModelSaver;
$this->validator = $validator;
$this->violationNormalizer = $violiationNormalizer;
$this->attributeRepository = $attributeRepository;
}

/**
Expand All @@ -104,16 +111,15 @@ public function cloneAction(Request $request) : JsonResponse
Response::HTTP_NOT_FOUND
);
}

unset($content['code_to_clone']);
// create a new product model
$cloneProductModel = $this->productModelFactory->create();

// clone product using Akeneo normalizer and updater for reusing code
$normalizedProduct = $this->normalizer->normalize($productModel, 'standard');
$normalizedProduct = $this->normalizeProduct($productModel);
$this->productModelUpdater->update($cloneProductModel, $normalizedProduct);
// set the new product model identifier 'code'
$cloneProductModel->setCode(isset($content['code']) ? $content['code'] : '');

$this->productModelUpdater->update($cloneProductModel, $content);
$cloneProductModel->setCode($content['code']);
// validate product model clone and return violations if found
$violations = $this->validator->validate($cloneProductModel);
if (count($violations) > 0) {
Expand All @@ -134,4 +140,14 @@ public function cloneAction(Request $request) : JsonResponse

return new JsonResponse();
}

protected function getNormalizer(): NormalizerInterface
{
return $this->normalizer;
}

protected function getAttributeRepository(): AttributeRepositoryInterface
{
return $this->attributeRepository;
}
}
Loading

0 comments on commit 2a36265

Please sign in to comment.