diff --git a/admin-dev/themes/new-theme/scss/pages/_attribute.scss b/admin-dev/themes/new-theme/scss/pages/_attribute.scss index 41200282d8094..f920fd7bab8d6 100644 --- a/admin-dev/themes/new-theme/scss/pages/_attribute.scss +++ b/admin-dev/themes/new-theme/scss/pages/_attribute.scss @@ -4,3 +4,11 @@ height: 25px; border: solid 1px $black; } + +.image-container { + width: auto; + max-width: 100%; + height: auto; + max-height: 100%; + margin: auto; +} diff --git a/src/Adapter/Attribute/CommandHandler/AddAttributeHandler.php b/src/Adapter/Attribute/CommandHandler/AddAttributeHandler.php index 9b1194fa6e84d..90da43bd50676 100644 --- a/src/Adapter/Attribute/CommandHandler/AddAttributeHandler.php +++ b/src/Adapter/Attribute/CommandHandler/AddAttributeHandler.php @@ -30,6 +30,7 @@ use PrestaShop\PrestaShop\Adapter\Attribute\Repository\AttributeRepository; use PrestaShop\PrestaShop\Adapter\Attribute\Validate\AttributeValidator; +use PrestaShop\PrestaShop\Adapter\File\Uploader\AttributeFileUploader; use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler; use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\Command\AddAttributeCommand; use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\CommandHandler\AddAttributeHandlerInterface; @@ -45,6 +46,7 @@ class AddAttributeHandler implements AddAttributeHandlerInterface public function __construct( private readonly AttributeRepository $attributeRepository, private readonly AttributeValidator $attributeValidator, + private readonly AttributeFileUploader $attributeFileUploader ) { } @@ -68,6 +70,13 @@ public function handle(AddAttributeCommand $command): AttributeId $id = $this->attributeRepository->add($attribute); + if (null !== $command->getTextureFilePath()) { + $this->attributeFileUploader->upload( + $command->getTextureFilePath(), + $id->getValue() + ); + } + return $id; } } diff --git a/src/Adapter/Attribute/CommandHandler/EditAttributeHandler.php b/src/Adapter/Attribute/CommandHandler/EditAttributeHandler.php index b0fa24fc2b392..580134a8e1c17 100644 --- a/src/Adapter/Attribute/CommandHandler/EditAttributeHandler.php +++ b/src/Adapter/Attribute/CommandHandler/EditAttributeHandler.php @@ -31,6 +31,7 @@ use PrestaShop\PrestaShop\Adapter\Attribute\Repository\AttributeRepository; use PrestaShop\PrestaShop\Adapter\Attribute\Validate\AttributeValidator; use PrestaShop\PrestaShop\Adapter\Domain\LocalizedObjectModelTrait; +use PrestaShop\PrestaShop\Adapter\File\Uploader\AttributeFileUploader; use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler; use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\Command\EditAttributeCommand; use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\CommandHandler\EditAttributeHandlerInterface; @@ -45,7 +46,8 @@ class EditAttributeHandler implements EditAttributeHandlerInterface public function __construct( private AttributeRepository $attributeRepository, - private AttributeValidator $attributeValidator + private AttributeValidator $attributeValidator, + private AttributeFileUploader $attributeFileUploader ) { } @@ -76,6 +78,11 @@ public function handle(EditAttributeCommand $command): void $propertiesToUpdate[] = 'id_shop_list'; } + if (null !== $command->getTextureFilePath()) { + $this->attributeFileUploader->deleteOldFile($command->getAttributeId()->getValue()); + $this->attributeFileUploader->upload($command->getTextureFilePath(), $command->getAttributeId()->getValue()); + } + $this->attributeValidator->validate($attribute); $this->attributeRepository->partialUpdate($attribute, $propertiesToUpdate); } diff --git a/src/Adapter/File/Uploader/AttributeFileUploader.php b/src/Adapter/File/Uploader/AttributeFileUploader.php new file mode 100644 index 0000000000000..0711429f54b05 --- /dev/null +++ b/src/Adapter/File/Uploader/AttributeFileUploader.php @@ -0,0 +1,52 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Adapter\File\Uploader; + +use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\AttributeFileUploaderInterface; +use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\Exception\AttributeUploadFailedException; +use PrestaShop\PrestaShop\Core\File\Exception\FileException; + +class AttributeFileUploader implements AttributeFileUploaderInterface +{ + public function upload(string $filePath, int $id): void + { + try { + if (file_exists($filePath)) { + move_uploaded_file($filePath, _PS_IMG_DIR_ . 'co/' . $id . '.jpg'); + } + } catch (FileException $e) { + throw new AttributeUploadFailedException(sprintf('Failed to copy the file %s.', $filePath)); + } + } + + public function deleteOldFile(int $id): void + { + if (file_exists(_PS_IMG_DIR_ . 'co/' . $id . '.jpg')) { + unlink(_PS_IMG_DIR_ . 'co/' . $id . '.jpg'); + } + } +} diff --git a/src/Core/Domain/AttributeGroup/Attribute/AttributeFileUploaderInterface.php b/src/Core/Domain/AttributeGroup/Attribute/AttributeFileUploaderInterface.php new file mode 100644 index 0000000000000..6298f0a32b147 --- /dev/null +++ b/src/Core/Domain/AttributeGroup/Attribute/AttributeFileUploaderInterface.php @@ -0,0 +1,36 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute; + +interface AttributeFileUploaderInterface +{ + /** + * @param string $filePath + * @param int $id + */ + public function upload(string $filePath, int $id): void; +} diff --git a/src/Core/Domain/AttributeGroup/Attribute/Command/AddAttributeCommand.php b/src/Core/Domain/AttributeGroup/Attribute/Command/AddAttributeCommand.php index b6567a935b4e2..6cd99f22c2dcd 100644 --- a/src/Core/Domain/AttributeGroup/Attribute/Command/AddAttributeCommand.php +++ b/src/Core/Domain/AttributeGroup/Attribute/Command/AddAttributeCommand.php @@ -56,6 +56,11 @@ class AddAttributeCommand */ private $associatedShopIds; + /** + * @var string|null + */ + private $pathName; + /** * @param int $attributeGroupId * @param array $localizedValue @@ -105,6 +110,20 @@ public function getAssociatedShopIds(): array return $this->associatedShopIds; } + /** + * @param string $pathName + */ + public function setTextureFilePath( + string $pathName, + ): void { + $this->pathName = $pathName; + } + + public function getTextureFilePath(): ?string + { + return $this->pathName; + } + /** * Asserts that attribute group's names are valid. * diff --git a/src/Core/Domain/AttributeGroup/Attribute/Command/EditAttributeCommand.php b/src/Core/Domain/AttributeGroup/Attribute/Command/EditAttributeCommand.php index 4009dfeeda2db..a0daddb0f05ec 100644 --- a/src/Core/Domain/AttributeGroup/Attribute/Command/EditAttributeCommand.php +++ b/src/Core/Domain/AttributeGroup/Attribute/Command/EditAttributeCommand.php @@ -46,6 +46,8 @@ class EditAttributeCommand private ?string $color; + private ?string $pathName; + /** * @var int[] */ @@ -125,6 +127,20 @@ public function setAssociatedShopIds(array $associatedShopIds): self return $this; } + /** + * @param string $pathName + */ + public function setTextureFilePath( + string $pathName, + ): void { + $this->pathName = $pathName; + } + + public function getTextureFilePath(): ?string + { + return $this->pathName ?? null; + } + /** * Asserts that attribute group's names are valid. * diff --git a/src/Core/Domain/AttributeGroup/Attribute/Exception/AttributeUploadFailedException.php b/src/Core/Domain/AttributeGroup/Attribute/Exception/AttributeUploadFailedException.php new file mode 100644 index 0000000000000..3e6ecd1138491 --- /dev/null +++ b/src/Core/Domain/AttributeGroup/Attribute/Exception/AttributeUploadFailedException.php @@ -0,0 +1,31 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\Exception; + +class AttributeUploadFailedException extends AttributeException +{ +} diff --git a/src/Core/Form/IdentifiableObject/DataHandler/AttributeFormDataHandler.php b/src/Core/Form/IdentifiableObject/DataHandler/AttributeFormDataHandler.php index 7f61f0689d2b7..e87e6f947341c 100644 --- a/src/Core/Form/IdentifiableObject/DataHandler/AttributeFormDataHandler.php +++ b/src/Core/Form/IdentifiableObject/DataHandler/AttributeFormDataHandler.php @@ -33,7 +33,6 @@ use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\Command\EditAttributeCommand; use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\ValueObject\AttributeId; use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\File\File; /** * Handles data of submitted Attribute Group form. @@ -58,14 +57,24 @@ public function __construct(CommandBusInterface $commandBus) */ public function create(array $data) { - /** @var AttributeId $attributeId */ - $attributeId = $this->commandBus->handle(new AddAttributeCommand( + $addAttributeCommand = new AddAttributeCommand( $data['attribute_group'], $data['name'], $data['color'] ?? '', $data['shop_association'], - )); - $this->handleUploadedFile($data['texture'], $attributeId->getValue()); + ); + + if (isset($data['texture'])) { + /** @var UploadedFile $file */ + $file = $data['texture']; + + $addAttributeCommand->setTextureFilePath( + $file->getPathname() + ); + } + + /** @var AttributeId $attributeId */ + $attributeId = $this->commandBus->handle($addAttributeCommand); return $attributeId->getValue(); } @@ -81,17 +90,15 @@ public function update($id, array $data) ->setColor($data['color']) ->setAssociatedShopIds($data['shop_association']); - $this->commandBus->handle($updateCommand); + if (isset($data['texture'])) { + /** @var UploadedFile $file */ + $file = $data['texture']; - // delete previous img if exist - if (file_exists(_PS_IMG_DIR_ . 'co' . '/' . $id . '.jpg')) { - unlink(_PS_IMG_DIR_ . 'co' . '/' . $id . '.jpg'); + $updateCommand->setTextureFilePath( + $file->getPathname() + ); } - $this->handleUploadedFile($data['texture'], $id); - } - protected function handleUploadedFile(UploadedFile $file, int $id): void - { - $file->move(_PS_IMG_DIR_ . 'co', $id . '.jpg'); + $this->commandBus->handle($updateCommand); } } diff --git a/src/Core/Grid/Data/Factory/AttributeGridDataFactory.php b/src/Core/Grid/Data/Factory/AttributeGridDataFactory.php index 528ba6ace5153..0f27d57e92bdb 100644 --- a/src/Core/Grid/Data/Factory/AttributeGridDataFactory.php +++ b/src/Core/Grid/Data/Factory/AttributeGridDataFactory.php @@ -26,31 +26,23 @@ namespace PrestaShop\PrestaShop\Core\Grid\Data\Factory; +use PrestaShop\PrestaShop\Adapter\Shop\Url\ImageFolderProvider; use PrestaShop\PrestaShop\Core\Grid\Data\GridData; use PrestaShop\PrestaShop\Core\Grid\Record\RecordCollection; use PrestaShop\PrestaShop\Core\Grid\Search\SearchCriteriaInterface; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** * Decorates database records for grid presentation */ final class AttributeGridDataFactory implements GridDataFactoryInterface { - /** - * @var GridDataFactoryInterface - */ - private $attributeDataFactory; - - - private UrlGeneratorInterface $urlGenerator; - /** * @param GridDataFactoryInterface $attributeDataFactory */ - public function __construct(GridDataFactoryInterface $attributeDataFactory, UrlGeneratorInterface $urlGenerator) - { - $this->attributeDataFactory = $attributeDataFactory; - $this->urlGenerator = $urlGenerator; + public function __construct( + private readonly GridDataFactoryInterface $attributeDataFactory, + private readonly ImageFolderProvider $imageFolderProvider + ) { } /** @@ -76,8 +68,8 @@ public function getData(SearchCriteriaInterface $searchCriteria) private function modifyRecords(array $records): array { foreach ($records as &$record) { - if (file_exists(_PS_IMG_DIR_ . 'co' . '/' . (int) $record['id_attribute'] . '.jpg')) { - $record['texture'] = $this->urlGenerator->generate('assets', ['path' => _PS_IMG_DIR_ . 'co' . '/' . (int) $record['id_attribute'] . '.jpg']); + if (file_exists(_PS_IMG_DIR_ . 'co/' . (int) $record['id_attribute'] . '.jpg')) { + $record['texture'] = $this->imageFolderProvider->getUrl() . '/' . (int) $record['id_attribute'] . '.jpg'; } } diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/attribute.yml b/src/PrestaShopBundle/Resources/config/services/adapter/attribute.yml index 460c9b9f90cf5..35679c994536f 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/attribute.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/attribute.yml @@ -15,3 +15,5 @@ services: PrestaShop\PrestaShop\Adapter\Attribute\CommandHandler\EditAttributeHandler: ~ PrestaShop\PrestaShop\Adapter\Attribute\QueryHandler\GetAttributeForEditingHandler: ~ PrestaShop\PrestaShop\Adapter\Attribute\Validate\AttributeValidator: ~ + PrestaShop\PrestaShop\Adapter\File\Uploader\AttributeFileUploader: ~ + PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\AttributeFileUploaderInterface: ~ diff --git a/src/PrestaShopBundle/Resources/config/services/adapter/shop.yml b/src/PrestaShopBundle/Resources/config/services/adapter/shop.yml index 68e7ba04e3cb4..8bd67691aaee6 100644 --- a/src/PrestaShopBundle/Resources/config/services/adapter/shop.yml +++ b/src/PrestaShopBundle/Resources/config/services/adapter/shop.yml @@ -65,6 +65,12 @@ services: - "@=service('prestashop.adapter.legacy.context').getContext().link" - 'img/p' + prestashop.adapter.shop.url.attribute_image_folder_provider: + class: 'PrestaShop\PrestaShop\Adapter\Shop\Url\ImageFolderProvider' + arguments: + - "@=service('prestashop.adapter.legacy.context').getContext().link" + - 'img/co' + prestashop.adapter.shop.url.category_image_folder_provider: class: 'PrestaShop\PrestaShop\Adapter\Shop\Url\ImageFolderProvider' arguments: diff --git a/src/PrestaShopBundle/Resources/config/services/core/grid/grid_data_factory.yml b/src/PrestaShopBundle/Resources/config/services/core/grid/grid_data_factory.yml index 989ae5d869366..4819f87c37bb4 100644 --- a/src/PrestaShopBundle/Resources/config/services/core/grid/grid_data_factory.yml +++ b/src/PrestaShopBundle/Resources/config/services/core/grid/grid_data_factory.yml @@ -397,7 +397,7 @@ services: class: 'PrestaShop\PrestaShop\Core\Grid\Data\Factory\AttributeGridDataFactory' arguments: - '@prestashop.core.grid.data.factory.attribute' - - '@prestashop.core.admin.url_generator' + - '@prestashop.adapter.shop.url.attribute_image_folder_provider' prestashop.core.grid.data.factory.attribute_group: class: '%prestashop.core.grid.data.factory.doctrine_grid_data_factory%' diff --git a/src/PrestaShopBundle/Resources/views/Admin/Common/Grid/Columns/Content/attribute_color.html.twig b/src/PrestaShopBundle/Resources/views/Admin/Common/Grid/Columns/Content/attribute_color.html.twig index 1194dd7c58791..cf807d82b1d79 100644 --- a/src/PrestaShopBundle/Resources/views/Admin/Common/Grid/Columns/Content/attribute_color.html.twig +++ b/src/PrestaShopBundle/Resources/views/Admin/Common/Grid/Columns/Content/attribute_color.html.twig @@ -23,7 +23,9 @@ * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) *#} {% if record['texture'] is defined %} - +
+ +
{% else %}
{% endif %} diff --git a/tests/Integration/Behaviour/Features/Context/Domain/AttributeFeatureContext.php b/tests/Integration/Behaviour/Features/Context/Domain/AttributeFeatureContext.php index 5c5167c60ea5d..16ca580a3e999 100644 --- a/tests/Integration/Behaviour/Features/Context/Domain/AttributeFeatureContext.php +++ b/tests/Integration/Behaviour/Features/Context/Domain/AttributeFeatureContext.php @@ -41,6 +41,7 @@ use PrestaShop\PrestaShop\Core\Domain\AttributeGroup\Attribute\ValueObject\AttributeId; use RuntimeException; use Tests\Integration\Behaviour\Features\Context\Util\NoExceptionAlthoughExpectedException; +use Tests\Resources\DummyFileUploader; class AttributeFeatureContext extends AbstractDomainFeatureContext { @@ -57,7 +58,8 @@ public function createAttribute(string $reference, TableNode $node): void $attributeGroupId, $properties['name'], $properties['color'], - $this->referencesToIds($properties['shopIds']) + $this->referencesToIds($properties['shopIds']), + $properties['texture'] ); $this->getSharedStorage()->set($reference, $attributeId->getValue()); @@ -104,6 +106,10 @@ public function editAttribute(string $reference, TableNode $node): void if (isset($properties['shopIds'])) { $command->setAssociatedShopIds($this->referencesToIds($properties['shopIds'])); } + if (isset($properties['texture']) && 'null' !== $properties['texture']) { + $file = DummyFileUploader::upload($properties['texture']); + $command->setTextureFilePath($file); + } try { $this->getCommandBus()->handle($command); @@ -149,7 +155,8 @@ private function createAttributeUsingCommand( int $attributeGroupId, array $localizedValues, string $color, - array $shopIds + array $shopIds, + string $filePath ): AttributeId { $command = new AddAttributeCommand( $attributeGroupId, @@ -158,6 +165,11 @@ private function createAttributeUsingCommand( $shopIds ); + if ('null' !== $filePath) { + $file = DummyFileUploader::upload($filePath); + $command->setTextureFilePath($file); + } + return $this->getCommandBus()->handle($command); } diff --git a/tests/Integration/Behaviour/Features/Scenario/Attribute/attribute_management.feature b/tests/Integration/Behaviour/Features/Scenario/Attribute/attribute_management.feature index e9532d4276831..40c1471644359 100644 --- a/tests/Integration/Behaviour/Features/Scenario/Attribute/attribute_management.feature +++ b/tests/Integration/Behaviour/Features/Scenario/Attribute/attribute_management.feature @@ -37,12 +37,14 @@ Feature: Attribute group management | name[fr-FR] | Couleur | | color | #44DB6A | | shopIds | shop1,shop2,shop4 | + | texture | null | Then attribute "attribute1" should have the following properties: | attribute_group | attributeGroup1 | | name[en-US] | Color | | name[fr-FR] | Couleur | | color | #44DB6A | | shopIds | shop1,shop2,shop4 | + | texture | null | Scenario: Editing attribute When I edit attribute "attribute1" with specified properties: @@ -51,12 +53,14 @@ Feature: Attribute group management | name[fr-FR] | Couleures | | color | #44DB6B | | shopIds | shop4 | + | texture | null | Then attribute "attribute1" should have the following properties: | attribute_group | attributeGroup2 | | name[en-US] | Colores | | name[fr-FR] | Couleures | | color | #44DB6B | | shopIds | shop4 | + | texture | null | # Check partial updates When I edit attribute "attribute1" with specified properties: | attribute_group | attributeGroup1 | @@ -101,8 +105,39 @@ Feature: Attribute group management | name[fr-FR] | Couleur | | color | wrong_color | | shopIds | shop1,shop2,shop4 | + | texture | null | Then I should get an error that attribute field color value is invalid + Scenario: Adding new attribute with a texture + When I create attribute "attribute3" with specified properties: + | attribute_group | attributeGroup1 | + | name[en-US] | Color | + | name[fr-FR] | Couleur | + | color | #44DB6B | + | shopIds | shop4 | + | texture | logo.jpg | + Then attribute "attribute3" should have the following properties: + | attribute_group | attributeGroup1 | + | name[en-US] | Color | + | name[fr-FR] | Couleur | + | color | #44DB6B | + | shopIds | shop4 | + | texture | logo.jpg | + When I edit attribute "attribute3" with specified properties: + | attribute_group | attributeGroup1 | + | name[en-US] | Color | + | name[fr-FR] | Couleur | + | color | #44DB6B | + | shopIds | shop4 | + | texture | app_icon.png | + Then attribute "attribute3" should have the following properties: + | attribute_group | attributeGroup1 | + | name[en-US] | Color | + | name[fr-FR] | Couleur | + | color | #44DB6B | + | shopIds | shop4 | + | texture | app_icon.png | + Scenario: Deleting attribute When I delete attribute "attribute1" Then attribute "attribute1" should be deleted