From 5ded48dafbe11ed07a2a14c62eae5ab7bb31f933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 17 Mar 2016 16:54:27 +0100 Subject: [PATCH 01/12] Allow retrieve collection by a collection of base ids --- ...rrayCacheCollectionReferenceRepository.php | 23 +++++++++++++ .../CollectionReferenceRepository.php | 6 ++++ .../DbalCollectionReferenceRepository.php | 19 +++++++++++ lib/classes/ACL.php | 33 +++++++++++++++---- lib/classes/collection.php | 11 ++++--- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php index 7351351e93..64c80ae955 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php @@ -72,6 +72,25 @@ class ArrayCacheCollectionReferenceRepository implements CollectionReferenceRepo return null; } + /** + * @param array $baseIds + * @return CollectionReference[] + */ + public function findMany(array $baseIds) + { + $references = $this->findAll(); + + $requested = []; + + foreach ($baseIds as $baseId) { + if (isset($references[$baseId])) { + $requested[] = $references[$baseId]; + } + } + + return $requested; + } + /** * @param int $databoxId * @param int $collectionId @@ -90,6 +109,10 @@ class ArrayCacheCollectionReferenceRepository implements CollectionReferenceRepo return null; } + /** + * @param array|null $baseIdsSubset + * @return CollectionReference[] + */ public function findHavingOrderMaster(array $baseIdsSubset = null) { return $this->repository->findHavingOrderMaster($baseIdsSubset); diff --git a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php index fdbf7289d1..7740f8ddc9 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php @@ -29,6 +29,12 @@ interface CollectionReferenceRepository */ public function find($baseId); + /** + * @param int[] $baseIds + * @return CollectionReference[] + */ + public function findMany(array $baseIds); + /** * @param int $databoxId * @param int $collectionId diff --git a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php index a67f8cad3d..00c8101259 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php @@ -95,6 +95,25 @@ WHERE base_id = :baseId'; return null; } + /** + * @param array $basesId + * @return CollectionReference[] + */ + public function findMany(array $basesId) + { + if (empty($basesId)) { + return []; + } + + $rows = $this->connection->fetchAll( + self::$selectQuery . ' WHERE base_id IN (:baseIds)', + ['baseIds' => $basesId], + ['baseIds' => Connection::PARAM_INT_ARRAY] + ); + + return $this->createManyReferences($rows); + } + /** * @param int $databoxId * @param int $collectionId diff --git a/lib/classes/ACL.php b/lib/classes/ACL.php index b28c134af6..7bb0766e61 100644 --- a/lib/classes/ACL.php +++ b/lib/classes/ACL.php @@ -1763,20 +1763,39 @@ class ACL implements cache_cacheableInterface /** * Returns an array of collections on which the user is 'order master' * - * @return array + * @return collection[] */ public function get_order_master_collections() { $sql = 'SELECT base_id FROM basusr WHERE order_master="1" AND usr_id= :usr_id'; - $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $this->user->getId()]); - $rs = $stmt->fetchAll(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); + $result = $this->app->getApplicationBox() + ->get_connection() + ->executeQuery($sql, [':usr_id' => $this->user->getId()]) + ->fetchAll(\PDO::FETCH_ASSOC); + + + $baseIds = []; + + foreach ($result as $item) { + $baseIds[] = $item['base_id']; + } + + $groups = []; + + foreach ($this->app['repo.collection-references']->findHavingOrderMaster($baseIds) as $index => $reference) { + $databoxId = $reference->getDataboxId(); + $group = isset($groups[$databoxId]) ? $groups[$databoxId] : []; + + $group[$reference->getCollectionId()] = $index; + $groups[$databoxId] = $group; + } $collections = []; - foreach ($rs as $row) { - $collections[] = \collection::getByBaseId($this->app, $row['base_id']); + foreach ($groups as $databoxId => $group) { + foreach ($group as $collectionId => $index) { + $collections[$index] = \collection::getByCollectionId($this->app, $databoxId, $collectionId); + } } return $collections; diff --git a/lib/classes/collection.php b/lib/classes/collection.php index 7ac76177bb..9c56620c05 100644 --- a/lib/classes/collection.php +++ b/lib/classes/collection.php @@ -192,16 +192,17 @@ class collection implements ThumbnailedElement, cache_cacheableInterface } /** - * @param Application $app - * @param databox $databox - * @param int $collectionId + * @param Application $app + * @param databox|int $databox + * @param int $collectionId * @return collection */ - public static function getByCollectionId(Application $app, databox $databox, $collectionId) + public static function getByCollectionId(Application $app, $databox, $collectionId) { assert(is_int($collectionId)); + $databoxId = $databox instanceof databox ? $databox->get_sbas_id() : (int)$databox; - return self::getAvailableCollection($app, $databox->get_sbas_id(), $collectionId); + return self::getAvailableCollection($app, $databoxId, $collectionId); } /** From 02829094ce0ce34b53e4558b45259b3acdda638e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 17 Mar 2016 18:00:58 +0100 Subject: [PATCH 02/12] WIP --- .../Controller/Api/OrderController.php | 49 ++++++++--- .../Controller/Prod/OrderController.php | 82 ++++++++++++------- .../Phrasea/ControllerProvider/Api/V2.php | 9 ++ .../Phrasea/ControllerProvider/Prod/Order.php | 13 +++ .../Phrasea/Order/OrderBasketProvider.php | 55 +++++++++++++ lib/Alchemy/Phrasea/Order/OrderValidator.php | 38 +++++++++ lib/classes/ACL.php | 18 +++- lib/conf.d/json_schema/orders.json | 19 ++++- 8 files changed, 239 insertions(+), 44 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Order/OrderBasketProvider.php create mode 100644 lib/Alchemy/Phrasea/Order/OrderValidator.php diff --git a/lib/Alchemy/Phrasea/Controller/Api/OrderController.php b/lib/Alchemy/Phrasea/Controller/Api/OrderController.php index 3bd6b54e01..0b872475c7 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Api/OrderController.php @@ -107,20 +107,10 @@ class OrderController extends Controller */ public function showAction(Request $request, $orderId) { - try { - Assertion::integerish($orderId); - } catch (InvalidArgumentException $exception) { - throw new BadRequestHttpException($exception->getMessage(), $exception); - } + $order = $this->findOr404($orderId); $includes = $request->get('includes', []); - $order = $this->app['repo.orders']->find((int)$orderId); - - if (!$order instanceof Order) { - throw new NotFoundHttpException(sprintf('Order "%d" was not found', (int) $orderId)); - } - if ($order->getUser()->getId() !== $this->getAuthenticatedUser()->getId()) { throw new AccessDeniedHttpException(sprintf('Cannot access order "%d"', $order->getId())); } @@ -130,6 +120,22 @@ class OrderController extends Controller return $this->returnResourceResponse($request, $includes, $resource); } + public function acceptElementsAction(Request $request, $orderId) + { + $order = $this->findOr404($orderId); + $data = $this->decodeJsonBody($request, 'orders.json#/definitions/order_element_collection'); + + + return Result::create($request, [])->createResponse(); + } + + public function denyElementsAction(Request $request, $orderId) + { + $order = $this->findOr404($orderId); + + return Result::create($request, [])->createResponse(); + } + /** * @param array $records * @return \record_adapter[] @@ -188,4 +194,25 @@ class OrderController extends Controller return Result::create($request, $fractal->createData($resource)->toArray())->createResponse(); } + + /** + * @param int $orderId + * @return Order + */ + private function findOr404($orderId) + { + try { + Assertion::integerish($orderId); + } catch (InvalidArgumentException $exception) { + throw new BadRequestHttpException($exception->getMessage(), $exception); + } + + $order = $this->app['repo.orders']->find((int)$orderId); + + if (!$order instanceof Order) { + throw new NotFoundHttpException(sprintf('Order "%d" was not found', (int)$orderId)); + } + + return $order; + } } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php index 06527ee501..699c390b5c 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php @@ -21,14 +21,19 @@ use Alchemy\Phrasea\Model\Entities\Basket; use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\Order as OrderEntity; use Alchemy\Phrasea\Model\Entities\Order; +use Alchemy\Phrasea\Model\Entities\OrderElement; +use Alchemy\Phrasea\Model\Repositories\OrderElementRepository; use Alchemy\Phrasea\Model\Repositories\OrderRepository; use Alchemy\Phrasea\Order\OrderFiller; +use Assert\Assertion; use Doctrine\Common\Collections\ArrayCollection; use Silex\Application; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class OrderController extends Controller @@ -155,56 +160,67 @@ class OrderController extends Controller */ public function sendOrder(Request $request, $order_id) { - $success = false; + $elementIds = $request->request->get('elements', []); + + try { + Assertion::isArray($elementIds); + } catch (\Exception $exception) { + throw new BadRequestHttpException('Improper request', $exception); + } + + /** @var OrderElement[] $elements */ + $elements = $this->getOrderElementRepository()->findBy([ + 'id' => $elementIds, + 'order' => $order_id, + ]); + + if (count($elements) !== count($elementIds)) { + throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $order_id)); + } + /** @var Order $order */ if (null === $order = $this->getOrderRepository()->find($order_id)) { throw new NotFoundHttpException('Order not found'); } - $manager = $this->getEntityManager(); - $basket = $order->getBasket(); - if (null === $basket) { - $basket = new Basket(); - $basket->setName($this->app->trans('Commande du %date%', [ - '%date%' => $order->getCreatedOn()->format('Y-m-d'), - ])); - $basket->setUser($order->getUser()); - $basket->setPusher($this->getAuthenticatedUser()); + $success = false; - $manager->persist($basket); - $manager->flush(); + $acceptor = $this->getAuthenticatedUser(); + + if ($this->app['validator.order']->isGrantedValidation($acceptor, $elements)) { + throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); } + $basket = $this->app['provider.order_basket']->provideBasketForOrderAndUser($order, $acceptor); + $n = 0; - $elements = $request->request->get('elements', []); - foreach ($order->getElements() as $orderElement) { - if (in_array($orderElement->getId(), $elements)) { - $sbas_id = \phrasea::sbasFromBas($this->app, $orderElement->getBaseId()); - $record = new \record_adapter($this->app, $sbas_id, $orderElement->getRecordId()); - $basketElement = new BasketElement(); - $basketElement->setRecord($record); - $basketElement->setBasket($basket); + foreach ($elements as $element) { + $sbas_id = \phrasea::sbasFromBas($this->app, $element->getBaseId()); + $record = new \record_adapter($this->app, $sbas_id, $element->getRecordId()); - $orderElement->setOrderMaster($this->getAuthenticatedUser()); - $orderElement->setDeny(false); - $orderElement->getOrder()->setBasket($basket); + $basketElement = new BasketElement(); + $basketElement->setRecord($record); + $basketElement->setBasket($basket); - $basket->addElement($basketElement); + $element->setOrderMaster($acceptor); + $element->setDeny(false); + $element->getOrder()->setBasket($basket); - $n++; - $this->getAclForUser($basket->getUser())->grant_hd_on($record, $this->getAuthenticatedUser(), 'order'); - } + $basket->addElement($basketElement); + + $n++; + $this->getAclForUser($basket->getUser())->grant_hd_on($record, $acceptor, 'order'); } try { if ($n > 0) { $order->setTodo($order->getTodo() - $n); - $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $this->getAuthenticatedUser(), $n)); + $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, $n)); } $success = true; - // There was a basketElement persist here. Seems useless as all entities are managed. + $manager = $this->getEntityManager(); $manager->persist($basket); $manager->persist($order); $manager->flush(); @@ -293,4 +309,12 @@ class OrderController extends Controller { return $this->app['repo.orders']; } + + /** + * @return OrderElementRepository + */ + private function getOrderElementRepository() + { + return $this->app['repo.order-elements']; + } } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php index 7fa1d9bc84..c713f3329c 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php @@ -102,8 +102,17 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface $controllers->get('/orders/', 'controller.api.v2.orders:indexAction') ->bind('api_v2_orders_index'); $controllers->get('/orders/{orderId}', 'controller.api.v2.orders:showAction') + ->assert('orderId', '\d+') ->bind('api_v2_orders_show'); + $controllers->post('/orders/{orderId}/accept', 'controller.api.v2.orders:acceptElementsAction') + ->assert('orderId', '\d+') + ->bind('api_v2_orders_accept'); + + $controllers->post('/orders/{orderId}/deny', 'controller.api.v2.orders:denyElementsAction') + ->assert('orderId', '\d+') + ->bind('api_v2_orders_deny'); + return $controllers; } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php index febdc98c08..872731d2ea 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php @@ -15,6 +15,8 @@ use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\LazyLocator; use Alchemy\Phrasea\Controller\Prod\OrderController; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; +use Alchemy\Phrasea\Order\OrderBasketProvider; +use Alchemy\Phrasea\Order\OrderValidator; use Silex\Application; use Silex\ControllerProviderInterface; use Silex\ServiceProviderInterface; @@ -25,6 +27,17 @@ class Order implements ControllerProviderInterface, ServiceProviderInterface public function register(Application $app) { + $app['provider.order_basket'] = $app->share(function (PhraseaApplication $app) { + return new OrderBasketProvider($app['orm.em'], $app['translator']); + }); + + $app['validator.order'] = $app->share(function (PhraseaApplication $app) { + $orderValidator = new OrderValidator(); + $orderValidator->setAclProvider($app['acl']); + + return $orderValidator; + }); + $app['controller.prod.order'] = $app->share(function (PhraseaApplication $app) { return (new OrderController($app)) ->setDispatcher($app['dispatcher']) diff --git a/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php b/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php new file mode 100644 index 0000000000..2aff1eb490 --- /dev/null +++ b/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php @@ -0,0 +1,55 @@ +manager = $manager; + $this->translator = $translator; + } + + public function provideBasketForOrderAndUser(Order $order, User $acceptor) + { + $basket = $order->getBasket(); + + if (null === $basket) { + $basket = new Basket(); + $basket->setName($this->translator->trans('Commande du %date%', [ + '%date%' => $order->getCreatedOn()->format('Y-m-d'), + ])); + $basket->setUser($order->getUser()); + $basket->setPusher($acceptor); + + $this->manager->persist($basket); + $this->manager->flush($basket); + } + + return $basket; + } +} diff --git a/lib/Alchemy/Phrasea/Order/OrderValidator.php b/lib/Alchemy/Phrasea/Order/OrderValidator.php new file mode 100644 index 0000000000..769d15f2d7 --- /dev/null +++ b/lib/Alchemy/Phrasea/Order/OrderValidator.php @@ -0,0 +1,38 @@ +getAclForUser($acceptor)->getOrderMasterCollectionsBaseIds(); + + $elementsCollections = []; + + foreach ($elements as $element) { + $elementsCollections[$element->getBaseId()] = true; + } + + return empty(array_diff(array_keys($elementsCollections), $acceptableCollections)); + } +} diff --git a/lib/classes/ACL.php b/lib/classes/ACL.php index 7bb0766e61..395175e443 100644 --- a/lib/classes/ACL.php +++ b/lib/classes/ACL.php @@ -1761,11 +1761,11 @@ class ACL implements cache_cacheableInterface } /** - * Returns an array of collections on which the user is 'order master' + * Returns base ids on which user is 'order master' * - * @return collection[] + * @return array */ - public function get_order_master_collections() + public function getOrderMasterCollectionsBaseIds() { $sql = 'SELECT base_id FROM basusr WHERE order_master="1" AND usr_id= :usr_id'; $result = $this->app->getApplicationBox() @@ -1780,6 +1780,18 @@ class ACL implements cache_cacheableInterface $baseIds[] = $item['base_id']; } + return $baseIds; + } + + /** + * Returns an array of collections on which the user is 'order master' + * + * @return collection[] + */ + public function get_order_master_collections() + { + $baseIds = $this->getOrderMasterCollectionsBaseIds(); + $groups = []; foreach ($this->app['repo.collection-references']->findHavingOrderMaster($baseIds) as $index => $reference) { diff --git a/lib/conf.d/json_schema/orders.json b/lib/conf.d/json_schema/orders.json index 9ae143cc33..9e10d09909 100644 --- a/lib/conf.d/json_schema/orders.json +++ b/lib/conf.d/json_schema/orders.json @@ -64,7 +64,7 @@ "owner_id", "created", "usage", - "records" + "elements" ] }, "order_element": { @@ -87,6 +87,23 @@ "id", "record_id" ] + }, + "order_element_id": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ] + }, + "order_element_collection": { + "type": "array", + "items": { + "$ref": "#/definitions/order_element_id" + } } }, "type": "object", From 431972e66effd8d8f711d6222f386ee1bbe4548d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 00:51:27 +0100 Subject: [PATCH 03/12] Allow some Acl method to be used with RecordReference --- lib/classes/ACL.php | 56 ++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/lib/classes/ACL.php b/lib/classes/ACL.php index 395175e443..b57a6551ff 100644 --- a/lib/classes/ACL.php +++ b/lib/classes/ACL.php @@ -10,15 +10,11 @@ */ use Alchemy\Phrasea\Application; -use Alchemy\Phrasea\Model\Entities\User; -use Doctrine\DBAL\DBALException; -use Alchemy\Phrasea\Model\RecordInterface; - -use Alchemy\Phrasea\Core\Event\Acl\AclEvents; use Alchemy\Phrasea\Core\Event\Acl\AccessPeriodChangedEvent; use Alchemy\Phrasea\Core\Event\Acl\AccessToBaseGrantedEvent; use Alchemy\Phrasea\Core\Event\Acl\AccessToBaseRevokedEvent; use Alchemy\Phrasea\Core\Event\Acl\AccessToSbasGrantedEvent; +use Alchemy\Phrasea\Core\Event\Acl\AclEvents; use Alchemy\Phrasea\Core\Event\Acl\DownloadQuotasOnBaseChangedEvent; use Alchemy\Phrasea\Core\Event\Acl\DownloadQuotasOnBaseRemovedEvent; use Alchemy\Phrasea\Core\Event\Acl\DownloadQuotasResetEvent; @@ -26,6 +22,10 @@ use Alchemy\Phrasea\Core\Event\Acl\MasksOnBaseChangedEvent; use Alchemy\Phrasea\Core\Event\Acl\RightsToBaseChangedEvent; use Alchemy\Phrasea\Core\Event\Acl\RightsToSbasChangedEvent; use Alchemy\Phrasea\Core\Event\Acl\SysadminChangedEvent; +use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\RecordInterface; +use Alchemy\Phrasea\Model\RecordReferenceInterface; +use Doctrine\DBAL\DBALException; class ACL implements cache_cacheableInterface @@ -52,51 +52,40 @@ class ACL implements cache_cacheableInterface ]; /** - * - * @var user + * @var User */ protected $user; /** - * - * @var Array + * @var array */ protected $_rights_sbas; /** - * - * @var Array + * @var array */ protected $_rights_bas; /** - * - * @var Array + * @var array */ protected $_rights_records_document; /** - * - * @var Array + * @var array */ protected $_rights_records_preview; /** - * - * @var Array + * @var array */ protected $_limited; /** - * - * @var boolean + * @var bool */ protected $is_admin; - /** - * - * @var Array - */ protected $_global_rights = [ 'addrecord' => false, 'addtoalbum' => false, @@ -121,7 +110,6 @@ class ACL implements cache_cacheableInterface ]; /** - * * @var Application */ protected $app; @@ -140,15 +128,11 @@ class ACL implements cache_cacheableInterface * * @param User $user * @param Application $app - * - * @return \ACL */ public function __construct(User $user, Application $app) { $this->user = $user; $this->app = $app; - - return $this; } /** @@ -164,10 +148,10 @@ class ACL implements cache_cacheableInterface /** * Check if a hd grant has been received for a record * - * @param \record_adapter $record - * @return boolean + * @param RecordReferenceInterface $record + * @return bool */ - public function has_hd_grant(RecordInterface $record) + public function has_hd_grant(RecordReferenceInterface $record) { $this->load_hd_grant(); @@ -179,7 +163,7 @@ class ACL implements cache_cacheableInterface return false; } - public function grant_hd_on(RecordInterface $record, User $pusher, $action) + public function grant_hd_on(RecordReferenceInterface $record, User $pusher, $action) { $sql = 'REPLACE INTO records_rights (id, usr_id, sbas_id, record_id, document, `case`, pusher_usr_id) @@ -203,7 +187,7 @@ class ACL implements cache_cacheableInterface return $this; } - public function grant_preview_on(RecordInterface $record, User $pusher, $action) + public function grant_preview_on(RecordReferenceInterface $record, User $pusher, $action) { $sql = 'REPLACE INTO records_rights (id, usr_id, sbas_id, record_id, preview, `case`, pusher_usr_id) @@ -230,10 +214,10 @@ class ACL implements cache_cacheableInterface /** * Check if a hd grant has been received for a record * - * @param \record_adapter $record - * @return boolean + * @param RecordReferenceInterface $record + * @return bool */ - public function has_preview_grant(RecordInterface $record) + public function has_preview_grant(RecordReferenceInterface $record) { $this->load_hd_grant(); From 2ca47d13f682c0786fadc57e827feed3f4d85e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 00:52:15 +0100 Subject: [PATCH 04/12] WIP --- .../Controller/Prod/OrderController.php | 157 +++++++++--------- .../Phrasea/ControllerProvider/Prod/Order.php | 2 +- lib/Alchemy/Phrasea/Order/OrderValidator.php | 131 +++++++++++++++ lib/Alchemy/Phrasea/Order/PartialOrder.php | 76 +++++++++ .../Record/RecordReferenceCollection.php | 15 +- 5 files changed, 302 insertions(+), 79 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Order/PartialOrder.php diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php index 699c390b5c..f6d2d192b1 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php @@ -22,9 +22,12 @@ use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\Order as OrderEntity; use Alchemy\Phrasea\Model\Entities\Order; use Alchemy\Phrasea\Model\Entities\OrderElement; +use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Repositories\OrderElementRepository; use Alchemy\Phrasea\Model\Repositories\OrderRepository; use Alchemy\Phrasea\Order\OrderFiller; +use Alchemy\Phrasea\Order\OrderValidator; +use Alchemy\Phrasea\Order\PartialOrder; use Assert\Assertion; use Doctrine\Common\Collections\ArrayCollection; use Silex\Application; @@ -141,10 +144,7 @@ class OrderController extends Controller */ public function displayOneOrder($order_id) { - $order = $this->getOrderRepository()->find($order_id); - if (null === $order) { - throw new NotFoundHttpException('Order not found'); - } + $order = $this->findOr404($order_id); return $this->render('prod/orders/order_item.html.twig', [ 'order' => $order, @@ -161,71 +161,38 @@ class OrderController extends Controller public function sendOrder(Request $request, $order_id) { $elementIds = $request->request->get('elements', []); + $acceptor = $this->getAuthenticatedUser(); - try { - Assertion::isArray($elementIds); - } catch (\Exception $exception) { - throw new BadRequestHttpException('Improper request', $exception); - } + $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); + $order = $this->findOr404($order_id); - /** @var OrderElement[] $elements */ - $elements = $this->getOrderElementRepository()->findBy([ - 'id' => $elementIds, - 'order' => $order_id, - ]); - - if (count($elements) !== count($elementIds)) { - throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $order_id)); - } - - /** @var Order $order */ - if (null === $order = $this->getOrderRepository()->find($order_id)) { - throw new NotFoundHttpException('Order not found'); - } + $basket = $this->app['provider.order_basket']->provideBasketForOrderAndUser($order, $acceptor); + $orderValidator = $this->getOrderValidator(); + $partialOrder = new PartialOrder($order, $elements); + $basketElements = $orderValidator->createBasketElements($partialOrder); + $orderValidator->accept($acceptor, $partialOrder); + $orderValidator->grantHD($basket->getUser(), $basketElements); $success = false; - $acceptor = $this->getAuthenticatedUser(); - - if ($this->app['validator.order']->isGrantedValidation($acceptor, $elements)) { - throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); - } - - $basket = $this->app['provider.order_basket']->provideBasketForOrderAndUser($order, $acceptor); - - $n = 0; - - foreach ($elements as $element) { - $sbas_id = \phrasea::sbasFromBas($this->app, $element->getBaseId()); - $record = new \record_adapter($this->app, $sbas_id, $element->getRecordId()); - - $basketElement = new BasketElement(); - $basketElement->setRecord($record); - $basketElement->setBasket($basket); - - $element->setOrderMaster($acceptor); - $element->setDeny(false); - $element->getOrder()->setBasket($basket); - - $basket->addElement($basketElement); - - $n++; - $this->getAclForUser($basket->getUser())->grant_hd_on($record, $acceptor, 'order'); - } - try { - if ($n > 0) { - $order->setTodo($order->getTodo() - $n); - $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, $n)); + $manager = $this->getEntityManager(); + if (!empty($basketElements)) { + foreach ($basketElements as $element) { + $basket->addElement($element); + $manager->persist($element); + } + + $order->setTodo($order->getTodo() - count($basketElements)); + $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, count($basketElements))); } $success = true; - $manager = $this->getEntityManager(); $manager->persist($basket); $manager->persist($order); $manager->flush(); } catch (\Exception $e) { - + // I don't know why only basket persistence is not checked } if ('json' === $request->getRequestFormat()) { @@ -254,32 +221,21 @@ class OrderController extends Controller public function denyOrder(Request $request, $order_id) { $success = false; - /** @var Order $order */ - $order = $this->getOrderRepository()->find($order_id); - if (null === $order) { - throw new NotFoundHttpException('Order not found'); - } + $elementIds = $request->request->get('elements', []); + $acceptor = $this->getAuthenticatedUser(); - $n = 0; - $elements = $request->request->get('elements', []); - $manager = $this->getEntityManager(); - foreach ($order->getElements() as $orderElement) { - if (in_array($orderElement->getId(),$elements)) { - $orderElement->setOrderMaster($this->getAuthenticatedUser()); - $orderElement->setDeny(true); + $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); + $order = $this->findOr404($order_id); - $manager->persist($orderElement); - $n++; - } - } + $this->getOrderValidator()->deny($acceptor, new PartialOrder($order, $elements)); try { - if ($n > 0) { - $order->setTodo($order->getTodo() - $n); - $this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($order, $this->getAuthenticatedUser(), $n)); + if (!empty($elements)) { + $this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($order, $acceptor, count($elements))); } $success = true; + $manager = $this->getEntityManager(); $manager->persist($order); $manager->flush(); } catch (\Exception $e) { @@ -317,4 +273,55 @@ class OrderController extends Controller { return $this->app['repo.order-elements']; } + + /** + * @param int $orderId + * @return Order + */ + private function findOr404($orderId) + { + if (null === $order = $this->getOrderRepository()->find($orderId)) { + throw new NotFoundHttpException('Order not found'); + } + + return $order; + } + + /** + * @param int $orderId + * @param array $elementIds + * @param User $acceptor + * @return OrderElement[] + */ + private function findRequestedElements($orderId, $elementIds, User $acceptor) + { + try { + Assertion::isArray($elementIds); + } catch (\Exception $exception) { + throw new BadRequestHttpException('Improper request', $exception); + } + + $elements = $this->getOrderElementRepository()->findBy([ + 'id' => $elementIds, + 'order' => $orderId, + ]); + + if (count($elements) !== count($elementIds)) { + throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $orderId)); + } + + if (!$this->getOrderValidator()->isGrantedValidation($acceptor, $elements)) { + throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); + } + + return $elements; + } + + /** + * @return OrderValidator + */ + private function getOrderValidator() + { + return $this->app['validator.order']; + } } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php index 872731d2ea..ce1408fe37 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php @@ -32,7 +32,7 @@ class Order implements ControllerProviderInterface, ServiceProviderInterface }); $app['validator.order'] = $app->share(function (PhraseaApplication $app) { - $orderValidator = new OrderValidator(); + $orderValidator = new OrderValidator($app['phraseanet.appbox'], $app['repo.collection-references']); $orderValidator->setAclProvider($app['acl']); return $orderValidator; diff --git a/lib/Alchemy/Phrasea/Order/OrderValidator.php b/lib/Alchemy/Phrasea/Order/OrderValidator.php index 769d15f2d7..150353268a 100644 --- a/lib/Alchemy/Phrasea/Order/OrderValidator.php +++ b/lib/Alchemy/Phrasea/Order/OrderValidator.php @@ -11,13 +11,37 @@ namespace Alchemy\Phrasea\Order; use Alchemy\Phrasea\Application\Helper\AclAware; +use Alchemy\Phrasea\Collection\Reference\CollectionReferenceRepository; +use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\OrderElement; use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Record\RecordReference; +use Alchemy\Phrasea\Record\RecordReferenceCollection; +use Assert\Assertion; class OrderValidator { + const VALIDATION_ACCEPT = false; + const VALIDATION_DENY = true; + use AclAware; + /** + * @var \appbox + */ + private $appbox; + + /** + * @var CollectionReferenceRepository + */ + private $repository; + + public function __construct(\appbox $appbox, CollectionReferenceRepository $repository) + { + $this->appbox = $appbox; + $this->repository = $repository; + } + /** * @param User $acceptor * @param OrderElement[] $elements @@ -35,4 +59,111 @@ class OrderValidator return empty(array_diff(array_keys($elementsCollections), $acceptableCollections)); } + + /** + * @param PartialOrder $order + * @return BasketElement[] + */ + public function createBasketElements(PartialOrder $order) + { + $basketElements = []; + + $references = $this->getRecordReferenceCollection($order); + + foreach ($references->toRecords($this->appbox) as $record) { + $basketElement = new BasketElement(); + $basketElement->setRecord($record); + + $basketElements[] = $basketElement; + } + + return $basketElements; + } + + /** + * @param User $acceptor + * @param PartialOrder $order + */ + public function accept(User $acceptor, PartialOrder $order) + { + $this->acceptOrDenyPartialOrder($acceptor, $order, self::VALIDATION_ACCEPT); + } + + /** + * @param User $acceptor + * @param PartialOrder $order + */ + public function deny(User $acceptor, PartialOrder $order) + { + $this->acceptOrDenyPartialOrder($acceptor, $order, self::VALIDATION_DENY); + } + + /** + * @param User $user + * @param BasketElement[] $elements + */ + public function grantHD(User $user, $elements) + { + Assertion::allIsInstanceOf($elements, BasketElement::class); + + $acl = $this->getAclForUser($user); + + foreach ($elements as $element) { + $recordReference = RecordReference::createFromDataboxIdAndRecordId( + $element->getSbasId(), + $element->getRecordId() + ); + + $acl->grant_hd_on($recordReference, $user, 'order'); + } + } + + /** + * @param PartialOrder $order + * @return RecordReferenceCollection + */ + private function getRecordReferenceCollection(PartialOrder $order) + { + $collections = []; + + foreach ($this->repository->findMany($order->getBaseIds()) as $collectionReference) { + $collections[$collectionReference->getBaseId()] = $collectionReference; + } + + $references = new RecordReferenceCollection(); + + foreach ($order->getElements() as $orderElement) { + if (!isset($collections[$orderElement->getBaseId()])) { + throw new \RuntimeException('At least one collection was not found.'); + } + + $references->addRecordReference(RecordReference::createFromDataboxIdAndRecordId( + $collections[$orderElement->getBaseId()], + $orderElement->getRecordId() + )); + } + + return $references; + } + + /** + * @param User $acceptor + * @param PartialOrder $order + * @param bool $deny + */ + private function acceptOrDenyPartialOrder(User $acceptor, PartialOrder $order, $deny) + { + $elements = $order->getElements(); + + if (empty($elements)) { + return; + } + + foreach ($elements as $element) { + $element->setOrderMaster($acceptor); + $element->setDeny($deny); + } + + $order->getOrder()->setTodo($order->getOrder()->getTodo() - count($elements)); + } } diff --git a/lib/Alchemy/Phrasea/Order/PartialOrder.php b/lib/Alchemy/Phrasea/Order/PartialOrder.php new file mode 100644 index 0000000000..4eb094bef2 --- /dev/null +++ b/lib/Alchemy/Phrasea/Order/PartialOrder.php @@ -0,0 +1,76 @@ +order = $order; + + $this->elements = []; + + foreach ($elements as $element) { + if (null === $element->getOrder() || $element->getOrder()->getId() !== $order->getId()) { + throw new \InvalidArgumentException('Elements should belong to same order'); + } + + $this->elements[$element->getId()] = $element; + } + } + + /** + * @return Order + */ + public function getOrder() + { + return $this->order; + } + + /** + * @return OrderElement[] + */ + public function getElements() + { + return $this->elements; + } + + public function getBaseIds() + { + $baseIds = []; + + foreach ($this->elements as $element) { + $baseIds[$element->getBaseId()] = true; + } + + return array_keys($baseIds); + } +} diff --git a/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php b/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php index 8335c4db90..802d4a9e67 100644 --- a/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php +++ b/lib/Alchemy/Phrasea/Record/RecordReferenceCollection.php @@ -15,8 +15,6 @@ use Assert\Assertion; class RecordReferenceCollection implements \IteratorAggregate { - private $groups; - /** * @param array $records * @return RecordReferenceCollection @@ -43,16 +41,27 @@ class RecordReferenceCollection implements \IteratorAggregate */ private $references = []; + /** + * @var null|array + */ + private $groups; + /** * @param RecordReferenceInterface[] $references */ - public function __construct($references) + public function __construct($references = []) { Assertion::allIsInstanceOf($references, RecordReferenceInterface::class); $this->references = $references instanceof \Traversable ? iterator_to_array($references) : $references; } + public function addRecordReference(RecordReferenceInterface $reference) + { + $this->references[] = $reference; + $this->groups = null; + } + public function getIterator() { return new \ArrayIterator($this->references); From a01257ae6b21f329b744e24576ef1bb7be7650f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 10:26:04 +0100 Subject: [PATCH 05/12] Add basket owning logic in Order --- lib/Alchemy/Phrasea/Model/Entities/Order.php | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/Order.php b/lib/Alchemy/Phrasea/Model/Entities/Order.php index 5fe32c9d95..4363343438 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/Order.php +++ b/lib/Alchemy/Phrasea/Model/Entities/Order.php @@ -1,9 +1,8 @@ todo; } + /** + * @param int $count + */ + public function decrementTodo($count) + { + $this->todo -= $count; + } + /** * Returns the total number of elements. * @@ -250,8 +258,16 @@ class Order */ public function setBasket(Basket $basket = null) { + if ($this->basket) { + $this->basket->setOrder(null); + } + $this->basket = $basket; + if ($basket) { + $basket->setOrder($this); + } + return $this; } From 6d086bcb52f8cc7b07e5707d6f302ecfecc52af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 10:29:20 +0100 Subject: [PATCH 06/12] fix Basket owning setting when providing it --- .../Phrasea/Controller/Prod/OrderController.php | 1 - lib/Alchemy/Phrasea/Order/OrderBasketProvider.php | 3 +++ lib/Alchemy/Phrasea/Order/OrderValidator.php | 10 +++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php index f6d2d192b1..c371e0f522 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php @@ -183,7 +183,6 @@ class OrderController extends Controller $manager->persist($element); } - $order->setTodo($order->getTodo() - count($basketElements)); $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, count($basketElements))); } $success = true; diff --git a/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php b/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php index 2aff1eb490..8a442bb9e0 100644 --- a/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php +++ b/lib/Alchemy/Phrasea/Order/OrderBasketProvider.php @@ -43,6 +43,9 @@ class OrderBasketProvider $basket->setName($this->translator->trans('Commande du %date%', [ '%date%' => $order->getCreatedOn()->format('Y-m-d'), ])); + + $order->setBasket($basket); + $basket->setUser($order->getUser()); $basket->setPusher($acceptor); diff --git a/lib/Alchemy/Phrasea/Order/OrderValidator.php b/lib/Alchemy/Phrasea/Order/OrderValidator.php index 150353268a..452ad3031d 100644 --- a/lib/Alchemy/Phrasea/Order/OrderValidator.php +++ b/lib/Alchemy/Phrasea/Order/OrderValidator.php @@ -124,21 +124,21 @@ class OrderValidator */ private function getRecordReferenceCollection(PartialOrder $order) { - $collections = []; + $databoxIdMap = []; foreach ($this->repository->findMany($order->getBaseIds()) as $collectionReference) { - $collections[$collectionReference->getBaseId()] = $collectionReference; + $databoxIdMap[$collectionReference->getBaseId()] = $collectionReference->getDataboxId(); } $references = new RecordReferenceCollection(); foreach ($order->getElements() as $orderElement) { - if (!isset($collections[$orderElement->getBaseId()])) { + if (!isset($databoxIdMap[$orderElement->getBaseId()])) { throw new \RuntimeException('At least one collection was not found.'); } $references->addRecordReference(RecordReference::createFromDataboxIdAndRecordId( - $collections[$orderElement->getBaseId()], + $databoxIdMap[$orderElement->getBaseId()], $orderElement->getRecordId() )); } @@ -164,6 +164,6 @@ class OrderValidator $element->setDeny($deny); } - $order->getOrder()->setTodo($order->getOrder()->getTodo() - count($elements)); + $order->getOrder()->decrementTodo(count($elements)); } } From b0045005f29c693d88e4e8abf1e1c66a425a5319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 10:30:28 +0100 Subject: [PATCH 07/12] Refactor OrderTest --- .../Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php index 16b555d70e..e6bb39931c 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php @@ -116,12 +116,11 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase foreach ($order->getElements() as $element) { $parameters[] = $element->getId(); } - $client = $this->getClient(); - $client->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); - $this->assertTrue($client->getResponse()->isRedirect()); - $url = parse_url($client->getResponse()->headers->get('location')); + $response = $this->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); + $this->assertTrue($response->isRedirect(), 'Could not validate some elements. not a redirect'); + $url = parse_url($response->headers->get('location')); parse_str($url['query']); - $this->assertTrue( strpos($url['query'], 'success=1') === 0); + $this->assertTrue(strpos($url['query'], 'success=1') === 0, 'Validation of elements is not successful'); } /** From c394887a197695414410c929faf1cdd03c802b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 11:10:06 +0100 Subject: [PATCH 08/12] Move Order controllers to same namespace --- .../Phrasea/ControllerProvider/Api/V2.php | 4 +- .../Phrasea/ControllerProvider/Prod/Order.php | 4 +- .../Controller/ApiOrderController.php} | 62 ++++++++++++++++++- .../Controller/ProdOrderController.php} | 12 ++-- 4 files changed, 68 insertions(+), 14 deletions(-) rename lib/Alchemy/Phrasea/{Controller/Api/OrderController.php => Order/Controller/ApiOrderController.php} (79%) rename lib/Alchemy/Phrasea/{Controller/Prod/OrderController.php => Order/Controller/ProdOrderController.php} (97%) diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php index c713f3329c..346294016b 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php @@ -12,10 +12,10 @@ namespace Alchemy\Phrasea\ControllerProvider\Api; use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\Api\BasketController; use Alchemy\Phrasea\Controller\Api\LazaretController; -use Alchemy\Phrasea\Controller\Api\OrderController; use Alchemy\Phrasea\Controller\Api\SearchController; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; use Alchemy\Phrasea\Core\Event\Listener\OAuthListener; +use Alchemy\Phrasea\Order\Controller\ApiOrderController; use Silex\Application; use Silex\Controller; use Silex\ControllerProviderInterface; @@ -51,7 +51,7 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface $app['controller.api.v2.orders'] = $app->share( function (PhraseaApplication $app) { - return (new OrderController($app)) + return (new ApiOrderController($app)) ->setDispatcher($app['dispatcher']) ->setJsonBodyHelper($app['json.body_helper']); } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php index ce1408fe37..983bacdbad 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Order.php @@ -13,8 +13,8 @@ namespace Alchemy\Phrasea\ControllerProvider\Prod; use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\LazyLocator; -use Alchemy\Phrasea\Controller\Prod\OrderController; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; +use Alchemy\Phrasea\Order\Controller\ProdOrderController; use Alchemy\Phrasea\Order\OrderBasketProvider; use Alchemy\Phrasea\Order\OrderValidator; use Silex\Application; @@ -39,7 +39,7 @@ class Order implements ControllerProviderInterface, ServiceProviderInterface }); $app['controller.prod.order'] = $app->share(function (PhraseaApplication $app) { - return (new OrderController($app)) + return (new ProdOrderController($app)) ->setDispatcher($app['dispatcher']) ->setEntityManagerLocator(new LazyLocator($app, 'orm.em')) ->setUserQueryFactory(new LazyLocator($app, 'phraseanet.user-query')) diff --git a/lib/Alchemy/Phrasea/Controller/Api/OrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php similarity index 79% rename from lib/Alchemy/Phrasea/Controller/Api/OrderController.php rename to lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php index 0b872475c7..055aa542f4 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/OrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php @@ -8,18 +8,23 @@ * file that was distributed with this source code. */ -namespace Alchemy\Phrasea\Controller\Api; +namespace Alchemy\Phrasea\Order\Controller; use Alchemy\Phrasea\Application\Helper\DispatcherAware; use Alchemy\Phrasea\Application\Helper\JsonBodyAware; +use Alchemy\Phrasea\Controller\Api\Result; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Core\Event\OrderEvent; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\Order; +use Alchemy\Phrasea\Model\Entities\OrderElement; +use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\Repositories\OrderElementRepository; use Alchemy\Phrasea\Order\OrderElementTransformer; use Alchemy\Phrasea\Order\OrderFiller; use Alchemy\Phrasea\Order\OrderTransformer; +use Alchemy\Phrasea\Order\OrderValidator; use Alchemy\Phrasea\Record\RecordReferenceCollection; use Assert\Assertion; use Assert\InvalidArgumentException; @@ -37,7 +42,7 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class OrderController extends Controller +class ApiOrderController extends Controller { use DispatcherAware; use JsonBodyAware; @@ -122,8 +127,9 @@ class OrderController extends Controller public function acceptElementsAction(Request $request, $orderId) { - $order = $this->findOr404($orderId); $data = $this->decodeJsonBody($request, 'orders.json#/definitions/order_element_collection'); + $acceptor = $this->getAuthenticatedUser(); + return Result::create($request, [])->createResponse(); @@ -215,4 +221,54 @@ class OrderController extends Controller return $order; } + + /** + * @param int $orderId + * @param array $elementIds + * @param User $acceptor + * @return OrderElement[] + */ + private function findRequestedElements($orderId, array $elementIds, User $acceptor) + { + $ids = []; + + foreach ($elementIds as $elementId) { + if (!isset($elementId->id)) { + throw new BadRequestHttpException('Invalid element id collection given'); + } + + $ids[] = $elementId->id; + } + + $elements = $this->getOrderElementRepository()->findBy([ + 'id' => $ids, + 'order' => $orderId, + ]); + + if (count($elements) !== count($elementIds)) { + throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $orderId)); + } + + if (!$this->getOrderValidator()->isGrantedValidation($acceptor, $elements)) { + throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); + } + + return $elements; + } + + /** + * @return OrderElementRepository + */ + private function getOrderElementRepository() + { + return $this->app['repo.order-elements']; + } + + /** + * @return OrderValidator + */ + private function getOrderValidator() + { + return $this->app['validator.order']; + } } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php similarity index 97% rename from lib/Alchemy/Phrasea/Controller/Prod/OrderController.php rename to lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php index c371e0f522..f7de064f97 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php @@ -1,5 +1,5 @@ Date: Fri, 18 Mar 2016 11:48:00 +0100 Subject: [PATCH 09/12] Reduce duplication for OrderController --- .../Phrasea/ControllerProvider/Api/V2.php | 2 + .../Order/Controller/ApiOrderController.php | 96 ++-------- .../Order/Controller/BaseOrderController.php | 170 ++++++++++++++++++ .../Order/Controller/ProdOrderController.php | 135 +------------- 4 files changed, 196 insertions(+), 207 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php b/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php index 346294016b..b6e7c8ac8e 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Api/V2.php @@ -13,6 +13,7 @@ use Alchemy\Phrasea\Application as PhraseaApplication; use Alchemy\Phrasea\Controller\Api\BasketController; use Alchemy\Phrasea\Controller\Api\LazaretController; use Alchemy\Phrasea\Controller\Api\SearchController; +use Alchemy\Phrasea\Controller\LazyLocator; use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait; use Alchemy\Phrasea\Core\Event\Listener\OAuthListener; use Alchemy\Phrasea\Order\Controller\ApiOrderController; @@ -53,6 +54,7 @@ class V2 implements ControllerProviderInterface, ServiceProviderInterface function (PhraseaApplication $app) { return (new ApiOrderController($app)) ->setDispatcher($app['dispatcher']) + ->setEntityManagerLocator(new LazyLocator($app, 'orm.em')) ->setJsonBodyHelper($app['json.body_helper']); } ); diff --git a/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php index 055aa542f4..52069fa411 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php @@ -10,24 +10,16 @@ namespace Alchemy\Phrasea\Order\Controller; -use Alchemy\Phrasea\Application\Helper\DispatcherAware; use Alchemy\Phrasea\Application\Helper\JsonBodyAware; use Alchemy\Phrasea\Controller\Api\Result; -use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Core\Event\OrderEvent; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\Order; -use Alchemy\Phrasea\Model\Entities\OrderElement; -use Alchemy\Phrasea\Model\Entities\User; -use Alchemy\Phrasea\Model\Repositories\OrderElementRepository; use Alchemy\Phrasea\Order\OrderElementTransformer; use Alchemy\Phrasea\Order\OrderFiller; use Alchemy\Phrasea\Order\OrderTransformer; -use Alchemy\Phrasea\Order\OrderValidator; use Alchemy\Phrasea\Record\RecordReferenceCollection; -use Assert\Assertion; -use Assert\InvalidArgumentException; use Doctrine\Common\Collections\ArrayCollection; use League\Fractal\Manager; use League\Fractal\Pagination\PagerfantaPaginatorAdapter; @@ -39,12 +31,9 @@ use Pagerfanta\Pagerfanta; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class ApiOrderController extends Controller +class ApiOrderController extends BaseOrderController { - use DispatcherAware; use JsonBodyAware; public function createAction(Request $request) @@ -127,17 +116,20 @@ class ApiOrderController extends Controller public function acceptElementsAction(Request $request, $orderId) { - $data = $this->decodeJsonBody($request, 'orders.json#/definitions/order_element_collection'); $acceptor = $this->getAuthenticatedUser(); + $elementIds = $this->fetchElementIdsFromRequest($request); - + $this->doAcceptElements($orderId, $elementIds, $acceptor); return Result::create($request, [])->createResponse(); } public function denyElementsAction(Request $request, $orderId) { - $order = $this->findOr404($orderId); + $acceptor = $this->getAuthenticatedUser(); + $elementIds = $this->fetchElementIdsFromRequest($request); + + $this->doDenyElements($orderId, $elementIds, $acceptor); return Result::create($request, [])->createResponse(); } @@ -202,73 +194,19 @@ class ApiOrderController extends Controller } /** - * @param int $orderId - * @return Order + * @param Request $request + * @return array */ - private function findOr404($orderId) + private function fetchElementIdsFromRequest(Request $request) { - try { - Assertion::integerish($orderId); - } catch (InvalidArgumentException $exception) { - throw new BadRequestHttpException($exception->getMessage(), $exception); + $data = $this->decodeJsonBody($request, 'orders.json#/definitions/order_element_collection'); + + $elementIds = []; + + foreach ($data as $elementId) { + $elementIds[] = $elementId->id; } - $order = $this->app['repo.orders']->find((int)$orderId); - - if (!$order instanceof Order) { - throw new NotFoundHttpException(sprintf('Order "%d" was not found', (int)$orderId)); - } - - return $order; - } - - /** - * @param int $orderId - * @param array $elementIds - * @param User $acceptor - * @return OrderElement[] - */ - private function findRequestedElements($orderId, array $elementIds, User $acceptor) - { - $ids = []; - - foreach ($elementIds as $elementId) { - if (!isset($elementId->id)) { - throw new BadRequestHttpException('Invalid element id collection given'); - } - - $ids[] = $elementId->id; - } - - $elements = $this->getOrderElementRepository()->findBy([ - 'id' => $ids, - 'order' => $orderId, - ]); - - if (count($elements) !== count($elementIds)) { - throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $orderId)); - } - - if (!$this->getOrderValidator()->isGrantedValidation($acceptor, $elements)) { - throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); - } - - return $elements; - } - - /** - * @return OrderElementRepository - */ - private function getOrderElementRepository() - { - return $this->app['repo.order-elements']; - } - - /** - * @return OrderValidator - */ - private function getOrderValidator() - { - return $this->app['validator.order']; + return $elementIds; } } diff --git a/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php new file mode 100644 index 0000000000..0ea5b21a63 --- /dev/null +++ b/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php @@ -0,0 +1,170 @@ +app['repo.orders']; + } + + /** + * @return OrderElementRepository + */ + protected function getOrderElementRepository() + { + return $this->app['repo.order-elements']; + } + + /** + * @param int $orderId + * @return Order + */ + protected function findOr404($orderId) + { + if (null === $order = $this->getOrderRepository()->find($orderId)) { + throw new NotFoundHttpException('Order not found'); + } + + return $order; + } + + /** + * @param int $orderId + * @param array $elementIds + * @param User $acceptor + * @return OrderElement[] + */ + protected function findRequestedElements($orderId, $elementIds, User $acceptor) + { + try { + Assertion::isArray($elementIds); + } catch (\Exception $exception) { + throw new BadRequestHttpException('Improper request', $exception); + } + + $elements = $this->getOrderElementRepository()->findBy([ + 'id' => $elementIds, + 'order' => $orderId, + ]); + + if (count($elements) !== count($elementIds)) { + throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $orderId)); + } + + if (!$this->getOrderValidator()->isGrantedValidation($acceptor, $elements)) { + throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); + } + + return $elements; + } + + /** + * @return OrderValidator + */ + protected function getOrderValidator() + { + return $this->app['validator.order']; + } + + /** + * @param int $order_id + * @param array $elementIds + * @param User $acceptor + * @return BasketElement[] + */ + protected function doAcceptElements($order_id, $elementIds, User $acceptor) + { + $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); + $order = $this->findOr404($order_id); + + $basket = $this->app['provider.order_basket']->provideBasketForOrderAndUser($order, $acceptor); + $orderValidator = $this->getOrderValidator(); + $partialOrder = new PartialOrder($order, $elements); + $basketElements = $orderValidator->createBasketElements($partialOrder); + $orderValidator->accept($acceptor, $partialOrder); + $orderValidator->grantHD($basket->getUser(), $basketElements); + + try { + $manager = $this->getEntityManager(); + + if (!empty($basketElements)) { + foreach ($basketElements as $element) { + $basket->addElement($element); + $manager->persist($element); + } + + $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, count($basketElements))); + } + + $manager->persist($basket); + $manager->persist($order); + $manager->flush(); + } catch (\Exception $e) { + // I don't know why only basket persistence is not checked + } + + return $basketElements; + } + + /** + * @param int $order_id + * @param array $elementIds + * @param User $acceptor + * @return OrderElement[] + */ + protected function doDenyElements($order_id, $elementIds, User $acceptor) + { + $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); + $order = $this->findOr404($order_id); + + $this->getOrderValidator()->deny($acceptor, new PartialOrder($order, $elements)); + + try { + if (!empty($elements)) { + $this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($order, $acceptor, count($elements))); + } + + $manager = $this->getEntityManager(); + $manager->persist($order); + $manager->flush(); + } catch (\Exception $e) { + // Don't know why this is ignored + } + + return $elements; + } +} diff --git a/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php index f7de064f97..f701235a0b 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php @@ -9,37 +9,23 @@ */ namespace Alchemy\Phrasea\Order\Controller; -use Alchemy\Phrasea\Application\Helper\DispatcherAware; use Alchemy\Phrasea\Application\Helper\EntityManagerAware; use Alchemy\Phrasea\Application\Helper\UserQueryAware; -use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Prod\OrderControllerException; use Alchemy\Phrasea\Controller\RecordsRequest; -use Alchemy\Phrasea\Core\Event\OrderDeliveryEvent; use Alchemy\Phrasea\Core\Event\OrderEvent; use Alchemy\Phrasea\Core\PhraseaEvents; -use Alchemy\Phrasea\Model\Entities\Order as OrderEntity; -use Alchemy\Phrasea\Model\Entities\OrderElement; -use Alchemy\Phrasea\Model\Entities\User; -use Alchemy\Phrasea\Model\Repositories\OrderElementRepository; -use Alchemy\Phrasea\Model\Repositories\OrderRepository; +use Alchemy\Phrasea\Model\Entities\Order; use Alchemy\Phrasea\Order\OrderFiller; -use Alchemy\Phrasea\Order\OrderValidator; -use Alchemy\Phrasea\Order\PartialOrder; -use Assert\Assertion; use Doctrine\Common\Collections\ArrayCollection; use Silex\Application; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class ProdOrderController extends Controller +class ProdOrderController extends BaseOrderController { - use DispatcherAware; use EntityManagerAware; use UserQueryAware; @@ -65,7 +51,7 @@ class ProdOrderController extends Controller $orderUsage = $request->request->get('use', ''); - $order = new OrderEntity(); + $order = new Order(); $order->setUser($this->getAuthenticatedUser()); $order->setDeadline($deadLine); $order->setOrderUsage($orderUsage); @@ -161,36 +147,9 @@ class ProdOrderController extends Controller $elementIds = $request->request->get('elements', []); $acceptor = $this->getAuthenticatedUser(); - $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); - $order = $this->findOr404($order_id); + $basketElements = $this->doAcceptElements($order_id, $elementIds, $acceptor); - $basket = $this->app['provider.order_basket']->provideBasketForOrderAndUser($order, $acceptor); - $orderValidator = $this->getOrderValidator(); - $partialOrder = new PartialOrder($order, $elements); - $basketElements = $orderValidator->createBasketElements($partialOrder); - $orderValidator->accept($acceptor, $partialOrder); - $orderValidator->grantHD($basket->getUser(), $basketElements); - - $success = false; - - try { - $manager = $this->getEntityManager(); - if (!empty($basketElements)) { - foreach ($basketElements as $element) { - $basket->addElement($element); - $manager->persist($element); - } - - $this->dispatch(PhraseaEvents::ORDER_DELIVER, new OrderDeliveryEvent($order, $acceptor, count($basketElements))); - } - $success = true; - - $manager->persist($basket); - $manager->persist($order); - $manager->flush(); - } catch (\Exception $e) { - // I don't know why only basket persistence is not checked - } + $success = !empty($basketElements); if ('json' === $request->getRequestFormat()) { return $this->app->json([ @@ -217,27 +176,12 @@ class ProdOrderController extends Controller */ public function denyOrder(Request $request, $order_id) { - $success = false; $elementIds = $request->request->get('elements', []); $acceptor = $this->getAuthenticatedUser(); - $elements = $this->findRequestedElements($order_id, $elementIds, $acceptor); - $order = $this->findOr404($order_id); + $elements = $this->doDenyElements($order_id, $elementIds, $acceptor); - $this->getOrderValidator()->deny($acceptor, new PartialOrder($order, $elements)); - - try { - if (!empty($elements)) { - $this->dispatch(PhraseaEvents::ORDER_DENY, new OrderDeliveryEvent($order, $acceptor, count($elements))); - } - $success = true; - - $manager = $this->getEntityManager(); - $manager->persist($order); - $manager->flush(); - } catch (\Exception $e) { - - } + $success=!empty($elements); if ('json' === $request->getRequestFormat()) { return $this->app->json([ @@ -255,70 +199,5 @@ class ProdOrderController extends Controller ]); } - /** - * @return OrderRepository - */ - private function getOrderRepository() - { - return $this->app['repo.orders']; - } - /** - * @return OrderElementRepository - */ - private function getOrderElementRepository() - { - return $this->app['repo.order-elements']; - } - - /** - * @param int $orderId - * @return OrderEntity - */ - private function findOr404($orderId) - { - if (null === $order = $this->getOrderRepository()->find($orderId)) { - throw new NotFoundHttpException('Order not found'); - } - - return $order; - } - - /** - * @param int $orderId - * @param array $elementIds - * @param User $acceptor - * @return OrderElement[] - */ - private function findRequestedElements($orderId, $elementIds, User $acceptor) - { - try { - Assertion::isArray($elementIds); - } catch (\Exception $exception) { - throw new BadRequestHttpException('Improper request', $exception); - } - - $elements = $this->getOrderElementRepository()->findBy([ - 'id' => $elementIds, - 'order' => $orderId, - ]); - - if (count($elements) !== count($elementIds)) { - throw new NotFoundHttpException(sprintf('At least one requested element does not exists or does not belong to order "%s"', $orderId)); - } - - if (!$this->getOrderValidator()->isGrantedValidation($acceptor, $elements)) { - throw new AccessDeniedHttpException('At least one element is in a collection you have no access to.'); - } - - return $elements; - } - - /** - * @return OrderValidator - */ - private function getOrderValidator() - { - return $this->app['validator.order']; - } } From 1a36543c4767958fbffd42c9a452017039eb90f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 12:54:45 +0100 Subject: [PATCH 10/12] some PHPCS fixing --- lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php | 3 +-- lib/Alchemy/Phrasea/Order/OrderFiller.php | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php index f701235a0b..03472510c8 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ProdOrderController.php @@ -18,7 +18,6 @@ use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\Order; use Alchemy\Phrasea\Order\OrderFiller; use Doctrine\Common\Collections\ArrayCollection; -use Silex\Application; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -181,7 +180,7 @@ class ProdOrderController extends BaseOrderController $elements = $this->doDenyElements($order_id, $elementIds, $acceptor); - $success=!empty($elements); + $success = !empty($elements); if ('json' === $request->getRequestFormat()) { return $this->app->json([ diff --git a/lib/Alchemy/Phrasea/Order/OrderFiller.php b/lib/Alchemy/Phrasea/Order/OrderFiller.php index ed38750f5b..eec1347f3e 100644 --- a/lib/Alchemy/Phrasea/Order/OrderFiller.php +++ b/lib/Alchemy/Phrasea/Order/OrderFiller.php @@ -34,7 +34,7 @@ class OrderFiller } /** - * @param \record_adapter[] $records + * @param \record_adapter[]|\Traversable $records */ public function assertAllRecordsHaveOrderMaster($records) { @@ -62,7 +62,7 @@ class OrderFiller } /** - * @param \record_adapter[] $records + * @param \record_adapter[]|\Traversable $records * @param Order $order */ public function fillOrder(Order $order, $records) From 11021ba8c8aa08c79d76c044d4f13235b89f8e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 14:22:49 +0100 Subject: [PATCH 11/12] Extract CollectionReferenceCollection class from ACL. --- .../CollectionReferenceCollection.php | 58 +++++++++++++++++++ lib/classes/ACL.php | 16 ++--- 2 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceCollection.php diff --git a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceCollection.php b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceCollection.php new file mode 100644 index 0000000000..45390e63a5 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceCollection.php @@ -0,0 +1,58 @@ +references = $references instanceof \Traversable ? iterator_to_array($references) : $references; + } + + /** + * Returns an array of array with actual index as leaf value. + * + * @return array> + */ + public function groupByDataboxIdAndCollectionId() + { + $groups = []; + + foreach ($this->references as $index => $reference) { + $databoxId = $reference->getDataboxId(); + $group = isset($groups[$databoxId]) ? $groups[$databoxId] : []; + + $group[$reference->getCollectionId()] = $index; + $groups[$databoxId] = $group; + } + + return $groups; + } + + /** + * @return \ArrayIterator|CollectionReference[] + */ + public function getIterator() + { + return new \ArrayIterator($this->references); + } +} diff --git a/lib/classes/ACL.php b/lib/classes/ACL.php index b57a6551ff..649a781528 100644 --- a/lib/classes/ACL.php +++ b/lib/classes/ACL.php @@ -10,6 +10,7 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Collection\Reference\CollectionReferenceCollection; use Alchemy\Phrasea\Core\Event\Acl\AccessPeriodChangedEvent; use Alchemy\Phrasea\Core\Event\Acl\AccessToBaseGrantedEvent; use Alchemy\Phrasea\Core\Event\Acl\AccessToBaseRevokedEvent; @@ -1776,24 +1777,19 @@ class ACL implements cache_cacheableInterface { $baseIds = $this->getOrderMasterCollectionsBaseIds(); - $groups = []; - - foreach ($this->app['repo.collection-references']->findHavingOrderMaster($baseIds) as $index => $reference) { - $databoxId = $reference->getDataboxId(); - $group = isset($groups[$databoxId]) ? $groups[$databoxId] : []; - - $group[$reference->getCollectionId()] = $index; - $groups[$databoxId] = $group; - } + $collectionReferences = $this->app['repo.collection-references']->findHavingOrderMaster($baseIds); + $groups = new CollectionReferenceCollection($collectionReferences); $collections = []; - foreach ($groups as $databoxId => $group) { + foreach ($groups->groupByDataboxIdAndCollectionId() as $databoxId => $group) { foreach ($group as $collectionId => $index) { $collections[$index] = \collection::getByCollectionId($this->app, $databoxId, $collectionId); } } + ksort($collections); + return $collections; } From 4aa9a55cc0fd8af514961362f33975db15f52bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Fri, 18 Mar 2016 15:18:00 +0100 Subject: [PATCH 12/12] Fix OrderElement accept/deny todo count --- .../Phrasea/Model/Entities/OrderElement.php | 7 ++-- .../Order/Controller/ApiOrderController.php | 20 +++++++--- .../Order/Controller/BaseOrderController.php | 38 ++++++++++++++++++- lib/Alchemy/Phrasea/Order/OrderValidator.php | 9 ++++- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/OrderElement.php b/lib/Alchemy/Phrasea/Model/Entities/OrderElement.php index e5ce58f0f2..010049ab98 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/OrderElement.php +++ b/lib/Alchemy/Phrasea/Model/Entities/OrderElement.php @@ -41,12 +41,13 @@ class OrderElement * @ORM\ManyToOne(targetEntity="User") * @ORM\JoinColumn(name="order_master", referencedColumnName="id") * - * @return User + * @var null|User **/ private $orderMaster; /** * @ORM\Column(type="boolean", nullable=true) + * @var bool|null */ private $deny; @@ -89,7 +90,7 @@ class OrderElement /** * Set deny * - * @param boolean $deny + * @param null|bool $deny * @return OrderElement */ public function setDeny($deny) @@ -102,7 +103,7 @@ class OrderElement /** * Get deny * - * @return boolean + * @return bool|null */ public function getDeny() { diff --git a/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php index 52069fa411..43bd44b134 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/ApiOrderController.php @@ -11,10 +11,12 @@ namespace Alchemy\Phrasea\Order\Controller; use Alchemy\Phrasea\Application\Helper\JsonBodyAware; +use Alchemy\Phrasea\Collection\Reference\CollectionReference; use Alchemy\Phrasea\Controller\Api\Result; use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Core\Event\OrderEvent; use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\Order; use Alchemy\Phrasea\Order\OrderElementTransformer; use Alchemy\Phrasea\Order\OrderFiller; @@ -116,20 +118,28 @@ class ApiOrderController extends BaseOrderController public function acceptElementsAction(Request $request, $orderId) { - $acceptor = $this->getAuthenticatedUser(); $elementIds = $this->fetchElementIdsFromRequest($request); - $this->doAcceptElements($orderId, $elementIds, $acceptor); + $elements = $this->doAcceptElements($orderId, $elementIds, $this->getAuthenticatedUser()); - return Result::create($request, [])->createResponse(); + $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) { - $acceptor = $this->getAuthenticatedUser(); $elementIds = $this->fetchElementIdsFromRequest($request); - $this->doDenyElements($orderId, $elementIds, $acceptor); + $this->doDenyElements($orderId, $elementIds, $this->getAuthenticatedUser()); return Result::create($request, [])->createResponse(); } diff --git a/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php b/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php index 0ea5b21a63..eaee31a899 100644 --- a/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php +++ b/lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php @@ -15,6 +15,7 @@ use Alchemy\Phrasea\Application\Helper\EntityManagerAware; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Core\Event\OrderDeliveryEvent; use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Model\Entities\Basket; use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\Order; use Alchemy\Phrasea\Model\Entities\OrderElement; @@ -23,9 +24,12 @@ use Alchemy\Phrasea\Model\Repositories\OrderElementRepository; use Alchemy\Phrasea\Model\Repositories\OrderRepository; use Alchemy\Phrasea\Order\OrderValidator; use Alchemy\Phrasea\Order\PartialOrder; +use Alchemy\Phrasea\Record\RecordReference; +use Alchemy\Phrasea\Record\RecordReferenceCollection; use Assert\Assertion; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class BaseOrderController extends Controller @@ -112,9 +116,14 @@ class BaseOrderController extends Controller $order = $this->findOr404($order_id); $basket = $this->app['provider.order_basket']->provideBasketForOrderAndUser($order, $acceptor); - $orderValidator = $this->getOrderValidator(); + $partialOrder = new PartialOrder($order, $elements); + + $orderValidator = $this->getOrderValidator(); + $basketElements = $orderValidator->createBasketElements($partialOrder); + $this->assertRequestedElementsWereNotAlreadyAdded($basket, $basketElements); + $orderValidator->accept($acceptor, $partialOrder); $orderValidator->grantHD($basket->getUser(), $basketElements); @@ -167,4 +176,31 @@ class BaseOrderController extends Controller return $elements; } + + /** + * @param Basket $basket + * @param BasketElement[] $elements + */ + protected function assertRequestedElementsWereNotAlreadyAdded(Basket $basket, $elements) + { + if ($basket->getElements()->isEmpty()) { + return; + } + + $references = new RecordReferenceCollection(); + + foreach ($elements as $element) { + $reference = RecordReference::createFromDataboxIdAndRecordId($element->getSbasId(), $element->getRecordId()); + + $references->addRecordReference($reference); + } + + $groups = $references->groupPerDataboxId(); + + foreach ($basket->getElements() as $element) { + if (isset($groups[$element->getSbasId()][$element->getRecordId()])) { + throw new ConflictHttpException('Some records have already been handled'); + } + } + } } diff --git a/lib/Alchemy/Phrasea/Order/OrderValidator.php b/lib/Alchemy/Phrasea/Order/OrderValidator.php index 452ad3031d..d0e919040f 100644 --- a/lib/Alchemy/Phrasea/Order/OrderValidator.php +++ b/lib/Alchemy/Phrasea/Order/OrderValidator.php @@ -159,11 +159,18 @@ class OrderValidator return; } + $decrementCount = 0; + foreach ($elements as $element) { $element->setOrderMaster($acceptor); + if (null === $element->getDeny()) { + ++$decrementCount; + } $element->setDeny($deny); } - $order->getOrder()->decrementTodo(count($elements)); + if ($decrementCount) { + $order->getOrder()->decrementTodo($decrementCount); + } } }