[11/06/2024] Les premières modifs + installation de quelques modules indispensables

This commit is contained in:
2024-06-11 14:57:59 +02:00
parent 5ac5653ae5
commit 77cf2c7cc6
1626 changed files with 171457 additions and 131 deletions

View File

@@ -0,0 +1,7 @@
name: "Auto Release"
on:
push:
branches: [ master, main ]
jobs:
release:
uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main

View File

@@ -0,0 +1,2 @@
node_modules
.DS_Store

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run build

View File

@@ -0,0 +1,72 @@
<?php
namespace TheliaLibrary\ApiExtend;
use OpenApi\Events\ModelExtendDataEvent;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Model\Api\Product;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use TheliaLibrary\Model\LibraryItemImage;
use TheliaLibrary\Model\LibraryItemImageQuery;
class ProductApiListener implements EventSubscriberInterface
{
public function __construct(
private ModelFactory $modelFactory,
private RequestStack $requestStack
){
}
/**
* @OA\Schema(
* schema="TheliaLibraryExtendProduct",
* @OA\Property(
* property="libraryImages",
* type="array",
* @OA\Items(
* ref="#/components/schemas/LibraryItemImage"
* )
* )
* )
*/
public function addImageToProduct(ModelExtendDataEvent $event)
{
/** @var Product $product */
$product = $event->getModel();
$productImage = LibraryItemImageQuery::create()
->filterByItemType('product')
->filterByItemId($product->getId())
->filterByVisible(1)
->find();
$request = $this->requestStack->getCurrentRequest();
$locale = $request->get('locale');
if (null == $locale) {
$locale = $request->getLocale();
}
$images = array_map(
function (LibraryItemImage $itemImage) use ($locale) {
return $this->modelFactory->buildModel('LibraryItemImage', $itemImage, $locale);
},
iterator_to_array($productImage)
);
if (!empty($images)) {
$event->setExtendDataKeyValue('libraryImages', $images);
}
}
public static function getSubscribedEvents()
{
$events = [];
if (class_exists('OpenApi\Events\ModelExtendDataEvent')){
$events[ModelExtendDataEvent::ADD_EXTEND_DATA_PREFIX.'product'] = ['addImageToProduct',0];
}
return $events;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Command;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
use Thelia\Command\ContainerAwareCommand;
use Thelia\Model\ConfigQuery;
use Thelia\Model\LangQuery;
use TheliaLibrary\Service\LibraryItemImageService;
class ImageMigrateCommand extends ContainerAwareCommand
{
protected LibraryItemImageService $libraryItemImageService;
public function __construct(LibraryItemImageService $libraryItemImageService)
{
parent::__construct();
$this->libraryItemImageService = $libraryItemImageService;
}
protected function configure(): void
{
$this
->setName('library:image:migrate')
->setDescription('Reset all password and send reset link')
->addOption(
'itemTypes',
null,
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Only image of this item type will be migrated',
[]
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$filesystem = new Filesystem();
$itemTypes = $input->getOption('itemTypes');
$baseSourceFilePath = ConfigQuery::read('images_library_path');
if ($baseSourceFilePath === null) {
$baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'images';
} else {
$baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath;
}
$langs = LangQuery::create()
->find();
foreach ($itemTypes as $itemType) {
$output->writeln('<bg=blue>============================================================= </>');
$output->writeln("<fg=blue>Image migration for item type $itemType started</>");
/** @var ModelCriteria $queryClass */
$queryClass = 'Thelia\\Model\\'.ucfirst($itemType).'ImageQuery';
/** @var ModelCriteria $query */
$images = $queryClass::create()->find();
$output->writeln(\count($images)." image found for type $itemType");
$progressBar = new ProgressBar($output, \count($images));
$progressBar->start();
$itemIdGetter = 'get'.ucfirst($itemType).'Id';
foreach ($images as $image) {
$tmpFilePath = '/tmp/image/'.$image->getFile();
$filesystem->copy($baseSourceFilePath.DS.$itemType.DS.$image->getFile(), $tmpFilePath);
$uploadedFile = new File(
$tmpFilePath
);
$itemImage = $this->libraryItemImageService->createAndAssociateImage(
$uploadedFile,
$image->getTitle(),
$image->getLocale(),
$itemType,
$image->$itemIdGetter(),
'thelia',
$image->getVisible(),
$image->getPosition()
);
$libraryImage = $itemImage->getLibraryImage();
$libraryFilePath = $libraryImage->getFileName();
foreach ($langs as $lang) {
$image->setLocale($lang->getLocale());
if (empty($image->getTitle())) {
continue;
}
$libraryImage->setLocale($lang->getLocale())
->setTitle($image->getTitle())
->setFileName($libraryFilePath)
->save();
}
$progressBar->advance();
}
$progressBar->finish();
$output->writeln('');
$output->writeln("<info>Image migration for item type $itemType ended</info>");
$output->writeln('<bg=blue>============================================================= </>');
}
return 1;
}
}

View File

@@ -0,0 +1,120 @@
# This is a fix for InnoDB in MySQL >= 4.1.x
# It "suspends judgement" for fkey relationships until are tables are set.
SET FOREIGN_KEY_CHECKS = 0;
-- ---------------------------------------------------------------------
-- library_image
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_image`;
CREATE TABLE `library_image`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- library_item_image
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_item_image`;
CREATE TABLE `library_item_image`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`image_id` INTEGER,
`item_type` VARCHAR(255),
`item_id` INTEGER,
`code` VARCHAR(255),
`visible` TINYINT,
`position` INTEGER,
PRIMARY KEY (`id`),
INDEX `library_item_image_item_index` (`item_type`, `item_id`),
INDEX `fi_library_item_image_image_id` (`image_id`),
CONSTRAINT `fk_library_item_image_image_id`
FOREIGN KEY (`image_id`)
REFERENCES `library_image` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- library_tag
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_tag`;
CREATE TABLE `library_tag`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`color_code` VARCHAR(255),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- library_image_tag
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_image_tag`;
CREATE TABLE `library_image_tag`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`image_id` INTEGER,
`tag_id` INTEGER,
PRIMARY KEY (`id`),
INDEX `fi_library_image_tag_image_id` (`image_id`),
INDEX `fi_library_image_tag_tag_id` (`tag_id`),
CONSTRAINT `fk_library_image_tag_image_id`
FOREIGN KEY (`image_id`)
REFERENCES `library_image` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE,
CONSTRAINT `fk_library_image_tag_tag_id`
FOREIGN KEY (`tag_id`)
REFERENCES `library_tag` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- library_image_i18n
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_image_i18n`;
CREATE TABLE `library_image_i18n`
(
`id` INTEGER NOT NULL,
`locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL,
`title` VARCHAR(255),
`file_name` VARCHAR(255),
PRIMARY KEY (`id`,`locale`),
CONSTRAINT `library_image_i18n_fk_0228d9`
FOREIGN KEY (`id`)
REFERENCES `library_image` (`id`)
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- library_tag_i18n
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_tag_i18n`;
CREATE TABLE `library_tag_i18n`
(
`id` INTEGER NOT NULL,
`locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL,
`title` VARCHAR(255),
PRIMARY KEY (`id`,`locale`),
CONSTRAINT `library_tag_i18n_fk_b8e890`
FOREIGN KEY (`id`)
REFERENCES `library_tag` (`id`)
ON DELETE CASCADE
) ENGINE=InnoDB;
# This restores the fkey checks, after having unset them earlier
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns="http://thelia.net/schema/dic/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd">
<loops>
<!-- sample definition
<loop name="MySuperLoop" class="TheliaLibrary\Loop\MySuperLoop" />
-->
</loops>
<forms>
<!--
<form name="MyFormName" class="TheliaLibrary\Form\MySuperForm" />
-->
</forms>
<commands>
<!--
<command class="TheliaLibrary\Command\MySuperCommand" />
-->
</commands>
<services>
<!-- needed to be used in loop... -->
<service id="thelia_library_image" alias="TheliaLibrary\Service\LibraryImageService" public="true"/>
</services>
<hooks>
<hook id="thelialibrary.tb.plugin" class="TheliaLibrary\Hook\BackHook">
<tag name="hook.event_listener" event="thelia.blocks.plugins" type="back" templates="render:tb-plugin/import-plugin.html" />
<tag name="hook.event_listener" event="thelia.blocks.plugincss" type="back" templates="render:tb-plugin/import-styles.html" />
</hook>
<!--
<hook id="thelialibrary.item.editions" class="TheliaLibrary\Hook\BackHook">
<tag name="hook.event_listener" event="item.edition.images" type="back" method="onItemEdition" />
</hook>
-->
</hooks>
<!--
<exports>
</exports>
-->
<!--
<imports>
</imports>
-->
</config>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="http://thelia.net/schema/dic/module"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_2.xsd">
<fullnamespace>TheliaLibrary\TheliaLibrary</fullnamespace>
<descriptive locale="en_US">
<title>A media library for Thelia</title>
<!--
<subtitle></subtitle>
<description></description>
<postscriptum></postscriptum>
-->
</descriptive>
<descriptive locale="fr_FR">
<title>Une médiathéque pour Thelia</title>
</descriptive>
<!-- <logo></logo> -->
<!--<images-folder>images</images-folder>-->
<languages>
<language>en_US</language>
<language>fr_FR</language>
</languages>
<version>1.2.7</version>
<authors>
<author>
<name></name>
<email></email>
</author>
</authors>
<type>classic</type>
<!--
module dependencies
<required>
<module version="&gt;=0.1">Front</module>
<module version="~1.0">HookCart</module>
<module version="&gt;0.2">HookSearch</module>
</required>
-->
<thelia>2.5.0</thelia>
<stability>other</stability>
<mandatory>0</mandatory>
<hidden>0</hidden>
</module>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<!--
if a /admin/module/thelialibrary/ route is provided, a "Configuration" button will be displayed
for the module in the module list. Clicking this button will invoke this route.
<route id="my_route_id" path="/admin/module/thelialibrary">
<default key="_controller">TheliaLibrary\Full\Class\Name\Of\YourConfigurationController::methodName</default>
</route>
<route id="my_route_id" path="/admin/module/thelialibrary/route-name">
<default key="_controller">TheliaLibrary\Full\Class\Name\Of\YourAdminController::methodName</default>
</route>
<route id="my_route_id" path="/my/route/name">
<default key="_controller">TheliaLibrary\Full\Class\Name\Of\YourOtherController::methodName</default>
</route>
...add as many routes as required.
<route>
...
</route>
-->
</routes>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<database defaultIdMethod="native" name="TheliaMain"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../vendor/thelia/propel/resources/xsd/database.xsd" >
<table name="library_image" namespace="TheliaLibrary\Model">
<column name="id" primaryKey="true" required="true" type="INTEGER" autoIncrement="true"/>
<column name="title" type="VARCHAR" />
<column name="file_name" type="VARCHAR" />
<behavior name="i18n">
<parameter name="i18n_columns" value="title, file_name" />
</behavior>
</table>
<table name="library_item_image" namespace="TheliaLibrary\Model">
<column name="id" primaryKey="true" required="true" type="INTEGER" autoIncrement="true"/>
<column name="image_id" type="INTEGER" />
<column name="item_type" type="VARCHAR" />
<column name="item_id" type="INTEGER" />
<column name="code" type="VARCHAR"/>
<column name="visible" type="TINYINT" />
<column name="position" type="INTEGER" />
<foreign-key foreignTable="library_image" name="fk_library_item_image_image_id" onDelete="CASCADE" onUpdate="RESTRICT">
<reference foreign="id" local="image_id" />
</foreign-key>
<index name="library_item_image_item_index">
<index-column name="item_type" />
<index-column name="item_id" />
</index>
</table>
<table name="library_tag" namespace="TheliaLibrary\Model">
<column name="id" primaryKey="true" required="true" type="INTEGER" autoIncrement="true"/>
<column name="title" type="VARCHAR" />
<column name="color_code" type="VARCHAR" />
<behavior name="i18n">
<parameter name="i18n_columns" value="title" />
</behavior>
</table>
<table name="library_image_tag" namespace="TheliaLibrary\Model">
<column name="id" primaryKey="true" required="true" type="INTEGER" autoIncrement="true"/>
<column name="image_id" type="INTEGER" />
<column name="tag_id" type="INTEGER" />
<foreign-key foreignTable="library_image" name="fk_library_image_tag_image_id" onDelete="CASCADE" onUpdate="RESTRICT">
<reference foreign="id" local="image_id" />
</foreign-key>
<foreign-key foreignTable="library_tag" name="fk_library_image_tag_tag_id" onDelete="CASCADE" onUpdate="RESTRICT">
<reference foreign="id" local="tag_id" />
</foreign-key>
</table>
</database>

View File

@@ -0,0 +1,2 @@
# Sqlfile -> Database map
TheliaMain.sql=TheliaMain

View File

@@ -0,0 +1,57 @@
# This is a fix for InnoDB in MySQL >= 4.1.x
# It "suspends judgement" for fkey relationships until are tables are set.
SET FOREIGN_KEY_CHECKS = 0;
-- ---------------------------------------------------------------------
-- library_tag_i18n
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `library_tag`;
CREATE TABLE `library_tag`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`color_code` VARCHAR(255),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
DROP TABLE IF EXISTS `library_image_tag`;
CREATE TABLE `library_image_tag`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`image_id` INTEGER,
`tag_id` INTEGER,
PRIMARY KEY (`id`),
INDEX `fi_library_image_tag_image_id` (`image_id`),
INDEX `fi_library_image_tag_tag_id` (`tag_id`),
CONSTRAINT `fk_library_image_tag_image_id`
FOREIGN KEY (`image_id`)
REFERENCES `library_image` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE,
CONSTRAINT `fk_library_image_tag_tag_id`
FOREIGN KEY (`tag_id`)
REFERENCES `library_tag` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE
) ENGINE=InnoDB;
DROP TABLE IF EXISTS `library_tag_i18n`;
CREATE TABLE `library_tag_i18n`
(
`id` INTEGER NOT NULL,
`locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL,
`title` VARCHAR(255),
PRIMARY KEY (`id`,`locale`),
CONSTRAINT `library_tag_i18n_fk_b8e890`
FOREIGN KEY (`id`)
REFERENCES `library_tag` (`id`)
ON DELETE CASCADE
) ENGINE=InnoDB;
# This restores the fkey checks, after having unset them earlier
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,197 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Admin;
use OpenApi\Annotations as OA;
use OpenApi\Controller\Admin\BaseAdminOpenApiController;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Service\OpenApiService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Core\HttpFoundation\Request;
use TheliaLibrary\Service\LibraryImageService;
/**
* @Route("/open_api/library/image", name="library_image")
*/
class ImageController extends BaseAdminOpenApiController
{
/**
* @Route("", name="_create", methods="POST")
*
* @OA\Post(
* path="/library/image",
* tags={ "Library image"},
* summary="Create a new image",
* @OA\RequestBody(
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* @OA\Property(
* property="title",
* type="string",
* ),
* @OA\Property(
* property="locale",
* type="string",
* ),
* @OA\Property(
* property="image",
* type="string",
* format="binary"
* )
* )
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryImage")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function createImage(
Request $request,
ModelFactory $modelFactory,
LibraryImageService $libraryImageService
) {
$locale = $this->findLocale($request);
$image = $libraryImageService->createImage(
$request->files->get('image'),
$request->request->get('title'),
$locale
);
return OpenApiService::jsonResponse($modelFactory->buildModel('LibraryImage', $image, $locale));
}
// Method POST because patch doesn't work with multipart/form-data
/**
* @Route("/{imageId}", name="_update", methods="POST", requirements={"imageId"="\d+"})
*
* @OA\Post(
* path="/library/image/{imageId}",
* tags={ "Library image"},
* summary="Update an image",
* @OA\Parameter(
* name="imageId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\RequestBody(
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* @OA\Property(
* property="title",
* type="string",
* ),
* @OA\Property(
* property="locale",
* type="string",
* ),
* @OA\Property(
* property="image",
* type="string",
* format="binary"
* )
* )
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryImage")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function updateImage(
$imageId,
Request $request,
ModelFactory $modelFactory,
LibraryImageService $libraryImageService
) {
$locale = $this->findLocale($request);
$image = $libraryImageService->updateImage(
$imageId,
$request->files->get('image'),
$request->request->get('title'),
$locale
);
return OpenApiService::jsonResponse($modelFactory->buildModel('LibraryImage', $image, $locale));
}
/**
* @Route("/{imageId}", name="_delete", methods="DELETE", requirements={"imageId"="\d+"})
*
* @OA\Delete(
* path="/library/image/{imageId}",
* tags={ "Library image"},
* summary="Delete an image",
* @OA\Parameter(
* name="imageId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Response(
* response="204",
* description="Success"
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function deleteImage(
$imageId,
LibraryImageService $libraryImageService
) {
$libraryImageService->deleteImage($imageId);
return new JsonResponse('Success', 204);
}
protected function findLocale(Request $request)
{
$locale = $request->get('locale');
if (null == $locale) {
$locale = $request->getSession()->getAdminEditionLang()->getLocale();
}
return $locale;
}
}

View File

@@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Admin;
use OpenApi\Annotations as OA;
use OpenApi\Controller\Admin\BaseAdminOpenApiController;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Service\OpenApiService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Core\HttpFoundation\Request;
use TheliaLibrary\Model\LibraryImageTag;
use TheliaLibrary\Model\LibraryTagQuery;
use TheliaLibrary\Service\LibraryImageTagService;
/**
* @Route("/open_api/library/image_tag", name="library_image_tag")
*/
class ImageTagController extends BaseAdminOpenApiController
{
/**
* @Route("", name="_associate", methods="POST")
*
* @OA\Post(
* path="/library/image_tag",
* tags={ "Library tag"},
* summary="Associate a tag to an image",
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* @OA\Property(
* property="imageId",
* type="integer",
* ),
* @OA\Property(
* property="tagId",
* type="integer",
* )
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryImageTag")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function createAssociation(
Request $request,
ModelFactory $modelFactory,
LibraryImageTagService $libraryImageTagService
) {
$data = json_decode($request->getContent(), true);
/** @var LibraryImageTag $openApiLibraryImageTag */
$openApiLibraryImageTag = $modelFactory->buildModel('LibraryImageTag', $data);
$openApiLibraryImageTag->validate(self::GROUP_UPDATE);
$image = $libraryImageTagService->associateImage(
$openApiLibraryImageTag->getImageId(),
$openApiLibraryImageTag->getTagId(),
);
$query = LibraryTagQuery::create();
$tag = $query->findOneById($openApiLibraryImageTag->getTagId());
return OpenApiService::jsonResponse(['imageTag' => $modelFactory->buildModel('LibraryImageTag', $image), 'tag' => $modelFactory->buildModel('LibraryTag', $tag)]);
}
/**
* @Route("/{imageTagId}", name="_delete_association", methods="DELETE", requirements={"imageTagId"="\d+"})
*
* @OA\Delete(
* path="/library/image_tag/{imageTagId}",
* tags={ "Library tag"},
* summary="Delete an association",
* @OA\Parameter(
* name="imageTagId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Response(
* response="204",
* description="Success"
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function deleteAssociation(
$imageTagId,
LibraryImageTagService $libraryImageTagService
) {
$libraryImageTagService->deleteImageAssociation($imageTagId);
return new JsonResponse('Success', 204);
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Admin;
use OpenApi\Annotations as OA;
use OpenApi\Controller\Admin\BaseAdminOpenApiController;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Service\OpenApiService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Core\HttpFoundation\Request;
use TheliaLibrary\Model\Api\LibraryItemImage;
use TheliaLibrary\Service\LibraryItemImageService;
/**
* @Route("/open_api/library/item_image", name="library_item_image")
*/
class ItemImageController extends BaseAdminOpenApiController
{
/**
* @Route("", name="_associate", methods="POST")
*
* @OA\Post(
* path="/library/item_image",
* tags={ "Library image"},
* summary="Associate an image to an item",
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* @OA\Property(
* property="imageId",
* type="integer",
* ),
* @OA\Property(
* property="itemType",
* type="string",
* ),
* @OA\Property(
* property="itemId",
* type="integer",
* ),
* @OA\Property(
* property="code",
* type="string",
* ),
* @OA\Property(
* property="visible",
* type="boolean",
* )
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryItemImage")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function createAssociation(
Request $request,
ModelFactory $modelFactory,
LibraryItemImageService $libraryItemImageService
) {
$data = json_decode($request->getContent(), true);
/** @var LibraryItemImage $openApiLibraryItemImage */
$openApiLibraryItemImage = $modelFactory->buildModel('LibraryItemImage', $data);
$openApiLibraryItemImage->validate(self::GROUP_UPDATE);
$image = $libraryItemImageService->associateImage(
$openApiLibraryItemImage->getImageId(),
$openApiLibraryItemImage->getItemType(),
$openApiLibraryItemImage->getItemId(),
$openApiLibraryItemImage->getCode(),
$openApiLibraryItemImage->isVisible(),
$openApiLibraryItemImage->getPosition()
);
return OpenApiService::jsonResponse($modelFactory->buildModel('LibraryItemImage', $image));
}
/**
* @Route("/{itemImageId}", name="_update_association", methods="PATCH", requirements={"itemImageId"="\d+"})
*
* @OA\Patch(
* path="/library/item_image/{itemImageId}",
* tags={ "Library image"},
* summary="Update an association",
* @OA\Parameter(
* name="itemImageId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* @OA\Property(
* property="visible",
* type="boolean",
* ),
* @OA\Property(
* property="code",
* type="string",
* ),
* @OA\Property(
* property="position",
* type="integer",
* ),
* @OA\Property(
* property="positionMovement",
* type="string",
* enum={"up", "down"}
* )
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryItemImage")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function updateAssociation(
$itemImageId,
Request $request,
ModelFactory $modelFactory,
LibraryItemImageService $libraryItemImageService
) {
$data = json_decode($request->getContent(), true);
$image = $libraryItemImageService->updateImageAssociation(
$itemImageId,
$data['code'] ?? null,
$data['visible'] ?? null,
$data['position'] ?? null,
$data['positionMovement'] ?? null
);
return OpenApiService::jsonResponse($modelFactory->buildModel('LibraryItemImage', $image));
}
/**
* @Route("/{itemImageId}", name="_delete_association", methods="DELETE", requirements={"itemImageId"="\d+"})
*
* @OA\Delete(
* path="/library/item_image/{itemImageId}",
* tags={ "Library image"},
* summary="Delete an association",
* @OA\Parameter(
* name="itemImageId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Response(
* response="204",
* description="Success"
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function deleteAssociation(
$itemImageId,
LibraryItemImageService $libraryItemImageService
) {
$libraryItemImageService->deleteImageAssociation($itemImageId);
return new JsonResponse('Success', 204);
}
}

View File

@@ -0,0 +1,216 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Admin;
use OpenApi\Annotations as OA;
use OpenApi\Controller\Admin\BaseAdminOpenApiController;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Service\OpenApiService;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Core\HttpFoundation\JsonResponse;
use Thelia\Core\HttpFoundation\Request;
use TheliaLibrary\Model\Base\LibraryTagQuery;
use TheliaLibrary\Model\LibraryTag;
use TheliaLibrary\Service\LibraryTagService;
/**
* @Route("/open_api/library/tag", name="library_tag")
*/
class TagController extends BaseAdminOpenApiController
{
/**
* @Route("", name="_view", methods="GET")
* @OA\Get(
* path="/library/tag",
* tags={"Library tag"},
* summary="Get tags",
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryTag")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function getTags(Request $request, ModelFactory $modelFactory)
{
$query = LibraryTagQuery::create();
$locale = $this->findLocale($request);
return OpenApiService::jsonResponse(array_map(
function (LibraryTag $tag) use ($modelFactory, $locale) {
/** @var \TheliaLibrary\Model\Api\LibraryTag $tagModel */
$tagModel = $modelFactory->buildModel('LibraryTag', $tag, $locale);
return $tagModel;
},
iterator_to_array($query->find())
));
}
/**
* @Route("", name="_create", methods="POST")
*
* @OA\Post(
* path="/library/tag",
* tags={ "Library tag"},
* summary="Create a new tag",
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* @OA\Property(
* property="title",
* type="string"
* ),
* @OA\Property(
* property="colorCode",
* default="#000000",
* type="string"
* ),
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryTag")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function createTag(
Request $request,
ModelFactory $modelFactory,
LibraryTagService $libraryTagService
) {
$locale = $this->findLocale($request);
$tag = $libraryTagService->createTag(
$request->request->get('title'),
$request->request->get('colorCode'),
$locale
);
return OpenApiService::jsonResponse($modelFactory->buildModel('LibraryTag', $tag, $locale));
}
/**
* @Route("/{tagId}", name="_update", methods="POST", requirements={"tagId"="\d+"})
* @OA\Post(
* path="/library/tag/{tagId}",
* tags={ "Library tag"},
* summary="update a tag",
* @OA\Parameter(
* name="tagId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* @OA\Property(
* property="title",
* type="string"
* ),
* @OA\Property(
* property="colorCode",
* default="#000000",
* type="string"
* ),
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryTag")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function updateTag(
$tagId,
Request $request,
ModelFactory $modelFactory,
LibraryTagService $libraryTagService
) {
$locale = $this->findLocale($request);
$tag = $libraryTagService->updateTag(
$tagId,
$request->request->get('title'),
$request->request->get('colorCode'),
$locale
);
return OpenApiService::jsonResponse($modelFactory->buildModel('LibraryTag', $tag, $locale));
}
/**
* @Route("/{tagId}", name="_delete", methods="DELETE", requirements={"tagId"="\d+"})
*
* @OA\Delete(
* path="/library/tag/{tagId}",
* tags={ "Library tag"},
* summary="Delete a tag",
* @OA\Parameter(
* name="tagId",
* in="path",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Response(
* response="204",
* description="Success"
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function deleteTag(
$tagId,
LibraryTagService $libraryTagService
) {
$libraryTagService->deleteTag($tagId);
return new JsonResponse('Success', 204);
}
protected function findLocale(Request $request)
{
$locale = $request->get('locale');
if (null == $locale) {
$locale = $request->getSession()->getAdminEditionLang()->getLocale();
}
return $locale;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Front;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Controller\Front\BaseFrontController;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Tools\URL;
use TheliaLibrary\Service\ImageService;
/**
* @Route("/image-library", name="image_library_")
*/
class ImageController extends BaseFrontController
{
/**
* @Route("/{identifier}/{region}/{size}/{rotation}/{quality}.{format}", name="view")
*/
public function getImage(
$identifier,
$region,
$size,
$rotation,
$quality,
$format,
ImageService $imageService
) {
$imagePath = $imageService->geFormattedImage($identifier, $region, $size, $rotation, $quality, $format);
return new BinaryFileResponse($imagePath);
}
/**
* @Route("/{identifier}/info.json", name="info")
*/
public function getImageInformation(
$identifier,
ImageService $imageService
): Response {
$image = $imageService->openImage($identifier);
$size = $image->getSize();
$maxSize = $imageService->getMaxSize($image);
return new Response(
json_encode(
[
'@context' => 'http://iiif.io/api/image/3/context.json',
'id' => URL::getInstance()->absoluteUrl('image-library/'.$identifier),
'type' => 'ImageService3',
'protocol' => 'http://iiif.io/api/image',
'profile' => 'level2',
'width' => $size->getWidth(),
'height' => $size->getHeight(),
'maxWidth' => $maxSize->getWidth(),
'maxHeight' => $maxSize->getHeight(),
]
),
200,
[
'Content-Type' => 'application/ld+json;profile="http://iiif.io/api/image/3/context.json"',
]
);
}
}

View File

@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Front;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Controller\Front\BaseFrontController;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Model\ConfigQuery;
use Thelia\Model\ProductImage;
use Thelia\Tools\URL;
use TheliaLibrary\Service\ImageService;
use TheliaMain\PropelResolver;
/**
* @Route("/legacy-image-library", name="legacy_image_library_")
*/
class LegacyImageController extends BaseFrontController
{
/**
* @Route("/{itemType}_image_{imageId}/{region}/{size}/{rotation}/{quality}.{format}", name="view")
*/
public function getImage(
$itemType,
$imageId,
$region,
$size,
$rotation,
$quality,
$format,
ImageService $imageService
) {
$sourceFilePath = $this->getSourceFilePath($itemType, $imageId);
if (!\in_array(strtolower($format), ['jpg', 'jpeg', 'png', 'gif', 'jp2', 'webp'])) {
throw new HttpException(400, 'Bad format value');
}
$formattedImagePath = THELIA_WEB_DIR.'legacy-image-library'.DS.$itemType.'_image_'.$imageId.DS.$region.DS.$size.DS.$rotation;
if (!is_dir($formattedImagePath)) {
if (!@mkdir($formattedImagePath, 0755, true)) {
throw new \RuntimeException(sprintf('Failed to create %s file in cache directory', $formattedImagePath));
}
}
$formattedImagePath .= DS.$quality.'.'.$format;
if (file_exists($formattedImagePath)) {
return new BinaryFileResponse($formattedImagePath);
}
$image = $imageService->getImagineInstance()->open($sourceFilePath);
$image = $imageService->applyRegion($image, $region);
$image = $imageService->applySize($image, $size);
$image = $imageService->applyRotation($image, $rotation, $format);
$image = $imageService->applyQuality($image, $quality);
$image->save($formattedImagePath);
return new BinaryFileResponse($formattedImagePath);
}
/**
* @Route("/{itemType}_image_{imageId}/info.json", name="info")
*/
public function getImageInformation(
$itemType,
$imageId,
ImageService $imageService
): Response {
$sourceFilePath = $this->getSourceFilePath($itemType, $imageId);
$image = $imageService->getImagineInstance()->open($sourceFilePath);
$size = $image->getSize();
$maxSize = $imageService->getMaxSize($image);
return new Response(
json_encode(
[
'@context' => 'http://iiif.io/api/image/3/context.json',
'id' => URL::getInstance()->absoluteUrl('legacy-image-library/'.$itemType.'_image_'.$imageId),
'type' => 'ImageService3',
'protocol' => 'http://iiif.io/api/image',
'profile' => 'level2',
'width' => $size->getWidth(),
'height' => $size->getHeight(),
'maxWidth' => $maxSize->getWidth(),
'maxHeight' => $maxSize->getHeight(),
]
),
200,
[
'Content-Type' => 'application/ld+json;profile="http://iiif.io/api/image/3/context.json"',
]
);
}
private function getSnakeFromCamelCase($camelCase): string
{
$pattern = '/(?<=\\w)(?=[A-Z])|(?<=[a-z])(?=[0-9])/';
$snakeCase = preg_replace($pattern, '_', $camelCase);
return strtolower($snakeCase);
}
private function getSourceFilePath($itemType, $imageId)
{
$tableMapClass = PropelResolver::getTableMapByTableName($this->getSnakeFromCamelCase($itemType));
$tableMap = new $tableMapClass();
/** @var ModelCriteria $queryClass */
$queryClass = $tableMap->getClassName().'ImageQuery';
/** @var ProductImage $image */
$image = $queryClass::create()
->filterById($imageId)
->filterByVisible(1)
->findOne();
if (null === $image) {
return new Response(null, 404);
}
$baseSourceFilePath = ConfigQuery::read('images_library_path');
if ($baseSourceFilePath === null) {
$baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'images';
} else {
$baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath;
}
return sprintf(
'%s/%s/%s',
$baseSourceFilePath,
$itemType,
$image->getFile()
);
}
}

View File

@@ -0,0 +1,224 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Front\OpenApi;
use OpenApi\Annotations as OA;
use OpenApi\Controller\Front\BaseFrontOpenApiController;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Service\OpenApiService;
use Propel\Runtime\ActiveQuery\Criteria;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Core\HttpFoundation\Request;
use TheliaLibrary\Model\LibraryImage;
use TheliaLibrary\Model\LibraryImageQuery;
use TheliaLibrary\Model\LibraryItemImageQuery;
/**
* @Route("/open_api/library/image", name="front_library_image")
*/
class ImageController extends BaseFrontOpenApiController
{
/**
* @Route("", name="_get", methods="GET")
*
* @OA\Get(
* path="/library/image",
* tags={"Library image"},
* summary="Get images",
* @OA\Parameter(
* name="id",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="itemId",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="itemType",
* in="query",
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="title",
* in="query",
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="code",
* in="query",
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="onlyVisible",
* in="query",
* @OA\Schema(
* type="boolean",
* default="true"
* )
* ),
* @OA\Parameter(
* name="offset",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="limit",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="tagId",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="width",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="height",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="locale",
* in="query",
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryImage")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function getImage(
Request $request,
ModelFactory $modelFactory
) {
$locale = $this->findLocale($request);
$imageQuery = LibraryImageQuery::create()->orderById(Criteria::DESC);
if (null !== $id = $request->get('id')) {
$imageQuery->filterById($id);
}
if (null !== $title = $request->get('title')) {
$imageQuery->useLibraryImageI18nQuery()
->filterByLocale($locale)
->filterByTitle("%$title%", Criteria::LIKE)
->endUse();
}
$itemImageQuery = null;
if (null !== $itemId = $request->get('itemId')) {
$itemImageQuery = $this->getOrInitItemJoin($imageQuery, $itemImageQuery)->filterByItemId($itemId);
}
if (null !== $itemType = $request->get('itemType')) {
$itemImageQuery = $this->getOrInitItemJoin($imageQuery, $itemImageQuery)->filterByItemType($itemType);
}
if (null !== $code = $request->get('code')) {
$itemImageQuery = $this->getOrInitItemJoin($imageQuery, $itemImageQuery)->filterByCode($code);
}
if (true === $request->get('onlyVisible')) {
$itemImageQuery = $this->getOrInitItemJoin($imageQuery, $itemImageQuery)->filterByVisible(true);
}
if (null !== $itemImageQuery) {
$itemImageQuery->orderByPosition();
$itemImageQuery->endUse();
}
if (null !== $tagId = $request->get('tagId')) {
$itemImageQuery = $imageQuery->useLibraryImageTagQuery()->filterByTagId($tagId)->endUse();
}
if (null !== $limit = $request->get('limit', 20)) {
$imageQuery->limit($limit);
}
if (null !== $offset = $request->get('offset', 0)) {
$imageQuery->offset($offset);
}
$width = $request->get('width');
$height = $request->get('height');
return OpenApiService::jsonResponse(array_map(
function (LibraryImage $image) use ($modelFactory, $locale, $width, $height) {
/** @var \TheliaLibrary\Model\Api\LibraryImage $imageModel */
$imageModel = $modelFactory->buildModel('LibraryImage', $image, $locale);
$imageModel->setWidth($width);
$imageModel->setHeight($height);
return $imageModel;
},
iterator_to_array($imageQuery->find())
));
}
protected function getOrInitItemJoin($query, $itemImageQuery = null): LibraryItemImageQuery
{
if (null !== $itemImageQuery) {
return $itemImageQuery;
}
return $query->useLibraryItemImageQuery();
}
protected function findLocale(Request $request)
{
$locale = $request->get('locale');
if (null == $locale) {
$locale = $request->getSession()->getLang()->getLocale();
}
return $locale;
}
}

View File

@@ -0,0 +1,197 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Controller\Front\OpenApi;
use OpenApi\Annotations as OA;
use OpenApi\Controller\Front\BaseFrontOpenApiController;
use OpenApi\Model\Api\ModelFactory;
use OpenApi\Service\OpenApiService;
use Symfony\Component\Routing\Annotation\Route;
use Thelia\Core\HttpFoundation\Request;
use TheliaLibrary\Model\LibraryItemImage;
use TheliaLibrary\Model\LibraryItemImageQuery;
/**
* @Route("/open_api/library/item_image", name="front_library_item_image")
*/
class ItemImageController extends BaseFrontOpenApiController
{
/**
* @Route("", name="_get", methods="GET")
*
* @OA\Get(
* path="/library/item_image",
* tags={"Library image"},
* summary="Get item images association",
* @OA\Parameter(
* name="itemId",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="itemType",
* in="query",
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="code",
* in="query",
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="offset",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Parameter(
* name="limit",
* in="query",
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(ref="#/components/schemas/LibraryItemImage")
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function getItemImage(
Request $request,
ModelFactory $modelFactory
) {
$locale = $this->findLocale($request);
$itemImageQuery = LibraryItemImageQuery::create();
if (null !== $imageId = $request->get('imageId')) {
$itemImageQuery->filterByImageId($imageId);
}
if (null !== $itemType = $request->get('itemType')) {
$itemImageQuery->filterByItemType($itemType);
}
if (null !== $itemId = $request->get('itemId')) {
$itemImageQuery->filterByItemId($itemId);
}
if (null !== $code = $request->get('code')) {
$itemImageQuery->filterByCode($code);
}
if (null !== $limit = $request->get('limit', 20)) {
$itemImageQuery->limit($limit);
}
if (null !== $offset = $request->get('offset', 0)) {
$itemImageQuery->offset($offset);
}
$itemImageQuery->orderByPosition();
return OpenApiService::jsonResponse(array_map(
function (LibraryItemImage $itemImage) use ($modelFactory, $locale) {
return $modelFactory->buildModel('LibraryItemImage', $itemImage, $locale);
},
iterator_to_array($itemImageQuery->find())
));
}
/**
* @Route("/types", name="_type_list", methods="GET")
*
* @OA\Get(
* path="/library/item_image/types",
* tags={"Library image"},
* summary="Get all item types availables",
* @OA\Parameter(
* name="onlyExisting",
* description="If false basic Thelia types will be added (product, content, ...) even if they have no image associated",
* in="query",
* @OA\Schema(
* type="boolean",
* default="false"
* )
* ),
* @OA\Response(
* response="200",
* description="Success",
* @OA\JsonContent(
* type="array",
* @OA\Items(
* type="string",
* )
* )
* ),
* @OA\Response(
* response="400",
* description="Bad request",
* @OA\JsonContent(ref="#/components/schemas/Error")
* )
* )
*/
public function getItemTypes(
Request $request
) {
$itemTypes = array_map(
function (LibraryItemImage $libraryItemImage) {
return $libraryItemImage->getItemType();
},
iterator_to_array(LibraryItemImageQuery::create()
->groupByItemType()
->find())
);
if (false === $request->get('onlyExisting', false) || 'false' === $request->get('onlyExisting', false)) {
$itemTypes = array_merge(
[
'product',
'category',
'content',
'folder',
],
$itemTypes
);
}
return OpenApiService::jsonResponse(
$itemTypes
);
}
protected function findLocale(Request $request)
{
$locale = $request->get('locale');
if (null == $locale) {
$locale = $request->getSession()->getAdminEditionLang()->getLocale();
}
return $locale;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Hook;
use Thelia\Core\Event\Hook\HookRenderEvent;
use Thelia\Core\Hook\BaseHook;
class BackHook extends BaseHook
{
public function onItemEdition(HookRenderEvent $event): void
{
if (!$this->getRequest()->get('force_legacy_imagemanager')) {
$content = $this->render('item-edition.html', [
'itemType' => $event->getArgument('itemType'),
'itemId' => $event->getArgument('itemId'),
]);
$event->add($content);
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
// 'an english string' => 'The displayed english string',
];

View File

@@ -0,0 +1,15 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
// 'an english string' => 'La traduction française de la chaine',
];

View File

@@ -0,0 +1,145 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Loop;
use Thelia\Action\Image;
use Thelia\Core\Template\Element\BaseI18nLoop;
use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
use Thelia\Core\Template\Loop\Argument\Argument;
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
use Thelia\Type\EnumType;
use Thelia\Type\TypeCollection;
use TheliaLibrary\Model\LibraryImage as LibraryImageModel;
use TheliaLibrary\Model\LibraryImageQuery;
use TheliaLibrary\Model\LibraryItemImageQuery;
use TheliaLibrary\Service\LibraryImageService;
/**
* Class LibraryImage.
*
* @method int getId()
* @method int getItemId()
* @method string getItemType()
* @method string getCode()
* @method bool getOnlyVisible()
* @method int getWidth()
* @method int getHeight()
*/
class LibraryImage extends BaseI18nLoop implements PropelSearchLoopInterface
{
protected function getArgDefinitions()
{
return new ArgumentCollection(
Argument::createIntTypeArgument('id'),
Argument::createIntTypeArgument('item_id'),
Argument::createAlphaNumStringTypeArgument('item_type'),
Argument::createAlphaNumStringTypeArgument('code'),
Argument::createBooleanTypeArgument('only_visible'),
Argument::createIntTypeArgument('width'),
Argument::createIntTypeArgument('height'),
Argument::createAlphaNumStringTypeArgument('format'),
new Argument(
'resize_mode',
new TypeCollection(
new EnumType([
Image::EXACT_RATIO_WITH_BORDERS,
Image::EXACT_RATIO_WITH_CROP,
Image::KEEP_IMAGE_RATIO,
])
),
'none'
),
Argument::createBooleanTypeArgument('allow_zoom', false),
);
}
public function buildModelCriteria()
{
$query = LibraryImageQuery::create();
if (null !== $id = $this->getId()) {
$query->filterById($id);
}
if (null !== $itemId = $this->getItemId()) {
$query = $this->getOrInitItemJoin($query)->filterByItemId($itemId);
}
if (null !== $itemType = $this->getItemType()) {
$query = $this->getOrInitItemJoin($query)->filterByItemType($itemType);
}
if (null !== $code = $this->getCode()) {
$query = $this->getOrInitItemJoin($query)->filterByCode($code);
}
if (true === $this->getOnlyVisible()) {
$query = $this->getOrInitItemJoin($query)->filterByVisible(true);
}
if ($query instanceof LibraryItemImageQuery) {
$query->orderByPosition();
$query = $query->endUse();
}
$this->configureI18nProcessing(
$query,
[
'TITLE',
'FILE_NAME',
]
);
return $query;
}
public function parseResults(LoopResult $loopResult)
{
/** @var LibraryImageModel $image */
foreach ($loopResult->getResultDataCollection() as $image) {
/** @var LibraryImageService $libraryImageService */
$libraryImageService = $this->container->get('thelia_library_image');
$imageUrl = $libraryImageService->getImagePublicUrl(
$image,
$this->getWidth(),
$this->getHeight(),
$this->getFormat()
);
$row = new LoopResultRow($image);
$row
->set('ID', $image->getId())
->set('TITLE', $image->getVirtualColumn('i18n_TITLE'))
->set('FILE_NAME', $image->getVirtualColumn('i18n_FILE_NAME'))
->set('URL', $imageUrl)
;
$this->addOutputFields($row, $image);
$loopResult->addRow($row);
}
return $loopResult;
}
protected function getOrInitItemJoin($query): LibraryItemImageQuery
{
if ($query instanceof LibraryItemImageQuery) {
return $query;
}
return $query->useLibraryItemImageQuery();
}
}

View File

@@ -0,0 +1,233 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model\Api;
use OpenApi\Annotations as OA;
use OpenApi\Constraint;
use OpenApi\Model\Api\BaseApiModel;
use OpenApi\Model\Api\ModelFactory;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Thelia\TaxEngine\TaxEngine;
use TheliaLibrary\Model\LibraryImageTagQuery;
use TheliaLibrary\Model\LibraryTagQuery;
use TheliaLibrary\Service\LibraryImageService;
/**
* Class LibraryImage.
*
* @OA\Schema(
* schema="LibraryImage",
* title="LibraryImage",
* )
*/
class LibraryImage extends BaseApiModel
{
/**
* @var int
* @OA\Property(
* type="integer",
* )
* @Constraint\NotBlank(groups={"read"})
*/
protected $id;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $title;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $fileName;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $url = null;
protected $width = null;
protected $height = null;
/**
* @var array
* @OA\Property(
* readOnly=true,
* type="array",
* @OA\Items(type="string")
* )
*/
protected $tags = [];
/**
* @var LibraryImageService
*/
protected $libraryImageService;
public function __construct(
ModelFactory $modelFactory,
RequestStack $requestStack,
TaxEngine $taxEngine,
EventDispatcherInterface $dispatcher,
LibraryImageService $libraryImageService,
ValidatorInterface $validator
) {
parent::__construct($modelFactory, $requestStack, $taxEngine, $dispatcher, $validator);
$this->libraryImageService = $libraryImageService;
}
public function createOrUpdateFromData($data, $locale = null): void
{
parent::createOrUpdateFromData($data, $locale);
}
/**
* @return int
*/
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
/**
* @return string
*/
public function getFileName(): ?string
{
return $this->fileName;
}
public function setFileName(?string $fileName = null): self
{
$this->fileName = $fileName;
return $this;
}
/**
* @return string
*/
public function getUrl(): ?string
{
return $this->libraryImageService->getImagePublicUrl($this->getTheliaModel(), $this->width, $this->height);
}
/**
* @param null $width
*/
public function setWidth($width): void
{
$this->width = $width;
}
/**
* @param null $height
*/
public function setHeight($height): void
{
$this->height = $height;
}
public function getTags(): array
{
return $this->tags;
}
/**
* @param array $tags
*/
public function setTags($tags): void
{
$this->tags = $tags;
}
protected function getTheliaModel($propelModelName = null)
{
return parent::getTheliaModel(\TheliaLibrary\Model\LibraryImage::class);
}
public function createFromTheliaModel($theliaModel, $locale = null): void
{
parent::createFromTheliaModel($theliaModel, $locale);
$tags = array_map(
function ($item) use ($locale) {
$query = LibraryTagQuery::create()
->filterById($item['TagId'])
->useI18nQuery()
->filterByLocale($locale)
->filterById($item['TagId'])
->endUse()
->with('LibraryTagI18n');
$tag = $query->find()->getFirst();
$association = LibraryImageTagQuery::create()->filterByImageId($this->getId())->filterByTagId($tag->getId())->findOne();
return [
'tag' => [
'id' => $tag->getId(),
'title' => $tag->getTitle(),
'colorCode' => $tag->getColorCode(),
],
'imageTag' => [
'id' => $association->getId(),
'imageId' => $this->getId(),
'tagId' => $tag->getId(),
],
];
},
LibraryImageTagQuery::create()
->filterByImageId($this->getId())
->find()
->toArray()
);
$this->setTags($tags);
}
}

View File

@@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model\Api;
use OpenApi\Annotations as OA;
use OpenApi\Constraint;
use OpenApi\Model\Api\BaseApiModel;
/**
* Class LibraryImageTag.
*
* @OA\Schema(
* schema="LibraryImageTag",
* title="LibraryImageTag",
* )
*/
class LibraryImageTag extends BaseApiModel
{
/**
* @var int
* @OA\Property(
* type="integer",
* )
* @Constraint\NotBlank(groups={"read"})
*/
protected $id;
/**
* @var int
* @OA\Property(
* type="integer",
* )
* @Constraint\NotBlank(groups={"read"})
*/
protected $imageId;
/**
* @var int
* @OA\Property(
* type="integer",
* )
* @Constraint\NotBlank(groups={"read"})
*/
protected $tagId;
/**
* @return int
*/
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
/**
* @return int
*/
public function getImageId(): ?int
{
return $this->imageId;
}
public function setImageId(int $id): self
{
$this->imageId = $id;
return $this;
}
/**
* @return int
*/
public function getTagId(): ?int
{
return $this->tagId;
}
public function setTagId(int $tagId): self
{
$this->tagId = $tagId;
return $this;
}
}

View File

@@ -0,0 +1,213 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model\Api;
use OpenApi\Annotations as OA;
use OpenApi\Constraint;
use OpenApi\Model\Api\BaseApiModel;
/**
* Class LibraryItemImage.
*
* @OA\Schema(
* schema="LibraryItemImage",
* title="LibraryItemImage",
* )
*/
class LibraryItemImage extends BaseApiModel
{
/**
* @var int
* @OA\Property(
* type="integer",
* )
* @Constraint\NotBlank(groups={"read"})
*/
protected $id;
/**
* @var int
* @OA\Property(
* type="integer",
* )
*/
protected $imageId;
/**
* @var LibraryImage
* @OA\Property(
* readOnly=true,
* ref="#/components/schemas/LibraryImage"
* )
*/
protected $image;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $itemType;
/**
* @var int
* @OA\Property(
* type="integer",
* )
*/
protected $itemId;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $code;
/**
* @var bool
* @OA\Property(
* type="boolean",
* )
*/
protected $visible = true;
/**
* @var int
* @OA\Property(
* type="integer",
* )
*/
protected $position;
/**
* @return int
*/
public function getId(): ?int
{
return $this->id;
}
/**
* @return LibraryImage
*/
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getImageId(): int
{
return $this->imageId;
}
/**
* @return LibraryImage
*/
public function getImage(): ?LibraryImage
{
return $this->image;
}
public function setLibraryImage(LibraryImage $image): self
{
$this->image = $image;
return $this;
}
public function setImageId(int $imageId): self
{
$this->imageId = $imageId;
return $this;
}
public function getItemType(): string
{
return $this->itemType;
}
public function setItemType(string $itemType): self
{
$this->itemType = $itemType;
return $this;
}
public function getItemId(): int
{
return $this->itemId;
}
public function setItemId(int $itemId): self
{
$this->itemId = $itemId;
return $this;
}
/**
* @return string
*/
public function getCode(): ?string
{
return $this->code;
}
public function setCode(?string $code): self
{
$this->code = $code;
return $this;
}
public function isVisible(): bool
{
return $this->visible;
}
public function setVisible(bool $visible = true): self
{
$this->visible = $visible;
return $this;
}
/**
* @return int
*/
public function getPosition(): ?int
{
return $this->position;
}
/**
* @param int $position
*/
public function setPosition(?int $position): self
{
$this->position = $position;
return $this;
}
protected function getTheliaModel($propelModelName = null)
{
return parent::getTheliaModel(\TheliaLibrary\Model\LibraryItemImage::class);
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model\Api;
use OpenApi\Annotations as OA;
use OpenApi\Constraint;
use OpenApi\Model\Api\BaseApiModel;
/**
* Class LibraryTag.
*
* @OA\Schema(
* schema="LibraryTag",
* title="LibraryTag",
* )
*/
class LibraryTag extends BaseApiModel
{
/**
* @var int
* @OA\Property(
* type="integer",
* )
* @Constraint\NotBlank(groups={"read"})
*/
protected $id;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $title;
/**
* @var string
* @OA\Property(
* type="string",
* )
*/
protected $colorCode;
/**
* @return int
*/
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
/**
* @return string
*/
public function getColorCode(): ?string
{
return $this->colorCode;
}
public function setColorCode(?string $colorCode = null): self
{
$this->colorCode = $colorCode;
return $this;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryImage as BaseLibraryImage;
/**
* Skeleton subclass for representing a row from the 'library_image' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryImage extends BaseLibraryImage
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryImageI18n as BaseLibraryImageI18n;
/**
* Skeleton subclass for representing a row from the 'library_image_i18n' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryImageI18n extends BaseLibraryImageI18n
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryImageI18nQuery as BaseLibraryImageI18nQuery;
/**
* Skeleton subclass for performing query and update operations on the 'library_image_i18n' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryImageI18nQuery extends BaseLibraryImageI18nQuery
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryImageQuery as BaseLibraryImageQuery;
/**
* Skeleton subclass for performing query and update operations on the 'library_image' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryImageQuery extends BaseLibraryImageQuery
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryImageTag as BaseLibraryImageTag;
/**
* Skeleton subclass for representing a row from the 'library_image_tag' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryImageTag extends BaseLibraryImageTag
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryImageTagQuery as BaseLibraryImageTagQuery;
/**
* Skeleton subclass for performing query and update operations on the 'library_image_tag' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryImageTagQuery extends BaseLibraryImageTagQuery
{
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use Propel\Runtime\Connection\ConnectionInterface;
use Thelia\Model\Tools\PositionManagementTrait;
use TheliaLibrary\Model\Base\LibraryItemImage as BaseLibraryItemImage;
/**
* Skeleton subclass for representing a row from the 'library_item_image' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryItemImage extends BaseLibraryItemImage
{
use PositionManagementTrait;
/**
* Calculate next position relative to our product.
*/
protected function addCriteriaToPositionQuery($query): void
{
/* @var $query LibraryItemImageQuery */
$query->filterByItemId($this->getItemId())
->filterByItemType($this->getItemType());
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
$this->setPosition($this->getNextPosition());
parent::preInsert($con);
return true;
}
public function preDelete(ConnectionInterface $con = null)
{
parent::preDelete($con);
$this->reorderBeforeDelete(
[
'item_id' => $this->getItemId(),
'item_type' => $this->getItemType(),
]
);
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryItemImageQuery as BaseLibraryItemImageQuery;
/**
* Skeleton subclass for performing query and update operations on the 'library_item_image' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryItemImageQuery extends BaseLibraryItemImageQuery
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryTag as BaseLibraryTag;
/**
* Skeleton subclass for representing a row from the 'library_tag' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryTag extends BaseLibraryTag
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryTagI18n as BaseLibraryTagI18n;
/**
* Skeleton subclass for representing a row from the 'library_tag_i18n' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryTagI18n extends BaseLibraryTagI18n
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryTagI18nQuery as BaseLibraryTagI18nQuery;
/**
* Skeleton subclass for performing query and update operations on the 'library_tag_i18n' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryTagI18nQuery extends BaseLibraryTagI18nQuery
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Model;
use TheliaLibrary\Model\Base\LibraryTagQuery as BaseLibraryTagQuery;
/**
* Skeleton subclass for performing query and update operations on the 'library_tag' table.
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
*/
class LibraryTagQuery extends BaseLibraryTagQuery
{
}

View File

@@ -0,0 +1,15 @@
# Thelia Library
Add a media library on Thelia.
### Composer
Add it in your main thelia composer.json file
```
composer require thelia/thelia-library-module:~1.0.0
```
## Usage
For now this module doesn't have an interface, only an api documented on {your_website_url}/open_api/doc

View File

@@ -0,0 +1,325 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Service;
use Imagine\Gd\Imagine;
use Imagine\Gmagick\Imagine as GmagickImagine;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use Imagine\Image\Palette\RGB;
use Imagine\Image\Point;
use Imagine\Imagick\Imagine as ImagickImagine;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Lang;
use TheliaLibrary\Model\LibraryImage;
use TheliaLibrary\Model\LibraryImageQuery;
use TheliaLibrary\TheliaLibrary;
class ImageService
{
public const MAX_ALLOWED_SIZE_FACTOR = 2;
public function __construct(private RequestStack $requestStack)
{
}
public function getUrlForImage(
$identifier,
$format,
$region = 'full',
$size = 'max',
$rotation = 0,
$quality = 'default'
) {
return "/image-library/$identifier/$region/$size/$rotation/$quality.$format";
}
public function geFormattedImage(
$identifier,
$region,
$size,
$rotation,
$quality,
$format
) {
if (!\in_array(strtolower($format), ['jpg', 'jpeg', 'png', 'gif', 'jp2', 'webp'])) {
throw new HttpException(400, 'Bad format value');
}
$formattedImagePath = THELIA_WEB_DIR.'image-library'.DS.$identifier.DS.$region.DS.$size.DS.$rotation;
if (!is_dir($formattedImagePath)) {
if (!@mkdir($formattedImagePath, 0755, true)) {
throw new \RuntimeException(sprintf('Failed to create %s file in cache directory', $formattedImagePath));
}
}
$formattedImagePath .= DS.$quality.'.'.$format;
if (file_exists($formattedImagePath)) {
return $formattedImagePath;
}
$image = $this->openImage($identifier);
$image = $this->applyRegion($image, $region);
$image = $this->applySize($image, $size);
$image = $this->applyRotation($image, $rotation, $format);
$image = $this->applyQuality($image, $quality);
$image->save($formattedImagePath);
return $formattedImagePath;
}
public function applyRegion(ImageInterface $image, $region)
{
if ($region === 'full') {
return $image;
}
$height = $image->getSize()->getHeight();
$width = $image->getSize()->getWidth();
if ($region === 'square') {
$squareSize = min($width, $height);
return $image->crop(
new Point(($width - $squareSize) / 2, ($height - $squareSize) / 2),
new Box($squareSize, $squareSize)
);
}
// If region start with pct: values are percent of size
$regionMode = strpos($region, 'pct:') === false ? 'value' : 'percentage';
$values = explode(',', str_replace('pct:', '', $region));
if (\count($values) !== 4) {
throw new HttpException(400, 'Bad region value');
}
$xPositionValue = $regionMode === 'value' ? $values[0] : $width * $values[0] / 100;
$yPositionValue = $regionMode === 'value' ? $values[1] : $height * $values[1] / 100;
$widthValue = $regionMode === 'value' ? $values[2] : $width * $values[2] / 100;
$heightValue = $regionMode === 'value' ? $values[3] : $height * $values[3] / 100;
// If width or height + start position go outside of image crop the width and/or height
if (($xPositionValue + $widthValue) > $width) {
$widthValue = $width - $xPositionValue;
}
if (($yPositionValue + $heightValue) > $height) {
$heightValue = $height - $yPositionValue;
}
if ($height <= 0 || $width <= 0) {
throw new HttpException(400, 'Size out of bound');
}
return $image->crop(
new Point($xPositionValue, $yPositionValue),
new Box($widthValue, $heightValue)
);
}
public function applySize(ImageInterface $image, $size)
{
$upscaleMode = false !== strpos($size, '^');
$keepAspectRatio = false !== strpos($size, '!');
$borders = false !== strpos($size, '*');
$size = str_replace('^', '', $size);
$size = str_replace('!', '', $size);
$size = str_replace('*', '', $size);
if ($size === 'max') {
if (!$upscaleMode) {
return $image;
}
return $image->resize($this->getMaxSize($image));
}
$width = $image->getSize()->getWidth();
$height = $image->getSize()->getHeight();
if (false !== strpos($size, 'pct:')) {
$values = explode(':', $size);
if (!isset($values[1])) {
throw new HttpException(400, 'Bad size values');
}
$percent = $values[1];
if (!$upscaleMode && $percent > 100) {
throw new HttpException(400, 'Size out of bound');
}
return $image->resize(
new Box(
$percent * $width / 100,
$percent * $height / 100,
)
);
}
$values = explode(',', $size);
if (\count($values) !== 2) {
throw new HttpException(400, 'Bad size values');
}
$newWidth = $values[0] != '' ? $values[0] : $values[1];
$newHeight = $values[1] != '' ? $values[1] : $values[0];
if ($keepAspectRatio) {
$originalRatio = $width / $height;
$askedRatio = $newWidth / $newHeight;
// Keep the ratio and take the larger image possible but not greater than asked width and height
$newWidth = $askedRatio <= $originalRatio ? $newWidth : $newHeight * $originalRatio;
$newHeight = $askedRatio >= $originalRatio ? $newHeight : $newWidth / $originalRatio;
}
if (!$upscaleMode && ($newWidth > $width || $newHeight > $height)) {
throw new HttpException(400, 'Size out of bound');
}
$resizedImage = $image->resize(
new Box(
$newWidth,
$newHeight,
)
);
if ($borders) {
$palette = new RGB();
$canvas = new Box($values[0], $values[1]);
$canvasInstance = $this->getImagineInstance()
->create($canvas, $palette->color('fff', 0));
$borderWidth = (int) (($values[0] - $resizedImage->getSize()->getWidth()) / 2);
$borderHeight = (int) (($values[1] - $resizedImage->getSize()->getHeight()) / 2);
return $canvasInstance
->paste($resizedImage, new Point($borderWidth, $borderHeight));
}
return $resizedImage;
}
public function applyRotation(ImageInterface $image, $rotation, $format)
{
if (false !== strpos($rotation, '!')) {
$image = $image->flipHorizontally();
}
$rotation = str_replace('!', '', $rotation);
if (!is_numeric($rotation)) {
throw new HttpException(400, 'Bad rotation values');
}
$rotationValue = (float) $rotation;
if (0 > $rotationValue || $rotationValue > 360) {
throw new HttpException(400, 'Rotation out of bound');
}
$color = new RGB();
$alpha = \in_array(
strtolower($format),
[
'png',
'gif',
'webp',
'tiff',
'jp2',
]
) ? 0 : 100;
return $image->rotate($rotationValue, $color->color('fff', $alpha));
}
public function applyQuality(ImageInterface $image, $quality)
{
if (!\in_array($quality, ['color', 'gray', 'default', 'bitonal'])) {
throw new HttpException(400, 'Bad quality values');
}
if ($quality === 'gray') {
$image->effects()->grayscale();
}
return $image;
}
public function getImageFileName(
LibraryImage $image = null
) {
if (null == $image) {
return null;
}
$locale = $this->requestStack?->getCurrentRequest()?->getSession()?->getLang()->getLocale();
if (null !== $locale) {
$image->setLocale($locale);
}
$fileName = $image->getFileName();
if (null === $fileName) {
$fileName = $image->setLocale(Lang::getDefaultLanguage()->getLocale())->getFileName() ?? null;
$image->setLocale($locale);
}
return $fileName;
}
public function openImage($identifier)
{
$imageModel = LibraryImageQuery::create()
->filterById($identifier)
->findOne();
if (null === $imageModel) {
throw new HttpException(404, 'Image not found');
}
$fileName = $this->getImageFileName($imageModel);
return $this->getImagineInstance()->open(TheliaLibrary::getImageDirectory().$fileName);
}
public function getImagineInstance()
{
$driver = ConfigQuery::read('imagine_graphic_driver', 'gd');
switch ($driver) {
case 'imagick':
$image = new ImagickImagine();
break;
case 'gmagick':
$image = new GmagickImagine();
break;
case 'gd':
default:
$image = new Imagine();
}
return $image;
}
public function getMaxSize(ImageInterface $image)
{
return new Box($image->getSize()->getWidth() * 2, $image->getSize()->getHeight() * 2);
}
}

View File

@@ -0,0 +1,172 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Service;
use Propel\Runtime\ActiveQuery\Criteria;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Thelia\Core\Translation\Translator;
use TheliaLibrary\Model\LibraryImage;
use TheliaLibrary\Model\LibraryImageI18nQuery;
use TheliaLibrary\Model\LibraryImageQuery;
use TheliaLibrary\TheliaLibrary;
class LibraryImageService
{
public const LIBRARY_IMAGE_DIR = THELIA_WEB_DIR.'library'.DS.'images'.DS;
public const LIBRARY_IMAGE_BASE_ROUTE = '/library/images/';
protected EventDispatcherInterface $eventDispatcher;
protected RequestStack $requestStack;
protected ImageService $imageService;
public function __construct(
EventDispatcherInterface $eventDispatcher,
RequestStack $requestStack,
ImageService $imageService
) {
$this->eventDispatcher = $eventDispatcher;
$this->requestStack = $requestStack;
$this->imageService = $imageService;
}
/**
* @throws \Exception
*/
public function createImage(
File $file,
string $title = null,
string $locale = null
): LibraryImage {
return $this->createOrUpdateImage($file, $title, $locale);
}
/**
* @throws \Exception
*/
public function updateImage(
$imageId,
File $file = null,
string $title = null,
string $locale = null
): LibraryImage {
return $this->createOrUpdateImage($file, $title, $locale, $imageId);
}
public function deleteImage(
$imageId
): bool {
$image = LibraryImageQuery::create()
->filterById($imageId)
->findOne();
if (null === $image) {
return false;
}
$imageFiles = LibraryImageI18nQuery::create()->filterById($imageId)
->find();
foreach ($imageFiles as $imageFile) {
if (null === $imageFile->getFileName()) {
continue;
}
$fileSystem = new Filesystem();
$fileSystem->remove(TheliaLibrary::getImageDirectory().$imageFile->getFileName());
}
$image->delete();
return true;
}
public function getImagePublicUrl(
LibraryImage $image = null,
$width = null,
$height = null,
$format = null
) {
if (null == $image) {
return null;
}
$fileName = $this->imageService->getImageFileName($image);
$format = $format ?? pathinfo($fileName, \PATHINFO_EXTENSION);
$size = 'max';
if ($width || $height) {
$size = $width.','.$height;
}
return $this->imageService->getUrlForImage($image->getId(), $format, 'full', $size);
}
protected function createOrUpdateImage(
File $file = null,
string $title = null,
string $locale = null,
int $imageId = null
) {
$image = null !== $imageId
? LibraryImageQuery::create()->filterById($imageId)->findOne()
: (new LibraryImage());
if (null === $image) {
throw new \Exception(Translator::getInstance()->trans("Can't update an image that doesn't exist"));
}
if (null == $locale) {
$locale = $this->requestStack->getCurrentRequest()->getSession()->getAdminEditionLang()->getLocale();
}
$image->setLocale($locale);
$imageName = null;
if (null !== $file) {
$fileName = method_exists($file, 'getClientOriginalName') ? $file->getClientOriginalName() : $file->getFilename();
// Remove old file if already exist for this locale
if (null !== $image->getFileName()) {
$fileSystem = new Filesystem();
$fileSystem->remove(TheliaLibrary::getImageDirectory().$image->getFileName());
}
$imageName = bin2hex(random_bytes(5)).'_'.$fileName;
$file->move(TheliaLibrary::getImageDirectory(), $imageName);
if (null === $title && null === $image->getTitle()) {
$title = $fileName;
}
}
if (null === $imageName && null !== $imageId) {
$i18nWithFilename = LibraryImageI18nQuery::create()
->filterById($imageId)
->filterByFileName(null, Criteria::ISNOTNULL)
->findOne();
$imageName = $i18nWithFilename?->getFileName();
}
if (null != $title) {
$image->setTitle($title);
}
$image->setFileName($imageName);
$image->save();
return $image;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Service;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use TheliaLibrary\Model\LibraryImageTag;
use TheliaLibrary\Model\LibraryImageTagQuery;
class LibraryImageTagService
{
protected EventDispatcherInterface $eventDispatcher;
public function __construct(
EventDispatcherInterface $eventDispatcher
) {
$this->eventDispatcher = $eventDispatcher;
}
public function associateImage(
string $imageId,
string $tagId
): LibraryImageTag {
$imageTag = (new LibraryImageTag())
->setImageId($imageId)
->setTagId($tagId);
$imageTag->save();
return $imageTag;
}
public function deleteImageAssociation(
$tagImageId
): bool {
$imageTag = LibraryImageTagQuery::create()
->filterById($tagImageId)
->findOne();
if (null === $imageTag) {
return false;
}
$imageTag->delete();
return true;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Service;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Thelia\Core\Translation\Translator;
use TheliaLibrary\Model\LibraryImageQuery;
use TheliaLibrary\Model\LibraryItemImage;
use TheliaLibrary\Model\LibraryItemImageQuery;
class LibraryItemImageService
{
protected LibraryImageService $libraryImageService;
protected EventDispatcherInterface $eventDispatcher;
public function __construct(
LibraryImageService $libraryImageService,
EventDispatcherInterface $eventDispatcher
) {
$this->eventDispatcher = $eventDispatcher;
$this->libraryImageService = $libraryImageService;
}
public function createAndAssociateImage(
File $file,
string $imageTitle = null,
string $locale = null,
$itemType,
$itemId,
$code = null,
$visible = true,
$position = null,
$replaceOld = false
): LibraryItemImage {
if ($replaceOld) {
$existingImages = LibraryImageQuery::create()
->useLibraryItemImageQuery()
->filterByItemType($itemType)
->filterByItemId($itemId)
->filterByCode($code)
->endUse()
->find();
if (!empty($existingImages)) {
foreach ($existingImages as $existingImage) {
$this->libraryImageService->deleteImage($existingImage->getId());
}
}
}
$image = $this->libraryImageService->createImage($file, $imageTitle, $locale);
return $this->associateImage(
$image->getId(),
$itemType,
$itemId,
$code,
$visible,
$position
);
}
public function associateImage(
$imageId,
$itemType,
$itemId,
$code = null,
$visible = true,
$position = null
): LibraryItemImage {
$itemImage = (new LibraryItemImage())
->setImageId($imageId)
->setItemType($itemType)
->setItemId($itemId)
->setCode($code)
->setVisible($visible);
$itemImage->save();
if (null != $position) {
$itemImage->changeAbsolutePosition($position);
}
return $itemImage;
}
public function updateImageAssociation(
$itemImageId,
$code = null,
$visible = true,
$position = null,
$positionMovement = null
): LibraryItemImage {
$itemImage = LibraryItemImageQuery::create()
->filterById($itemImageId)
->findOne();
if (null === $itemImage) {
throw new \Exception(Translator::getInstance()->trans("Can't update an item image that doesn't exist"));
}
$itemImage->setCode($code);
$itemImage->setVisible($visible);
$itemImage->save();
if (null != $position) {
$itemImage->changeAbsolutePosition($position);
}
if ('up' === strtolower($positionMovement)) {
$itemImage->movePositionUp();
}
if ('down' === strtolower($positionMovement)) {
$itemImage->movePositionDown();
}
return $itemImage;
}
public function deleteImageAssociation(
$itemImageId
): bool {
$itemImage = LibraryItemImageQuery::create()
->filterById($itemImageId)
->findOne();
if (null === $itemImage) {
return false;
}
$itemImage->delete();
return true;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary\Service;
use Thelia\Core\Translation\Translator;
use TheliaLibrary\Model\LibraryTag;
use TheliaLibrary\Model\LibraryTagQuery;
class LibraryTagService
{
public function createTag(
string $title,
string $colorCode
): LibraryTag {
$imageTag = (new LibraryTag())
->setTitle($title)
->setColorCode($colorCode);
$imageTag->save();
return $imageTag;
}
public function updateTag(
int $tagId,
string $title = null,
string $colorCode = null
): LibraryTag {
$tag = LibraryTagQuery::create()
->filterById($tagId)
->findOne();
if (null === $tag) {
throw new \Exception(Translator::getInstance()->trans("Can't update a tag that doesn't exist"));
}
if (null != $title) {
$tag->setTitle($title);
}
if (null != $colorCode) {
$tag->setColorCode($colorCode);
}
$tag->save();
return $tag;
}
public function deleteTag(
$tagId
): bool {
$tag = LibraryTagQuery::create()
->filterById($tagId)
->findOne();
if (null === $tag) {
throw new \Exception(Translator::getInstance()->trans("Can't delete a tag that doesn't exist"));
}
$tag->delete();
return true;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Thelia package.
* http://www.thelia.net
*
* (c) OpenStudio <info@thelia.net>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TheliaLibrary;
use Propel\Runtime\Connection\ConnectionInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
use Symfony\Component\Finder\Finder;
use Thelia\Install\Database;
use Thelia\Module\BaseModule;
class TheliaLibrary extends BaseModule
{
/** @var string */
public const DOMAIN_NAME = 'thelialibrary';
public const DEFAULT_IMAGE_DIRECTORY = THELIA_LOCAL_DIR.'library/images/';
public function preActivation(ConnectionInterface $con = null): bool
{
if (!$this->getConfigValue('is_initialized', false)) {
$database = new Database($con);
$database->insertSql(null, [__DIR__.'/Config/TheliaMain.sql']);
$this->setConfigValue('is_initialized', true);
}
return true;
}
/**
* Execute sql files in Config/update/ folder named with module version (ex: 1.0.1.sql).
*
* @param $currentVersion
* @param $newVersion
*/
public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void
{
if (file_exists(__DIR__.DS.'Config'.DS.'update')) {
$finder = Finder::create()
->name('*.sql')
->depth(0)
->sortByName()
->in(__DIR__.DS.'Config'.DS.'update');
$database = new Database($con);
/** @var \SplFileInfo $file */
foreach ($finder as $file) {
if (version_compare($currentVersion, $file->getBasename('.sql'), '<')) {
$database->insertSql(null, [$file->getPathname()]);
}
}
}
}
public static function getImageDirectory(): string
{
return self::getConfigValue('image_directory', self::DEFAULT_IMAGE_DIRECTORY);
}
/*
* You may now override BaseModuleInterface methods, such as:
* install, destroy, preActivation, postActivation, preDeactivation, postDeactivation
*
* Have fun !
*/
/**
* Defines how services are loaded in your modules.
*/
public static function configureServices(ServicesConfigurator $servicesConfigurator): void
{
$servicesConfigurator->load(self::getModuleCode().'\\', __DIR__)
->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*'])
->autowire(true)
->autoconfigure(true);
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "thelia/thelia-library-module",
"description": "Add a media library to Thelia",
"license": "LGPL-3.0-or-later",
"type": "thelia-module",
"require": {
"thelia/installer": "~1.3",
"thelia/open-api-module": "^2.1.1"
},
"extra": {
"installer-name": "TheliaLibrary"
}
}

View File

@@ -0,0 +1,17 @@
const { src, dest, watch } = require("gulp");
function copy() {
return src("node_modules/@thelia/media-library/dist/*").pipe(
dest("templates/backOffice/default/tb-plugin/vendor/")
);
}
function defaultTask() {
if (process.env.NODE_ENV === "production") {
return copy();
} else {
watch(["node_modules/@thelia/media-library/dist/*"], copy);
}
}
exports.default = defaultTask;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
{
"name": "tb-plugin",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "husky install",
"build": "NODE_ENV=production node_modules/.bin/gulp",
"dev": "node_modules/.bin/gulp"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"gulp": "^4.0.2",
"husky": "^8.0.1"
},
"dependencies": {
"@thelia/blocks-editor": "^1.3.9",
"@thelia/media-library": "^1.1.4"
}
}

View File

@@ -0,0 +1,10 @@
itemType: {$itemType} <br>
itemId: {$itemId}
<a href="{navigate to="current" force_legacy_imagemanager=true}">
use old image manager
</a>
<div id="imageManager"></div>
<script type="text/javascript" crossorigin src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script type="text/javascript" crossorigin src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>

View File

@@ -0,0 +1,2 @@
<script src="{encore_module_asset file="tb-plugin/vendor/index.global.js" module="TheliaLibrary"}"></script>

View File

@@ -0,0 +1 @@
<link rel="stylesheet" href="{encore_module_asset file='tb-plugin/vendor/index.css' module="TheliaLibrary"}" />

View File

@@ -0,0 +1,488 @@
/* src/Input/Inputs.css */
.Input {
&__Wrapper {
@apply relative;
&__Header {
@apply flex justify-between;
}
&__Error {
@apply text-error mt-1 text-sm;
}
}
&__Text {
@apply relative w-full rounded-md border border-mediumGrey outline-none py-2 px-4;
height: 40px;
&__Wrapper {
@apply relative w-full flex flex-wrap items-stretch;
}
&:hover,
&:focus {
@apply border-darkCharbon;
}
&--withIcon {
@apply pl-10;
}
&--withIcons {
@apply px-10;
}
&--emphasize {
@apply text-vermillon;
}
}
&__Select {
background: url("data:image/svg+xml,<svg height='10px' width='10px' viewBox='0 0 16 16' fill='currentcolor' xmlns='http://www.w3.org/2000/svg'><path d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/></svg>") no-repeat;
background-color: white;
background-position: calc(100% - 12px) center;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
&__Separator {
@apply absolute h-6 bg-darkCharbon bottom-2 right-8 z-10;
width: 1px;
}
}
&__Icon {
@apply absolute py-1 h-full leading-snug rounded text-base font-normal text-center flex items-center justify-center;
&--left {
@apply left-0 pr-5 pl-4;
}
&--right {
@apply right-0 pr-4 pl-5;
}
}
}
/* src/Library/Library.css */
.Modal-Library {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
height: max-content;
width: 90%;
max-height: 90%;
overflow: auto;
border-radius: 8px;
background-color: white;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 350ms;
}
.Modal-Upload {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
height: max-content;
width: 50%;
max-height: 90%;
overflow: auto;
border-radius: 8px;
background-color: white;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 350ms;
}
.Modal-Tags {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: max-content;
max-width: 700px;
height: max-content;
max-height: 80%;
border-radius: 8px;
background-color: white;
}
.Modal__Content__Loader {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
text-align: center;
}
.Modal__Content__Loader span {
display: block;
}
.Library__NoContent {
width: 100%;
text-align: center;
}
.Library {
display: flex;
flex-direction: column;
gap: 30px;
}
.Library__Filters {
display: flex;
gap: 15px;
}
.Library__Filters .Input__Wrapper {
width: 400px;
}
.Library__Content {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.Library__Image {
display: flex;
flex-direction: column;
max-width: 160px;
height: max-content;
border: 1px solid #EBEBEB;
border-radius: 4px;
position: relative;
justify-content: center;
align-items: center;
gap: 10px;
padding-bottom: 16px;
}
.Library__Image img {
border-radius: 4px;
object-fit: contain;
}
.Library__Image__Title {
text-align: center;
overflow: hidden;
white-space: nowrap;
font-weight: 600;
text-overflow: ellipsis;
max-width: 130px;
}
.Library__Image__Action__Title {
text-align: center;
overflow: hidden;
white-space: nowrap;
font-weight: 600;
text-overflow: ellipsis;
max-width: 120px;
color: white;
}
.Library__Image__Tags {
position: absolute;
display: flex;
justify-content: flex-end;
gap: 4px;
flex-wrap: wrap;
top: 7px;
right: 7px;
}
.Library__Image__Tag {
width: 50px;
height: 20px;
border-radius: 20px;
text-align: center;
letter-spacing: 0.05em;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
background-color: white;
text-overflow: ellipsis;
overflow: hidden;
padding: 1px 3px;
}
.Library__Image__Actions {
display: none;
position: absolute;
background-color: rgba(0, 0, 0, 0.9);
height: 100%;
width: 100%;
top: 0;
border-radius: 4px;
}
.Library__Image__Actions__Wrapper {
display: flex;
gap: 0.5rem;
}
.Library__Add__Button {
display: flex;
align-items: center;
justify-content: center;
color: #fff;
border-radius: 100%;
background-color: #D21919;
cursor: pointer;
}
.Library__Add__Button:disabled {
background-color: #EBEBEB;
color: #fff;
}
.Library__Add__Button:enabled:hover {
background-color: #FA533C;
color: #fff;
}
.Library__Add__Button:hover {
background-color: transparent;
color: #222222;
}
.Library__Image:hover .Library__Image__Actions {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.Library__Image__Select__Action {
background: transparent;
border: 1px solid #fff;
color: #fff;
text-transform: uppercase;
padding: 6px 15px;
letter-spacing: 0.05em;
font-size: 12px;
font-weight: 600;
border-radius: 4px;
}
.Library__Image__Select__Action:hover {
background: #fff;
color: #000;
}
.Library__Image__Delete__Action {
background-color: #D21919;
border-radius: 100%;
width: 30px;
height: 30px;
color: #fff;
}
.Library__Image__Tag__Action {
background-color: #008958;
border-radius: 100%;
width: 30px;
height: 30px;
color: #fff;
}
.BlockImage__TagSelector {
display: flex;
align-items: center;
position: relative;
border-radius: 6px;
outline: none;
border: 1px solid #9B9B9B;
height: max-content;
min-height: 40px;
padding: 5px;
}
.BlockImage__TagSelector__Add {
position: absolute;
bottom: 3px;
right: 3px;
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
color: white;
border-radius: 4px;
background-color: #444444;
}
.BlockImage__TagSelector__Add:hover {
background-color: #333333;
}
.BlockImage__TagSelector__Tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.BlockImage__TagSelector__Tag {
max-width: 100px;
width: max-content;
max-height: 30px;
text-overflow: ellipsis;
overflow: hidden;
border-radius: 90px;
display: flex;
justify-content: space-between;
background-color: #333333;
color: white;
align-items: center;
padding: 4px;
padding-left: 14px;
}
.BlockImage__TagSelector__Tag > span {
font-weight: 600;
font-size: 14px;
margin-right: 7px;
}
.BlockImage__TagSelector__Tag__Remove {
background-color: white;
color: #111;
border-radius: 90px;
height: 20px;
width: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.TagList {
top: 100%;
left: 0;
background-color: white;
border-radius: 6px;
overflow: auto;
width: 100%;
position: absolute;
z-index: 30;
max-height: 200px;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.25);
}
.TagList__Item {
cursor: pointer;
display: flex;
flex-direction: column;
padding: 20px 15px;
}
.Select__Wrapper {
position: relative;
width: 250px;
}
@media screen and (max-width: 768px) {
.Library__Image__Title {
max-width: 100px;
}
.Library__Filters {
flex-direction: column;
}
.Library__Filters .Input__Wrapper,
.Library__Filters .Select__Wrapper {
width: auto;
}
.Library__Item {
padding: 0px;
}
}
/* src/Image/Image.css */
.BlockImage__Upload__Wrapper {
display: flex;
gap: 16px;
text-align: center;
}
.BlockImage__FromLibrary {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #fff;
border-radius: 4px;
padding: 50px 20px;
height: 200px;
}
.BlockImage__FromLibrary__Icon {
background-color: #f5f5f5;
padding: 10px;
border-radius: 100%;
margin-bottom: 10px;
}
.BlockImage__FromLocal {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 4px;
padding: 50px 20px;
height: 200px;
border: 1px dashed #787878;
}
.BlockImage__FromLocal.BlockImage__FromLocal--active {
border: 2px dashed #dc3018;
}
input.BlockImage__FromLocal__FileInput {
display: none;
}
.BlockImage__Button {
display: block;
cursor: pointer;
color: #dc3018;
font-weight: 600;
font-size: 12px;
line-height: 16px;
text-align: center;
letter-spacing: 0.05em;
text-transform: uppercase;
border: 1px solid #dc3018;
border-radius: 4px;
padding: 6px 15px;
}
.BlockImage__Button:hover {
background-color: #dc3018;
color: #fff;
}
.BlockImage__FromLocal__Icon {
margin-bottom: 10px;
}
.BlockImage__Infos__Form {
display: flex;
flex-direction: column;
justify-content: center;
gap: 16px;
width: 100%;
}
.BlockImage__Preview {
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #fff;
border-radius: 4px;
gap: 18px;
max-width: 290px;
}
.BlockImage__Preview__Infos {
width: 100%;
}
.BlockImage__Preview img {
border: 1px solid #EBEBEB;
padding: 5px;
border-radius: 4px;
}
.BlockImage__Preview__FileName {
display: block;
font-weight: 600;
font-size: 14px;
line-height: 19px;
color: #333333;
margin-bottom: 10px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.BlockImage__Infos {
display: flex;
gap: 40px;
}
.BlockImage__Upload--light .BlockImage__FromLocal,
.BlockImage__Upload--light .BlockImage__FromLibrary {
border: none;
background-color: inherit;
padding: 0px;
width: auto;
height: auto;
}
.BlockImage__Upload--light .BlockImage__FromLocal .BlockImage__FromLocal__Icon,
.BlockImage__Upload--light .BlockImage__FromLibrary .BlockImage__FromLibrary__Icon {
display: none;
}
@media screen and (max-width: 1366px) {
.BlockImage__Upload__Wrapper {
flex-direction: column;
}
.BlockImage__FromLibrary {
width: 100%;
}
.BlockImage__FromLocal {
width: 100%;
}
.BlockImage__Infos {
flex-direction: column;
}
}
/*# sourceMappingURL=index.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,44 @@
import { BlockPluginDefinition } from '@thelia/blocks-editor';
declare type ImageTag = {
imageTag: {
id: number;
imageId: number;
tagId: number;
};
tag: {
id: number;
title: string;
colorCode: string;
};
};
declare type LibraryImage = {
id: number | null;
fileName: string;
title: string;
tags: ImageTag[];
width: string;
height: string;
link?: {
url: string;
target?: string;
};
target: HTMLAnchorElement["target"];
};
declare const UploadImage: ({ onSelect, compact, uploadModes, }: {
onSelect: (image: LibraryImage) => void;
compact?: boolean | undefined;
uploadModes?: ("local" | "library")[] | undefined;
}) => JSX.Element;
declare const initialData: LibraryImage;
declare const blockImage: BlockPluginDefinition<LibraryImage>;
declare function WrappedComponent(props: {
isOpen: boolean;
setIsOpen: Function;
limit?: number;
onSelect: (image: LibraryImage) => void;
}): JSX.Element;
export { blockImage as BlockImage, initialData as BlockImageInitialData, LibraryImage, UploadImage, WrappedComponent as default };

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
{$hasLink = isset($data['link']) && isset($data['link']['url']) && $data['link']['url']}
{if $hasLink}
<a href="{$data['link']['url']}" {if isset($data['link']['target']) && $data['link']['target']}target="{$data['link']['target']}"{/if}>
{/if}
{if isset($data['id']|default:null)}
{loop type="library_image" name="library_image" id=$data['id']|default:null}
<img class="tb-{$type['id']|default:null}" src="{$URL}" alt="{$TITLE}" loading="lazy" style="{if $data['width'] != ""}width: {$data['width']};{/if} {if $data['height'] != ""}height: {$data['height']};{/if}" />
{/loop}
{else}
<img class="tb-{$type['id']|default:null}" src="{$data['url']|default:null}" alt="{$data['alt']|default:null}" loading="lazy" style="{if $data['width'] != ""}width: {$data['width']};{/if} {if $data['height'] != ""}height: {$data['height']};{/if}" />
{/if}
{if $hasLink}
</a>
{/if}