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/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) {