Files
Phraseanet/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php
jygaulier 07f4462672 PHRAS-3520_stamper-enhance (#4385)
* WIP DO NOT MERGE
add: stamp subdefs of class "preview"
apply watermark on stamped subdef (cache to be fixed)
fix: admin/collection settings (new ux, clean xml)

* WIP DO NOT MERGE
add: stamp subdefs of class "preview"
apply watermark on stamped subdef (cache to be fixed)
fix: admin/collection settings (new ux, clean xml)

* conf.export_stamp_choice now supports 'manage_collection' and 'manage_databox' (and bc true) to restrict right to remove stamp.

* fix test

* WIP DO NOT MERGE
add: stamp subdefs of class "preview"
apply watermark on stamped subdef (cache to be fixed)
fix: admin/collection settings (new ux, clean xml)

* conf.export_stamp_choice now supports 'manage_collection' and 'manage_databox' (and bc true) to restrict right to remove stamp.

* fix test

* fix compatibility with download-async ; add registry/actions/stamp-subdefs=false to conf
2023-11-22 12:30:55 +01:00

402 lines
12 KiB
PHP

<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Order\Controller;
use Alchemy\Phrasea\Application\Helper\DelivererAware;
use Alchemy\Phrasea\Application\Helper\FilesystemAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\Controller\RecordsRequest;
use Alchemy\Phrasea\Core\Event\ExportEvent;
use Alchemy\Phrasea\Core\Event\OrderEvent;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Http\DeliverDataInterface;
use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\BasketElement;
use Alchemy\Phrasea\Model\Entities\Order;
use Alchemy\Phrasea\Model\Entities\Token;
use Alchemy\Phrasea\Order\OrderElementTransformer;
use Alchemy\Phrasea\Order\OrderFiller;
use Alchemy\Phrasea\Order\OrderTransformer;
use Alchemy\Phrasea\Order\OrderViewBuilder;
use Alchemy\Phrasea\Record\RecordReferenceCollection;
use Doctrine\Common\Collections\ArrayCollection;
use League\Fractal\Manager;
use League\Fractal\Pagination\PagerfantaPaginatorAdapter;
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\Item;
use League\Fractal\Resource\ResourceInterface;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ApiOrderController extends BaseOrderController
{
use DelivererAware;
use FilesystemAware;
use JsonBodyAware;
/**
* @param Request $request
* @return Response
*/
public function createAction(Request $request)
{
$data = $this->decodeJsonBody($request, 'orders.json#/definitions/order_request');
$availableRecords = $this->toRequestedRecords($data->data->records);
$records = $this->filterOrderableRecords($availableRecords);
$recordRequest = new RecordsRequest(
$records, // orderable records
new ArrayCollection([]), // rejected (rights)
new ArrayCollection($availableRecords), // all records from request
null, // basket
RecordsRequest::FLATTEN_YES // orderable records is stories + children
);
$filler = new OrderFiller($this->app['repo.collection-references'], $this->app['orm.em']);
$filler->assertAllRecordsHaveOrderMaster($recordRequest);
$order = new Order();
$order->setUser($this->getAuthenticatedUser());
$now = new \DateTime();
try {
$dead = new \DateTime($data->data->deadline);
}
catch (\Exception $e) {
$dead = clone($now);
}
$now->setTimezone(new \DateTimeZone('UTC'));
$order->setCreatedOn($now); // safe value in case of data->deadline is invalid
$dead->setTimezone(new \DateTimeZone('UTC'));
$order->setDeadline($dead);
$order->setOrderUsage($data->data->usage);
$order->setNotificationMethod(Order::NOTIFY_WEBHOOK);
$filler->fillOrder($order, $recordRequest);
$this->dispatch(PhraseaEvents::ORDER_CREATE, new OrderEvent($order));
$view = $this->getViewBuilder()->buildView($order, []);
$resource = new Item($view, $this->getOrderTransformer());
return $this->returnResourceResponse($request, ['elements'], $resource);
}
public function indexAction(Request $request)
{
$page = max((int) $request->get('page', '1'), 1);
$perPage = min(max((int)$request->get('per_page', '10'), 1), 100);
$fractal = $this->buildFractalManager($request->get('includes', []));
$routeGenerator = function ($page) use ($perPage) {
return $this->app->path('api_v2_orders_index', [
'page' => $page,
'per_page' => $perPage,
]);
};
$builder = $this->getOrderRepository()->createQueryBuilder('o');
$builder
->where($builder->expr()->eq('o.user', $this->getAuthenticatedUser()->getId()))
;
if (in_array('elements', $fractal->getRequestedIncludes(), false)) {
$builder
->addSelect('e')
->leftJoin('o.elements', 'e')
;
}
$collection = $this->getViewBuilder()->buildViews(
$builder->getQuery()->getResult(),
$fractal->getRequestedIncludes()
);
$resource = new Collection($collection, $this->getOrderTransformer());
$pager = new Pagerfanta(new DoctrineORMAdapter($builder, false));
$pager->setCurrentPage($page);
$pager->setMaxPerPage($perPage);
$resource->setPaginator(new PagerfantaPaginatorAdapter($pager, $routeGenerator));
return $this->returnResourceResponse($request, $fractal, $resource);
}
/**
* @param Request $request
* @param int $orderId
* @return Response
*/
public function showAction(Request $request, $orderId)
{
$order = $this->findOr404($orderId);
$fractal = $this->buildFractalManager($request->get('includes', []));
if (! $this->isOrderAccessible($order)) {
throw new AccessDeniedHttpException(sprintf('Cannot access order "%d"', $order->getId()));
}
$model = $this->getViewBuilder()->buildView($order, $fractal->getRequestedIncludes());
$resource = new Item($model, $this->getOrderTransformer());
return $this->returnResourceResponse($request, $fractal, $resource);
}
/**
* @param $orderId
* @return Response
* @throws \Exception
*/
public function getArchiveAction($orderId)
{
$order = $this->findOr404($orderId);
if (! $this->isOrderAccessible($order)) {
throw new AccessDeniedHttpException(sprintf('Cannot access order "%d"', $order->getId()));
}
$basket = $order->getBasket();
if (null === $basket instanceof Basket) {
throw new NotFoundHttpException(sprintf('No archive could be downloaded for Order "%d"', $order->getId()));
}
$export = new \set_export($this->app, '', $basket->getId());
$exportName = sprintf('%s/%s.zip', $this->app['tmp.download.path'], $export->getExportName());
$user = $this->getAuthenticatedUser();
$subdefs = $this->findDataboxSubdefNames();
try {
$exportData = $export->prepare_export(
$user,
$this->getFilesystem(),
$subdefs,
true,
true,
\set_export::STAMP_SYNC,
false,
false
);
}
catch (\Exception $e) {
throw new NotFoundHttpException(sprintf('No archive could be downloaded for Order "%d"', $order->getId()));
}
/** @var Token $token */
$token = $this->app['manipulator.token']->createDownloadToken($user, serialize($exportData));
$lst = [];
foreach ($exportData['files'] as $file) {
$lst[] = $this->getApplicationBox()->get_collection($file['base_id'])->get_databox()->get_sbas_id() . '_' . $file['record_id'];
}
$this->dispatch(
PhraseaEvents::EXPORT_CREATE,
new ExportEvent($user, $basket->getId(), implode(';', $lst), $subdefs, $exportName)
);
set_time_limit(0);
ignore_user_abort(true);
$tmpDownloadDir = \p4string::addEndSlash($this->app['tmp.download.path']);
if(is_null($tmpDownloadDir)){
$tmpDownloadDir = '';
}
$zipFile = $tmpDownloadDir.'order_'.$token->getValue() . '.zip';
$file = \set_export::build_zip($this->app, $token, $exportData, $zipFile);
return $this->deliverFile($file, $exportName, DeliverDataInterface::DISPOSITION_INLINE, 'application/zip');
}
public function acceptElementsAction(Request $request, $orderId)
{
$elementIds = $this->fetchElementIdsFromRequest($request);
$elements = $this->doAcceptElements($orderId, $elementIds, $this->getAuthenticatedUser());
$resource = new Collection($elements, function (BasketElement $element) {
return [
'id' => $element->getId(),
'created' => $element->getCreated(),
'databox_id' => $element->getSbasId(),
'record_id' => $element->getRecordId(),
'index' => $element->getOrd(),
];
});
return $this->returnResourceResponse($request, [], $resource);
}
public function denyElementsAction(Request $request, $orderId)
{
$elementIds = $this->fetchElementIdsFromRequest($request);
$this->doDenyElements($orderId, $elementIds, $this->getAuthenticatedUser());
return Result::create($request, [])->createResponse();
}
/**
* @param array $records
* @return \record_adapter[]
*/
private function toRequestedRecords(array $records)
{
$requestedRecords = [];
foreach ($records as $item) {
$requestedRecords[] = [
'databox_id' => $item->databox_id,
'record_id' => $item->record_id,
];
}
return RecordReferenceCollection::fromArrayOfArray($requestedRecords)->toRecords($this->getApplicationBox());
}
/**
* @param \record_adapter[] $records
* @return \record_adapter[]
*/
private function filterOrderableRecords(array $records)
{
$acl = $this->getAclForUser();
$filtered = [];
foreach ($records as $index => $record) {
try {
if ($acl->has_right_on_base($record->getBaseId(), \ACL::CANCMD)) {
$filtered[$index] = $record;
}
}
catch (\Exception $e) {
// will NOT happen since \ACL::CANCMD IS a known right
}
}
return $filtered;
}
/**
* @return OrderTransformer
*/
private function getOrderTransformer()
{
return new OrderTransformer(new OrderElementTransformer($this->app['media_accessor.subdef_url_generator']));
}
/**
* @param string|array $includes
* @return Manager
*/
private function buildFractalManager($includes)
{
$fractal = new Manager();
$fractal->parseIncludes($includes ?: []);
return $fractal;
}
/**
* @param Request $request
* @param string|array|Manager $includes
* @param ResourceInterface $resource
* @return Response
*/
private function returnResourceResponse(Request $request, $includes, ResourceInterface $resource)
{
$fractal = $includes instanceof Manager ? $includes : $this->buildFractalManager($includes);
return Result::create($request, $fractal->createData($resource)->toArray())->createResponse();
}
/**
* @param Request $request
* @return array
*/
private function fetchElementIdsFromRequest(Request $request)
{
$data = $this->decodeJsonBody($request, 'orders.json#/definitions/order_element_collection');
$elementIds = [];
foreach ($data as $elementId) {
$elementIds[] = $elementId->id;
}
return $elementIds;
}
private function getViewBuilder()
{
return new OrderViewBuilder(
$this->app,
$this->getApplicationBox(),
$this->app['service.media_subdef']
);
}
/**
* @param $order
* @return bool
*/
private function isOrderAccessible(Order $order)
{
if ($order->getUser()->getId() === $this->getAuthenticatedUser()->getId()) {
return true;
}
if ($order->getUser()->isAdmin()) {
return true;
}
return false;
}
/**
* @return string[]
*/
private function findDataboxSubdefNames()
{
$subdefNames = [
'document' => true,
];
foreach ($this->getApplicationBox()->get_databoxes() as $databox) {
foreach ($databox->get_subdef_structure() as $subdefGroup) {
/** @var \databox_subdef $subdef */
foreach ($subdefGroup as $subdef) {
$subdefNames[$subdef->get_name()] = true;
}
}
}
return array_keys($subdefNames);
}
}