From 9313f3da2b374bceff467e21d990e71110f90cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 17 Mar 2016 11:20:31 +0100 Subject: [PATCH 01/26] Added new Twig filter to sort a collection set. Only currently used in admin/tree.html.twig but could be used in collection rights. --- .../Phrasea/Twig/PhraseanetExtension.php | 21 +++++++++++++++++++ templates/web/admin/tree.html.twig | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php index 56c7040c7d..aa1842ac7f 100644 --- a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php +++ b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php @@ -8,6 +8,7 @@ use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\RecordInterface; use Alchemy\Phrasea\Http\StaticFile\StaticMode; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag; +use Assert\Assertion; class PhraseanetExtension extends \Twig_Extension { @@ -22,6 +23,7 @@ class PhraseanetExtension extends \Twig_Extension public function getFilters() { return array( + new \Twig_SimpleFilter('sort_collections', array($this, 'sortCollections')), ); } @@ -305,6 +307,25 @@ class PhraseanetExtension extends \Twig_Extension return $this->app['border-manager']->getCheckerFromFQCN($checkerFQCN); } + /** + * @param \collection[] $collections + * @return \collection[] + */ + public function sortCollections($collections) + { + Assertion::allIsInstanceOf($collections, 'collection'); + + if ($collections instanceof \Traversable) { + $collections = iterator_to_array($collections); + } + + usort($collections, function (\collection $left, \collection $right) { + return ($left->get_ord() < $right->get_ord()) ? -1 : (($left->get_ord() < $right->get_ord()) ? 1 : 0); + }); + + return $collections; + } + public function getName() { return 'phraseanet'; diff --git a/templates/web/admin/tree.html.twig b/templates/web/admin/tree.html.twig index a682e7eb36..259ec05c4c 100644 --- a/templates/web/admin/tree.html.twig +++ b/templates/web/admin/tree.html.twig @@ -166,7 +166,7 @@ {% endif %} - {% for collection in databox.get_collections() %} + {% for collection in databox.get_collections()|sort_collections %} {% if (collection.get_base_id() in app.getAclForUser(app.getAuthenticatedUser()).get_granted_base(['canadmin'])|keys or collection.get_base_id() in app.getAclForUser(app.getAuthenticatedUser()).get_granted_base(['manage'])|keys or collection.get_base_id() in app.getAclForUser(app.getAuthenticatedUser()).get_granted_base(['modify_struct'])|keys) %} From 7c65b2a7e9a4db636d9f468baecfea609491f446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 17 Mar 2016 12:21:05 +0100 Subject: [PATCH 02/26] Extract method from Twig PhraseanetExtension --- .../Phrasea/Collection/CollectionHelper.php | 39 +++++++++++++++++++ .../Phrasea/Twig/PhraseanetExtension.php | 23 +---------- 2 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Collection/CollectionHelper.php diff --git a/lib/Alchemy/Phrasea/Collection/CollectionHelper.php b/lib/Alchemy/Phrasea/Collection/CollectionHelper.php new file mode 100644 index 0000000000..a56df2e356 --- /dev/null +++ b/lib/Alchemy/Phrasea/Collection/CollectionHelper.php @@ -0,0 +1,39 @@ +get_ord() < $right->get_ord()) ? -1 : (($left->get_ord() < $right->get_ord()) ? 1 : 0); + }); + + return $collections; + } +} diff --git a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php index aa1842ac7f..b1d081ff7a 100644 --- a/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php +++ b/lib/Alchemy/Phrasea/Twig/PhraseanetExtension.php @@ -3,12 +3,12 @@ namespace Alchemy\Phrasea\Twig; use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Collection\CollectionHelper; use Alchemy\Phrasea\Model\Entities\ElasticsearchRecord; use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\RecordInterface; use Alchemy\Phrasea\Http\StaticFile\StaticMode; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag; -use Assert\Assertion; class PhraseanetExtension extends \Twig_Extension { @@ -23,7 +23,7 @@ class PhraseanetExtension extends \Twig_Extension public function getFilters() { return array( - new \Twig_SimpleFilter('sort_collections', array($this, 'sortCollections')), + new \Twig_SimpleFilter('sort_collections', array(CollectionHelper::class, 'sort')), ); } @@ -307,25 +307,6 @@ class PhraseanetExtension extends \Twig_Extension return $this->app['border-manager']->getCheckerFromFQCN($checkerFQCN); } - /** - * @param \collection[] $collections - * @return \collection[] - */ - public function sortCollections($collections) - { - Assertion::allIsInstanceOf($collections, 'collection'); - - if ($collections instanceof \Traversable) { - $collections = iterator_to_array($collections); - } - - usort($collections, function (\collection $left, \collection $right) { - return ($left->get_ord() < $right->get_ord()) ? -1 : (($left->get_ord() < $right->get_ord()) ? 1 : 0); - }); - - return $collections; - } - public function getName() { return 'phraseanet'; From ba646fa733627952fba8d7f172c457bf097d324d Mon Sep 17 00:00:00 2001 From: Florian BLOUET Date: Mon, 21 Mar 2016 10:20:56 +0100 Subject: [PATCH 03/26] PHRAS-1039 - fix preview backward navigation --- resources/www/prod/js/jquery.p4.preview.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/www/prod/js/jquery.p4.preview.js b/resources/www/prod/js/jquery.p4.preview.js index d09db584a1..9276cb0b58 100644 --- a/resources/www/prod/js/jquery.p4.preview.js +++ b/resources/www/prod/js/jquery.p4.preview.js @@ -379,7 +379,10 @@ function reloadPreview() { function getPrevious() { if (p4.preview.mode == 'RESULT') { posAsk = parseInt(p4.preview.current.pos) - 1; - posAsk = (posAsk < 0) ? ((parseInt(p4.tot) - 1)) : posAsk; + if (p4.navigation.page === 1) { + // may go to last result + posAsk = (posAsk < 0) ? ((parseInt(p4.tot) - 1)) : posAsk; + } openPreview('RESULT', posAsk, '', false); } else { From 0d6c44fac3bafba641ec335f698e0b5fbb443831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 25 Feb 2016 19:32:12 +0100 Subject: [PATCH 04/26] Add some missing copyrights --- lib/Alchemy/Phrasea/Collection/Collection.php | 8 ++++++++ lib/Alchemy/Phrasea/Collection/CollectionFactory.php | 8 ++++++++ lib/Alchemy/Phrasea/Collection/CollectionRepository.php | 8 ++++++++ .../Phrasea/Collection/CollectionRepositoryFactory.php | 8 ++++++++ .../Phrasea/Collection/CollectionRepositoryRegistry.php | 8 ++++++++ lib/Alchemy/Phrasea/Collection/CollectionService.php | 8 ++++++++ .../Factory/ArrayCachedCollectionRepositoryFactory.php | 8 ++++++++ .../Factory/CachedCollectionRepositoryFactory.php | 8 ++++++++ .../Factory/DbalCollectionRepositoryFactory.php | 8 ++++++++ .../Reference/ArrayCacheCollectionReferenceRepository.php | 8 ++++++++ .../Phrasea/Collection/Reference/CollectionReference.php | 8 ++++++++ .../Reference/CollectionReferenceRepository.php | 8 ++++++++ .../Reference/DbalCollectionReferenceRepository.php | 8 ++++++++ .../Repository/ArrayCacheCollectionRepository.php | 8 ++++++++ .../Collection/Repository/CachedCollectionRepository.php | 4 ++-- .../Collection/Repository/DbalCollectionRepository.php | 8 ++++++++ 16 files changed, 122 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Collection/Collection.php b/lib/Alchemy/Phrasea/Collection/Collection.php index d9ee19910a..42c162ceda 100644 --- a/lib/Alchemy/Phrasea/Collection/Collection.php +++ b/lib/Alchemy/Phrasea/Collection/Collection.php @@ -1,4 +1,12 @@ Date: Thu, 25 Feb 2016 19:33:42 +0100 Subject: [PATCH 05/26] Remove some unused statements --- lib/Alchemy/Phrasea/Controller/Api/BasketController.php | 1 - lib/Alchemy/Phrasea/Controller/Api/V1Controller.php | 3 --- 2 files changed, 4 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/BasketController.php b/lib/Alchemy/Phrasea/Controller/Api/BasketController.php index 2b989b273e..d0432f57de 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/BasketController.php +++ b/lib/Alchemy/Phrasea/Controller/Api/BasketController.php @@ -17,7 +17,6 @@ use Alchemy\Phrasea\Core\Event\Basket\ElementsAdded; use Alchemy\Phrasea\Core\Event\Basket\ElementsRemoved; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Model\Entities\Basket; -use Alchemy\Phrasea\Model\Entities\BasketElement; use Symfony\Component\HttpFoundation\Request; class BasketController extends Controller diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php index d36672fb15..b98ee254d2 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1Controller.php @@ -48,8 +48,6 @@ use Alchemy\Phrasea\Model\Entities\ValidationParticipant; use Alchemy\Phrasea\Model\Manipulator\TaskManipulator; use Alchemy\Phrasea\Model\Manipulator\UserManipulator; use Alchemy\Phrasea\Model\Provider\SecretProvider; -use Alchemy\Phrasea\Model\RecordInterface; -use Alchemy\Phrasea\Model\RecordReferenceInterface; use Alchemy\Phrasea\Model\Repositories\BasketRepository; use Alchemy\Phrasea\Model\Repositories\FeedEntryRepository; use Alchemy\Phrasea\Model\Repositories\FeedRepository; @@ -63,7 +61,6 @@ use Alchemy\Phrasea\SearchEngine\SearchEngineResult; use Alchemy\Phrasea\SearchEngine\SearchEngineSuggestion; use Alchemy\Phrasea\Status\StatusStructure; use Alchemy\Phrasea\TaskManager\LiveInformation; -use Assert\Assertion; use Doctrine\ORM\EntityManager; use Firebase\JWT\JWT; use Symfony\Component\Form\Form; From 51519ce4a2055cbf460a63f8e7bb233b363bc65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 25 Feb 2016 19:36:18 +0100 Subject: [PATCH 06/26] Avoid N+1 in User_Query --- lib/classes/User/Query.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/classes/User/Query.php b/lib/classes/User/Query.php index 184baf0cb8..11543ccc0b 100644 --- a/lib/classes/User/Query.php +++ b/lib/classes/User/Query.php @@ -13,7 +13,6 @@ use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Model\Entities\User; use Doctrine\Common\Collections\ArrayCollection; use Alchemy\Phrasea\Utilities\Countries; -use Doctrine\Common\Collections\Collection; class User_Query implements User_QueryInterface { @@ -81,7 +80,7 @@ class User_Query implements User_QueryInterface /** * Return query results * - * @return User[]|Collection + * @return User[]|\Doctrine\Common\Collections\Collection */ public function get_results() { @@ -257,13 +256,22 @@ class User_Query implements User_QueryInterface $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); - $users = new ArrayCollection(); + $userIndexes = []; - foreach ($rs as $row) { - $users[] = $this->app['repo.users']->find($row['id']); + foreach ($rs as $index => $row) { + $userIndexes[$row['id']] = $index; } - $this->results = $users; + $users = []; + + /** @var User $user */ + foreach ($this->app['repo.users']->findBy(['id' => array_keys($userIndexes)]) as $user) { + $users[$userIndexes[$user->getId()]] = $user; + } + + ksort($users); + + $this->results = new ArrayCollection($users); return $this; } From 2b5557ac7c6d00e4982a2f5336acd1d8ea9886d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 25 Feb 2016 19:38:57 +0100 Subject: [PATCH 07/26] Add lookup by order master to collection reference repository --- ...rrayCacheCollectionReferenceRepository.php | 9 +++++++ .../CollectionReferenceRepository.php | 8 +++++++ .../DbalCollectionReferenceRepository.php | 24 ++++++++++++++++++- .../Provider/RepositoriesServiceProvider.php | 3 ++- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php index a97f47e043..17a9a1d790 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php @@ -90,6 +90,15 @@ class ArrayCacheCollectionReferenceRepository implements CollectionReferenceRepo return null; } + /** + * @param array|null $subset + * @return CollectionReference[] + */ + public function findHavingOrderMaster(array $subset = null) + { + return $this->repository->findHavingOrderMaster($subset); + } + /** * @param CollectionReference $reference * @return void diff --git a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php index 72b6ad4d40..d40e827f76 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php @@ -36,6 +36,14 @@ interface CollectionReferenceRepository */ public function findByCollectionId($databoxId, $collectionId); + /** + * Find Collection references having at least one Order Master + * + * @param array|null $subset Restrict search to a subset of base ids. + * @return CollectionReference[] + */ + public function findHavingOrderMaster(array $subset = null); + /** * @param CollectionReference $reference * @return void diff --git a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php index 67e01582e7..68026bd087 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php @@ -10,7 +10,6 @@ namespace Alchemy\Phrasea\Collection\Reference; -use Alchemy\Phrasea\Core\Database\QueryBuilder; use Doctrine\DBAL\Connection; class DbalCollectionReferenceRepository implements CollectionReferenceRepository @@ -107,6 +106,29 @@ class DbalCollectionReferenceRepository implements CollectionReferenceRepository return null; } + public function findHavingOrderMaster(array $subset = null) + { + $query = self::$selectQuery + . ' WHERE EXISTS(SELECT 1 FROM basusr WHERE basusr.order_master = 1 AND basusr.base_id = bas.base_id)'; + + $parameters = []; + $types = []; + + if (null !== $subset) { + if (empty($subset)) { + return []; + } + + $query .= ' AND bas.base_id IN (:subset)'; + $parameters['subset'] = $subset; + $types['subset'] = Connection::PARAM_INT_ARRAY; + } + + $rows = $this->connection->fetchAll($query, $parameters); + + return $this->createManyReferences($rows); + } + public function save(CollectionReference $collectionReference) { $query = self::$insertQuery; diff --git a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php index 5e50367f09..61d2fbbd83 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php @@ -175,7 +175,8 @@ class RepositoriesServiceProvider implements ServiceProviderInterface $repository = new DbalCollectionReferenceRepository($app->getApplicationBox()->get_connection()); return new ArrayCacheCollectionReferenceRepository($repository); - }); + }); + $app['repo.collections-registry'] = $app->share(function (PhraseaApplication $app) { $factory = new CollectionFactory($app); $connectionProvider = new DataboxConnectionProvider($app->getApplicationBox()); From cb87d7c0f11c5ec5d13e71708d854d4e17f3a49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 25 Feb 2016 19:40:46 +0100 Subject: [PATCH 08/26] Refactor OrderController --- .../Controller/Prod/OrderController.php | 122 +++++++++--------- .../Prod/OrderControllerException.php | 16 +++ .../Phrasea/Controller/RecordsRequest.php | 6 +- 3 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Controller/Prod/OrderControllerException.php diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php index 832c726ee0..839f1e2169 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php @@ -46,73 +46,31 @@ class OrderController extends Controller */ public function createOrder(Request $request) { - $success = false; - $collectionHasOrderAdmins = new ArrayCollection(); - $toRemove = []; - $records = RecordsRequest::fromRequest($this->app, $request, true, ['cancmd']); - $hasOneAdmin = []; - if (!$records->isEmpty()) { + try { + if ($records->isEmpty()) { + throw new OrderControllerException($this->app->trans('There is no record eligible for an order')); + } + + if (null !== $deadLine = $request->request->get('deadline')) { + $deadLine = new \DateTime($deadLine); + } + + $orderUsage = $request->request->get('use', ''); + $order = new OrderEntity(); $order->setUser($this->getAuthenticatedUser()); - $order->setDeadline((null !== $deadLine = $request->request->get('deadline')) ? new \DateTime($deadLine) : $deadLine); - $order->setOrderUsage($request->request->get('use', '')); - foreach ($records as $key => $record) { - if ($collectionHasOrderAdmins->containsKey($record->get_base_id())) { - if (!$collectionHasOrderAdmins->get($record->get_base_id())) { - $records->remove($key); - } - } + $order->setDeadline($deadLine); + $order->setOrderUsage($orderUsage); - if (!isset($hasOneAdmin[$record->get_base_id()])) { - $query = $this->createUserQuery(); - $hasOneAdmin[$record->get_base_id()] = (Boolean) count($query->on_base_ids([$record->get_base_id()]) - ->who_have_right(['order_master']) - ->execute()->get_results()); - } + $this->fillOrderFromRequest($records, $order); - $collectionHasOrderAdmins->set($record->get_base_id(), $hasOneAdmin[$record->get_base_id()]); - - if (!$hasOneAdmin[$record->get_base_id()]) { - $toRemove[] = $key; - } else { - $orderElement = new OrderElement(); - $order->addElement($orderElement); - $orderElement->setOrder($order); - $orderElement->setBaseId($record->get_base_id()); - $orderElement->setRecordId($record->get_record_id()); - $this->getEntityManager()->persist($orderElement); - } - } - - foreach ($toRemove as $key) { - if ($records->containsKey($key)) { - $records->remove($key); - } - } - - $noAdmins = $collectionHasOrderAdmins->forAll(function ($key, $hasAdmin) { - return false === $hasAdmin; - }); - - if ($noAdmins) { - $msg = $this->app->trans('There is no one to validate orders, please contact an administrator'); - } else { - $order->setTodo($order->getElements()->count()); - - try { - $this->dispatch(PhraseaEvents::ORDER_CREATE, new OrderEvent($order)); - $this->getEntityManager()->persist($order); - $this->getEntityManager()->flush(); - $msg = $this->app->trans('The records have been properly ordered'); - $success = true; - } catch (\Exception $e) { - $msg = $this->app->trans('An error occured'); - } - } - } else { - $msg = $this->app->trans('There is no record eligible for an order'); + $success = true; + $msg = $this->app->trans('The records have been properly ordered'); + } catch (OrderControllerException $exception) { + $success = false; + $msg = $exception->getMessage(); } if ('json' === $request->getRequestFormat()) { @@ -322,4 +280,46 @@ class OrderController extends Controller { return $this->app['repo.orders']; } + + private function fillOrderFromRequest(RecordsRequest $records, Order $order) + { + $collectionIds = []; + + foreach ($records->collections() as $collection) { + $collectionIds[] = $collection->get_base_id(); + } + + $hasOneAdmin = []; + + foreach ($this->app['repo.collection-references']->findHavingOrderMaster($collectionIds) as $reference) { + $hasOneAdmin[$reference->getBaseId()] = $reference; + } + + if (!empty(array_diff($collectionIds, array_keys($hasOneAdmin)))) { + throw new OrderControllerException($this->app->trans('There is no one to validate orders, please contact an administrator')); + } + + $entityManager = $this->getEntityManager(); + + foreach ($records as $key => $record) { + $orderElement = new OrderElement(); + $order->addElement($orderElement); + $orderElement->setOrder($order); + $orderElement->setBaseId($record->get_base_id()); + $orderElement->setRecordId($record->get_record_id()); + $entityManager->persist($orderElement); + } + + $order->setTodo($order->getElements()->count()); + + $entityManager->persist($order); + + try { + $entityManager->flush(); + + $this->dispatch(PhraseaEvents::ORDER_CREATE, new OrderEvent($order)); + } catch (\Exception $e) { + throw new OrderControllerException($this->app->trans('An error occurred'), 0, $e); + } + } } diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderControllerException.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderControllerException.php new file mode 100644 index 0000000000..e3564970f4 --- /dev/null +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderControllerException.php @@ -0,0 +1,16 @@ +toArray(); - array_walk($records, function ($record) use (&$i) { + array_walk($records, function (\record_adapter $record) use (&$i) { $record->setNumber($i++); }); } @@ -103,8 +103,8 @@ class RecordsRequest extends ArrayCollection /** @var \record_adapter $record */ foreach ($this as $record) { - if (false === array_key_exists($record->get_base_id(), $this->collections)) { - $this->collections[$record->get_base_id()] = $record->get_collection(); + if (! isset($this->collections[$record->getBaseId()])) { + $this->collections[$record->getBaseId()] = $record->get_collection(); } } From 1982d0a625c919c64df2d5e979a862c7d85cd993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 25 Feb 2016 20:17:01 +0100 Subject: [PATCH 09/26] Prepare extraction of class from OrderController --- .../Controller/Prod/OrderController.php | 64 ++++++++++++------- lib/Alchemy/Phrasea/Model/Entities/Order.php | 2 + 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php index 839f1e2169..a881d1162f 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php @@ -23,6 +23,7 @@ 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\OrderRepository; +use Assert\Assertion; use Doctrine\Common\Collections\ArrayCollection; use Silex\Application; use Symfony\Component\HttpFoundation\JsonResponse; @@ -65,12 +66,16 @@ class OrderController extends Controller $order->setOrderUsage($orderUsage); $this->fillOrderFromRequest($records, $order); + $this->dispatch(PhraseaEvents::ORDER_CREATE, new OrderEvent($order)); $success = true; $msg = $this->app->trans('The records have been properly ordered'); } catch (OrderControllerException $exception) { $success = false; $msg = $exception->getMessage(); + } catch (\Exception $exception) { + $success = false; + $msg = $this->app->trans('An error occurred'); } if ('json' === $request->getRequestFormat()) { @@ -281,32 +286,23 @@ class OrderController extends Controller return $this->app['repo.orders']; } - private function fillOrderFromRequest(RecordsRequest $records, Order $order) + /** + * @param \record_adapter[] $records + * @param Order $order + */ + private function fillOrderFromRequest($records, Order $order) { - $collectionIds = []; - - foreach ($records->collections() as $collection) { - $collectionIds[] = $collection->get_base_id(); - } - - $hasOneAdmin = []; - - foreach ($this->app['repo.collection-references']->findHavingOrderMaster($collectionIds) as $reference) { - $hasOneAdmin[$reference->getBaseId()] = $reference; - } - - if (!empty(array_diff($collectionIds, array_keys($hasOneAdmin)))) { - throw new OrderControllerException($this->app->trans('There is no one to validate orders, please contact an administrator')); - } + $this->assertAllRecordsHaveOrderMaster($records); $entityManager = $this->getEntityManager(); foreach ($records as $key => $record) { $orderElement = new OrderElement(); + $orderElement->setBaseId($record->getBaseId()); + $orderElement->setRecordId($record->getRecordId()); + $order->addElement($orderElement); - $orderElement->setOrder($order); - $orderElement->setBaseId($record->get_base_id()); - $orderElement->setRecordId($record->get_record_id()); + $entityManager->persist($orderElement); } @@ -314,12 +310,32 @@ class OrderController extends Controller $entityManager->persist($order); - try { - $entityManager->flush(); + $entityManager->flush(); + } - $this->dispatch(PhraseaEvents::ORDER_CREATE, new OrderEvent($order)); - } catch (\Exception $e) { - throw new OrderControllerException($this->app->trans('An error occurred'), 0, $e); + /** + * @param \record_adapter[] $records + */ + private function assertAllRecordsHaveOrderMaster($records) + { + Assertion::allIsInstanceOf($records, \record_adapter::class); + + $collectionIds = []; + + foreach ($records as $record) { + $collectionIds[] = $record->getBaseId(); + } + + $collectionIds = array_unique($collectionIds); + + $hasOneAdmin = []; + + foreach ($this->app['repo.collection-references']->findHavingOrderMaster($collectionIds) as $reference) { + $hasOneAdmin[] = $reference->getBaseId(); + } + + if (!empty(array_diff($collectionIds, $hasOneAdmin))) { + throw new OrderControllerException($this->app->trans('There is no one to validate orders, please contact an administrator')); } } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/Order.php b/lib/Alchemy/Phrasea/Model/Entities/Order.php index ccf108d508..5fe32c9d95 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/Order.php +++ b/lib/Alchemy/Phrasea/Model/Entities/Order.php @@ -160,6 +160,7 @@ class Order public function addElement(OrderElement $elements) { $this->elements[] = $elements; + $elements->setOrder($this); return $this; } @@ -172,6 +173,7 @@ class Order public function removeElement(OrderElement $elements) { $this->elements->removeElement($elements); + $elements->setOrder(null); } /** From 4d65085b466c430b744b0bb38dcacb1b9d06cf66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Thu, 25 Feb 2016 20:44:17 +0100 Subject: [PATCH 10/26] Extract OrderFiller --- .../Controller/Prod/OrderController.php | 67 +++----------- lib/Alchemy/Phrasea/Order/OrderFiller.php | 88 +++++++++++++++++++ 2 files changed, 99 insertions(+), 56 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Order/OrderFiller.php diff --git a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php index a881d1162f..06527ee501 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/OrderController.php @@ -21,9 +21,8 @@ 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\OrderRepository; -use Assert\Assertion; +use Alchemy\Phrasea\Order\OrderFiller; use Doctrine\Common\Collections\ArrayCollection; use Silex\Application; use Symfony\Component\HttpFoundation\JsonResponse; @@ -65,7 +64,16 @@ class OrderController extends Controller $order->setDeadline($deadLine); $order->setOrderUsage($orderUsage); - $this->fillOrderFromRequest($records, $order); + $filler = new OrderFiller($this->app['repo.collection-references'], $this->getEntityManager()); + + try { + $filler->assertAllRecordsHaveOrderMaster($records); + } catch (\RuntimeException $exception) { + throw new OrderControllerException($this->app->trans('There is no one to validate orders, please contact an administrator')); + } + + $filler->fillOrder($order, $records); + $this->dispatch(PhraseaEvents::ORDER_CREATE, new OrderEvent($order)); $success = true; @@ -285,57 +293,4 @@ class OrderController extends Controller { return $this->app['repo.orders']; } - - /** - * @param \record_adapter[] $records - * @param Order $order - */ - private function fillOrderFromRequest($records, Order $order) - { - $this->assertAllRecordsHaveOrderMaster($records); - - $entityManager = $this->getEntityManager(); - - foreach ($records as $key => $record) { - $orderElement = new OrderElement(); - $orderElement->setBaseId($record->getBaseId()); - $orderElement->setRecordId($record->getRecordId()); - - $order->addElement($orderElement); - - $entityManager->persist($orderElement); - } - - $order->setTodo($order->getElements()->count()); - - $entityManager->persist($order); - - $entityManager->flush(); - } - - /** - * @param \record_adapter[] $records - */ - private function assertAllRecordsHaveOrderMaster($records) - { - Assertion::allIsInstanceOf($records, \record_adapter::class); - - $collectionIds = []; - - foreach ($records as $record) { - $collectionIds[] = $record->getBaseId(); - } - - $collectionIds = array_unique($collectionIds); - - $hasOneAdmin = []; - - foreach ($this->app['repo.collection-references']->findHavingOrderMaster($collectionIds) as $reference) { - $hasOneAdmin[] = $reference->getBaseId(); - } - - if (!empty(array_diff($collectionIds, $hasOneAdmin))) { - throw new OrderControllerException($this->app->trans('There is no one to validate orders, please contact an administrator')); - } - } } diff --git a/lib/Alchemy/Phrasea/Order/OrderFiller.php b/lib/Alchemy/Phrasea/Order/OrderFiller.php new file mode 100644 index 0000000000..ed38750f5b --- /dev/null +++ b/lib/Alchemy/Phrasea/Order/OrderFiller.php @@ -0,0 +1,88 @@ +repository = $repository; + $this->manager = $manager; + } + + /** + * @param \record_adapter[] $records + */ + public function assertAllRecordsHaveOrderMaster($records) + { + Assertion::allIsInstanceOf($records, \record_adapter::class); + + $collectionIds = []; + + foreach ($records as $record) { + $collectionIds[] = $record->getBaseId(); + } + + $collectionIds = array_unique($collectionIds); + + $hasOneAdmin = []; + + foreach ($this->repository->findHavingOrderMaster($collectionIds) as $reference) { + $hasOneAdmin[] = $reference->getBaseId(); + } + + $collectionsWithoutAdmin = array_diff($collectionIds, $hasOneAdmin); + + if (!empty($collectionsWithoutAdmin)) { + throw new \RuntimeException(sprintf('Some collections have no order master: %s', implode(', ', $collectionsWithoutAdmin))); + } + } + + /** + * @param \record_adapter[] $records + * @param Order $order + */ + public function fillOrder(Order $order, $records) + { + Assertion::allIsInstanceOf($records, \record_adapter::class); + + foreach ($records as $key => $record) { + $orderElement = new OrderElement(); + $orderElement->setBaseId($record->getBaseId()); + $orderElement->setRecordId($record->getRecordId()); + + $order->addElement($orderElement); + + $this->manager->persist($orderElement); + } + + $order->setTodo($order->getElements()->count()); + + $this->manager->persist($order); + + $this->manager->flush(); + } +} From 421e62d13620ae1ec9b8842a39035ffdf5c0e454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Mon, 29 Feb 2016 15:25:01 +0100 Subject: [PATCH 11/26] Typos in Users in RegenerateSqliteDb command. PHPCS of tests --- .../Command/Developer/RegenerateSqliteDb.php | 4 +- .../Phrasea/Controller/Prod/OrderTest.php | 142 +++++++----------- tests/classes/PhraseanetTestCase.php | 8 + 3 files changed, 66 insertions(+), 88 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php b/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php index 8fc2e6c14e..1a1c3770b1 100644 --- a/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php +++ b/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php @@ -160,8 +160,8 @@ class RegenerateSqliteDb extends Command $fixtures['lazaret']['lazaret_1'] = $DI['lazaret_1']->getId(); $fixtures['user']['user_1'] = $DI['user_1']->getId(); - $fixtures['user']['user_2'] = $DI['user_1']->getId(); - $fixtures['user']['user_3'] = $DI['user_1']->getId(); + $fixtures['user']['user_2'] = $DI['user_2']->getId(); + $fixtures['user']['user_3'] = $DI['user_3']->getId(); $fixtures['user']['user_1_deleted'] = $DI['user_1_deleted']->getId(); $fixtures['user']['user_2_deleted'] = $DI['user_2_deleted']->getId(); $fixtures['user']['user_3_deleted'] = $DI['user_3_deleted']->getId(); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php index 069ced705a..e1a69f7b4c 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php @@ -3,7 +3,7 @@ namespace Alchemy\Tests\Phrasea\Controller\Prod; use Alchemy\Phrasea\Core\PhraseaEvents; -use Doctrine\Common\Collections\ArrayCollection; +use Alchemy\Phrasea\Notification\Deliverer; use Symfony\Component\EventDispatcher\Event; use Alchemy\Phrasea\Model\Entities\Order; use Alchemy\Phrasea\Model\Entities\OrderElement; @@ -16,87 +16,62 @@ use Alchemy\Phrasea\Model\Entities\OrderElement; */ class OrderTest extends \PhraseanetAuthenticatedWebTestCase { - /** - * - * @return Client A Client instance - */ - protected $client; - - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::createOrder - * @covers Alchemy\Phrasea\Controller\Prod\Order::connect - * @covers Alchemy\Phrasea\Controller\Prod\Order::call - */ public function testCreateOrder() { - self::$DI['app']['phraseanet.user-query'] = $this->getMockBuilder('\User_Query')->disableOriginalConstructor()->getMock(); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('get_results')->will($this->returnValue(new ArrayCollection([self::$DI['user_alt2']]))); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('on_base_ids')->will($this->returnSelf()); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('who_have_right')->will($this->returnSelf()); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('execute')->will($this->returnSelf()); + $app = $this->getApplication(); - self::$DI['app']['notification.deliverer'] = $this->getMockBuilder('Alchemy\Phrasea\Notification\Deliverer') + $app['notification.deliverer'] = $this->getMockBuilder(Deliverer::class) ->disableOriginalConstructor() ->getMock(); $triggered = false; - self::$DI['app']['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function (Event $event) use (&$triggered) { + $app['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function (Event $event) use (&$triggered) { $triggered = true; }); - self::$DI['client']->request('POST', '/prod/order/', [ - 'lst' => self::$DI['record_1']->get_serialize_key(), + $client = $this->getClient(); + $client->request('POST', '/prod/order/', [ + 'lst' => $this->getRecord1()->get_serialize_key(), 'deadline' => '+10 minutes' ]); - $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); - $url = parse_url(self::$DI['client']->getResponse()->headers->get('location')); + $this->assertTrue($client->getResponse()->isRedirect(), 'Response should be redirect'); + $url = parse_url($client->getResponse()->headers->get('location')); $var = []; parse_str($url['query'], $var); - $this->assertTrue(!!$var['success']); - $this->assertTrue($triggered); + $this->assertTrue(!!$var['success'], 'Response should have a success parameter'); + $this->assertTrue($triggered, 'Creation listener should have been triggered'); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::createOrder - */ public function testCreateOrderJson() { - self::$DI['app']['phraseanet.user-query'] = $this->getMockBuilder('\User_Query')->disableOriginalConstructor()->getMock(); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('get_results')->will($this->returnValue(new ArrayCollection([self::$DI['user_alt2']]))); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('on_base_ids')->will($this->returnSelf()); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('who_have_right')->will($this->returnSelf()); - self::$DI['app']['phraseanet.user-query']->expects($this->any())->method('execute')->will($this->returnSelf()); + $app = $this->getApplication(); - - self::$DI['app']['notification.deliverer'] = $this->getMockBuilder('Alchemy\Phrasea\Notification\Deliverer') + $app['notification.deliverer'] = $this->getMockBuilder(Deliverer::class) ->disableOriginalConstructor() ->getMock(); $triggered = false; - self::$DI['app']['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function (Event $event) use (&$triggered) { + $app['dispatcher']->addListener(PhraseaEvents::ORDER_CREATE, function (Event $event) use (&$triggered) { $triggered = true; }); $response = $this->XMLHTTPRequest('POST', '/prod/order/', [ - 'lst' => self::$DI['record_1']->get_serialize_key(), + 'lst' => $this->getRecord1()->get_serialize_key(), 'deadline' => '+10 minutes' ]); - $this->assertTrue($response->isOk()); - $this->assertTrue($triggered); + $this->assertTrue($response->isOk(), 'Invalid response from create order'); + $this->assertTrue($triggered, 'Order create listener not triggered'); $this->assertEquals('application/json', $response->headers->get('Content-Type')); $content = json_decode($response->getContent()); - $this->assertTrue(is_object($content)); + $this->assertTrue(is_object($content), 'content of response should be a valid JSON object'); $this->assertObjectHasAttribute('success', $content, $response->getContent()); $this->assertObjectHasAttribute('msg', $content, $response->getContent()); - $this->assertTrue($content->success); + $this->assertTrue($content->success, 'Success attribute of response content should be true'); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::displayOrders - */ public function testDisplayOrders() { $this->XMLHTTPRequest('POST', '/prod/order/', [ - 'lst' => self::$DI['record_1']->get_serialize_key(), + 'lst' => $this->getRecord1()->get_serialize_key(), 'deadline' => '+10 minutes' ]); $response = $this->request('GET', '/prod/order/', [ @@ -105,19 +80,14 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase $this->assertTrue($response->isOk()); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::displayOneOrder - */ public function testDisplayOneOrder() { $order = $this->createOneOrder('I need this pictures'); - self::$DI['client']->request('GET', '/prod/order/' . $order->getId() . '/'); - $this->assertTrue(self::$DI['client']->getResponse()->isOk()); + $client = $this->getClient(); + $client->request('GET', '/prod/order/' . $order->getId() . '/'); + $this->assertTrue($client->getResponse()->isOk()); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::sendOrder - */ public function testSendOrder() { $order = $this->createOneOrder('I need this pictures'); @@ -129,16 +99,14 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase foreach ($order->getElements() as $element) { $parameters[] = $element->getId(); } - self::$DI['client']->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); - $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); - $url = parse_url(self::$DI['client']->getResponse()->headers->get('location')); + $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')); parse_str($url['query']); $this->assertTrue( strpos($url['query'], 'success=1') === 0); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::sendOrder - */ public function testSendOrderJson() { $order = $this->createOneOrder('I need this pictures'); @@ -161,9 +129,6 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase $this->assertObjectHasAttribute('order_id', $content, $response->getContent()); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::denyOrder - */ public function testDenyOrder() { $order = $this->createOneOrder('I need this pictures'); @@ -175,17 +140,15 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase foreach ($order->getElements() as $element) { $parameters[] = $element->getId(); } - self::$DI['client']->request('POST', '/prod/order/' . $order->getId() . '/deny/', ['elements' => $parameters]); - $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); - $url = parse_url(self::$DI['client']->getResponse()->headers->get('location')); + $client = $this->getClient(); + $client->request('POST', '/prod/order/' . $order->getId() . '/deny/', ['elements' => $parameters]); + $this->assertTrue($client->getResponse()->isRedirect()); + $url = parse_url($client->getResponse()->headers->get('location')); $var = []; parse_str($url['query'], $var); $this->assertTrue( ! ! $var['success']); } - /** - * @covers Alchemy\Phrasea\Controller\Prod\Order::denyOrder - */ public function testDenyOrderJson() { $order = $this->createOneOrder('I need this pictures'); @@ -219,9 +182,10 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase foreach ($order->getElements() as $element) { $parameters[] = $element->getId(); } - self::$DI['client']->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); + $this->getClient()->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); - $testOrder = self::$DI['app']['orm.em']->getRepository('Phraseanet:Order')->find($order->getId()); + $app = $this->getApplication(); + $testOrder = $app['orm.em']->getRepository('Phraseanet:Order')->find($order->getId()); $this->assertEquals(0, $testOrder->getTodo()); } @@ -229,36 +193,41 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase { $order = $this->createOneOrder('I need this pictures'); $orderElement = new OrderElement(); - $orderElement->setBaseId(self::$DI['record_2']->get_base_id()); - $orderElement->setRecordId(self::$DI['record_2']->get_record_id()); + $record2 = $this->getRecord2(); + $orderElement->setBaseId($record2->getBaseId()); + $orderElement->setRecordId($record2->getRecordId()); $orderElement->setOrder($order); $order->addElement($orderElement); $order->setTodo(2); - self::$DI['app']['orm.em']->persist($order); - self::$DI['app']['orm.em']->persist($orderElement); - self::$DI['app']['orm.em']->flush(); + $app = $this->getApplication(); + $entityManager = $app['orm.em']; + $entityManager->persist($order); + $entityManager->persist($orderElement); + $entityManager->flush(); $parameters = [$order->getElements()->first()->getId()]; - self::$DI['client']->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); - $testOrder = self::$DI['app']['orm.em']->getRepository('Phraseanet:Order')->find($order->getId()); + $client = $this->getClient(); + $client->request('POST', '/prod/order/' . $order->getId() . '/send/', ['elements' => $parameters]); + $testOrder = $entityManager->getRepository('Phraseanet:Order')->find($order->getId()); $this->assertEquals(1, $testOrder->getTodo()); $parameters = [$orderElement->getId()]; - self::$DI['client']->request('POST', '/prod/order/' . $order->getId() . '/deny/', ['elements' => $parameters]); + $client->request('POST', '/prod/order/' . $order->getId() . '/deny/', ['elements' => $parameters]); - $testOrder = self::$DI['app']['orm.em']->getRepository('Phraseanet:Order')->find($order->getId()); + $testOrder = $entityManager->getRepository('Phraseanet:Order')->find($order->getId()); $this->assertEquals(0, $testOrder->getTodo()); } private function createOneOrder($usage) { - self::$DI['app']['notification.deliverer'] = $this->getMockBuilder('Alchemy\Phrasea\Notification\Deliverer') + $app = $this->getApplication(); + $app['notification.deliverer'] = $this->getMockBuilder('Alchemy\Phrasea\Notification\Deliverer') ->disableOriginalConstructor() ->getMock(); - $receveid = [self::$DI['record_1']->get_serialize_key() => self::$DI['record_1']]; + $record1 = $this->getRecord1(); $order = new Order(); $order->setOrderUsage($usage); @@ -266,16 +235,17 @@ class OrderTest extends \PhraseanetAuthenticatedWebTestCase $order->setDeadline(new \DateTime('+10 minutes')); $orderElement = new OrderElement(); - $orderElement->setBaseId(self::$DI['record_1']->get_base_id()); - $orderElement->setRecordId(self::$DI['record_1']->get_record_id()); + $orderElement->setBaseId($record1->getBaseId()); + $orderElement->setRecordId($record1->getRecordId()); $orderElement->setOrder($order); $order->addElement($orderElement); $order->setTodo(1); - self::$DI['app']['orm.em']->persist($order); - self::$DI['app']['orm.em']->persist($orderElement); - self::$DI['app']['orm.em']->flush(); + $entityManager = $app['orm.em']; + $entityManager->persist($order); + $entityManager->persist($orderElement); + $entityManager->flush(); return $order; } diff --git a/tests/classes/PhraseanetTestCase.php b/tests/classes/PhraseanetTestCase.php index 207cec250f..32440a1549 100644 --- a/tests/classes/PhraseanetTestCase.php +++ b/tests/classes/PhraseanetTestCase.php @@ -305,6 +305,14 @@ abstract class PhraseanetTestCase extends WebTestCase return self::$DI['record_1']; } + /** + * @return record_adapter + */ + public function getRecord2() + { + return self::$DI['record_2']; + } + /** * @return record_adapter */ From 1a9035c60f8d48b9834ec2f4319942744258bb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Mon, 29 Feb 2016 15:25:53 +0100 Subject: [PATCH 12/26] Add missing parameters types array in Repository call --- .../DbalCollectionReferenceRepository.php | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php index 68026bd087..3713ee0ad1 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php @@ -26,17 +26,23 @@ class DbalCollectionReferenceRepository implements CollectionReferenceRepository 'aliases' => 'alias' ]; - private static $selectQuery = 'SELECT base_id AS baseId, sbas_id AS databoxId, server_coll_id AS collectionId, - ord AS displayIndex, active AS isActive, aliases AS alias - FROM bas'; + private static $selectQuery = 'SELECT + base_id AS baseId, + sbas_id AS databoxId, + server_coll_id AS collectionId, + ord AS displayIndex, + active AS isActive, + aliases AS alias +FROM bas'; private static $insertQuery = 'INSERT INTO bas (sbas_id, server_coll_id, ord, active, aliases) - VALUES (:databoxId, :collectionId, - (SELECT COALESCE(MAX(b.ord), 0) + 1 AS ord FROM bas b WHERE b.sbas_id = :databoxId), - :isActive, :alias)'; +VALUES (:databoxId, :collectionId, (SELECT COALESCE(MAX(b.ord), 0) + 1 AS ord FROM bas b WHERE b.sbas_id = :databoxId), :isActive, :alias)'; - private static $updateQuery = 'UPDATE bas SET ord = :displayIndex, active = :isActive, aliases = :alias - WHERE base_id = :baseId'; + private static $updateQuery = 'UPDATE bas SET + ord = :displayIndex, + active = :isActive, + aliases = :alias +WHERE base_id = :baseId'; private static $deleteQuery = 'DELETE FROM bas WHERE base_id = :baseId'; @@ -124,7 +130,7 @@ class DbalCollectionReferenceRepository implements CollectionReferenceRepository $types['subset'] = Connection::PARAM_INT_ARRAY; } - $rows = $this->connection->fetchAll($query, $parameters); + $rows = $this->connection->fetchAll($query, $parameters, $types); return $this->createManyReferences($rows); } From 6b7e28e2bdab3d5ca7a8bf5eb5661c3ba42482bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Burnichon?= Date: Wed, 2 Mar 2016 14:40:46 +0100 Subject: [PATCH 13/26] Change variable name to make it more explicit. --- .../Reference/ArrayCacheCollectionReferenceRepository.php | 8 ++------ .../Reference/CollectionReferenceRepository.php | 4 ++-- .../Reference/DbalCollectionReferenceRepository.php | 8 ++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php index 17a9a1d790..7351351e93 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/ArrayCacheCollectionReferenceRepository.php @@ -90,13 +90,9 @@ class ArrayCacheCollectionReferenceRepository implements CollectionReferenceRepo return null; } - /** - * @param array|null $subset - * @return CollectionReference[] - */ - public function findHavingOrderMaster(array $subset = null) + public function findHavingOrderMaster(array $baseIdsSubset = null) { - return $this->repository->findHavingOrderMaster($subset); + return $this->repository->findHavingOrderMaster($baseIdsSubset); } /** diff --git a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php index d40e827f76..fdbf7289d1 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/CollectionReferenceRepository.php @@ -39,10 +39,10 @@ interface CollectionReferenceRepository /** * Find Collection references having at least one Order Master * - * @param array|null $subset Restrict search to a subset of base ids. + * @param array|null $baseIdsSubset Restrict search to a subset of base ids. * @return CollectionReference[] */ - public function findHavingOrderMaster(array $subset = null); + public function findHavingOrderMaster(array $baseIdsSubset = null); /** * @param CollectionReference $reference diff --git a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php index 3713ee0ad1..a67f8cad3d 100644 --- a/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php +++ b/lib/Alchemy/Phrasea/Collection/Reference/DbalCollectionReferenceRepository.php @@ -112,7 +112,7 @@ WHERE base_id = :baseId'; return null; } - public function findHavingOrderMaster(array $subset = null) + public function findHavingOrderMaster(array $baseIdsSubset = null) { $query = self::$selectQuery . ' WHERE EXISTS(SELECT 1 FROM basusr WHERE basusr.order_master = 1 AND basusr.base_id = bas.base_id)'; @@ -120,13 +120,13 @@ WHERE base_id = :baseId'; $parameters = []; $types = []; - if (null !== $subset) { - if (empty($subset)) { + if (null !== $baseIdsSubset) { + if (empty($baseIdsSubset)) { return []; } $query .= ' AND bas.base_id IN (:subset)'; - $parameters['subset'] = $subset; + $parameters['subset'] = $baseIdsSubset; $types['subset'] = Connection::PARAM_INT_ARRAY; } From 9398891f5c93c95fe1f8a39385ad3846148720eb 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 14/26] 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 f07b7b6722d336f3c33bf58038e5a5752c620f15 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 15/26] WIP --- .../Controller/Prod/OrderController.php | 82 ++++++++++++------- .../Phrasea/ControllerProvider/Prod/Order.php | 13 +++ .../Phrasea/Order/OrderBasketProvider.php | 55 +++++++++++++ lib/Alchemy/Phrasea/Order/OrderValidator.php | 38 +++++++++ lib/classes/ACL.php | 18 +++- 5 files changed, 174 insertions(+), 32 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/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) { From b150cd89256e19a35fff12d25447dbe912c23d92 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 16/26] 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 a0983d39b2ebbfa9256c79c8b929a59d51636a86 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 17/26] 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 5b031592455858180d0798e9f057fba5822cce2d 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 18/26] 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 a5f9ace717645f8deca0e15bb730e5851eda94cd 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 19/26] 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 65550e8bd119183815c60f881425121594d72805 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 20/26] 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 e1a69f7b4c..19c49119c3 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Prod/OrderTest.php @@ -99,12 +99,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'); } public function testSendOrderJson() From a547a04aa68aceacc47e00fbaebf8fe27f5289f4 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 21/26] Move Order controllers to same namespace --- .../Phrasea/ControllerProvider/Prod/Order.php | 4 ++-- .../Controller/ProdOrderController.php} | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) rename lib/Alchemy/Phrasea/{Controller/Prod/OrderController.php => Order/Controller/ProdOrderController.php} (97%) 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/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 22/26] Reduce duplication for OrderController --- .../Order/Controller/BaseOrderController.php | 170 ++++++++++++++++++ .../Order/Controller/ProdOrderController.php | 135 +------------- 2 files changed, 177 insertions(+), 128 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Order/Controller/BaseOrderController.php 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 79119fe10a2762bc2e4cf3e72c46f59b3b033626 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 23/26] 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 96295eab7b064b593168f93575c52119f8d1b29e 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 24/26] 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 25745732b6b66d6b00091fd7449790447180730c 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 25/26] Fix OrderElement accept/deny todo count --- .../Phrasea/Model/Entities/OrderElement.php | 7 ++-- .../Order/Controller/BaseOrderController.php | 38 ++++++++++++++++++- lib/Alchemy/Phrasea/Order/OrderValidator.php | 9 ++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/OrderElement.php b/lib/Alchemy/Phrasea/Model/Entities/OrderElement.php index d5277abf55..d232795067 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/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); + } } } From 755aac9e3e550fd17f55bbcfd7afaf86c426f656 Mon Sep 17 00:00:00 2001 From: Florian BLOUET Date: Mon, 21 Mar 2016 11:22:39 +0100 Subject: [PATCH 26/26] PHRAS-1038 - fix story navigation in workzone context --- lib/classes/eventsmanager/notify/push.php | 2 +- .../notify/validationreminder.php | 2 +- resources/www/prod/js/jquery.p4.preview.js | 48 +++++++++++-------- templates/web/prod/WorkZone/Macros.html.twig | 2 +- .../web/prod/preview/appears_in.html.twig | 4 +- .../web/prod/preview/reg_train.html.twig | 2 +- templates/web/prod/results/item.html.twig | 2 +- templates/web/prod/results/record.html.twig | 2 +- 8 files changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/classes/eventsmanager/notify/push.php b/lib/classes/eventsmanager/notify/push.php index 39c4b765e5..2bb3fbeb85 100644 --- a/lib/classes/eventsmanager/notify/push.php +++ b/lib/classes/eventsmanager/notify/push.php @@ -39,7 +39,7 @@ class eventsmanager_notify_push extends eventsmanager_notifyAbstract $sender = $user->getDisplayName(); $ret = [ - 'text' => $this->app->trans('%user% vous a envoye un %before_link% panier %after_link%', ['%user%' => $sender, '%before_link%' => '', '%after_link%' => '']) , 'class' => ($unread == 1 ? 'reload_baskets' : '') ]; diff --git a/lib/classes/eventsmanager/notify/validationreminder.php b/lib/classes/eventsmanager/notify/validationreminder.php index 66058c38b8..f80bd29d8e 100644 --- a/lib/classes/eventsmanager/notify/validationreminder.php +++ b/lib/classes/eventsmanager/notify/validationreminder.php @@ -53,7 +53,7 @@ class eventsmanager_notify_validationreminder extends eventsmanager_notifyAbstra $basket_name = $this->app->trans('Une selection'); } - $bask_link = '' . $basket_name . ''; diff --git a/resources/www/prod/js/jquery.p4.preview.js b/resources/www/prod/js/jquery.p4.preview.js index 9276cb0b58..2dd0ae4a37 100644 --- a/resources/www/prod/js/jquery.p4.preview.js +++ b/resources/www/prod/js/jquery.p4.preview.js @@ -34,13 +34,21 @@ function getNewVideoToken(lst, obj) { * @param contId * @param reload */ -function openPreview(env, pos, contId, reload) { - +function openPreview(event, env, pos, contId, reload) { if (contId == undefined) contId = ''; var roll = 0; var justOpen = false; + var options_serial = p4.tot_options; + var query = p4.tot_query; + var navigation = p4.navigation; + var navigationContext = ''; + + // keep relative position for answer train: + var relativePos = pos; + var absolutePos = 0; + if (!p4.preview.open) { showOverlay(); @@ -66,6 +74,15 @@ function openPreview(env, pos, contId, reload) { if (env == 'BASK') roll = 1; + // if comes from story and in workzone + if (env == 'REG') { + navigationContext = 'storyFromResults'; + var $source = $(event); + if( $source.hasClass('CHIM')) { + navigationContext = 'storyFromWorkzone'; + } + } + } if (reload === true) @@ -78,21 +95,14 @@ function openPreview(env, pos, contId, reload) { $('#PREVIEWIMGCONT').empty(); - var options_serial = p4.tot_options; - var query = p4.tot_query; - var navigation = p4.navigation; - - // keep relative position for answer train: - var relativePos = pos; - // update real absolute position with pagination: - var absolutePos = parseInt(navigation.perPage,10) * (parseInt(navigation.page, 10) - 1) + parseInt(pos,10); - - // if comes from story, work with relative positionning - if (env == 'REG') { - // @TODO - if event comes from workzone (basket|story), - // we can use the relative position in order to display the doubleclicked records - // except we can't know the original event in this implementation + if (navigationContext === 'storyFromWorkzone') { + // if event comes from workzone, set to relative position (CHIM == chutier image) + absolutePos = relativePos; + } else if (navigationContext === 'storyFromResults') { absolutePos = 0; + } else { + // update real absolute position with pagination for records: + absolutePos = parseInt(navigation.perPage, 10) * (parseInt(navigation.page, 10) - 1) + parseInt(pos, 10); } prevAjax = $.ajax({ @@ -361,7 +371,7 @@ function getNext() { if (p4.preview.mode == 'RESULT') { posAsk = parseInt(p4.preview.current.pos) + 1; posAsk = (posAsk >= parseInt(p4.tot) || isNaN(posAsk)) ? 0 : posAsk; - openPreview('RESULT', posAsk, '', false); + openPreview(false, 'RESULT', posAsk, '', false); } else { if (!$('#PREVIEWCURRENT li.selected').is(':last-child')) @@ -383,7 +393,7 @@ function getPrevious() { // may go to last result posAsk = (posAsk < 0) ? ((parseInt(p4.tot) - 1)) : posAsk; } - openPreview('RESULT', posAsk, '', false); + openPreview(false, 'RESULT', posAsk, '', false); } else { if (!$('#PREVIEWCURRENT li.selected').is(':first-child')) @@ -431,7 +441,7 @@ function setCurrent(current) { var absolutePos = jsopt[1]; var relativePos = parseInt(absolutePos, 10) - parseInt(p4.navigation.perPage, 10) * (parseInt(p4.navigation.page, 10) - 1); // keep relative position for answer train: - openPreview(jsopt[0], relativePos, jsopt[2],false); + openPreview(this, jsopt[0], relativePos, jsopt[2],false); }); }); } diff --git a/templates/web/prod/WorkZone/Macros.html.twig b/templates/web/prod/WorkZone/Macros.html.twig index a7079fea50..17f59eb435 100644 --- a/templates/web/prod/WorkZone/Macros.html.twig +++ b/templates/web/prod/WorkZone/Macros.html.twig @@ -277,7 +277,7 @@ {% import 'common/thumbnail.html.twig' as thumbnail %} -
diff --git a/templates/web/prod/preview/appears_in.html.twig b/templates/web/prod/preview/appears_in.html.twig index 8e7acd40af..1a5dbbfdf8 100644 --- a/templates/web/prod/preview/appears_in.html.twig +++ b/templates/web/prod/preview/appears_in.html.twig @@ -3,7 +3,7 @@
  • {{ 'Apparait aussi dans ces reportages' | trans }}
  • {% for par in parents %} -
  • {{ par.get_title() }} @@ -16,7 +16,7 @@
    • {{ 'Apparait aussi dans ces paniers' | trans }}
    • {% for basket in baskets %} -
    • {##} diff --git a/templates/web/prod/preview/reg_train.html.twig b/templates/web/prod/preview/reg_train.html.twig index e7f4b244da..e518fddc67 100644 --- a/templates/web/prod/preview/reg_train.html.twig +++ b/templates/web/prod/preview/reg_train.html.twig @@ -25,7 +25,7 @@ %}
      -
      diff --git a/templates/web/prod/results/record.html.twig b/templates/web/prod/results/record.html.twig index 48dc7cf2f1..4d046df964 100644 --- a/templates/web/prod/results/record.html.twig +++ b/templates/web/prod/results/record.html.twig @@ -5,7 +5,7 @@ sbas="{{ record.databoxId }}" id="{{ prefix|default('IMGT') }}_{{ record.id }}" class="IMGT diapo {% if record.story %}grouping{% endif %} type-{{ record.type }}" - onDblClick="openPreview('{{ record.story ? 'REG' : 'RESULT' }}', '{{ record.position|default(0) }}', '{{ record.id }}');"> + onDblClick="openPreview(this, '{{ record.story ? 'REG' : 'RESULT' }}', '{{ record.position|default(0) }}', '{{ record.id }}');">