From 7a85e00c1655b9bd9c5e06520608a3d5af756b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 13 Apr 2018 22:16:40 +0200 Subject: [PATCH 001/114] initial commit --- ChillTaskBundle.php | 29 +++ Controller/DefaultController.php | 13 ++ DependencyInjection/ChillTaskExtension.php | 28 +++ DependencyInjection/Configuration.php | 29 +++ Entity/AbstractTask.php | 169 ++++++++++++++ Entity/RecurringTask.php | 210 ++++++++++++++++++ Entity/SingleTask.php | 138 ++++++++++++ Repository/AbstractTaskRepository.php | 13 ++ Repository/RecurringTaskRepository.php | 13 ++ Repository/SingleTaskRepository.php | 13 ++ Resources/config/routing.yml | 1 + .../migrations/Version20180413135614.php | 43 ++++ .../migrations/Version20180413201023.php | 49 ++++ Tests/Controller/DefaultControllerTest.php | 17 ++ 14 files changed, 765 insertions(+) create mode 100644 ChillTaskBundle.php create mode 100644 Controller/DefaultController.php create mode 100644 DependencyInjection/ChillTaskExtension.php create mode 100644 DependencyInjection/Configuration.php create mode 100644 Entity/AbstractTask.php create mode 100644 Entity/RecurringTask.php create mode 100644 Entity/SingleTask.php create mode 100644 Repository/AbstractTaskRepository.php create mode 100644 Repository/RecurringTaskRepository.php create mode 100644 Repository/SingleTaskRepository.php create mode 100644 Resources/config/routing.yml create mode 100644 Resources/migrations/Version20180413135614.php create mode 100644 Resources/migrations/Version20180413201023.php create mode 100644 Tests/Controller/DefaultControllerTest.php diff --git a/ChillTaskBundle.php b/ChillTaskBundle.php new file mode 100644 index 000000000..6155882cd --- /dev/null +++ b/ChillTaskBundle.php @@ -0,0 +1,29 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +/** + * + * + */ +class ChillTaskBundle extends Bundle +{ + +} diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php new file mode 100644 index 000000000..74b3aa34e --- /dev/null +++ b/Controller/DefaultController.php @@ -0,0 +1,13 @@ +?php + +namespace Chill\TaskBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; + +class DefaultController extends Controller +{ + public function indexAction() + { + return $this->render('ChillTaskBundle:Default:index.html.twig'); + } +} diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php new file mode 100644 index 000000000..0a933e247 --- /dev/null +++ b/DependencyInjection/ChillTaskExtension.php @@ -0,0 +1,28 @@ +processConfiguration($configuration, $configs); + + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + //$loader->load('services.yml'); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php new file mode 100644 index 000000000..e0989cd69 --- /dev/null +++ b/DependencyInjection/Configuration.php @@ -0,0 +1,29 @@ +root('chill_task'); + + // Here you should define the parameters that are allowed to + // configure your bundle. See the documentation linked above for + // more information on that topic. + + return $treeBuilder; + } +} diff --git a/Entity/AbstractTask.php b/Entity/AbstractTask.php new file mode 100644 index 000000000..3e65be165 --- /dev/null +++ b/Entity/AbstractTask.php @@ -0,0 +1,169 @@ +type = $type; + + return $this; + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set currentStates + * + * @param json $currentStates + * + * @return AbstractTask + */ + public function setCurrentStates($currentStates) + { + $this->currentStates = $currentStates; + + return $this; + } + + /** + * Get currentStates + * + * @return json + */ + public function getCurrentStates() + { + return $this->currentStates; + } + + /** + * Set title + * + * @param string $title + * + * @return AbstractTask + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * Get title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set description + * + * @param string $description + * + * @return AbstractTask + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Get description + * + * @return string + */ + public function getDescription() + { + return $this->description; + } +} + diff --git a/Entity/RecurringTask.php b/Entity/RecurringTask.php new file mode 100644 index 000000000..20bffab18 --- /dev/null +++ b/Entity/RecurringTask.php @@ -0,0 +1,210 @@ +singleTasks = new ArrayCollection(); + } + + + /** + * Get id + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Set firstOccurenceEndDate + * + * @param \DateTime $firstOccurenceEndDate + * + * @return RecurringTask + */ + public function setFirstOccurenceEndDate($firstOccurenceEndDate) + { + $this->firstOccurenceEndDate = $firstOccurenceEndDate; + + return $this; + } + + /** + * Get firstOccurenceEndDate + * + * @return \DateTime + */ + public function getFirstOccurenceEndDate() + { + return $this->firstOccurenceEndDate; + } + + /** + * Set lastOccurenceEndDate + * + * @param \DateTime $lastOccurenceEndDate + * + * @return RecurringTask + */ + public function setLastOccurenceEndDate($lastOccurenceEndDate) + { + $this->lastOccurenceEndDate = $lastOccurenceEndDate; + + return $this; + } + + /** + * Get lastOccurenceEndDate + * + * @return \DateTime + */ + public function getLastOccurenceEndDate() + { + return $this->lastOccurenceEndDate; + } + + /** + * Set occurenceFrequency + * + * @param string $occurenceFrequency + * + * @return RecurringTask + */ + public function setOccurenceFrequency($occurenceFrequency) + { + $this->occurenceFrequency = $occurenceFrequency; + + return $this; + } + + /** + * Get occurenceFrequency + * + * @return string + */ + public function getOccurenceFrequency() + { + return $this->occurenceFrequency; + } + + /** + * Set occurenceStartDate + * + * @param dateinterval $occurenceStartDate + * + * @return RecurringTask + */ + public function setOccurenceStartDate($occurenceStartDate) + { + $this->occurenceStartDate = $occurenceStartDate; + + return $this; + } + + /** + * Get occurenceStartDate + * + * @return dateinterval + */ + public function getOccurenceStartDate() + { + return $this->occurenceStartDate; + } + + /** + * Set occurenceWarningInterval + * + * @param dateinterval $occurenceWarningInterval + * + * @return RecurringTask + */ + public function setOccurenceWarningInterval($occurenceWarningInterval) + { + $this->occurenceWarningInterval = $occurenceWarningInterval; + + return $this; + } + + /** + * Get occurenceWarningInterval + * + * @return dateinterval + */ + public function getOccurenceWarningInterval() + { + return $this->occurenceWarningInterval; + } +} + diff --git a/Entity/SingleTask.php b/Entity/SingleTask.php new file mode 100644 index 000000000..81d987b5c --- /dev/null +++ b/Entity/SingleTask.php @@ -0,0 +1,138 @@ +id; + } + + /** + * Set startDate + * + * @param \DateTime $startDate + * + * @return SingleTask + */ + public function setStartDate($startDate) + { + $this->startDate = $startDate; + + return $this; + } + + /** + * Get startDate + * + * @return \DateTime + */ + public function getStartDate() + { + return $this->startDate; + } + + /** + * Set endDate + * + * @param \DateTime $endDate + * + * @return SingleTask + */ + public function setEndDate($endDate) + { + $this->endDate = $endDate; + + return $this; + } + + /** + * Get endDate + * + * @return \DateTime + */ + public function getEndDate() + { + return $this->endDate; + } + + /** + * Set warningInterval + * + * @param string $warningInterval + * + * @return SingleTask + */ + public function setWarningInterval($warningInterval) + { + $this->warningInterval = $warningInterval; + + return $this; + } + + /** + * Get warningInterval + * + * @return \DateInterval + */ + public function getWarningInterval(): ?\DateInterval + { + return $this->warningInterval; + } +} + diff --git a/Repository/AbstractTaskRepository.php b/Repository/AbstractTaskRepository.php new file mode 100644 index 000000000..d5c7627c0 --- /dev/null +++ b/Repository/AbstractTaskRepository.php @@ -0,0 +1,13 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE SCHEMA chill_task'); + $this->addSql('CREATE SEQUENCE chill_task.single_task_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_task.single_task (id INT NOT NULL, assignee_id INT DEFAULT NULL, person_id INT DEFAULT NULL, circle_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, current_states JSONB NOT NULL, title TEXT NOT NULL, description TEXT NOT NULL, start_date DATE DEFAULT NULL, end_date DATE DEFAULT NULL, warning_interval INTERVAL DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_194CB3D859EC7D60 ON chill_task.single_task (assignee_id)'); + $this->addSql('CREATE INDEX IDX_194CB3D8217BBB47 ON chill_task.single_task (person_id)'); + $this->addSql('CREATE INDEX IDX_194CB3D870EE2FF6 ON chill_task.single_task (circle_id)'); + $this->addSql('COMMENT ON COLUMN chill_task.single_task.warning_interval IS \'(DC2Type:dateinterval)\''); + $this->addSql('ALTER TABLE chill_task.single_task ADD CONSTRAINT FK_194CB3D859EC7D60 FOREIGN KEY (assignee_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_task.single_task ADD CONSTRAINT FK_194CB3D8217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_task.single_task ADD CONSTRAINT FK_194CB3D870EE2FF6 FOREIGN KEY (circle_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP SCHEMA chill_task CASCADE'); + + } +} diff --git a/Resources/migrations/Version20180413201023.php b/Resources/migrations/Version20180413201023.php new file mode 100644 index 000000000..f068fe0e2 --- /dev/null +++ b/Resources/migrations/Version20180413201023.php @@ -0,0 +1,49 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE SEQUENCE chill_task.recurring_task_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_task.recurring_task (id INT NOT NULL, assignee_id INT DEFAULT NULL, person_id INT DEFAULT NULL, circle_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, current_states JSONB NOT NULL, title TEXT NOT NULL, description TEXT NOT NULL, first_occurence_end_date DATE NOT NULL, last_occurence_end_date DATE NOT NULL, occurence_frequency VARCHAR(255) NOT NULL, occurence_start_date INTERVAL NOT NULL, occurence_warning_interval INTERVAL DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_9F663B9059EC7D60 ON chill_task.recurring_task (assignee_id)'); + $this->addSql('CREATE INDEX IDX_9F663B90217BBB47 ON chill_task.recurring_task (person_id)'); + $this->addSql('CREATE INDEX IDX_9F663B9070EE2FF6 ON chill_task.recurring_task (circle_id)'); + $this->addSql('COMMENT ON COLUMN chill_task.recurring_task.occurence_start_date IS \'(DC2Type:dateinterval)\''); + $this->addSql('COMMENT ON COLUMN chill_task.recurring_task.occurence_warning_interval IS \'(DC2Type:dateinterval)\''); + $this->addSql('ALTER TABLE chill_task.recurring_task ADD CONSTRAINT FK_9F663B9059EC7D60 FOREIGN KEY (assignee_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_task.recurring_task ADD CONSTRAINT FK_9F663B90217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_task.recurring_task ADD CONSTRAINT FK_9F663B9070EE2FF6 FOREIGN KEY (circle_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_task.single_task ADD recurringTask_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_task.single_task ADD CONSTRAINT FK_194CB3D840868C31 FOREIGN KEY (recurringTask_id) REFERENCES chill_task.recurring_task (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_194CB3D840868C31 ON chill_task.single_task (recurringTask_id)'); + + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_task.single_task DROP CONSTRAINT FK_194CB3D840868C31'); + $this->addSql('DROP SEQUENCE chill_task.recurring_task_id_seq CASCADE'); + + $this->addSql('DROP TABLE chill_task.recurring_task'); + $this->addSql('ALTER TABLE chill_task.single_task DROP recurringTask_id'); + } +} diff --git a/Tests/Controller/DefaultControllerTest.php b/Tests/Controller/DefaultControllerTest.php new file mode 100644 index 000000000..a370af892 --- /dev/null +++ b/Tests/Controller/DefaultControllerTest.php @@ -0,0 +1,17 @@ +request('GET', '/'); + + $this->assertContains('Hello World', $client->getResponse()->getContent()); + } +} From 17347270290213be81720662794317b90dda487d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 13 Apr 2018 22:25:02 +0200 Subject: [PATCH 002/114] add composer.json file --- .gitignore | 1 + composer.json | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..57872d0f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..7155669c2 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "chill-project/task", + "description": "Associate task with peoples in chill.social", + "type": "symfony-bundle", + "keywords": ["chill", "social work", "tasks"], + "homepage": "https://framagit.org/Chill-project/Chill-Task", + "autoload": { + "psr-4": { "Chill\\TaskBundle\\" : "" } + }, + "require": { + "php": "~7.2", + "chill-project/person": "dev-upgrade-sf3@dev" + }, + "license": "AGPL-3.0-or-later", + "authors": [ + { + "name": "Champs-Libres", + "email": "info@champs-libres.coop" + } + ] +} From a02b9edc45c92bbef41cb931eeccf2d5b7beb250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 16 Apr 2018 15:09:50 +0200 Subject: [PATCH 003/114] first implementation of controller --- Controller/DefaultController.php | 13 -- Controller/SingleTaskController.php | 101 ++++++++++++++++ DataFixtures/ORM/LoadTaskACL.php | 86 ++++++++++++++ DependencyInjection/ChillTaskExtension.php | 3 +- Entity/AbstractTask.php | 56 ++++++++- Entity/SingleTask.php | 14 ++- Form/SingleTaskType.php | 74 ++++++++++++ Resources/config/routing.yml | 4 +- Resources/config/services.yml | 2 + Resources/config/services/controller.yml | 4 + Resources/config/services/security.yml | 10 ++ Resources/views/SingleTask/new.html.twig | 29 +++++ Security/Authorization/TaskVoter.php | 131 +++++++++++++++++++++ 13 files changed, 509 insertions(+), 18 deletions(-) delete mode 100644 Controller/DefaultController.php create mode 100644 Controller/SingleTaskController.php create mode 100644 DataFixtures/ORM/LoadTaskACL.php create mode 100644 Form/SingleTaskType.php create mode 100644 Resources/config/services.yml create mode 100644 Resources/config/services/controller.yml create mode 100644 Resources/config/services/security.yml create mode 100644 Resources/views/SingleTask/new.html.twig create mode 100644 Security/Authorization/TaskVoter.php diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php deleted file mode 100644 index 74b3aa34e..000000000 --- a/Controller/DefaultController.php +++ /dev/null @@ -1,13 +0,0 @@ -?php - -namespace Chill\TaskBundle\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\Controller; - -class DefaultController extends Controller -{ - public function indexAction() - { - return $this->render('ChillTaskBundle:Default:index.html.twig'); - } -} diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php new file mode 100644 index 000000000..b2158d0bd --- /dev/null +++ b/Controller/SingleTaskController.php @@ -0,0 +1,101 @@ +em = $em; + $this->formFactory = $formFactory; + }*/ + + /** + * @Route("/{_locale}/task/single-task/new") + */ + public function newAction(Request $request) + { + $personId = $request->query->getInt('person_id', null); + + if ($personId === null) { + return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST); + } + + $person = $this->getDoctrine()->getManager() + ->getRepository(Person::class) + ->find($personId); + + if ($person === null) { + throw $this->createNotFoundException("Invalid person id"); + } + + $task = (new SingleTask()) + ->setPerson($person) + ->setAssignee($this->getUser()) + ; + + $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' + . 'allowed to create this task'); + + $form = $this->createCreateForm($task); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($task); + + $this->addFlash('success', "The task is created"); + + $em->flush(); + } + + return $this->render('ChillTaskBundle:SingleTask:new.html.twig', array( + 'form' => $form->createView(), + 'task' => $task + )); + } + + /** + * + * @param SingleTask $task + * @return \Symfony\Component\Form\FormInterface + */ + protected function createCreateForm(SingleTask $task) + { + $form = $this->createForm(SingleTaskType::class, $task, [ + 'center' => $task->getCenter() + ]); + + $form->add('submit', SubmitType::class); + + return $form; + } + +} diff --git a/DataFixtures/ORM/LoadTaskACL.php b/DataFixtures/ORM/LoadTaskACL.php new file mode 100644 index 000000000..f7f88c79c --- /dev/null +++ b/DataFixtures/ORM/LoadTaskACL.php @@ -0,0 +1,86 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\TaskBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; +use Chill\MainBundle\DataFixtures\ORM\LoadScopes; +use Chill\TaskBundle\Security\Authorization\TaskVoter; + +/** + * Add a role UPDATE & CREATE for all groups except administrative, + * and a role SEE for administrative + * + * @author Julien Fastré + */ +class LoadTaskACL extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 16000; + } + + + public function load(ObjectManager $manager) + { + foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { + $permissionsGroup = $this->getReference($permissionsGroupRef); + foreach (LoadScopes::$references as $scopeRef){ + $scope = $this->getReference($scopeRef); + //create permission group + switch ($permissionsGroup->getName()) { + case 'social': + if ($scope->getName()['en'] === 'administrative') { + break 2; // we do not want any power on administrative + } + break; + case 'administrative': + case 'direction': + if (in_array($scope->getName()['en'], array('administrative', 'social'))) { + break 2; // we do not want any power on social or administrative + } + break; + } + + printf("Adding CHILL_TASK_TASK_UPDATE & CHILL_TASK_TASK_CREATE permissions to %s " + . "permission group, scope '%s' \n", + $permissionsGroup->getName(), $scope->getName()['en']); + $roleScopeUpdate = (new RoleScope()) + ->setRole(TaskVoter::UPDATE) + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeUpdate); + $roleScopeCreate = (new RoleScope()) + ->setRole(TaskVoter::CREATE) + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeCreate); + + $manager->persist($roleScopeUpdate); + $manager->persist($roleScopeCreate); + } + + } + + $manager->flush(); + } + +} diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index 0a933e247..2506082da 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -23,6 +23,7 @@ class ChillTaskExtension extends Extension $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - //$loader->load('services.yml'); + $loader->load('services/controller.yml'); + $loader->load('services/security.yml'); } } diff --git a/Entity/AbstractTask.php b/Entity/AbstractTask.php index 3e65be165..4b5df1093 100644 --- a/Entity/AbstractTask.php +++ b/Entity/AbstractTask.php @@ -6,13 +6,15 @@ use Doctrine\ORM\Mapping as ORM; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\HasScopeInterface; +use Chill\MainBundle\Entity\HasCenterInterface; /** * AbstractTask * * @ORM\MappedSuperclass() */ -abstract class AbstractTask +abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { /** @@ -27,7 +29,7 @@ abstract class AbstractTask * * @ORM\Column(name="current_states", type="json") */ - private $currentStates = ''; + private $currentStates = []; /** * @var string @@ -165,5 +167,55 @@ abstract class AbstractTask { return $this->description; } + + public function getAssignee(): ?User + { + return $this->assignee; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function getCircle(): ?Scope + { + return $this->circle; + } + + public function setAssignee(User $assignee) + { + $this->assignee = $assignee; + return $this; + } + + public function setPerson(Person $person) + { + $this->person = $person; + return $this; + } + + public function setCircle(Scope $circle) + { + $this->circle = $circle; + return $this; + } + + public function getCenter(): ?\Chill\MainBundle\Entity\Center + { + if ($this->getPerson() instanceof Person) { + return $this->getPerson()->getCenter(); + } + + return null; + + } + + public function getScope(): ?\Chill\MainBundle\Entity\Scope + { + return $this->getCircle(); + } + + } diff --git a/Entity/SingleTask.php b/Entity/SingleTask.php index 81d987b5c..77627906d 100644 --- a/Entity/SingleTask.php +++ b/Entity/SingleTask.php @@ -130,9 +130,21 @@ class SingleTask extends AbstractTask * * @return \DateInterval */ - public function getWarningInterval(): ?\DateInterval + public function getWarningInterval() { return $this->warningInterval; } + + function getRecurringTask(): RecurringTask + { + return $this->recurringTask; + } + + function setRecurringTask(RecurringTask $recurringTask) + { + $this->recurringTask = $recurringTask; + } + + } diff --git a/Form/SingleTaskType.php b/Form/SingleTaskType.php new file mode 100644 index 000000000..93ed932ba --- /dev/null +++ b/Form/SingleTaskType.php @@ -0,0 +1,74 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Form\Type\ChillDateType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Chill\MainBundle\Form\Type\UserPickerType; +use Chill\MainBundle\Form\Type\ScopePickerType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Role\Role; + +/** + * + * + * @author Julien Fastré + */ +class SingleTaskType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('title', TextType::class) + ->add('description', TextareaType::class, [ + 'required' => false + ]) + ->add('assignee', UserPickerType::class, [ + 'required' => false, + 'center' => $options['center'], + 'role' => new Role(\Chill\PersonBundle\Security\Authorization\PersonVoter::UPDATE) + ]) + ->add('circle', ScopePickerType::class, [ + 'center' => $options['center'], + 'role' => new Role(\Chill\ActivityBundle\Security\Authorization\ActivityVoter::SEE) + ]) + ->add('startDate', ChillDateType::class, [ + 'required' => false + ]) + ->add('endDate', ChillDateType::class, [ + 'required' => false + ]) + ->add('warningInterval', TextType::class, [ + 'required' => false + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired('center') + ->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ]) + ; + } +} diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index 8b1378917..53d5e4f81 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -1 +1,3 @@ - +chill_task_controllers: + resource: "@ChillTaskBundle/Controller" + type: annotation diff --git a/Resources/config/services.yml b/Resources/config/services.yml new file mode 100644 index 000000000..5779e425e --- /dev/null +++ b/Resources/config/services.yml @@ -0,0 +1,2 @@ +services: + diff --git a/Resources/config/services/controller.yml b/Resources/config/services/controller.yml new file mode 100644 index 000000000..9edb4e9ad --- /dev/null +++ b/Resources/config/services/controller.yml @@ -0,0 +1,4 @@ +services: + #chill_task.single_task_controller: + # class: Chill\TaskBundle\Controller\SingleTaskController + # autowire: true diff --git a/Resources/config/services/security.yml b/Resources/config/services/security.yml new file mode 100644 index 000000000..0acc6e56f --- /dev/null +++ b/Resources/config/services/security.yml @@ -0,0 +1,10 @@ +services: + chill_task.task_voter: + class: Chill\TaskBundle\Security\Authorization\TaskVoter + arguments: + - "@security.access.decision_manager" + - "@chill.main.security.authorization.helper" + - "@logger" + tags: + - { name: security.voter } + - { name: chill.role } diff --git a/Resources/views/SingleTask/new.html.twig b/Resources/views/SingleTask/new.html.twig new file mode 100644 index 000000000..e656a69fa --- /dev/null +++ b/Resources/views/SingleTask/new.html.twig @@ -0,0 +1,29 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_single_task_new' %} +{% set person = task.person %} + +{% block title %}{{ 'New task'|trans }}{% endblock %} + +{% block personcontent %} +

{{ 'New task'|trans }}

+ +{{ form(form) }} + +{% endblock %} diff --git a/Security/Authorization/TaskVoter.php b/Security/Authorization/TaskVoter.php new file mode 100644 index 000000000..aacad0dad --- /dev/null +++ b/Security/Authorization/TaskVoter.php @@ -0,0 +1,131 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\TaskBundle\Security\Authorization; + +use Chill\MainBundle\Security\Authorization\AbstractChillVoter; +use Chill\TaskBundle\Entity\AbstractTask; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Psr\Log\LoggerInterface; +use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Chill\MainBundle\Entity\User; + +/** + * + * + * @author Julien Fastré + */ +class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface +{ + const CREATE = 'CHILL_TASK_TASK_CREATE'; + const UPDATE = 'CHILL_TASK_TASK_UPDATE'; + const SHOW = 'CHILL_TASK_TASK_SHOW'; + + const ROLES = [ + self::CREATE, + self::UPDATE, + self::SHOW + ]; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var AccessDecisionManagerInterface + */ + protected $accessDecisionManager; + + /** + * + * @var LoggerInterface + */ + protected $logger; + + public function __construct( + AccessDecisionManagerInterface $accessDecisionManager, + AuthorizationHelper $authorizationHelper, + LoggerInterface $logger + ) { + $this->accessDecisionManager = $accessDecisionManager; + $this->authorizationHelper = $authorizationHelper; + $this->logger = $logger; + } + + public function supports($attribute, $subject) + { + return $subject instanceof AbstractTask + && in_array($attribute, self::ROLES); + } + + /** + * + * @param string $attribute + * @param AbstractTask $subject + * @param TokenInterface $token + * @return boolean + */ + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + $this->logger->debug(sprintf("Voting from %s class", self::class)); + + if (!$token->getUser() instanceof User) { + return false; + } + + if ($subject->getPerson() === null) { + throw new \LogicException("You should associate a person with task " + . "in order to check autorizations"); + } + + if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $subject->getPerson())) { + + return false; + } + + return $this->authorizationHelper->userHasAccess( + $token->getUser(), + $subject, + $attribute + ); + } + + public function getRoles() + { + return self::ROLES; + } + + public function getRolesWithHierarchy(): array + { + return [ + 'Task' => self::ROLES + ]; + } + + public function getRolesWithoutScope() + { + return []; + } + +} From fd8b6490d0fd05ae46bb40e8bb1309e57c565c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 17 Apr 2018 11:39:16 +0200 Subject: [PATCH 004/114] add validation to single task controller::new --- Controller/SingleTaskController.php | 23 +++++++++++++++-------- Entity/AbstractTask.php | 15 ++++++++++++--- Entity/SingleTask.php | 7 +++++++ Form/SingleTaskType.php | 13 +++++++++---- Resources/translations/validators.fr.yml | 1 + 5 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 Resources/translations/validators.fr.yml diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index b2158d0bd..1594b3e7b 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -13,6 +13,7 @@ use Chill\TaskBundle\Form\SingleTaskType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormFactoryInterface; use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\Security\Core\Role\Role; class SingleTaskController extends Controller { @@ -58,6 +59,7 @@ class SingleTaskController extends Controller $task = (new SingleTask()) ->setPerson($person) ->setAssignee($this->getUser()) + ->setType('task_default') ; $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' @@ -67,13 +69,17 @@ class SingleTaskController extends Controller $form->handleRequest($request); - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($task); - - $this->addFlash('success', "The task is created"); - - $em->flush(); + if ($form->isSubmitted()) { + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($task); + + $this->addFlash('success', "The task is created"); + + $em->flush(); + } else { + $this->addFlash('error', "This form contains errors"); + } } return $this->render('ChillTaskBundle:SingleTask:new.html.twig', array( @@ -90,7 +96,8 @@ class SingleTaskController extends Controller protected function createCreateForm(SingleTask $task) { $form = $this->createForm(SingleTaskType::class, $task, [ - 'center' => $task->getCenter() + 'center' => $task->getCenter(), + 'role' => new Role(TaskVoter::CREATE) ]); $form->add('submit', SubmitType::class); diff --git a/Entity/AbstractTask.php b/Entity/AbstractTask.php index 4b5df1093..0badeb83f 100644 --- a/Entity/AbstractTask.php +++ b/Entity/AbstractTask.php @@ -8,11 +8,17 @@ use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Entity\HasCenterInterface; +use Symfony\Component\Validator\Constraints as Assert; +use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency; /** * AbstractTask * * @ORM\MappedSuperclass() + * @UserCircleConsistency( + * "CHILL_TASK_TASK_SHOW", + * getUserFunction="getAssignee" + * ) */ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { @@ -35,6 +41,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface * @var string * * @ORM\Column(name="title", type="text") + * @Assert\NotBlank() */ private $title = ''; @@ -60,6 +67,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface * @ORM\ManyToOne( * targetEntity="\Chill\PersonBundle\Entity\Person" * ) + * @Assert\NotNull() */ private $person; @@ -69,6 +77,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface * @ORM\ManyToOne( * targetEntity="\Chill\MainBundle\Entity\Scope" * ) + * @Assert\NotNull() */ private $circle; @@ -81,7 +90,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface */ public function setType($type) { - $this->type = $type; + $this->type = (string) $type; return $this; } @@ -129,7 +138,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface */ public function setTitle($title) { - $this->title = $title; + $this->title = (string) $title; return $this; } @@ -153,7 +162,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface */ public function setDescription($description) { - $this->description = $description; + $this->description = (string) $description; return $this; } diff --git a/Entity/SingleTask.php b/Entity/SingleTask.php index 77627906d..3a7861fdb 100644 --- a/Entity/SingleTask.php +++ b/Entity/SingleTask.php @@ -3,6 +3,7 @@ namespace Chill\TaskBundle\Entity; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints as Assert; /** * SingleTask @@ -25,6 +26,11 @@ class SingleTask extends AbstractTask * @var \DateTime * * @ORM\Column(name="start_date", type="date", nullable=true) + * @Assert\Date() + * @Assert\Expression( + * "value === null or value < this.getEndDate()", + * message="The start date must be before the end date" + * ) */ private $startDate; @@ -32,6 +38,7 @@ class SingleTask extends AbstractTask * @var \DateTime * * @ORM\Column(name="end_date", type="date", nullable=true) + * @Assert\Date() */ private $endDate; diff --git a/Form/SingleTaskType.php b/Form/SingleTaskType.php index 93ed932ba..b2c884496 100644 --- a/Form/SingleTaskType.php +++ b/Form/SingleTaskType.php @@ -20,6 +20,7 @@ namespace Chill\TaskBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Form\Type\ChillDateType; +use Chill\MainBundle\Entity\Center; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Chill\MainBundle\Form\Type\UserPickerType; @@ -28,6 +29,8 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Role\Role; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Chill\MainBundle\Form\Type\DateIntervalType; /** * @@ -46,11 +49,11 @@ class SingleTaskType extends AbstractType ->add('assignee', UserPickerType::class, [ 'required' => false, 'center' => $options['center'], - 'role' => new Role(\Chill\PersonBundle\Security\Authorization\PersonVoter::UPDATE) + 'role' => $options['role'] ]) ->add('circle', ScopePickerType::class, [ 'center' => $options['center'], - 'role' => new Role(\Chill\ActivityBundle\Security\Authorization\ActivityVoter::SEE) + 'role' => $options['role'] ]) ->add('startDate', ChillDateType::class, [ 'required' => false @@ -58,7 +61,7 @@ class SingleTaskType extends AbstractType ->add('endDate', ChillDateType::class, [ 'required' => false ]) - ->add('warningInterval', TextType::class, [ + ->add('warningInterval', DateIntervalType::class, [ 'required' => false ]) ; @@ -68,7 +71,9 @@ class SingleTaskType extends AbstractType { $resolver ->setRequired('center') - ->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ]) + ->setAllowedTypes('center', [ Center::class ]) + ->setRequired('role') + ->setAllowedTypes('role', [ Role::class ]) ; } } diff --git a/Resources/translations/validators.fr.yml b/Resources/translations/validators.fr.yml new file mode 100644 index 000000000..c6c444163 --- /dev/null +++ b/Resources/translations/validators.fr.yml @@ -0,0 +1 @@ +The start date must be before the end date: La date de début doit être avant la date de fin \ No newline at end of file From 9dfa39ff073c1c369cc963609b84422333dfd018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 17 Apr 2018 21:57:56 +0200 Subject: [PATCH 005/114] add list of task --- Controller/TaskController.php | 66 +++++++++++++ DependencyInjection/ChillTaskExtension.php | 21 +++- Entity/SingleTask.php | 22 +++++ Repository/SingleTaskRepository.php | 77 ++++++++++++++- Resources/config/services/repositories.yml | 10 ++ Resources/views/Task/index.html.twig | 106 +++++++++++++++++++++ 6 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 Controller/TaskController.php create mode 100644 Resources/config/services/repositories.yml create mode 100644 Resources/views/Task/index.html.twig diff --git a/Controller/TaskController.php b/Controller/TaskController.php new file mode 100644 index 000000000..decb68944 --- /dev/null +++ b/Controller/TaskController.php @@ -0,0 +1,66 @@ +getDoctrine() + ->getManager(); + // collect parameters for filter + $params = []; + + $params['person'] = $person; + + $singleTasks = $this->get('chill_task.single_task_repository') + ->findByParameters($params, $this->getUser()); + + return $this->render('ChillTaskBundle:Task:index.html.twig', [ + 'single_tasks' => $singleTasks, + 'person' => $person + ]); + } + + protected function getPersonParam(Request $request, EntityManagerInterface $em) + { + $person = $em->getRepository(Person::class) + ->find($request->query->getInt('person_id')) + ; + + if (NULL === $person) { + throw $this->createNotFoundException('person not found'); + } + + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are " + . "not allowed to see this person"); + + return $person; + } + + protected function getUserParam(Request $request, EntityManagerInterface $em) + { + $user = $em->getRepository(User::class) + ->find($request->query->getInt('user_id')) + ; + + if (NULL === $user) { + throw $this->createNotFoundException('user not found'); + } + + return $user; + } + +} diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index 2506082da..b9c44e94f 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -6,13 +6,15 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; /** * This is the class that loads and manages your bundle configuration. * * @link http://symfony.com/doc/current/cookbook/bundles/extension.html */ -class ChillTaskExtension extends Extension +class ChillTaskExtension extends Extension implements PrependExtensionInterface { /** * {@inheritdoc} @@ -25,5 +27,22 @@ class ChillTaskExtension extends Extension $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services/controller.yml'); $loader->load('services/security.yml'); + $loader->load('services/repositories.yml'); } + + public function prepend(ContainerBuilder $container) + { + $this->prependAuthorization($container); + } + + public function prependAuthorization(ContainerBuilder $container) + { + $container->prependExtensionConfig('security', array( + 'role_hierarchy' => array( + TaskVoter::UPDATE => [TaskVoter::SHOW], + TaskVoter::CREATE => [TaskVoter::SHOW] + ) + )); + } + } diff --git a/Entity/SingleTask.php b/Entity/SingleTask.php index 3a7861fdb..c56c584ef 100644 --- a/Entity/SingleTask.php +++ b/Entity/SingleTask.php @@ -142,6 +142,28 @@ class SingleTask extends AbstractTask return $this->warningInterval; } + /** + * Get the Warning date, computed from the difference between the + * end date and the warning interval + * + * Return null if warningDate or endDate is null + * + * @return \DateTimeImmutable + */ + public function getWarningDate() + { + if ($this->getWarningInterval() === null) { + return null; + } + + if ($this->getEndDate() === null) { + return null; + } + + return \DateTimeImmutable::createFromMutable($this->getEndDate()) + ->sub($this->getWarningInterval()); + } + function getRecurringTask(): RecurringTask { return $this->recurringTask; diff --git a/Repository/SingleTaskRepository.php b/Repository/SingleTaskRepository.php index 321358766..26583bd8f 100644 --- a/Repository/SingleTaskRepository.php +++ b/Repository/SingleTaskRepository.php @@ -2,12 +2,83 @@ namespace Chill\TaskBundle\Repository; +use Chill\MainBundle\Entity\User; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Role\Role; +use Chill\TaskBundle\Security\Authorization\TaskVoter; + /** * SingleTaskRepository - * - * This class was generated by the Doctrine ORM. Add your own custom - * repository methods below. */ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository { + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + public function setAuthorizationHelper(AuthorizationHelper $authorizationHelper) + { + $this->authorizationHelper = $authorizationHelper; + } + + public function findByParameters($params, User $currentUser) + { + $qb = $this->createQueryBuilder('st'); + + $this->buildQuery($qb, $params, $currentUser); + + return $qb + ->getQuery() + ->getResult() + ; + } + + protected function buildQuery(QueryBuilder $qb, $params, User $currentUser) + { + $this->buildACLQuery($qb, $currentUser); + + if (\array_key_exists('person', $params)) { + $qb->andWhere($qb->expr()->eq('st.person', ':person')); + $qb->setParameter('person', $params['person']); + } + } + + protected function buildACLQuery(QueryBuilder $qb, User $currentUser) + { + if (NULL === $this->authorizationHelper) { + throw new \LogicException("Injecting the authorization helper is " + . "required to run this query. Please use dependency injection " + . "to initialize this repository or use the method " + . "`setAuthorizationHelper`"); + } + + $role = new Role(TaskVoter::SHOW); + $qb->join('st.person', 'p'); + + $centers = $this->authorizationHelper + ->getReachableCenters($currentUser, $role) + ; + + $i = 0; + $where = $qb->expr()->orX(); + + foreach($centers as $center) { + $circles = $this->authorizationHelper + ->getReachableCircles($currentUser, $role, $center); + + $centerWhere = $qb->expr()->andX(); + + $centerWhere->add($qb->expr()->eq('p.center', ':center_'.$i)); + $qb->setParameter('center_'.$i, $center); + $centerWhere->add($qb->expr()->in('st.circle', ':circles_'.$i)); + $qb->setParameter('circles_'.$i, $circles); + $where->add($centerWhere); + $i ++; + } + + $qb->where($where); + } } diff --git a/Resources/config/services/repositories.yml b/Resources/config/services/repositories.yml new file mode 100644 index 000000000..3951cdeed --- /dev/null +++ b/Resources/config/services/repositories.yml @@ -0,0 +1,10 @@ +services: + chill_task.single_task_repository: + class: Chill\TaskBundle\Repository\SingleTaskRepository + factory: ['@doctrine.orm.entity_manager', getRepository] + arguments: + - 'Chill\TaskBundle\Entity\SingleTask' + calls: + - method: setAuthorizationHelper + arguments: + - "@chill.main.security.authorization.helper" diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig new file mode 100644 index 000000000..c5de9e688 --- /dev/null +++ b/Resources/views/Task/index.html.twig @@ -0,0 +1,106 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_single_task_new' %} + +{% block title %}{{ 'Task list'|trans }}{% endblock %} + +{% macro thead() %} + + + {{ 'Title'|trans }} + {{ 'Task type'|trans }} + {{ 'Task status'|trans }} + {{ 'Task start date'|trans }} + {{ 'Task warning date'|trans }} + {{ 'Task end date'|trans }} + + +{% endmacro %} + +{% macro row(task) %} + + {{ task.title }} + {{ task.type }} + todo + {{ task.startDate|localizeddate('medium', 'none') }} + {{ task.warningDate|localizeddate('medium', 'none') }} + {{ task.endDate|localizeddate('medium', 'none') }} + +{% endmacro %} + +{% import _self as helper %} + +{# filter tasks #} + +{% set ended_tasks = [] %} +{% for task in single_tasks if (task.endDate|date("U") > 'now'|date("U")) %} + {% set ended_tasks = ended_tasks|merge([ task ]) %} +{% endfor %} + +{% set warning_tasks = [] %} +{% for task in single_tasks if (task.warningDate|date('U') > 'now'|date("U") and task not in ended_tasks) %} + {% set warning_tasks = warning_tasks|merge([ task ]) %} +{% endfor %} + +{% set rest_tasks = [] %} +{% for task in single_tasks if (task not in ended_tasks and task not in warning_tasks) %} + {% set rest_tasks = rest_tasks|merge([ task ]) %} +{% endfor %} + +{% block personcontent %} +

{{ 'Task list'|trans }}

+ + {% if ended_tasks|length > 0 %} +

{{ 'Task with expired deadline'|trans }}

+ + {{ helper.thead() }} + + {% for task in ended_tasks %} + {{ helper.row(task) }} + {% endfor %} + +
+ {% endif %} + + {% if warning_tasks|length > 0 %} +

{{ 'Task with warning'|trans }}

+ + {{ helper.thead() }} + + {% for task in warning_tasks %} + {{ helper.row(task) }} + {% endfor %} + +
+ {% endif %} + + {% if rest_tasks|length > 0 %} +

{{ 'Task '|trans }}

+ + {{ helper.thead() }} + + {% for task in rest_tasks %} + {{ helper.row(task) }} + {% endfor %} + +
+ {% endif %} + + +{% endblock %} From 196fc2c38f02c9ec60cb6f4666be65e70a4f5def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 20 Apr 2018 21:55:56 +0200 Subject: [PATCH 006/114] improve task list --- Controller/TaskController.php | 61 +++++++-- Repository/SingleTaskRepository.php | 152 +++++++++++++++++++++ Resources/views/Task/index.html.twig | 111 +++++++-------- Tests/Controller/DefaultControllerTest.php | 17 --- 4 files changed, 250 insertions(+), 91 deletions(-) delete mode 100644 Tests/Controller/DefaultControllerTest.php diff --git a/Controller/TaskController.php b/Controller/TaskController.php index decb68944..91094c480 100644 --- a/Controller/TaskController.php +++ b/Controller/TaskController.php @@ -9,29 +9,68 @@ use Chill\PersonBundle\Entity\Person; use Chill\TaskBundle\Entity\SingleTask; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\TaskBundle\Repository\SingleTaskRepository; +use Symfony\Component\HttpFoundation\Response; class TaskController extends Controller { /** - * @Route("/{_locale}/task/task/list/{personId}") + * @Route( + * "/{_locale}/task/task/list/{personId}", + * name="chill_task_task_list" + * ) */ public function listAction(Request $request, Person $personId) { $person = $personId; - $em = $this->getDoctrine() - ->getManager(); - // collect parameters for filter - $params = []; + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + /* @var $paginatorFactory \Chill\MainBundle\Pagination\PaginatorFactory */ + $paginatorFactory = $this->get('chill_main.paginator_factory'); + /* @var $viewParams array The parameters for the view */ + $viewParams['person'] = $person; + // collect parameters for filter $params['person'] = $person; - $singleTasks = $this->get('chill_task.single_task_repository') - ->findByParameters($params, $this->getUser()); + if ($request->query->has('date_status')) { + $statuses = $request->query->get('date_status'); + $singleStatus = count($statuses) === 1; + // check for invalid parameters + $diff = \array_diff( + $statuses, + SingleTaskRepository::DATE_STATUSES) + ; + + if (count($diff) > 0) { + return new Response( + 'date_status not allowed: '. \implode(', ', $diff), + Response::HTTP_BAD_REQUEST + ); + } + } + + foreach(SingleTaskRepository::DATE_STATUSES as $type) { + if($request->query->has('date_status') + && FALSE === \in_array($type, $statuses ?? [])) { + continue; + } + + $params['date_status'] = $type; + $count = $taskRepository + ->countByParameters($params, $this->getUser()) + ; + $paginator = $paginatorFactory->create($count); + $viewParams['single_task_'.$type.'_count'] = $count; + $viewParams['single_task_'.$type.'_paginator'] = $paginator; + $viewParams['single_task_'.$type.'_tasks'] = $taskRepository + ->findByParameters($params, $this->getUser(), + $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, + $singleStatus ? $paginator->getItemsPerPage() : 10) + ; + } - return $this->render('ChillTaskBundle:Task:index.html.twig', [ - 'single_tasks' => $singleTasks, - 'person' => $person - ]); + return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); } protected function getPersonParam(Request $request, EntityManagerInterface $em) diff --git a/Repository/SingleTaskRepository.php b/Repository/SingleTaskRepository.php index 26583bd8f..67a26fcf6 100644 --- a/Repository/SingleTaskRepository.php +++ b/Repository/SingleTaskRepository.php @@ -7,12 +7,26 @@ use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Role\Role; use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Doctrine\DBAL\Types\Type; /** * SingleTaskRepository */ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository { + + const DATE_STATUS_ENDED = 'ended'; + const DATE_STATUS_WARNING = 'warning'; + const DATE_STATUS_CURRENT = 'current'; + const DATE_STATUS_NOT_STARTED = 'not_started'; + + const DATE_STATUSES = [ + self::DATE_STATUS_ENDED, + self::DATE_STATUS_WARNING, + self::DATE_STATUS_CURRENT, + self::DATE_STATUS_NOT_STARTED + ]; + /** * * @var AuthorizationHelper @@ -24,6 +38,42 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository $this->authorizationHelper = $authorizationHelper; } + /** + * Count the tasks for given parameters. + * + * The parameters are describe in @see SingleTaskRepository::filterByParameters. + * + * @see SingleTaskRepository::filterByParameters + * @param array $params + * @param User $currentUser + * @return int + */ + public function countByParameters($params, User $currentUser) + { + $qb = $this->createQueryBuilder('st') + ->select('COUNT(st)'); + + $this->buildQuery($qb, $params, $currentUser); + + return (int) $qb + ->getQuery() + ->getSingleScalarResult() + ; + } + + /** + * Find task for given parameters. + * + * Available parameters: + * + * - `person` : filter by person associated with the task ; + * - `date_status`: type of task. To choose between : + * `ended`, `warning`, `current`, `not_started` + * + * @param type $params + * @param User $currentUser + * @return type + */ public function findByParameters($params, User $currentUser) { $qb = $this->createQueryBuilder('st'); @@ -44,8 +94,110 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository $qb->andWhere($qb->expr()->eq('st.person', ':person')); $qb->setParameter('person', $params['person']); } + + if (\array_key_exists('date_status', $params)) { + $this->addTypeFilter($qb, $params); + } } + protected function addTypeFilter(QueryBuilder $qb, $params) + { + $andWhere = $qb->expr()->andX(); + + switch ($params['date_status']) { + case self::DATE_STATUS_ENDED: + $andWhere->add($this->buildNowIsAfterEndDate($qb)); + break; + + case self::DATE_STATUS_WARNING: + $andWhere + ->add($this->buildNowIsAfterEndDate($qb, true)) + ->add($this->buildNowIsAfterWarningDate($qb)) + ; + break; + + case self::DATE_STATUS_CURRENT: + // st.endDate is NULL or (st.endDate is not null and st.endDate < now)) + $andWhere + ->add($this->buildNowIsAfterEndDate($qb, true)) + ->add($this->buildNowIsAfterWarningDate($qb, true)) + ->add($this->buildNowIsAfterStartDate($qb, false)) + ; + break; + + case self::DATE_STATUS_NOT_STARTED: + $andWhere + ->add($this->buildNowIsAfterEndDate($qb, true)) + ->add($this->buildNowIsAfterWarningDate($qb, true)) + ->add($this->buildNowIsAfterStartDate($qb, true)) + ; + } + $qb->setParameter('now', new \DateTime('today'), Type::DATE); + $qb->andWhere($andWhere); + } + + private function buildNowIsAfterEndDate(QueryBuilder $qb, $negative = false) + { + if ($negative === false) { + return $qb->expr()->andX() + ->add($qb->expr()->isNotNull('st.endDate')) + ->add($qb->expr()->lt('st.endDate', ':now')) + ; + } else { + return $qb->expr()->orX() + ->add( + $qb->expr()->andX() + ->add($qb->expr()->isNotNull('st.endDate')) + ->add($qb->expr()->gt('st.endDate', ':now')) + ) + ->add($qb->expr()->isNull('st.endDate')) + ; + } + } + + private function buildNowIsAfterWarningDate(QueryBuilder $qb, $negative = false) + { + if ($negative === false) { + return $qb->expr()->andX() + ->add($qb->expr()->lt( + $qb->expr()->diff('st.endDate', 'st.warningInterval'), ':now' + ) + ); + } else { + return $qb->expr()->orX() + ->add( + $qb->expr()->andX() + ->add($qb->expr()->isNotNull('st.endDate')) + ->add($qb->expr()->isNotNull('st.warningInterval')) + ->add($qb->expr()->gt( + $qb->expr()->diff('st.endDate', 'st.warningInterval'), + ':now' + ) + ) + ) + ->add($qb->expr()->isNull('st.endDate')) + ->add($qb->expr()->isNull('st.warningInterval')) + ; + } + } + + private function buildNowIsAfterStartDate(QueryBuilder $qb, $negative = false) + { + if ($negative === false) { + return $qb->expr()->orX() + ->add($qb->expr()->lt('st.startDate', ':now')) + ->add($qb->expr()->isNull('st.startDate')) + ; + } else { + return + $qb->expr()->andX() + ->add($qb->expr()->gt('st.startDate', ':now')) + ->add($qb->expr()->isNotNull('st.startDate')) + ; + } + } + + protected function buildACLQuery(QueryBuilder $qb, User $currentUser) { if (NULL === $this->authorizationHelper) { diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig index c5de9e688..e379ea44d 100644 --- a/Resources/views/Task/index.html.twig +++ b/Resources/views/Task/index.html.twig @@ -21,85 +21,70 @@ {% block title %}{{ 'Task list'|trans }}{% endblock %} {% macro thead() %} - - - {{ 'Title'|trans }} - {{ 'Task type'|trans }} - {{ 'Task status'|trans }} - {{ 'Task start date'|trans }} - {{ 'Task warning date'|trans }} - {{ 'Task end date'|trans }} - - + {% endmacro %} {% macro row(task) %} - - {{ task.title }} - {{ task.type }} - todo - {{ task.startDate|localizeddate('medium', 'none') }} - {{ task.warningDate|localizeddate('medium', 'none') }} - {{ task.endDate|localizeddate('medium', 'none') }} - + +{% endmacro %} + +{% macro date_status(title, tasks, count, paginator) %} + {% if tasks|length > 0 %} +

{{ title|trans }}

+ + + + + + + + + + + + + + {% for task in tasks %} + + + + + + + + + {% endfor %} + +
{{ 'Title'|trans }}{{ 'Task type'|trans }}{{ 'Task status'|trans }}{{ 'Task start date'|trans }}{{ 'Task warning date'|trans }}{{ 'Task end date'|trans }}
{{ task.title }}{{ task.type }}todo{% if task.startDate is not null %}{{ task.startDate|localizeddate('medium', 'none') }}{% endif %}{% if task.warningDate is not null %}{{ task.warningDate|localizeddate('medium', 'none') }}{% endif %}{% if task.endDate is not null %}{{ task.endDate|localizeddate('medium', 'none') }}{% endif %}
+ + {% if tasks|length > paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + + {% endif %} {% endmacro %} {% import _self as helper %} {# filter tasks #} -{% set ended_tasks = [] %} -{% for task in single_tasks if (task.endDate|date("U") > 'now'|date("U")) %} - {% set ended_tasks = ended_tasks|merge([ task ]) %} -{% endfor %} - -{% set warning_tasks = [] %} -{% for task in single_tasks if (task.warningDate|date('U') > 'now'|date("U") and task not in ended_tasks) %} - {% set warning_tasks = warning_tasks|merge([ task ]) %} -{% endfor %} - -{% set rest_tasks = [] %} -{% for task in single_tasks if (task not in ended_tasks and task not in warning_tasks) %} - {% set rest_tasks = rest_tasks|merge([ task ]) %} -{% endfor %} - {% block personcontent %}

{{ 'Task list'|trans }}

- {% if ended_tasks|length > 0 %} -

{{ 'Task with expired deadline'|trans }}

- - {{ helper.thead() }} - - {% for task in ended_tasks %} - {{ helper.row(task) }} - {% endfor %} - -
+ {% if single_task_ended_tasks is defined %} + {{ helper.date_status('Tasks with expired deadline', single_task_ended_tasks, single_task_ended_count, single_task_ended_paginator) }} {% endif %} - {% if warning_tasks|length > 0 %} -

{{ 'Task with warning'|trans }}

- - {{ helper.thead() }} - - {% for task in warning_tasks %} - {{ helper.row(task) }} - {% endfor %} - -
+ {% if single_task_warning_tasks is defined %} + {{ helper.date_status('Tasks with warning deadline reached', single_task_warning_tasks, single_task_warning_count, single_task_warning_paginator) }} {% endif %} - {% if rest_tasks|length > 0 %} -

{{ 'Task '|trans }}

- - {{ helper.thead() }} - - {% for task in rest_tasks %} - {{ helper.row(task) }} - {% endfor %} - -
+ {% if single_task_current_tasks is defined %} + {{ helper.date_status('Current tasks', single_task_current_tasks, single_task_current_count, single_task_current_paginator) }} + {% endif %} + + {% if single_task_not_started_tasks is defined %} + {{ helper.date_status('Tasks not started', single_task_not_started_tasks, single_task_not_started_count, single_task_not_started_paginator) }} {% endif %} diff --git a/Tests/Controller/DefaultControllerTest.php b/Tests/Controller/DefaultControllerTest.php deleted file mode 100644 index a370af892..000000000 --- a/Tests/Controller/DefaultControllerTest.php +++ /dev/null @@ -1,17 +0,0 @@ -request('GET', '/'); - - $this->assertContains('Hello World', $client->getResponse()->getContent()); - } -} From a01081a162fa5d75e40ae750b238474db849c852 Mon Sep 17 00:00:00 2001 From: nobohan Date: Mon, 23 Apr 2018 12:36:37 +0200 Subject: [PATCH 007/114] SingleTask template: add CSS classes to twig --- Resources/views/SingleTask/new.html.twig | 26 +++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Resources/views/SingleTask/new.html.twig b/Resources/views/SingleTask/new.html.twig index e656a69fa..32c8226b8 100644 --- a/Resources/views/SingleTask/new.html.twig +++ b/Resources/views/SingleTask/new.html.twig @@ -1,16 +1,16 @@ {# * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} @@ -22,8 +22,24 @@ {% block title %}{{ 'New task'|trans }}{% endblock %} {% block personcontent %} -

{{ 'New task'|trans }}

+

{{ 'New task'|trans }}

-{{ form(form) }} + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.description) }} + {{ form_row(form.assignee) }} + {{ form_row(form.circle) }} + {{ form_row(form.startDate) }} + {{ form_row(form.endDate) }} + {{ form_row(form.warningInterval) }} + +
+ + + +
+ + {{ form_end(form) }} {% endblock %} From 60f6cd95b44d71daf4711cfa732ef9c795f228e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 23 Apr 2018 21:33:07 +0200 Subject: [PATCH 008/114] improve single task list - move to a new controller - refactor to be more adaptive to context - partial autowiring --- Controller/SingleTaskController.php | 122 +++++++++++++++++---- Controller/TaskController.php | 86 --------------- DependencyInjection/ChillTaskExtension.php | 15 ++- Resources/config/services/controller.yml | 7 +- Resources/config/services/repositories.yml | 2 + 5 files changed, 121 insertions(+), 111 deletions(-) diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 1594b3e7b..36494ab17 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -14,28 +14,14 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormFactoryInterface; use Chill\TaskBundle\Security\Authorization\TaskVoter; use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\Entity\User; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\TaskBundle\Repository\SingleTaskRepository; +use Chill\MainBundle\Pagination\PaginatorFactory; class SingleTaskController extends Controller { - /** - * - * @var EntityManager - */ - protected $em; - - /** - * - * @var FormFactoryInterface - */ - protected $formFactory; - - /*public function __construct( - EntityManager $em, - FormFactoryInterface $formFactory) - { - $this->em = $em; - $this->formFactory = $formFactory; - }*/ + /** * @Route("/{_locale}/task/single-task/new") @@ -73,10 +59,15 @@ class SingleTaskController extends Controller if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($task); + + $em->flush(); $this->addFlash('success', "The task is created"); - - $em->flush(); + + return $this->redirectToRoute('chill_task_task_list', [ + 'personId' => $task->getPerson()->getId() + ]); + } else { $this->addFlash('error', "This form contains errors"); } @@ -104,5 +95,94 @@ class SingleTaskController extends Controller return $form; } + + /** + * @Route( + * "/{_locale}/task/task/list/person/{personId}", + * name="chill_task_task_list" + * ) + */ + public function listAction( + Request $request, + Person $personId, + PaginatorFactory $paginatorFactory + ) { + $person = $personId; + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + /* @var $viewParams array The parameters for the view */ + $viewParams['person'] = $person; + + // collect parameters for filter + $params['person'] = $person; + + if ($request->query->has('date_status')) { + $statuses = $request->query->get('date_status'); + $singleStatus = count($statuses) === 1; + // check for invalid parameters + $diff = \array_diff( + $statuses, + SingleTaskRepository::DATE_STATUSES) + ; + + if (count($diff) > 0) { + return new Response( + 'date_status not allowed: '. \implode(', ', $diff), + Response::HTTP_BAD_REQUEST + ); + } + } + + foreach(SingleTaskRepository::DATE_STATUSES as $type) { + if($request->query->has('date_status') + && FALSE === \in_array($type, $statuses ?? [])) { + continue; + } + + $params['date_status'] = $type; + $count = $taskRepository + ->countByParameters($params, $this->getUser()) + ; + $paginator = $paginatorFactory->create($count); + $viewParams['single_task_'.$type.'_count'] = $count; + $viewParams['single_task_'.$type.'_paginator'] = $paginator; + $viewParams['single_task_'.$type.'_tasks'] = $taskRepository + ->findByParameters($params, $this->getUser(), + $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, + $singleStatus ? $paginator->getItemsPerPage() : 10) + ; + } + + return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); + } + + protected function getPersonParam(Request $request, EntityManagerInterface $em) + { + $person = $em->getRepository(Person::class) + ->find($request->query->getInt('person_id')) + ; + + if (NULL === $person) { + throw $this->createNotFoundException('person not found'); + } + + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are " + . "not allowed to see this person"); + + return $person; + } + + protected function getUserParam(Request $request, EntityManagerInterface $em) + { + $user = $em->getRepository(User::class) + ->find($request->query->getInt('user_id')) + ; + + if (NULL === $user) { + throw $this->createNotFoundException('user not found'); + } + + return $user; + } } diff --git a/Controller/TaskController.php b/Controller/TaskController.php index 91094c480..a2d66f01a 100644 --- a/Controller/TaskController.php +++ b/Controller/TaskController.php @@ -14,92 +14,6 @@ use Symfony\Component\HttpFoundation\Response; class TaskController extends Controller { - /** - * @Route( - * "/{_locale}/task/task/list/{personId}", - * name="chill_task_task_list" - * ) - */ - public function listAction(Request $request, Person $personId) - { - $person = $personId; - /* @var $taskRepository SingleTaskRepository */ - $taskRepository = $this->get('chill_task.single_task_repository'); - /* @var $paginatorFactory \Chill\MainBundle\Pagination\PaginatorFactory */ - $paginatorFactory = $this->get('chill_main.paginator_factory'); - /* @var $viewParams array The parameters for the view */ - $viewParams['person'] = $person; - - // collect parameters for filter - $params['person'] = $person; - - if ($request->query->has('date_status')) { - $statuses = $request->query->get('date_status'); - $singleStatus = count($statuses) === 1; - // check for invalid parameters - $diff = \array_diff( - $statuses, - SingleTaskRepository::DATE_STATUSES) - ; - - if (count($diff) > 0) { - return new Response( - 'date_status not allowed: '. \implode(', ', $diff), - Response::HTTP_BAD_REQUEST - ); - } - } - - foreach(SingleTaskRepository::DATE_STATUSES as $type) { - if($request->query->has('date_status') - && FALSE === \in_array($type, $statuses ?? [])) { - continue; - } - - $params['date_status'] = $type; - $count = $taskRepository - ->countByParameters($params, $this->getUser()) - ; - $paginator = $paginatorFactory->create($count); - $viewParams['single_task_'.$type.'_count'] = $count; - $viewParams['single_task_'.$type.'_paginator'] = $paginator; - $viewParams['single_task_'.$type.'_tasks'] = $taskRepository - ->findByParameters($params, $this->getUser(), - $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, - $singleStatus ? $paginator->getItemsPerPage() : 10) - ; - } - - return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); - } - - protected function getPersonParam(Request $request, EntityManagerInterface $em) - { - $person = $em->getRepository(Person::class) - ->find($request->query->getInt('person_id')) - ; - if (NULL === $person) { - throw $this->createNotFoundException('person not found'); - } - - $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are " - . "not allowed to see this person"); - - return $person; - } - - protected function getUserParam(Request $request, EntityManagerInterface $em) - { - $user = $em->getRepository(User::class) - ->find($request->query->getInt('user_id')) - ; - - if (NULL === $user) { - throw $this->createNotFoundException('user not found'); - } - - return $user; - } } diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index b9c44e94f..c1603516b 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -33,9 +33,22 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface public function prepend(ContainerBuilder $container) { $this->prependAuthorization($container); + $this->prependRoute($container); } - public function prependAuthorization(ContainerBuilder $container) + protected function prependRoute(ContainerBuilder $container) + { + //declare routes for person bundle + $container->prependExtensionConfig('chill_main', array( + 'routing' => array( + 'resources' => array( + '@ChillTaskBundle/Resources/config/routing.yml' + ) + ) + )); + } + + protected function prependAuthorization(ContainerBuilder $container) { $container->prependExtensionConfig('security', array( 'role_hierarchy' => array( diff --git a/Resources/config/services/controller.yml b/Resources/config/services/controller.yml index 9edb4e9ad..ffc82a48a 100644 --- a/Resources/config/services/controller.yml +++ b/Resources/config/services/controller.yml @@ -1,4 +1,5 @@ services: - #chill_task.single_task_controller: - # class: Chill\TaskBundle\Controller\SingleTaskController - # autowire: true + Chill\TaskBundle\Controller\: + #autowire: true + resource: '../../../Controller' + tags: ['controller.service_arguments'] diff --git a/Resources/config/services/repositories.yml b/Resources/config/services/repositories.yml index 3951cdeed..062a3fa95 100644 --- a/Resources/config/services/repositories.yml +++ b/Resources/config/services/repositories.yml @@ -8,3 +8,5 @@ services: - method: setAuthorizationHelper arguments: - "@chill.main.security.authorization.helper" + + Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository' From e1871a2d15f064d674f6d270b32da7cee6607527 Mon Sep 17 00:00:00 2001 From: nobohan Date: Tue, 24 Apr 2018 17:53:12 +0200 Subject: [PATCH 009/114] add show, edit, delete actions for tasks + template twig --- Controller/SingleTaskController.php | 372 ++++++++++++++---- .../views/SingleTask/confirm_delete.html.twig | 19 + Resources/views/SingleTask/edit.html.twig | 51 +++ Resources/views/SingleTask/show.html.twig | 75 ++++ Resources/views/Task/index.html.twig | 52 ++- 5 files changed, 473 insertions(+), 96 deletions(-) create mode 100644 Resources/views/SingleTask/confirm_delete.html.twig create mode 100644 Resources/views/SingleTask/edit.html.twig create mode 100644 Resources/views/SingleTask/show.html.twig diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 36494ab17..92b2b86ae 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -22,140 +22,348 @@ use Chill\MainBundle\Pagination\PaginatorFactory; class SingleTaskController extends Controller { - + /** + * @Route( + * "/{_locale}/task/task/list/person/{personId}", + * name="chill_task_task_list" + * ) + */ + public function listAction( + Request $request, + Person $personId + ) { + $person = $personId; + /* @var $paginatorFactory \Chill\MainBundle\Pagination\PaginatorFactory */ + $paginatorFactory = $this->get('chill_main.paginator_factory'); + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + /* @var $viewParams array The parameters for the view */ + $viewParams['person'] = $person; + + // collect parameters for filter + $params['person'] = $person; + + if ($request->query->has('date_status')) { + $statuses = $request->query->get('date_status'); + $singleStatus = count($statuses) === 1; + // check for invalid parameters + $diff = \array_diff( + $statuses, + SingleTaskRepository::DATE_STATUSES) + ; + + if (count($diff) > 0) { + return new Response( + 'date_status not allowed: '. \implode(', ', $diff), + Response::HTTP_BAD_REQUEST + ); + } + } + + foreach(SingleTaskRepository::DATE_STATUSES as $type) { + if($request->query->has('date_status') + && FALSE === \in_array($type, $statuses ?? [])) { + continue; + } + + $params['date_status'] = $type; + $count = $taskRepository + ->countByParameters($params, $this->getUser()) + ; + $paginator = $paginatorFactory->create($count); + // $paginator->setItemsPerPage(2); + + $viewParams['single_task_'.$type.'_count'] = $count; + $viewParams['single_task_'.$type.'_paginator'] = $paginator; + $viewParams['single_task_'.$type.'_tasks'] = $taskRepository + ->findByParameters($params, $this->getUser(), + $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, + $singleStatus ? $paginator->getItemsPerPage() : 10) + ; + } + + return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); + } + + /** - * @Route("/{_locale}/task/single-task/new") + * @Route( + * "/{_locale}/task/single-task/new", + * name="chill_task_single_task_new" + * ) */ public function newAction(Request $request) { $personId = $request->query->getInt('person_id', null); - + if ($personId === null) { return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST); } - + $person = $this->getDoctrine()->getManager() ->getRepository(Person::class) ->find($personId); - + if ($person === null) { throw $this->createNotFoundException("Invalid person id"); } - + $task = (new SingleTask()) ->setPerson($person) ->setAssignee($this->getUser()) ->setType('task_default') ; - - $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' - . 'allowed to create this task'); - - $form = $this->createCreateForm($task); - + + // $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' + // . 'allowed to create this task'); + + $form = $this->setCreateForm($task, new Role(TaskVoter::CREATE)); + $form->handleRequest($request); - + if ($form->isSubmitted()) { if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($task); - + $em->flush(); $this->addFlash('success', "The task is created"); - + return $this->redirectToRoute('chill_task_task_list', [ 'personId' => $task->getPerson()->getId() ]); - + } else { $this->addFlash('error', "This form contains errors"); } } - + return $this->render('ChillTaskBundle:SingleTask:new.html.twig', array( 'form' => $form->createView(), 'task' => $task )); } - + + /** - * + * @Route( + * "/{_locale}/task/single-task/{id}/show", + * name="chill_task_single_task_show" + * ) + */ + public function showAction(Request $request, $id) + { + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + + $task = $taskRepository->find($id); + + if (!is_null($task->getPerson() === !null)) { + $personId = $task->getPerson()->getId(); + + if ($personId === null) { + return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST); + } + + $person = $this->getDoctrine()->getManager() + ->getRepository(Person::class) + ->find($personId); + + if ($person === null) { + throw $this->createNotFoundException("Invalid person id"); + } + } + // $this->denyAccessUnlessGranted(TaskVoter::SEE, $task, 'You are not ' + // . 'allowed to view this task'); + + if (!$task) { + throw $this->createNotFoundException('Unable to find Task entity.'); + } + + return $this->render('ChillTaskBundle:SingleTask:show.html.twig', array( + 'task' => $task, + )); + } + + + /** + * @Route( + * "/{_locale}/task/single-task/{id}/edit", + * name="chill_task_single_task_edit" + * ) + */ + public function editAction(Request $request, $id) + { + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + + $task = $taskRepository->find($id); + + if (!is_null($task->getPerson() === !null)) { + $personId = $task->getPerson()->getId(); + if ($personId === null) { + return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST); + } + + $person = $this->getDoctrine()->getManager() + ->getRepository(Person::class) + ->find($personId); + + if ($person === null) { + throw $this->createNotFoundException("Invalid person id"); + } + } + // $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not ' + // . 'allowed to edit this task'); + + if (!$task) { + throw $this->createNotFoundException('Unable to find Task entity.'); + } + + $form = $this->setCreateForm($task, new Role(TaskVoter::UPDATE)); + + $form->handleRequest($request); + + if ($form->isSubmitted()) { + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($task); + + $em->flush(); + + $this->addFlash('success', "Success : task updated!"); + + return $this->redirectToRoute('chill_task_task_list', [ + 'personId' => $task->getPerson()->getId() + ]); + + } else { + $this->addFlash('error', "This form contains errors"); + } + } + + return $this->render('ChillTaskBundle:SingleTask:edit.html.twig', array( + 'task' => $task, + 'form' => $form->createView() + )); + } + + + /** + * @Route( + * "/{_locale}/task/single-task/{id}/delete", + * name="chill_task_single_task_delete" + * ) + */ + public function deleteAction(Request $request, $id) + { + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + + $task = $taskRepository->find($id); + + if (!is_null($task->getPerson() === !null)) { + + $personId = $task->getPerson()->getId(); + if ($personId === null) { + return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST); + } + + $person = $this->getDoctrine()->getManager() + ->getRepository(Person::class) + ->find($personId); + + if ($person === null) { + throw $this->createNotFoundException("Invalid person id"); + } + + } + + // $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not ' + // . 'allowed to delete this task'); + + if (!$task) { + throw $this->createNotFoundException('Unable to find Task entity.'); + } + + $form = $this->createDeleteForm($id); + + if ($request->getMethod() === Request::METHOD_DELETE) { + $form->handleRequest($request); + + if ($form->isValid()) { + $logger = $this->get('chill.main.logger'); + + $logger->notice("A task has been removed", array( + 'by_user' => $this->getUser()->getUsername(), + 'task_id' => $task->getId(), + 'description' => $task->getDescription(), + 'assignee' => $task->getAssignee(), + 'scope_id' => $task->getScope()->getId(), + //'start_date' => $task->getStartDate()->format('Y-m-d'), + //'end_date' => $task->getEndDate()->format('Y-m-d'), + //'warning_interval' => $task->getWarningInterval()->format('Y-m-d') + )); + + $em = $this->getDoctrine()->getManager(); + $em->remove($task); + $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans("The task has been successfully removed.")); + + return $this->redirect($this->generateUrl( + 'chill_task_task_list', array( + 'personId' => $personId + ))); + } + } + + + return $this->render('ChillTaskBundle:SingleTask:confirm_delete.html.twig', array( + 'task' => $task, + 'delete_form' => $form->createView() + )); + } + + /** + * * @param SingleTask $task + * @param Role $role * @return \Symfony\Component\Form\FormInterface */ - protected function createCreateForm(SingleTask $task) + protected function setCreateForm(SingleTask $task, Role $role) { $form = $this->createForm(SingleTaskType::class, $task, [ 'center' => $task->getCenter(), - 'role' => new Role(TaskVoter::CREATE) + 'role' => $role ]); - + $form->add('submit', SubmitType::class); - + return $form; } - + /** - * @Route( - * "/{_locale}/task/task/list/person/{personId}", - * name="chill_task_task_list" - * ) + * Creates a form to delete a Task entity by id. + * + * @param mixed $id The entity id + * + * @return \Symfony\Component\Form\Form The form */ - public function listAction( - Request $request, - Person $personId, - PaginatorFactory $paginatorFactory - ) { - $person = $personId; - /* @var $taskRepository SingleTaskRepository */ - $taskRepository = $this->get('chill_task.single_task_repository'); - /* @var $viewParams array The parameters for the view */ - $viewParams['person'] = $person; - - // collect parameters for filter - $params['person'] = $person; - - if ($request->query->has('date_status')) { - $statuses = $request->query->get('date_status'); - $singleStatus = count($statuses) === 1; - // check for invalid parameters - $diff = \array_diff( - $statuses, - SingleTaskRepository::DATE_STATUSES) - ; - - if (count($diff) > 0) { - return new Response( - 'date_status not allowed: '. \implode(', ', $diff), - Response::HTTP_BAD_REQUEST - ); - } - } - - foreach(SingleTaskRepository::DATE_STATUSES as $type) { - if($request->query->has('date_status') - && FALSE === \in_array($type, $statuses ?? [])) { - continue; - } - - $params['date_status'] = $type; - $count = $taskRepository - ->countByParameters($params, $this->getUser()) - ; - $paginator = $paginatorFactory->create($count); - $viewParams['single_task_'.$type.'_count'] = $count; - $viewParams['single_task_'.$type.'_paginator'] = $paginator; - $viewParams['single_task_'.$type.'_tasks'] = $taskRepository - ->findByParameters($params, $this->getUser(), - $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, - $singleStatus ? $paginator->getItemsPerPage() : 10) - ; - } - - return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); + private function createDeleteForm($id) + { + return $this->createFormBuilder() + ->setAction($this->generateUrl( + 'chill_task_single_task_delete', + array('id' => $id))) + ->setMethod('DELETE') + ->add('submit', SubmitType::class, array('label' => 'Delete')) + ->getForm() + ; } - + protected function getPersonParam(Request $request, EntityManagerInterface $em) { $person = $em->getRepository(Person::class) @@ -171,7 +379,7 @@ class SingleTaskController extends Controller return $person; } - + protected function getUserParam(Request $request, EntityManagerInterface $em) { $user = $em->getRepository(User::class) diff --git a/Resources/views/SingleTask/confirm_delete.html.twig b/Resources/views/SingleTask/confirm_delete.html.twig new file mode 100644 index 000000000..c0afde28a --- /dev/null +++ b/Resources/views/SingleTask/confirm_delete.html.twig @@ -0,0 +1,19 @@ +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_task_list' %} +{% set person = task.person %} + +{% block title 'Remove task'|trans %} + +{% block personcontent %} + +{{ include('ChillMainBundle:Util:confirmation_template.html.twig', + { + 'title' : 'Remove task'|trans, + 'confirm_question' : 'Are you sure you want to remove the task about "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname } ), + 'cancel_route' : 'chill_task_task_list', + 'cancel_parameters' : { 'personId' : task.person.id, 'id' : task.id }, + 'form' : delete_form + } ) }} + +{% endblock %} diff --git a/Resources/views/SingleTask/edit.html.twig b/Resources/views/SingleTask/edit.html.twig new file mode 100644 index 000000000..ba6515b74 --- /dev/null +++ b/Resources/views/SingleTask/edit.html.twig @@ -0,0 +1,51 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_single_task_edit' %} +{% set person = task.person %} + +{% block title %}{{ 'Edit task'|trans }}{% endblock %} + +{% block personcontent %} +

{{ 'Edit task'|trans }}

+ + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.description) }} + {{ form_row(form.assignee) }} + {{ form_row(form.circle) }} + {{ form_row(form.startDate) }} + {{ form_row(form.endDate) }} + {{ form_row(form.warningInterval) }} + + {{ form_widget(form) }} + + + {{ form_end(form) }} + +{% endblock %} diff --git a/Resources/views/SingleTask/show.html.twig b/Resources/views/SingleTask/show.html.twig new file mode 100644 index 000000000..6e073772a --- /dev/null +++ b/Resources/views/SingleTask/show.html.twig @@ -0,0 +1,75 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_single_task_show' %} +{% set person = task.person %} + +{% block title %}{{ 'Task'|trans }}{% endblock %} + + +{% block personcontent %} +

{{ 'Task'|trans }}

+ +
+
{{ 'Title'|trans }}
+
{{ task.title }}
+ +
{{ 'Description'|trans }}
+
{{ task.description }}
+ +
{{ 'Assignee'|trans }}
+
{{ task.assignee }}
+ +
{{ 'Scope'|trans }}
+
{{ task.scope.name|localize_translatable_string }}
+ +
{{ 'Start date'|trans }}
+
{{ task.startDate|localizeddate('long', 'none') }}
+ +
{{ 'End date'|trans }}
+
{{ task.endDate|localizeddate('long', 'none') }}
+ +
{{ 'Warning interval'|trans }}
+
{{ task.warningInterval|localizeddate('long', 'none') }}
+
+ + + + +{% endblock %} diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig index e379ea44d..a07e40111 100644 --- a/Resources/views/Task/index.html.twig +++ b/Resources/views/Task/index.html.twig @@ -1,16 +1,16 @@ {# * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} @@ -32,12 +32,12 @@ {% if tasks|length > 0 %}

{{ title|trans }}

- +
- - - + + + @@ -52,6 +52,23 @@ + {% endfor %} @@ -60,7 +77,7 @@ {% if tasks|length > paginator.getTotalItems %} {{ chill_pagination(paginator) }} {% endif %} - + {% endif %} {% endmacro %} @@ -70,22 +87,29 @@ {% block personcontent %}

{{ 'Task list'|trans }}

- + {% if single_task_ended_tasks is defined %} {{ helper.date_status('Tasks with expired deadline', single_task_ended_tasks, single_task_ended_count, single_task_ended_paginator) }} {% endif %} - + {% if single_task_warning_tasks is defined %} {{ helper.date_status('Tasks with warning deadline reached', single_task_warning_tasks, single_task_warning_count, single_task_warning_paginator) }} {% endif %} - + {% if single_task_current_tasks is defined %} {{ helper.date_status('Current tasks', single_task_current_tasks, single_task_current_count, single_task_current_paginator) }} {% endif %} - + {% if single_task_not_started_tasks is defined %} {{ helper.date_status('Tasks not started', single_task_not_started_tasks, single_task_not_started_count, single_task_not_started_paginator) }} {% endif %} - - + + + {% endblock %} From adc830142b2aeb45a620f1143d8da9a7b489ea59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Apr 2018 12:03:16 +0200 Subject: [PATCH 010/114] improve autowiring and add workflow to tasks --- Controller/SingleTaskController.php | 108 +- Controller/TaskController.php | 137 +- DependencyInjection/ChillTaskExtension.php | 51 +- Entity/AbstractTask.php | 12 +- Resources/config/services/controller.yml | 6 +- Resources/config/services/repositories.yml | 1 + Resources/config/services/workflow.yml | 2 + Workflow/TaskWorkflowManager.php | 41 + composer.json | 25 +- composer.lock | 4403 ++++++++++++++++++++ 10 files changed, 4669 insertions(+), 117 deletions(-) create mode 100644 Resources/config/services/workflow.yml create mode 100644 Workflow/TaskWorkflowManager.php create mode 100644 composer.lock diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 1594b3e7b..5e4aa6f3b 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -14,28 +14,11 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormFactoryInterface; use Chill\TaskBundle\Security\Authorization\TaskVoter; use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\TaskBundle\Repository\SingleTaskRepository; class SingleTaskController extends Controller { - /** - * - * @var EntityManager - */ - protected $em; - - /** - * - * @var FormFactoryInterface - */ - protected $formFactory; - - /*public function __construct( - EntityManager $em, - FormFactoryInterface $formFactory) - { - $this->em = $em; - $this->formFactory = $formFactory; - }*/ /** * @Route("/{_locale}/task/single-task/new") @@ -104,5 +87,92 @@ class SingleTaskController extends Controller return $form; } + + /** + * @Route( + * "/{_locale}/task/task/list/person/{personId}", + * name="chill_task_task_list_by_person" + * ) + */ + public function listAction( + Request $request, + Person $personId, + PaginatorFactory $paginatorFactory, + SingleTaskRepository $taskRepository + ) { + $person = $personId; + /* @var $viewParams array The parameters for the view */ + $viewParams['person'] = $person; + // collect parameters for filter + $params['person'] = $person; + + if ($request->query->has('date_status')) { + $statuses = $request->query->get('date_status'); + $singleStatus = count($statuses) === 1; + // check for invalid parameters + $diff = \array_diff( + $statuses, + SingleTaskRepository::DATE_STATUSES) + ; + + if (count($diff) > 0) { + return new Response( + 'date_status not allowed: '. \implode(', ', $diff), + Response::HTTP_BAD_REQUEST + ); + } + } + + foreach(SingleTaskRepository::DATE_STATUSES as $type) { + if($request->query->has('date_status') + && FALSE === \in_array($type, $statuses ?? [])) { + continue; + } + + $params['date_status'] = $type; + $count = $taskRepository + ->countByParameters($params, $this->getUser()) + ; + $paginator = $paginatorFactory->create($count); + $viewParams['single_task_'.$type.'_count'] = $count; + $viewParams['single_task_'.$type.'_paginator'] = $paginator; + $viewParams['single_task_'.$type.'_tasks'] = $taskRepository + ->findByParameters($params, $this->getUser(), + $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, + $singleStatus ? $paginator->getItemsPerPage() : 10) + ; + } + + return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); + } + + protected function getPersonParam(Request $request, EntityManagerInterface $em) + { + $person = $em->getRepository(Person::class) + ->find($request->query->getInt('person_id')) + ; + + if (NULL === $person) { + throw $this->createNotFoundException('person not found'); + } + + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are " + . "not allowed to see this person"); + + return $person; + } + + protected function getUserParam(Request $request, EntityManagerInterface $em) + { + $user = $em->getRepository(User::class) + ->find($request->query->getInt('user_id')) + ; + + if (NULL === $user) { + throw $this->createNotFoundException('user not found'); + } + + return $user; + } } diff --git a/Controller/TaskController.php b/Controller/TaskController.php index 91094c480..525abab35 100644 --- a/Controller/TaskController.php +++ b/Controller/TaskController.php @@ -3,103 +3,62 @@ namespace Chill\TaskBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; -use Symfony\Component\HttpFoundation\Request; -use Chill\PersonBundle\Entity\Person; -use Chill\TaskBundle\Entity\SingleTask; -use Chill\MainBundle\Entity\User; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\TaskBundle\Repository\SingleTaskRepository; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\Workflow\Registry; use Symfony\Component\HttpFoundation\Response; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\Request; + class TaskController extends Controller { - /** - * @Route( - * "/{_locale}/task/task/list/{personId}", - * name="chill_task_task_list" - * ) - */ - public function listAction(Request $request, Person $personId) - { - $person = $personId; - /* @var $taskRepository SingleTaskRepository */ - $taskRepository = $this->get('chill_task.single_task_repository'); - /* @var $paginatorFactory \Chill\MainBundle\Pagination\PaginatorFactory */ - $paginatorFactory = $this->get('chill_main.paginator_factory'); - /* @var $viewParams array The parameters for the view */ - $viewParams['person'] = $person; - - // collect parameters for filter - $params['person'] = $person; - - if ($request->query->has('date_status')) { - $statuses = $request->query->get('date_status'); - $singleStatus = count($statuses) === 1; - // check for invalid parameters - $diff = \array_diff( - $statuses, - SingleTaskRepository::DATE_STATUSES) - ; - - if (count($diff) > 0) { - return new Response( - 'date_status not allowed: '. \implode(', ', $diff), - Response::HTTP_BAD_REQUEST + public function applyTransitionAction( + $type, + $taskId, + $transition, + SingleTaskRepository $singleTaskRepository, + Registry $registry, + EntityManagerInterface $em, + Request $request + ) { + switch ($type) { + case 'single-task': + $task = $singleTaskRepository + ->find($taskId) + ; + $defaultReturnPath = $this->generateUrl( + 'chill_task_task_list_by_person', + [ 'personId' => $task->getPerson() ] ); - } - } - - foreach(SingleTaskRepository::DATE_STATUSES as $type) { - if($request->query->has('date_status') - && FALSE === \in_array($type, $statuses ?? [])) { - continue; - } - - $params['date_status'] = $type; - $count = $taskRepository - ->countByParameters($params, $this->getUser()) - ; - $paginator = $paginatorFactory->create($count); - $viewParams['single_task_'.$type.'_count'] = $count; - $viewParams['single_task_'.$type.'_paginator'] = $paginator; - $viewParams['single_task_'.$type.'_tasks'] = $taskRepository - ->findByParameters($params, $this->getUser(), - $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, - $singleStatus ? $paginator->getItemsPerPage() : 10) - ; + break; + default: + return new Response("The type '$type' is not implemented", + Response::HTTP_BAD_REQUEST); } - return $this->render('ChillTaskBundle:Task:index.html.twig', $viewParams); - } - - protected function getPersonParam(Request $request, EntityManagerInterface $em) - { - $person = $em->getRepository(Person::class) - ->find($request->query->getInt('person_id')) - ; - - if (NULL === $person) { - throw $this->createNotFoundException('person not found'); + if (NULL === $task) { + $this->createHttpNotFoundException("task with id '$taskId' and type " + . "'$type' does not exists"); } - - $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are " - . "not allowed to see this person"); - - return $person; - } - - protected function getUserParam(Request $request, EntityManagerInterface $em) - { - $user = $em->getRepository(User::class) - ->find($request->query->getInt('user_id')) - ; - - if (NULL === $user) { - throw $this->createNotFoundException('user not found'); - } - - return $user; - } + // we simply check that the user can see the task. Other ACL checks + // should be performed using `guard` events. + $this->denyAccessUnlessGranted($task, TaskVoter::SHOW); + + $workflow = $registry->get($task); + + if ($workflow->can($task, $transition)) { + $workflow->apply($task, $transition); + + $em->flush(); + + $this->addFlash('success', 'The transition is sucessfully applyed'); + + } else { + $this->addFlash('error', 'The transition could not be applyed'); + } + + return $this->redirect($request->query->get('return_path', $defaultReturnPath)); + } } diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index b9c44e94f..ab3ba2862 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -8,6 +8,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; use Chill\TaskBundle\Security\Authorization\TaskVoter; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Chill\TaskBundle\Workflow\TaskWorkflowManager; /** * This is the class that loads and manages your bundle configuration. @@ -28,11 +29,26 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface $loader->load('services/controller.yml'); $loader->load('services/security.yml'); $loader->load('services/repositories.yml'); + $loader->load('services/workflow.yml'); } public function prepend(ContainerBuilder $container) { $this->prependAuthorization($container); + $this->prependRoute($container); + $this->prependWorkflows($container); + } + + protected function prependRoute(ContainerBuilder $container) + { + //declare routes for task bundle + $container->prependExtensionConfig('chill_main', array( + 'routing' => array( + 'resources' => array( + '@ChillTaskBundle/Resources/config/routing.yml' + ) + ) + )); } public function prependAuthorization(ContainerBuilder $container) @@ -44,5 +60,38 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface ) )); } - + + protected function prependWorkflows(ContainerBuilder $container) + { + $container->prependExtensionConfig('framework', [ + 'workflows' => [ + 'task_default' => [ + 'marking_store' => [ + 'type' => 'multiple_state', + 'arguments' => [ + 'currentStates' + ], + ], + 'type' => 'workflow', + 'support_strategy' => TaskWorkflowManager::class, + 'places' => [ 'new', 'in_progress', 'closed', 'canceled'], + 'initial_place' => 'new', + 'transitions' => [ + 'start' => [ + 'from' => 'new', + 'to' => 'in_progress' + ], + 'close' => [ + 'from' => 'in_progress', + 'to' => 'closed' + ], + 'cancel' => [ + 'from' => ['new', 'in_progress'], + 'to' => 'canceled' + ] + ] + ] + ] + ]); + } } diff --git a/Entity/AbstractTask.php b/Entity/AbstractTask.php index 0badeb83f..d9d59c7b6 100644 --- a/Entity/AbstractTask.php +++ b/Entity/AbstractTask.php @@ -107,26 +107,30 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface /** * Set currentStates + * + * The current states are sorted in a single array, non associative. * - * @param json $currentStates + * @param $currentStates * * @return AbstractTask */ public function setCurrentStates($currentStates) { - $this->currentStates = $currentStates; + $this->currentStates = \array_keys($currentStates); return $this; } /** * Get currentStates + * + * The states are returned as required by marking store format. * - * @return json + * @return array */ public function getCurrentStates() { - return $this->currentStates; + return \array_fill_keys($this->currentStates, 1); } /** diff --git a/Resources/config/services/controller.yml b/Resources/config/services/controller.yml index 9edb4e9ad..d396c5324 100644 --- a/Resources/config/services/controller.yml +++ b/Resources/config/services/controller.yml @@ -1,4 +1,4 @@ services: - #chill_task.single_task_controller: - # class: Chill\TaskBundle\Controller\SingleTaskController - # autowire: true + Chill\TaskBundle\Controller\: + resource: ../../../Controller + tags: ['controller.service_arguments'] diff --git a/Resources/config/services/repositories.yml b/Resources/config/services/repositories.yml index 3951cdeed..3cc867d96 100644 --- a/Resources/config/services/repositories.yml +++ b/Resources/config/services/repositories.yml @@ -8,3 +8,4 @@ services: - method: setAuthorizationHelper arguments: - "@chill.main.security.authorization.helper" + Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository' diff --git a/Resources/config/services/workflow.yml b/Resources/config/services/workflow.yml new file mode 100644 index 000000000..acf583d43 --- /dev/null +++ b/Resources/config/services/workflow.yml @@ -0,0 +1,2 @@ +services: + Chill\TaskBundle\Workflow\TaskWorkflowManager: ~ \ No newline at end of file diff --git a/Workflow/TaskWorkflowManager.php b/Workflow/TaskWorkflowManager.php new file mode 100644 index 000000000..e942e303b --- /dev/null +++ b/Workflow/TaskWorkflowManager.php @@ -0,0 +1,41 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Workflow; + +use Chill\TaskBundle\Entity\AbstractTask; +use Symfony\Component\Workflow\SupportStrategy\SupportStrategyInterface; +use Symfony\Component\Workflow\Workflow; + +/** + * + * + * @author Julien Fastré + */ +class TaskWorkflowManager implements SupportStrategyInterface +{ + public function supports(Workflow $workflow, $subject): bool + { + if (!$subject instanceof AbstractTask) { + return false; + } + + dump($workflow->getName()); + + return $workflow->getName() === 'task_default'; + } +} diff --git a/composer.json b/composer.json index 7155669c2..156d13d4b 100644 --- a/composer.json +++ b/composer.json @@ -7,15 +7,38 @@ "autoload": { "psr-4": { "Chill\\TaskBundle\\" : "" } }, + "autoload-dev": { + "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] + }, "require": { "php": "~7.2", "chill-project/person": "dev-upgrade-sf3@dev" }, + "require-dev": { + "fzaninotto/faker": "^1.7", + "phpunit/phpunit": "^7.1" + }, + "minimum-stability": "dev", + "prefer-stable": true, "license": "AGPL-3.0-or-later", "authors": [ { "name": "Champs-Libres", "email": "info@champs-libres.coop" } - ] + ], + "scripts": { + "post-install-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ], + "post-update-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ] + }, + "extra": { + "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", + "symfony-app-dir": "Resources/test/Fixtures/App/" + } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..2ad9ca212 --- /dev/null +++ b/composer.lock @@ -0,0 +1,4403 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c40de51f6aab3bbe16dc8bd7becbfbe8", + "packages": [ + { + "name": "champs-libres/composer-bundle-migration", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/Champs-Libres/ComposerBundleMigration.git", + "reference": "556eecdf6de3a0bd7f7e7807dca00be5bb2babf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Champs-Libres/ComposerBundleMigration/zipball/556eecdf6de3a0bd7f7e7807dca00be5bb2babf0", + "reference": "556eecdf6de3a0bd7f7e7807dca00be5bb2babf0", + "shasum": "" + }, + "require": { + "php": ">5.4" + }, + "suggest": { + "doctrine/doctrine-migrations-bundle": "doctrine migrations for symfony app", + "doctrine/migrations": "the original doctrine migration bundle" + }, + "type": "library", + "autoload": { + "psr-4": { + "ComposerBundleMigration\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0" + ], + "authors": [ + { + "name": "Julien Fastré", + "email": "julien.fastre@champs-libres.coop" + }, + { + "name": "Champs-Libres", + "email": "info@champs-libres.coop" + } + ], + "description": "Move DoctrineMigrations files from installed bundle to root package.", + "keywords": [ + "doctrine", + "doctrine migrations", + "symfony" + ], + "time": "2015-02-23T08:33:12+00:00" + }, + { + "name": "chill-project/custom-fields", + "version": "dev-upgrade-sf3", + "source": { + "type": "git", + "url": "/var/www/app/vendor/chill-project/custom-fields", + "reference": "7950ccfddb230cad5191594785710505ec8ad49e" + }, + "require": { + "chill-project/main": "dev-upgrade-sf3@dev", + "chill-project/person": "dev-upgrade-sf3@dev" + }, + "require-dev": { + "doctrine/doctrine-fixtures-bundle": "~2.2", + "fzaninotto/faker": "~1", + "phpunit/phpunit": "^6.2" + }, + "type": "symfony-bundle", + "extra": { + "symfony-app-dir": "Tests/Fixtures/App/app", + "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations" + }, + "autoload": { + "psr-4": { + "Chill\\CustomFieldsBundle\\": "" + } + }, + "autoload-dev": { + "classmap": [ + "Resources/test/Fixtures/App/app/AppKernel.php" + ] + }, + "scripts": { + "post-install-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ], + "post-update-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ] + }, + "license": [ + "AGPL-3.0" + ], + "authors": [ + { + "name": "Champs-Libres", + "email": "info@champs-libres.coop", + "homepage": "http://www.champs-libres.coop" + } + ], + "description": "This bundle allow to add custom fields on entities.", + "homepage": "https://github.com/Chill-project/CustomFields", + "keywords": [ + "chill", + "social work" + ], + "time": "2018-04-24T12:31:41+00:00" + }, + { + "name": "chill-project/main", + "version": "dev-upgrade-sf3", + "source": { + "type": "git", + "url": "/var/www/app/vendor/chill-project/main", + "reference": "19e30d90c5bc5d3f3058b6f1914df8f683edfbb6" + }, + "require": { + "champs-libres/composer-bundle-migration": "~1.0", + "doctrine/common": "~2.8", + "doctrine/dbal": "~2.7", + "doctrine/doctrine-bundle": "~1.9", + "doctrine/doctrine-migrations-bundle": "~1.3", + "doctrine/migrations": "~1.0", + "doctrine/orm": "~2.6", + "php": "~7.2", + "phpoffice/phpspreadsheet": "~1.2", + "sensio/distribution-bundle": "^5.0", + "symfony/assetic-bundle": "~2.8", + "symfony/monolog-bundle": "~3.2", + "symfony/symfony": "~3.4", + "twig/extensions": "~1.5" + }, + "require-dev": { + "doctrine/doctrine-fixtures-bundle": "~3.0", + "phpunit/phpunit": "~5.6", + "symfony/dom-crawler": "~3.4", + "symfony/phpunit-bridge": "~3.4" + }, + "type": "symfony-bundle", + "extra": { + "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", + "symfony-app-dir": "Tests/Fixtures/App/" + }, + "autoload": { + "psr-4": { + "Chill\\MainBundle\\": "" + } + }, + "autoload-dev": { + "classmap": [ + "Resources/test/Fixtures/App/app/AppKernel.php" + ] + }, + "scripts": { + "post-install-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ], + "post-update-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ] + }, + "license": [ + "AGPL-3.0" + ], + "authors": [ + { + "name": "Champs-Libres", + "email": "info@champs-libres.coop", + "homepage": "http://www.champs-libres.coop" + } + ], + "description": "The main bundle for the Chill App", + "homepage": "http://chill.social", + "keywords": [ + "chill", + "social work", + "software for social service" + ], + "support": { + "email": "dev@lists.chill.social", + "issues": "https://git.framasoft.org/Chill-project/Chill-Main/issues", + "sources": "https://git.framasoft.org/Chill-project/Chill-Main", + "docs": "http://docs.chill.social" + }, + "time": "2018-04-24T12:21:11+00:00" + }, + { + "name": "chill-project/person", + "version": "dev-upgrade-sf3", + "source": { + "type": "git", + "url": "/var/www/app/vendor/chill-project/person", + "reference": "c847be8534ddd7522ea5e231da658097e6e573b1" + }, + "require": { + "chill-project/custom-fields": "dev-upgrade-sf3@dev", + "chill-project/main": "dev-upgrade-sf3" + }, + "require-dev": { + "fzaninotto/faker": "~1", + "phpunit/phpunit": "~5.6", + "symfony/phpunit-bridge": "~3" + }, + "type": "symfony-bundle", + "extra": { + "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", + "symfony-app-dir": "Tests/Fixtures/App/" + }, + "autoload": { + "psr-4": { + "Chill\\PersonBundle\\": "" + } + }, + "autoload-dev": { + "classmap": [ + "Resources/test/Fixtures/App/app/AppKernel.php" + ] + }, + "scripts": { + "post-install-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ], + "post-update-cmd": [ + "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", + "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" + ] + }, + "license": [ + "AGPL-3.0" + ], + "authors": [ + { + "name": "Champs-Libres", + "email": "info@champs-libres.coop", + "homepage": "http://www.champs-libres.coop" + } + ], + "description": "A bundle to deal with persons", + "homepage": "https://github.com/Chill-project/Person", + "keywords": [ + "chill", + "persons", + "social work" + ], + "time": "2018-04-24T12:33:27+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", + "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2018-03-29T19:57:20+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-12-06T07:11:42+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "shasum": "" + }, + "require": { + "php": "~7.1" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2017-08-25T07:02:50+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-07-22T10:37:32+00:00" + }, + { + "name": "doctrine/common", + "version": "v2.8.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", + "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~7.1" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2017-08-31T08:43:38+00:00" + }, + { + "name": "doctrine/dbal", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "11037b4352c008373561dc6fc836834eed80c3b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/11037b4352c008373561dc6fc836834eed80c3b5", + "reference": "11037b4352c008373561dc6fc836834eed80c3b5", + "shasum": "" + }, + "require": { + "doctrine/common": "^2.7.1", + "ext-pdo": "*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^4.0", + "phpunit/phpunit": "^7.0", + "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5", + "symfony/console": "^2.0.5||^3.0", + "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2018-04-07T18:44:18+00:00" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "703fad32e4c8cbe609caf45a71a1d4266c830f0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/703fad32e4c8cbe609caf45a71a1d4266c830f0f", + "reference": "703fad32e4c8cbe609caf45a71a1d4266c830f0f", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^2.5.12", + "doctrine/doctrine-cache-bundle": "~1.2", + "jdorn/sql-formatter": "^1.2.16", + "php": "^5.5.9|^7.0", + "symfony/console": "~2.7|~3.0|~4.0", + "symfony/dependency-injection": "~2.7|~3.0|~4.0", + "symfony/doctrine-bridge": "~2.7|~3.0|~4.0", + "symfony/framework-bundle": "^2.7.22|~3.0|~4.0" + }, + "conflict": { + "symfony/http-foundation": "<2.6" + }, + "require-dev": { + "doctrine/orm": "~2.4", + "phpunit/phpunit": "^4.8.36|^5.7|^6.4", + "satooshi/php-coveralls": "^1.0", + "symfony/phpunit-bridge": "~2.7|~3.0|~4.0", + "symfony/property-info": "~2.8|~3.0|~4.0", + "symfony/validator": "~2.7|~3.0|~4.0", + "symfony/web-profiler-bundle": "~2.7|~3.0|~4.0", + "symfony/yaml": "~2.7|~3.0|~4.0", + "twig/twig": "~1.26|~2.0" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "orm", + "persistence" + ], + "time": "2018-04-19T14:07:39+00:00" + }, + { + "name": "doctrine/doctrine-cache-bundle", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineCacheBundle.git", + "reference": "4c8e363f96427924e7e519c5b5119b4f54512697" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/4c8e363f96427924e7e519c5b5119b4f54512697", + "reference": "4c8e363f96427924e7e519c5b5119b4f54512697", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.4.2", + "doctrine/inflector": "~1.0", + "php": ">=5.3.2", + "symfony/doctrine-bridge": "~2.7|~3.3|~4.0" + }, + "require-dev": { + "instaclick/coding-standard": "~1.1", + "instaclick/object-calisthenics-sniffs": "dev-master", + "instaclick/symfony2-coding-standard": "dev-remaster", + "phpunit/phpunit": "~4|~5", + "predis/predis": "~0.8", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "~1.5", + "symfony/console": "~2.7|~3.3|~4.0", + "symfony/finder": "~2.7|~3.3|~4.0", + "symfony/framework-bundle": "~2.7|~3.3|~4.0", + "symfony/phpunit-bridge": "~2.7|~3.3|~4.0", + "symfony/security-acl": "~2.7|~3.3", + "symfony/validator": "~2.7|~3.3|~4.0", + "symfony/yaml": "~2.7|~3.3|~4.0" + }, + "suggest": { + "symfony/security-acl": "For using this bundle to cache ACLs" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineCacheBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Fabio B. Silva", + "email": "fabio.bat.silva@gmail.com" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@hotmail.com" + }, + { + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Bundle for Doctrine Cache", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2018-03-27T09:22:12+00:00" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "a9e506369f931351a2a6dd2aef588a822802b1b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/a9e506369f931351a2a6dd2aef588a822802b1b7", + "reference": "a9e506369f931351a2a6dd2aef588a822802b1b7", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "~1.0", + "doctrine/migrations": "^1.1", + "php": ">=5.4.0", + "symfony/framework-bundle": "~2.7|~3.3|~4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "time": "2017-11-01T09:13:26+00:00" + }, + { + "name": "doctrine/inflector", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2018-01-09T20:05:19+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "doctrine/migrations", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "e3faf7c96b8a6084045dedcaf51f74c7834644d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/e3faf7c96b8a6084045dedcaf51f74c7834644d4", + "reference": "e3faf7c96b8a6084045dedcaf51f74c7834644d4", + "shasum": "" + }, + "require": { + "doctrine/dbal": "~2.6", + "ocramius/proxy-manager": "^1.0|^2.0", + "php": "^7.1", + "symfony/console": "~3.3|^4.0", + "symfony/yaml": "~3.3|^4.0" + }, + "require-dev": { + "doctrine/coding-standard": "^1.0", + "doctrine/orm": "~2.5", + "jdorn/sql-formatter": "~1.1", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "~6.2", + "squizlabs/php_codesniffer": "^3.0" + }, + "suggest": { + "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "v1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\Migrations\\": "lib/Doctrine/DBAL/Migrations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "Database Schema migrations using Doctrine DBAL", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "migrations" + ], + "time": "2017-11-24T14:13:17+00:00" + }, + { + "name": "doctrine/orm", + "version": "v2.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/doctrine2.git", + "reference": "87ee409783a4a322b5597ebaae558661404055a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/87ee409783a4a322b5597ebaae558661404055a7", + "reference": "87ee409783a4a322b5597ebaae558661404055a7", + "shasum": "" + }, + "require": { + "doctrine/annotations": "~1.5", + "doctrine/cache": "~1.6", + "doctrine/collections": "^1.4", + "doctrine/common": "^2.7.1", + "doctrine/dbal": "^2.6", + "doctrine/instantiator": "~1.1", + "ext-pdo": "*", + "php": "^7.1", + "symfony/console": "~3.0|~4.0" + }, + "require-dev": { + "doctrine/coding-standard": "^1.0", + "phpunit/phpunit": "^6.5", + "squizlabs/php_codesniffer": "^3.2", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "orm" + ], + "time": "2018-02-27T07:30:56+00:00" + }, + { + "name": "fig/link-util", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/link-util.git", + "reference": "1a07821801a148be4add11ab0603e4af55a72fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/link-util/zipball/1a07821801a148be4add11ab0603e4af55a72fac", + "reference": "1a07821801a148be4add11ab0603e4af55a72fac", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/link": "~1.0@dev" + }, + "require-dev": { + "phpunit/phpunit": "^5.1", + "squizlabs/php_codesniffer": "^2.3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Link\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common utility implementations for HTTP links", + "keywords": [ + "http", + "http-link", + "link", + "psr", + "psr-13", + "rest" + ], + "time": "2016-10-17T18:31:11+00:00" + }, + { + "name": "jdorn/sql-formatter", + "version": "v1.2.17", + "source": { + "type": "git", + "url": "https://github.com/jdorn/sql-formatter.git", + "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jdorn/sql-formatter/zipball/64990d96e0959dff8e059dfcdc1af130728d92bc", + "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "lib" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "http://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/jdorn/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "time": "2014-01-12T16:20:24+00:00" + }, + { + "name": "kriswallsmith/assetic", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/kriswallsmith/assetic.git", + "reference": "e911c437dbdf006a8f62c2f59b15b2d69a5e0aa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/e911c437dbdf006a8f62c2f59b15b2d69a5e0aa1", + "reference": "e911c437dbdf006a8f62c2f59b15b2d69a5e0aa1", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/process": "~2.1|~3.0" + }, + "conflict": { + "twig/twig": "<1.27" + }, + "require-dev": { + "leafo/lessphp": "^0.3.7", + "leafo/scssphp": "~0.1", + "meenie/javascript-packer": "^1.1", + "mrclay/minify": "<2.3", + "natxet/cssmin": "3.0.4", + "patchwork/jsqueeze": "~1.0|~2.0", + "phpunit/phpunit": "~4.8 || ^5.6", + "psr/log": "~1.0", + "ptachoire/cssembed": "~1.0", + "symfony/phpunit-bridge": "~2.7|~3.0", + "twig/twig": "~1.23|~2.0", + "yfix/packager": "dev-master" + }, + "suggest": { + "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler", + "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler", + "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin", + "patchwork/jsqueeze": "Assetic provides the integration with the JSqueeze JavaScript compressor", + "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris", + "twig/twig": "Assetic provides the integration with the Twig templating engine" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-0": { + "Assetic": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kris Wallsmith", + "email": "kris.wallsmith@gmail.com", + "homepage": "http://kriswallsmith.net/" + } + ], + "description": "Asset Management for PHP", + "homepage": "https://github.com/kriswallsmith/assetic", + "keywords": [ + "assets", + "compression", + "minification" + ], + "time": "2016-11-11T18:43:20+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.23.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2017-06-19T01:22:40+00:00" + }, + { + "name": "ocramius/package-versions", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", + "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0", + "php": "^7.1.0" + }, + "require-dev": { + "composer/composer": "^1.6.3", + "ext-zip": "*", + "infection/infection": "^0.7.1", + "phpunit/phpunit": "^7.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "time": "2018-02-05T13:05:30+00:00" + }, + { + "name": "ocramius/proxy-manager", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "81d53b2878f1d1c40ad27270e64b51798485dfc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/81d53b2878f1d1c40ad27270e64b51798485dfc5", + "reference": "81d53b2878f1d1c40ad27270e64b51798485dfc5", + "shasum": "" + }, + "require": { + "ocramius/package-versions": "^1.1.3", + "php": "^7.2.0", + "zendframework/zend-code": "^3.3.0" + }, + "require-dev": { + "couscous/couscous": "^1.6.1", + "ext-phar": "*", + "humbug/humbug": "1.0.0-RC.0@RC", + "nikic/php-parser": "^3.1.1", + "padraic/phpunit-accelerator": "dev-master@DEV", + "phpbench/phpbench": "^0.12.2", + "phpstan/phpstan": "dev-master#856eb10a81c1d27c701a83f167dc870fd8f4236a as 0.9.999", + "phpstan/phpstan-phpunit": "dev-master#5629c0a1f4a9c417cb1077cf6693ad9753895761", + "phpunit/phpunit": "^6.4.3", + "squizlabs/php_codesniffer": "^2.9.1" + }, + "suggest": { + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.io/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "time": "2017-11-16T23:22:31+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.12", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2018-04-04T21:24:14+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "36acc372875c4d894dc093825ce4f62209db5a76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/36acc372875c4d894dc093825ce4f62209db5a76", + "reference": "36acc372875c4d894dc093825ce4f62209db5a76", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "php": "^5.6|^7.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dompdf/dompdf": "^0.8.0", + "friendsofphp/php-cs-fixer": "@stable", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^7.0.0", + "phpunit/phpunit": "^5.7", + "squizlabs/php_codesniffer": "^2.7", + "tecnickcom/tcpdf": "^6.2" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnick.com/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "http://blog.maartenballiauw.be" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Franck Lefevre", + "homepage": "http://rootslabs.net" + }, + { + "name": "Mark Baker", + "homepage": "http://markbakeruk.net" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "time": "2018-04-10T03:53:16+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/link", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/link.git", + "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562", + "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Link\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for HTTP links", + "keywords": [ + "http", + "http-link", + "link", + "psr", + "psr-13", + "rest" + ], + "time": "2016-10-28T16:06:13+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "sensio/distribution-bundle", + "version": "v5.0.21", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/SensioDistributionBundle.git", + "reference": "eb6266b3b472e4002538610b28a0a04bcf94891a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/SensioDistributionBundle/zipball/eb6266b3b472e4002538610b28a0a04bcf94891a", + "reference": "eb6266b3b472e4002538610b28a0a04bcf94891a", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "sensiolabs/security-checker": "~3.0|~4.0", + "symfony/class-loader": "~2.3|~3.0", + "symfony/config": "~2.3|~3.0", + "symfony/dependency-injection": "~2.3|~3.0", + "symfony/filesystem": "~2.3|~3.0", + "symfony/http-kernel": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sensio\\Bundle\\DistributionBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Base bundle for Symfony Distributions", + "keywords": [ + "configuration", + "distribution" + ], + "time": "2017-08-25T16:55:44+00:00" + }, + { + "name": "sensiolabs/security-checker", + "version": "v4.1.8", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/security-checker.git", + "reference": "dc270d5fec418cc6ac983671dba5d80ffaffb142" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/dc270d5fec418cc6ac983671dba5d80ffaffb142", + "reference": "dc270d5fec418cc6ac983671dba5d80ffaffb142", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "symfony/console": "~2.7|~3.0|~4.0" + }, + "bin": [ + "security-checker" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-0": { + "SensioLabs\\Security": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "A security checker for your composer.lock", + "time": "2018-02-28T22:10:01+00:00" + }, + { + "name": "symfony/assetic-bundle", + "version": "v2.8.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/assetic-bundle.git", + "reference": "2e0a23a4874838e26de6f025e02fc63328921a4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/assetic-bundle/zipball/2e0a23a4874838e26de6f025e02fc63328921a4c", + "reference": "2e0a23a4874838e26de6f025e02fc63328921a4c", + "shasum": "" + }, + "require": { + "kriswallsmith/assetic": "~1.4", + "php": ">=5.3.0", + "symfony/console": "~2.3|~3.0", + "symfony/dependency-injection": "~2.3|~3.0", + "symfony/framework-bundle": "~2.3|~3.0", + "symfony/yaml": "~2.3|~3.0" + }, + "conflict": { + "kriswallsmith/spork": "<=0.2", + "twig/twig": "<1.27" + }, + "require-dev": { + "kriswallsmith/spork": "~0.3", + "patchwork/jsqueeze": "~1.0", + "symfony/class-loader": "~2.3|~3.0", + "symfony/css-selector": "~2.3|~3.0", + "symfony/dom-crawler": "~2.3|~3.0", + "symfony/phpunit-bridge": "~2.7|~3.0", + "symfony/twig-bundle": "~2.3|~3.0" + }, + "suggest": { + "kriswallsmith/spork": "to be able to dump assets in parallel", + "symfony/twig-bundle": "to use the Twig integration" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\AsseticBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kris Wallsmith", + "email": "kris.wallsmith@gmail.com", + "homepage": "http://kriswallsmith.net/" + } + ], + "description": "Integrates Assetic into Symfony2", + "homepage": "https://github.com/symfony/AsseticBundle", + "keywords": [ + "assets", + "compression", + "minification" + ], + "time": "2017-07-14T07:26:46+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "8781649349fe418d51d194f8c9d212c0b97c40dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/8781649349fe418d51d194f8c9d212c0b97c40dd", + "reference": "8781649349fe418d51d194f8c9d212c0b97c40dd", + "shasum": "" + }, + "require": { + "monolog/monolog": "~1.22", + "php": ">=5.3.2", + "symfony/config": "~2.7|~3.0|~4.0", + "symfony/dependency-injection": "~2.7|~3.0|~4.0", + "symfony/http-kernel": "~2.7|~3.0|~4.0", + "symfony/monolog-bridge": "~2.7|~3.0|~4.0" + }, + "require-dev": { + "symfony/console": "~2.3|~3.0|~4.0", + "symfony/phpunit-bridge": "^3.3|^4.0", + "symfony/yaml": "~2.3|~3.0|~4.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony MonologBundle", + "homepage": "http://symfony.com", + "keywords": [ + "log", + "logging" + ], + "time": "2018-03-05T14:51:36+00:00" + }, + { + "name": "symfony/polyfill-apcu", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-apcu.git", + "reference": "e8ae2136ddb53dea314df56fcd88e318ab936c00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/e8ae2136ddb53dea314df56fcd88e318ab936c00", + "reference": "e8ae2136ddb53dea314df56fcd88e318ab936c00", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Apcu\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "apcu", + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "254919c03761d46c29291616576ed003f10e91c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/254919c03761d46c29291616576ed003f10e91c1", + "reference": "254919c03761d46c29291616576ed003f10e91c1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0|~4.0" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "ebc999ce5f14204c5150b9bd15f8f04e621409d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ebc999ce5f14204c5150b9bd15f8f04e621409d8", + "reference": "ebc999ce5f14204c5150b9bd15f8f04e621409d8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-util": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php56\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "e17c808ec4228026d4f5a8832afa19be85979563" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/e17c808ec4228026d4f5a8832afa19be85979563", + "reference": "e17c808ec4228026d4f5a8832afa19be85979563", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Util\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony utilities for portability of PHP codes", + "homepage": "https://symfony.com", + "keywords": [ + "compat", + "compatibility", + "polyfill", + "shim" + ], + "time": "2018-01-31T18:08:44+00:00" + }, + { + "name": "symfony/symfony", + "version": "v3.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/symfony.git", + "reference": "5304a36c5efbb01af7efe2bb5b1953dbaeebc293" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/symfony/zipball/5304a36c5efbb01af7efe2bb5b1953dbaeebc293", + "reference": "5304a36c5efbb01af7efe2bb5b1953dbaeebc293", + "shasum": "" + }, + "require": { + "doctrine/common": "~2.4", + "ext-xml": "*", + "fig/link-util": "^1.0", + "php": "^5.5.9|>=7.0.8", + "psr/cache": "~1.0", + "psr/container": "^1.0", + "psr/link": "^1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "symfony/polyfill-apcu": "~1.1", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php56": "~1.0", + "symfony/polyfill-php70": "~1.6", + "twig/twig": "^1.35|^2.4.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", + "phpdocumentor/type-resolver": "<0.2.1", + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/container-implementation": "1.0", + "psr/log-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" + }, + "replace": { + "symfony/asset": "self.version", + "symfony/browser-kit": "self.version", + "symfony/cache": "self.version", + "symfony/class-loader": "self.version", + "symfony/config": "self.version", + "symfony/console": "self.version", + "symfony/css-selector": "self.version", + "symfony/debug": "self.version", + "symfony/debug-bundle": "self.version", + "symfony/dependency-injection": "self.version", + "symfony/doctrine-bridge": "self.version", + "symfony/dom-crawler": "self.version", + "symfony/dotenv": "self.version", + "symfony/event-dispatcher": "self.version", + "symfony/expression-language": "self.version", + "symfony/filesystem": "self.version", + "symfony/finder": "self.version", + "symfony/form": "self.version", + "symfony/framework-bundle": "self.version", + "symfony/http-foundation": "self.version", + "symfony/http-kernel": "self.version", + "symfony/inflector": "self.version", + "symfony/intl": "self.version", + "symfony/ldap": "self.version", + "symfony/lock": "self.version", + "symfony/monolog-bridge": "self.version", + "symfony/options-resolver": "self.version", + "symfony/process": "self.version", + "symfony/property-access": "self.version", + "symfony/property-info": "self.version", + "symfony/proxy-manager-bridge": "self.version", + "symfony/routing": "self.version", + "symfony/security": "self.version", + "symfony/security-bundle": "self.version", + "symfony/security-core": "self.version", + "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", + "symfony/security-http": "self.version", + "symfony/serializer": "self.version", + "symfony/stopwatch": "self.version", + "symfony/templating": "self.version", + "symfony/translation": "self.version", + "symfony/twig-bridge": "self.version", + "symfony/twig-bundle": "self.version", + "symfony/validator": "self.version", + "symfony/var-dumper": "self.version", + "symfony/web-link": "self.version", + "symfony/web-profiler-bundle": "self.version", + "symfony/web-server-bundle": "self.version", + "symfony/workflow": "self.version", + "symfony/yaml": "self.version" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.6", + "doctrine/data-fixtures": "1.0.*", + "doctrine/dbal": "~2.4", + "doctrine/doctrine-bundle": "~1.4", + "doctrine/orm": "~2.4,>=2.4.5", + "egulias/email-validator": "~1.2,>=1.2.8|~2.0", + "monolog/monolog": "~1.11", + "ocramius/proxy-manager": "~0.4|~1.0|~2.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "predis/predis": "~1.0", + "symfony/phpunit-bridge": "~3.4|~4.0", + "symfony/security-acl": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "src/Symfony/Bridge/Doctrine/", + "Symfony\\Bridge\\Monolog\\": "src/Symfony/Bridge/Monolog/", + "Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/", + "Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/", + "Symfony\\Bundle\\": "src/Symfony/Bundle/", + "Symfony\\Component\\": "src/Symfony/Component/" + }, + "classmap": [ + "src/Symfony/Component/Intl/Resources/stubs" + ], + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "The Symfony PHP framework", + "homepage": "https://symfony.com", + "keywords": [ + "framework" + ], + "time": "2018-04-06T15:20:04+00:00" + }, + { + "name": "twig/extensions", + "version": "v1.5.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig-extensions.git", + "reference": "d188c76168b853481cc75879ea045bf93d718e9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/d188c76168b853481cc75879ea045bf93d718e9c", + "reference": "d188c76168b853481cc75879ea045bf93d718e9c", + "shasum": "" + }, + "require": { + "twig/twig": "~1.27|~2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "~3.3@dev", + "symfony/translation": "~2.3|~3.0" + }, + "suggest": { + "symfony/translation": "Allow the time_diff output to be translated" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_Extensions_": "lib/" + }, + "psr-4": { + "Twig\\Extensions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Common additional features for Twig that do not directly belong in core", + "homepage": "http://twig.sensiolabs.org/doc/extensions/index.html", + "keywords": [ + "i18n", + "text" + ], + "time": "2017-06-08T18:19:53+00:00" + }, + { + "name": "twig/twig", + "version": "v2.4.8", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "7b604c89da162034bdf4bb66310f358d313dd16d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7b604c89da162034bdf4bb66310f358d313dd16d", + "reference": "7b604c89da162034bdf4bb66310f358d313dd16d", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2018-04-02T09:24:19+00:00" + }, + { + "name": "zendframework/zend-code", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/6b1059db5b368db769e4392c6cb6cc139e56640d", + "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d", + "shasum": "" + }, + "require": { + "php": "^7.1", + "zendframework/zend-eventmanager": "^2.6 || ^3.0" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "zendframework/zend-coding-standard": "^1.0.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2017-10-20T15:21:32+00:00" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "9d72db10ceb6e42fb92350c0cb54460da61bd79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/9d72db10ceb6e42fb92350c0cb54460da61bd79c", + "reference": "9d72db10ceb6e42fb92350c0cb54460da61bd79c", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "phpunit/phpunit": "^6.0.7 || ^5.7.14", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + }, + "suggest": { + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "event", + "eventmanager", + "events", + "zf2" + ], + "time": "2017-07-11T19:17:22+00:00" + } + ], + "packages-dev": [ + { + "name": "fzaninotto/faker", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2017-08-15T16:48:10+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-30T07:14:17+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.7.6", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-04-18T13:57:24+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "774a82c0c5da4c1c7701790c262035d235ab7856" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/774a82c0c5da4c1c7701790c262035d235ab7856", + "reference": "774a82c0c5da4c1c7701790c262035d235ab7856", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.1", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.1", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-xdebug": "^2.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06T15:39:20+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2018-02-01T13:07:23+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2018-02-01T13:16:43+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "7.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "6d51299e307dc510149e0b7cd1931dd11770e1cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6d51299e307dc510149e0b7cd1931dd11770e1cb", + "reference": "6d51299e307dc510149e0b7cd1931dd11770e1cb", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.1", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^6.0.1", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.0", + "phpunit/phpunit-mock-objects": "^6.1.1", + "sebastian/comparator": "^2.1 || ^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-04-18T13:41:53+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "6.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/70c740bde8fd9ea9ea295be1cd875dd7b267e157", + "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.1", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2018-04-11T04:50:36+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5", + "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-04-18T13:33:00+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", + "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2018-02-01T13:45:15+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-01-29T19:49:41+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "chill-project/person": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "~7.2" + }, + "platform-dev": [] +} From ff25a38e6496e3617f472a1594bb7ccd287536b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Apr 2018 12:18:44 +0200 Subject: [PATCH 011/114] fix error in return path and fix date interval conversion --- Controller/SingleTaskController.php | 18 ++++++++---------- Resources/views/SingleTask/show.html.twig | 6 +++--- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 7cc2f3bb0..2b105e7ff 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -18,8 +18,6 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\TaskBundle\Repository\SingleTaskRepository; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\TaskBundle\Repository\SingleTaskRepository; -use Chill\MainBundle\Pagination\PaginatorFactory; class SingleTaskController extends Controller { @@ -52,8 +50,8 @@ class SingleTaskController extends Controller ->setType('task_default') ; - // $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' - // . 'allowed to create this task'); + $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' + . 'allowed to create this task'); $form = $this->setCreateForm($task, new Role(TaskVoter::CREATE)); @@ -68,7 +66,7 @@ class SingleTaskController extends Controller $this->addFlash('success', "The task is created"); - return $this->redirectToRoute('chill_task_task_list', [ + return $this->redirectToRoute('chill_task_task_list_by_person', [ 'personId' => $task->getPerson()->getId() ]); @@ -112,8 +110,8 @@ class SingleTaskController extends Controller throw $this->createNotFoundException("Invalid person id"); } } - // $this->denyAccessUnlessGranted(TaskVoter::SEE, $task, 'You are not ' - // . 'allowed to view this task'); + $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task, 'You are not ' + . 'allowed to view this task'); if (!$task) { throw $this->createNotFoundException('Unable to find Task entity.'); @@ -152,8 +150,8 @@ class SingleTaskController extends Controller throw $this->createNotFoundException("Invalid person id"); } } - // $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not ' - // . 'allowed to edit this task'); + $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not ' + . 'allowed to edit this task'); if (!$task) { throw $this->createNotFoundException('Unable to find Task entity.'); @@ -172,7 +170,7 @@ class SingleTaskController extends Controller $this->addFlash('success', "Success : task updated!"); - return $this->redirectToRoute('chill_task_task_list', [ + return $this->redirectToRoute('chill_task_task_list_by_person', [ 'personId' => $task->getPerson()->getId() ]); diff --git a/Resources/views/SingleTask/show.html.twig b/Resources/views/SingleTask/show.html.twig index 6e073772a..0c0389c78 100644 --- a/Resources/views/SingleTask/show.html.twig +++ b/Resources/views/SingleTask/show.html.twig @@ -44,13 +44,13 @@
{{ 'End date'|trans }}
{{ task.endDate|localizeddate('long', 'none') }}
-
{{ 'Warning interval'|trans }}
-
{{ task.warningInterval|localizeddate('long', 'none') }}
+
{{ 'Warning date'|trans }}
+
{{ task.warningDate|localizeddate('long', 'none') }}
  • - + {{ 'Back to the list'|trans }}
  • From a58ae088ffb66a7d8a30834902b07d30edba86a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Apr 2018 12:18:44 +0200 Subject: [PATCH 012/114] fix error in return path and fix date interval conversion --- Controller/SingleTaskController.php | 24 +++++++++++------------ Resources/views/SingleTask/show.html.twig | 6 +++--- Resources/views/Task/index.html.twig | 4 ++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 7cc2f3bb0..4959cd97f 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -18,8 +18,6 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\TaskBundle\Repository\SingleTaskRepository; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Security\Authorization\PersonVoter; -use Chill\TaskBundle\Repository\SingleTaskRepository; -use Chill\MainBundle\Pagination\PaginatorFactory; class SingleTaskController extends Controller { @@ -52,8 +50,8 @@ class SingleTaskController extends Controller ->setType('task_default') ; - // $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' - // . 'allowed to create this task'); + $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' + . 'allowed to create this task'); $form = $this->setCreateForm($task, new Role(TaskVoter::CREATE)); @@ -68,7 +66,7 @@ class SingleTaskController extends Controller $this->addFlash('success', "The task is created"); - return $this->redirectToRoute('chill_task_task_list', [ + return $this->redirectToRoute('chill_task_task_list_by_person', [ 'personId' => $task->getPerson()->getId() ]); @@ -112,8 +110,8 @@ class SingleTaskController extends Controller throw $this->createNotFoundException("Invalid person id"); } } - // $this->denyAccessUnlessGranted(TaskVoter::SEE, $task, 'You are not ' - // . 'allowed to view this task'); + $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task, 'You are not ' + . 'allowed to view this task'); if (!$task) { throw $this->createNotFoundException('Unable to find Task entity.'); @@ -152,8 +150,8 @@ class SingleTaskController extends Controller throw $this->createNotFoundException("Invalid person id"); } } - // $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not ' - // . 'allowed to edit this task'); + $this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not ' + . 'allowed to edit this task'); if (!$task) { throw $this->createNotFoundException('Unable to find Task entity.'); @@ -172,7 +170,7 @@ class SingleTaskController extends Controller $this->addFlash('success', "Success : task updated!"); - return $this->redirectToRoute('chill_task_task_list', [ + return $this->redirectToRoute('chill_task_task_list_by_person', [ 'personId' => $task->getPerson()->getId() ]); @@ -218,8 +216,8 @@ class SingleTaskController extends Controller } - // $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not ' - // . 'allowed to delete this task'); + $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not ' + . 'allowed to delete this task'); if (!$task) { throw $this->createNotFoundException('Unable to find Task entity.'); @@ -252,7 +250,7 @@ class SingleTaskController extends Controller ->trans("The task has been successfully removed.")); return $this->redirect($this->generateUrl( - 'chill_task_task_list', array( + 'chill_task_task_list_by_person', array( 'personId' => $personId ))); } diff --git a/Resources/views/SingleTask/show.html.twig b/Resources/views/SingleTask/show.html.twig index 6e073772a..0c0389c78 100644 --- a/Resources/views/SingleTask/show.html.twig +++ b/Resources/views/SingleTask/show.html.twig @@ -44,13 +44,13 @@
    {{ 'End date'|trans }}
    {{ task.endDate|localizeddate('long', 'none') }}
    -
    {{ 'Warning interval'|trans }}
    -
    {{ task.warningInterval|localizeddate('long', 'none') }}
    +
    {{ 'Warning date'|trans }}
    +
    {{ task.warningDate|localizeddate('long', 'none') }}
    • - + {{ 'Back to the list'|trans }}
    • diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig index a07e40111..c28665a12 100644 --- a/Resources/views/Task/index.html.twig +++ b/Resources/views/Task/index.html.twig @@ -57,12 +57,12 @@
    • - {% if not is_granted('CHILL_TASK_TASK_UPDATE', task) %} + {% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
    • {% endif %} - {% if not is_granted('CHILL_TASK_TASK_CREATE', task) %} + {% if is_granted('CHILL_TASK_TASK_CREATE', task) %}
    • From 4fe9c4296eea24a419a0756ae29fb3ef7a511843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Apr 2018 12:24:46 +0200 Subject: [PATCH 013/114] upgrade route name --- Resources/views/SingleTask/confirm_delete.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/views/SingleTask/confirm_delete.html.twig b/Resources/views/SingleTask/confirm_delete.html.twig index c0afde28a..46fdae741 100644 --- a/Resources/views/SingleTask/confirm_delete.html.twig +++ b/Resources/views/SingleTask/confirm_delete.html.twig @@ -11,7 +11,7 @@ { 'title' : 'Remove task'|trans, 'confirm_question' : 'Are you sure you want to remove the task about "%name%" ?'|trans({ '%name%' : person.firstname ~ ' ' ~ person.lastname } ), - 'cancel_route' : 'chill_task_task_list', + 'cancel_route' : 'chill_task_task_list_by_person', 'cancel_parameters' : { 'personId' : task.person.id, 'id' : task.id }, 'form' : delete_form } ) }} From c99583b6651b437fd53c217a8cf3b90399eca336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Apr 2018 15:35:52 +0200 Subject: [PATCH 014/114] implementing workflow on tasks --- ChillTaskBundle.php | 9 +- Controller/TaskController.php | 26 ++++- DependencyInjection/ChillTaskExtension.php | 5 +- .../TaskWorkflowDefinitionCompilerPass.php | 46 ++++++++ Resources/config/services/templating.yml | 6 ++ Resources/config/services/workflow.yml | 6 +- Resources/views/Task/index.html.twig | 6 +- Templating/TaskTwigExtension.php | 59 ++++++++++ Tests/Controller/SingleTaskControllerTest.php | 102 ++++++++++++++++++ Tests/Controller/TaskControllerTest.php | 9 ++ Workflow/Definition/DefaultTaskDefinition.php | 92 ++++++++++++++++ Workflow/TaskWorkflowDefinition.php | 27 +++++ Workflow/TaskWorkflowManager.php | 48 ++++++++- 13 files changed, 429 insertions(+), 12 deletions(-) create mode 100644 DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php create mode 100644 Resources/config/services/templating.yml create mode 100644 Templating/TaskTwigExtension.php create mode 100644 Tests/Controller/SingleTaskControllerTest.php create mode 100644 Tests/Controller/TaskControllerTest.php create mode 100644 Workflow/Definition/DefaultTaskDefinition.php create mode 100644 Workflow/TaskWorkflowDefinition.php diff --git a/ChillTaskBundle.php b/ChillTaskBundle.php index 6155882cd..acd8b8607 100644 --- a/ChillTaskBundle.php +++ b/ChillTaskBundle.php @@ -18,6 +18,8 @@ namespace Chill\TaskBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Chill\TaskBundle\DependencyInjection\Compiler\TaskWorkflowDefinitionCompilerPass; /** * @@ -25,5 +27,10 @@ use Symfony\Component\HttpKernel\Bundle\Bundle; */ class ChillTaskBundle extends Bundle { - + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new TaskWorkflowDefinitionCompilerPass()); + } } diff --git a/Controller/TaskController.php b/Controller/TaskController.php index 525abab35..94e356941 100644 --- a/Controller/TaskController.php +++ b/Controller/TaskController.php @@ -9,12 +9,30 @@ use Symfony\Component\Workflow\Registry; use Symfony\Component\HttpFoundation\Response; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; class TaskController extends Controller { + /** + * Apply a transition to a task + * + * @Route( + * "/{_locale}/task/transition/{kind}/{taskId}/{transition}", + * name="chill_task_task_transition" + * ) + * + * @param string $kind + * @param int $taskId + * @param string $transition + * @param SingleTaskRepository $singleTaskRepository + * @param Registry $registry + * @param EntityManagerInterface $em + * @param Request $request + * @return Response + */ public function applyTransitionAction( - $type, + $kind, $taskId, $transition, SingleTaskRepository $singleTaskRepository, @@ -22,7 +40,7 @@ class TaskController extends Controller EntityManagerInterface $em, Request $request ) { - switch ($type) { + switch ($kind) { case 'single-task': $task = $singleTaskRepository ->find($taskId) @@ -33,7 +51,7 @@ class TaskController extends Controller ); break; default: - return new Response("The type '$type' is not implemented", + return new Response("The type '$kind' is not implemented", Response::HTTP_BAD_REQUEST); } @@ -44,7 +62,7 @@ class TaskController extends Controller // we simply check that the user can see the task. Other ACL checks // should be performed using `guard` events. - $this->denyAccessUnlessGranted($task, TaskVoter::SHOW); + $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task); $workflow = $registry->get($task); diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index 519391f88..31dcd3d4a 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -30,6 +30,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface $loader->load('services/security.yml'); $loader->load('services/repositories.yml'); $loader->load('services/workflow.yml'); + $loader->load('services/templating.yml'); } public function prepend(ContainerBuilder $container) @@ -72,7 +73,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface 'currentStates' ], ], - 'type' => 'workflow', + 'type' => 'state_machine', 'support_strategy' => TaskWorkflowManager::class, 'places' => [ 'new', 'in_progress', 'closed', 'canceled'], 'initial_place' => 'new', @@ -82,7 +83,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface 'to' => 'in_progress' ], 'close' => [ - 'from' => 'in_progress', + 'from' => ['new', 'in_progress'], 'to' => 'closed' ], 'cancel' => [ diff --git a/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php b/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php new file mode 100644 index 000000000..d52e5484d --- /dev/null +++ b/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php @@ -0,0 +1,46 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Chill\TaskBundle\Workflow\TaskWorkflowManager; +use Symfony\Component\DependencyInjection\Reference; + +/** + * + * + * @author Julien Fastré + */ +class TaskWorkflowDefinitionCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition(TaskWorkflowManager::class)) { + throw new \LogicException("The service ".TaskWorkflowManager::class." is " + . "not registered"); + } + + $workflowManagerDefinition = $container->getDefinition(TaskWorkflowManager::class); + + foreach ($container->findTaggedServiceIds('chill_task.workflow_definition') as $id => $tags) { + $workflowManagerDefinition + ->addMethodCall('addDefinition', [new Reference($id)]); + } + } +} diff --git a/Resources/config/services/templating.yml b/Resources/config/services/templating.yml new file mode 100644 index 000000000..dca4e36f6 --- /dev/null +++ b/Resources/config/services/templating.yml @@ -0,0 +1,6 @@ +services: + Chill\TaskBundle\Templating\TaskTwigExtension: + arguments: + $taskWorkflowManager: '@Chill\TaskBundle\Workflow\TaskWorkflowManager' + tags: + - { name: 'twig.extension' } diff --git a/Resources/config/services/workflow.yml b/Resources/config/services/workflow.yml index acf583d43..01a26f1fc 100644 --- a/Resources/config/services/workflow.yml +++ b/Resources/config/services/workflow.yml @@ -1,2 +1,6 @@ services: - Chill\TaskBundle\Workflow\TaskWorkflowManager: ~ \ No newline at end of file + Chill\TaskBundle\Workflow\TaskWorkflowManager: ~ + + Chill\TaskBundle\Workflow\Definition\DefaultTaskDefinition: + tags: + - { name: 'chill_task.workflow_definition' } \ No newline at end of file diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig index c28665a12..6e745b9c5 100644 --- a/Resources/views/Task/index.html.twig +++ b/Resources/views/Task/index.html.twig @@ -48,7 +48,11 @@
- + diff --git a/Templating/TaskTwigExtension.php b/Templating/TaskTwigExtension.php new file mode 100644 index 000000000..fd799dd34 --- /dev/null +++ b/Templating/TaskTwigExtension.php @@ -0,0 +1,59 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Templating; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; +use Chill\TaskBundle\Entity\AbstractTask; +use Chill\TaskBundle\Workflow\TaskWorkflowManager; + +/** + * + * + * @author Julien Fastré + */ +class TaskTwigExtension extends \Twig_Extension +{ + /** + * + * @var TaskWorkflowManager + */ + protected $taskWorkflowManager; + + public function __construct(TaskWorkflowManager $taskWorkflowManager) + { + $this->taskWorkflowManager = $taskWorkflowManager; + } + + + public function getFunctions() + { + return [ + new TwigFunction('task_workflow_metadata', [ $this, 'getWorkflowMetadata' ] ) + ]; + } + + public function getWorkflowMetadata( + AbstractTask $task, + string $key, + $metadataSubject = null, + string $name = null + ) { + return $this->taskWorkflowManager->getWorkflowMetadata($task, $key, $metadataSubject, $name); + } +} diff --git a/Tests/Controller/SingleTaskControllerTest.php b/Tests/Controller/SingleTaskControllerTest.php new file mode 100644 index 000000000..d7337029d --- /dev/null +++ b/Tests/Controller/SingleTaskControllerTest.php @@ -0,0 +1,102 @@ +faker = Faker\Factory::create('fr'); + } + + /** + * + * @return \Chill\PersonBundle\Entity\Person + */ + protected function getRandomPerson($centerName) + { + $em = self::$kernel + ->getContainer() + ->get('doctrine.orm.entity_manager') + ; + + $centers = $em + ->getRepository(Center::class) + ->findAll(); + + $center = \array_filter( + $centers, + function(Center $c) use ($centerName) { + return $centerName === $c->getName(); + })[0]; + + $ids = $em + ->createQuery('SELECT p.id FROM ChillPersonBundle:Person p ' + . 'WHERE p.center = :center' + ) + ->setParameter('center', $center) + ->getResult() + ; + + $id = $ids[\array_rand($ids)]; + + return self::$kernel + ->getContainer() + ->get('doctrine.orm.entity_manager') + ->getRepository(\Chill\PersonBundle\Entity\Person::class) + ->find($id) + ; + } + + public function testNew() + { + $client = static::createClient( + array(), + TestHelper::getAuthenticatedClientOptions() + ); + $person = $this->getRandomPerson('Center A'); + + $crawler = $client->request('GET', '/fr/task/single-task/new', [ + 'person_id' => $person->getId() + ]); + var_dump($crawler->text()); + + $this->assertTrue($client->getResponse()->isSuccessful()); + + + + $form = $crawler->selectButton('Envoi')->form(); + + $title = $this->faker->sentence; + $circles = $form->get('circle') + ->availableOptionsValues() + ; + + $client->submit($form, [ + 'title' => $title, + 'circle' => $circles[\array_rand($circles)] + ]); + + $this->assertTrue($client->getResponse()->isRedirect(sprintf( + '/fr/task/task/list/%d', $person->getId()))); + + $crawler = $client->followRedirect(); + + $this->assertContains($title, $crawler->text(), + "Assert that newly created task title is shown in list page") + ; + } + +} diff --git a/Tests/Controller/TaskControllerTest.php b/Tests/Controller/TaskControllerTest.php new file mode 100644 index 000000000..18b980329 --- /dev/null +++ b/Tests/Controller/TaskControllerTest.php @@ -0,0 +1,9 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Workflow\Definition; + +use Chill\TaskBundle\Entity\AbstractTask; +use Chill\TaskBundle\Entity\SingleTask; +use Symfony\Component\Workflow\Transition; + +/** + * + * + * @author Julien Fastré + */ +class DefaultTaskDefinition implements \Chill\TaskBundle\Workflow\TaskWorkflowDefinition +{ + const TRANSITION_METADATA = [ + 'close' => [ + 'verb' => 'close', + 'background-color' => 'var(--yellow)', // css variable, see https://developer.mozilla.org/fr/docs/Web/CSS/Les_variables_CSS + 'text-color' => 'black' + ], + 'cancel' => [ + 'verb' => 'cancel', + 'background-color' => 'var(--red)', // css variable, see https://developer.mozilla.org/fr/docs/Web/CSS/Les_variables_CSS + 'text-color' => 'black' + ], + 'start' => [ + 'verb' => 'start', + 'background-color' => 'var(--green)', // css variable, see https://developer.mozilla.org/fr/docs/Web/CSS/Les_variables_CSS + 'text-color' => 'black' + ] + ]; + + public function supports(AbstractTask $task) + { + + return $task instanceof SingleTask + && $task->getType() === 'task_default'; + } + + public function getAssociatedWorkflowName() + { + return 'task_default'; + } + + public function getWorkflowMetadata( + string $key, + $metadataSubject = null + ) { + $keys = \explode('.', $key); + + switch($keys[0]) { + case 'transition': + if (!$metadataSubject instanceof Transition) { + throw new \LogicException("You must give a transition as metadatasubject"); + } + + return $this->getTransitionMetadata(\implode('.', \array_slice($keys, 1)), $metadataSubject); + default: + throw new \LogicException("this key '$key' is not implemented"); + } + } + + protected function getTransitionMetadata($key, Transition $transition) + { + if (!\array_key_exists($transition->getName(), self::TRANSITION_METADATA)) { + throw new \LogicException("the metadata for this transition are not defined"); + } + + if (!\array_key_exists($key, self::TRANSITION_METADATA[$transition->getName()])) { + throw new \LogicException("The metadata ".$key." is not " + . "defined for the transition ".$transition.getName()); + } + + return self::TRANSITION_METADATA[$transition->getName()][$key]; + } +} diff --git a/Workflow/TaskWorkflowDefinition.php b/Workflow/TaskWorkflowDefinition.php new file mode 100644 index 000000000..d49591a64 --- /dev/null +++ b/Workflow/TaskWorkflowDefinition.php @@ -0,0 +1,27 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Workflow; + +/** + * + * @author Julien Fastré + */ +interface TaskWorkflowDefinition +{ + +} diff --git a/Workflow/TaskWorkflowManager.php b/Workflow/TaskWorkflowManager.php index e942e303b..8675f828e 100644 --- a/Workflow/TaskWorkflowManager.php +++ b/Workflow/TaskWorkflowManager.php @@ -28,14 +28,56 @@ use Symfony\Component\Workflow\Workflow; */ class TaskWorkflowManager implements SupportStrategyInterface { + /** + * + * @var TaskWorkflowDefinition[] + */ + protected $definitions = array(); + + public function addDefinition(TaskWorkflowDefinition $definition) { + $this->definitions[] = $definition; + } + + /** + * + * @param AbstractTask $task + * @return TaskWorkflowDefinition + * @throws \LogicException + */ + public function getTaskWorkflowDefinition(AbstractTask $task) + { + $definitions = array(); + + foreach($this->definitions as $tested) { + if ($tested->supports($task)) { + $definitions[] = $tested; + } + } + + $count = count($definitions); + if ($count > 1) { + throw new \LogicException("More than one TaskWorkflowDefinition supports " + . "this task. This should not happens."); + } elseif ($count === 0) { + throw new \LogicException("No taskWorkflowDefinition supports this task."); + } + + return $definitions[0]; + } + public function supports(Workflow $workflow, $subject): bool { if (!$subject instanceof AbstractTask) { return false; } - dump($workflow->getName()); - - return $workflow->getName() === 'task_default'; + return $workflow->getName() === $this + ->getTaskWorkflowDefinition($subject)->getAssociatedWorkflowName(); + } + + public function getWorkflowMetadata(AbstractTask $task, string $key, $metadataSubject = null, string $name = null) + { + return $this->getTaskWorkflowDefinition($task) + ->getWorkflowMetadata($key, $metadataSubject); } } From fa797e06cf4d3a459b5e68c77a7913ec8dc38494 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 25 Apr 2018 17:59:04 +0200 Subject: [PATCH 015/114] add translations --- Resources/translations/messages.fr.yml | 43 +++++++++++++++++++++++ Resources/views/SingleTask/show.html.twig | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 Resources/translations/messages.fr.yml diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml new file mode 100644 index 000000000..85d9b9f83 --- /dev/null +++ b/Resources/translations/messages.fr.yml @@ -0,0 +1,43 @@ +'New task': 'Nouvelle tâche' +'Add a new task': 'Ajouter une nouvelle tâche' +Title: Titre +Description: Description +Assignee: 'Personne assignée' +Scope: Cercle +'Start date': 'Date de début' +'End date': 'Date de fin' +'Warning date': "Date d'avertissement" +'Warning interval': "Délai d'avertissement" +'N': '' +'Unit': '' +Task: Tâche +Details: Détails +Person: Personne +Scope: Cercle +Date: Date +User: Utilisateur +'Task list': 'Liste de tâches' +'Tasks with expired deadline': 'Tâches avec une date limite dépassée' +'Tasks with warning deadline reached': "Tâches avec une date d'avertissement atteinte" +'Current tasks': 'Tâches en cours' +'Tasks not started': 'Tâches non commencées' +'Task start date': 'Date de début de la tâche' +'Task warning date': "Date d'avertissement de la tâche" +'Task end date': 'Date de fin de la tâche' +'Task type': 'Type de tâche' +'Task status': 'Status de la tâche' +'Edit the task': 'Éditer la tâche' +'Edit task': 'Éditer la tâche' +'Save task': 'Enregistrer la tâche' +'View the task': 'Voir la tâche' +'Update the task': 'Mettre à jour la tâche' +'Remove task': 'Supprimer la tâche' +'Are you sure you want to remove the task about "%name%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche de "%name%"?' + +#Flash messages +'The task is created': 'La tâche a été créée' +'The task has been successfully removed.': 'La tâche a bien été supprimée' +'This form contains errors': 'Ce formulaire contient des erreurs' +'Success : task updated!': 'Succès: la tâche a été mise à jour' +'The transition is sucessfully applyed': 'La transition a bien été effectuée' +'The transition could not be applyed': "La transition n'a pas pu être appliquée" diff --git a/Resources/views/SingleTask/show.html.twig b/Resources/views/SingleTask/show.html.twig index 0c0389c78..c24e0bdd2 100644 --- a/Resources/views/SingleTask/show.html.twig +++ b/Resources/views/SingleTask/show.html.twig @@ -57,7 +57,7 @@ {% if not is_granted('CHILL_TASK_TASK_UPDATE', task) %}
  • - {{ 'Edit the activity'|trans }} + {{ 'Edit the task'|trans }}
  • {% endif %} From 46db1b73894aeee6e2fd6f5f20f2f3e5768dff1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 25 Apr 2018 21:16:16 +0200 Subject: [PATCH 016/114] Check ACL on default task --- Resources/config/services/workflow.yml | 8 +++- Workflow/Event/DefaultTaskGuardEvent.php | 59 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Workflow/Event/DefaultTaskGuardEvent.php diff --git a/Resources/config/services/workflow.yml b/Resources/config/services/workflow.yml index 01a26f1fc..a80cf40dd 100644 --- a/Resources/config/services/workflow.yml +++ b/Resources/config/services/workflow.yml @@ -3,4 +3,10 @@ services: Chill\TaskBundle\Workflow\Definition\DefaultTaskDefinition: tags: - - { name: 'chill_task.workflow_definition' } \ No newline at end of file + - { name: 'chill_task.workflow_definition' } + + Chill\TaskBundle\Workflow\Event\DefaultTaskGuardEvent: + arguments: + - '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + tags: + - { name: kernel.event_subscriber } \ No newline at end of file diff --git a/Workflow/Event/DefaultTaskGuardEvent.php b/Workflow/Event/DefaultTaskGuardEvent.php new file mode 100644 index 000000000..e4ceff327 --- /dev/null +++ b/Workflow/Event/DefaultTaskGuardEvent.php @@ -0,0 +1,59 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Workflow\Event; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Workflow\Event\GuardEvent; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Chill\TaskBundle\Security\Authorization\TaskVoter; + +/** + * + * + * @author Julien Fastré + */ +class DefaultTaskGuardEvent implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + 'workflow.task_default.guard' => [ + 'checkACL' + ] + ]; + } + + /** + * + * @var AuthorizationCheckerInterface + */ + protected $authorizationChecker; + + public function __construct(AuthorizationCheckerInterface $authorizationChecker) + { + $this->authorizationChecker = $authorizationChecker; + } + + public function checkACL(GuardEvent $event) + { + if (FALSE === $this->authorizationChecker->isGranted(TaskVoter::UPDATE, + $event->getSubject())) { + $event->setBlocked(true); + } + } +} From d2510744300984478f99eb6cea01be008540e77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 26 Apr 2018 11:15:22 +0200 Subject: [PATCH 017/114] link between lists of task and tasks --- Controller/SingleTaskController.php | 7 ++-- Resources/views/Task/index.html.twig | 53 +++++++++++++++++++++------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 4959cd97f..521647546 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -298,10 +298,9 @@ class SingleTaskController extends Controller $viewParams['person'] = $person; // collect parameters for filter $params['person'] = $person; + $statuses = $request->query->get('date_status', []); - if ($request->query->has('date_status')) { - $statuses = $request->query->get('date_status'); - $singleStatus = count($statuses) === 1; + if ($statuses) { // check for invalid parameters $diff = \array_diff( $statuses, @@ -315,6 +314,8 @@ class SingleTaskController extends Controller ); } } + + $viewParams['isSingleStatus'] = $singleStatus = count($statuses) === 1; foreach(SingleTaskRepository::DATE_STATUSES as $type) { if($request->query->has('date_status') diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig index 6e745b9c5..e37535395 100644 --- a/Resources/views/Task/index.html.twig +++ b/Resources/views/Task/index.html.twig @@ -28,7 +28,7 @@ {% endmacro %} -{% macro date_status(title, tasks, count, paginator) %} +{% macro date_status(title, tasks, count, paginator, status, isSingleStatus, person) %} {% if tasks|length > 0 %}

    {{ title|trans }}

    @@ -78,8 +78,33 @@
    {{ 'Title'|trans }}{{ 'Task type'|trans }}{{ 'Task status'|trans }}{{ 'Title'|trans }}{{ 'Task type'|trans }}{{ 'Task status'|trans }} {{ 'Task start date'|trans }} {{ 'Task warning date'|trans }} {{ 'Task end date'|trans }}{% if task.startDate is not null %}{{ task.startDate|localizeddate('medium', 'none') }}{% endif %} {% if task.warningDate is not null %}{{ task.warningDate|localizeddate('medium', 'none') }}{% endif %} {% if task.endDate is not null %}{{ task.endDate|localizeddate('medium', 'none') }}{% endif %} +
      +
    • + +
    • + {% if not is_granted('CHILL_TASK_TASK_UPDATE', task) %} +
    • + +
    • + {% endif %} + {% if not is_granted('CHILL_TASK_TASK_CREATE', task) %} +
    • + +
    • + {% endif %} +
    +
    {{ task.title }} {{ task.type }}todo + {% for transition in workflow_transitions(task) %} + {{ task_workflow_metadata(task, 'transition.verb', transition) }} + {% endfor %} + {% if task.startDate is not null %}{{ task.startDate|localizeddate('medium', 'none') }}{% endif %} {% if task.warningDate is not null %}{{ task.warningDate|localizeddate('medium', 'none') }}{% endif %} {% if task.endDate is not null %}{{ task.endDate|localizeddate('medium', 'none') }}{% endif %}
    - {% if tasks|length > paginator.getTotalItems %} + + {% if isSingleStatus %} + {% if tasks|length > paginator.getTotalItems %} {{ chill_pagination(paginator) }} + {% endif %} + + + + {% else %} + {% endif %} {% endif %} @@ -93,27 +118,29 @@

    {{ 'Task list'|trans }}

    {% if single_task_ended_tasks is defined %} - {{ helper.date_status('Tasks with expired deadline', single_task_ended_tasks, single_task_ended_count, single_task_ended_paginator) }} + {{ helper.date_status('Tasks with expired deadline', single_task_ended_tasks, single_task_ended_count, single_task_ended_paginator, 'ended', isSingleStatus, person) }} {% endif %} {% if single_task_warning_tasks is defined %} - {{ helper.date_status('Tasks with warning deadline reached', single_task_warning_tasks, single_task_warning_count, single_task_warning_paginator) }} + {{ helper.date_status('Tasks with warning deadline reached', single_task_warning_tasks, single_task_warning_count, single_task_warning_paginator, 'warning', isSingleStatus, person) }} {% endif %} {% if single_task_current_tasks is defined %} - {{ helper.date_status('Current tasks', single_task_current_tasks, single_task_current_count, single_task_current_paginator) }} + {{ helper.date_status('Current tasks', single_task_current_tasks, single_task_current_count, single_task_current_paginator, 'current', isSingleStatus, person) }} {% endif %} {% if single_task_not_started_tasks is defined %} - {{ helper.date_status('Tasks not started', single_task_not_started_tasks, single_task_not_started_count, single_task_not_started_paginator) }} + {{ helper.date_status('Tasks not started', single_task_not_started_tasks, single_task_not_started_count, single_task_not_started_paginator, 'not_started', isSingleStatus, person) }} {% endif %} - + {% if isSingleStatus == false %} + + {% endif %} {% endblock %} From 0d9e80eb7282159548d2ac01b6021f2b8c82c5fa Mon Sep 17 00:00:00 2001 From: nobohan Date: Thu, 26 Apr 2018 11:46:01 +0200 Subject: [PATCH 018/114] add DELETE to the TaskVoter --- Security/Authorization/TaskVoter.php | 40 +++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Security/Authorization/TaskVoter.php b/Security/Authorization/TaskVoter.php index aacad0dad..7de8fdb83 100644 --- a/Security/Authorization/TaskVoter.php +++ b/Security/Authorization/TaskVoter.php @@ -29,7 +29,7 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Chill\MainBundle\Entity\User; /** - * + * * * @author Julien Fastré */ @@ -38,31 +38,33 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf const CREATE = 'CHILL_TASK_TASK_CREATE'; const UPDATE = 'CHILL_TASK_TASK_UPDATE'; const SHOW = 'CHILL_TASK_TASK_SHOW'; - + const DELETE = 'CHILL_TASK_TASK_DELETE'; + const ROLES = [ self::CREATE, self::UPDATE, - self::SHOW + self::SHOW, + self::DELETE ]; - + /** * * @var AuthorizationHelper */ protected $authorizationHelper; - + /** * * @var AccessDecisionManagerInterface */ protected $accessDecisionManager; - + /** * * @var LoggerInterface */ protected $logger; - + public function __construct( AccessDecisionManagerInterface $accessDecisionManager, AuthorizationHelper $authorizationHelper, @@ -72,15 +74,15 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf $this->authorizationHelper = $authorizationHelper; $this->logger = $logger; } - + public function supports($attribute, $subject) { - return $subject instanceof AbstractTask + return $subject instanceof AbstractTask && in_array($attribute, self::ROLES); } /** - * + * * @param string $attribute * @param AbstractTask $subject * @param TokenInterface $token @@ -89,28 +91,28 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $this->logger->debug(sprintf("Voting from %s class", self::class)); - + if (!$token->getUser() instanceof User) { return false; } - + if ($subject->getPerson() === null) { throw new \LogicException("You should associate a person with task " . "in order to check autorizations"); } - + if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $subject->getPerson())) { - + return false; } - + return $this->authorizationHelper->userHasAccess( - $token->getUser(), - $subject, + $token->getUser(), + $subject, $attribute ); } - + public function getRoles() { return self::ROLES; @@ -122,7 +124,7 @@ class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterf 'Task' => self::ROLES ]; } - + public function getRolesWithoutScope() { return []; From eadaeaef35ddf2f7cf1ac767f99e1b7ff2054d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 26 Apr 2018 12:11:06 +0200 Subject: [PATCH 019/114] add closed tasks - the entity has a new column / property "closed" (boolean) - An event listener in created during container compilation, which listen on tasks status changed - The taskworkflow manager and task workflow definition indicate if the tasks is closed ; - Add list for closed tasks in controller, and change parameter 'date_status' to 'status'; - and change query to allow to filter on closed tasks --- Controller/SingleTaskController.php | 45 ++++++++++--------- Controller/TaskController.php | 4 +- .../TaskWorkflowDefinitionCompilerPass.php | 9 ++++ Entity/AbstractTask.php | 23 +++++++++- Repository/SingleTaskRepository.php | 27 ++++++++--- .../migrations/Version20180426093011.php | 28 ++++++++++++ Resources/views/Task/index.html.twig | 6 ++- Workflow/Definition/DefaultTaskDefinition.php | 8 +++- Workflow/TaskWorkflowManager.php | 12 +++++ 9 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 Resources/migrations/Version20180426093011.php diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php index 521647546..a887b0b01 100644 --- a/Controller/SingleTaskController.php +++ b/Controller/SingleTaskController.php @@ -298,39 +298,42 @@ class SingleTaskController extends Controller $viewParams['person'] = $person; // collect parameters for filter $params['person'] = $person; - $statuses = $request->query->get('date_status', []); + $possibleStatuses = \array_merge(SingleTaskRepository::DATE_STATUSES, [ 'closed' ]); + $statuses = $request->query->get('status', $possibleStatuses); - if ($statuses) { - // check for invalid parameters - $diff = \array_diff( - $statuses, - SingleTaskRepository::DATE_STATUSES) - ; - - if (count($diff) > 0) { - return new Response( - 'date_status not allowed: '. \implode(', ', $diff), - Response::HTTP_BAD_REQUEST - ); - } + // check for invalid statuses + $diff = \array_diff($statuses, $possibleStatuses); + if (count($diff) > 0) { + return new Response( + 'date_status not allowed: '. \implode(', ', $diff), + Response::HTTP_BAD_REQUEST + ); } $viewParams['isSingleStatus'] = $singleStatus = count($statuses) === 1; - foreach(SingleTaskRepository::DATE_STATUSES as $type) { - if($request->query->has('date_status') - && FALSE === \in_array($type, $statuses ?? [])) { + foreach($statuses as $status) { + if($request->query->has('status') + && FALSE === \in_array($status, $statuses)) { continue; } - $params['date_status'] = $type; + // different query if regarding to date or 'closed' + if (in_array($status, SingleTaskRepository::DATE_STATUSES)) { + $params['date_status'] = $status; + $params['is_closed'] = false; + } else { + $params['date_status'] = null; + $params['is_closed'] = true; + } + $count = $taskRepository ->countByParameters($params, $this->getUser()) ; $paginator = $paginatorFactory->create($count); - $viewParams['single_task_'.$type.'_count'] = $count; - $viewParams['single_task_'.$type.'_paginator'] = $paginator; - $viewParams['single_task_'.$type.'_tasks'] = $taskRepository + $viewParams['single_task_'.$status.'_count'] = $count; + $viewParams['single_task_'.$status.'_paginator'] = $paginator; + $viewParams['single_task_'.$status.'_tasks'] = $taskRepository ->findByParameters($params, $this->getUser(), $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0, $singleStatus ? $paginator->getItemsPerPage() : 10) diff --git a/Controller/TaskController.php b/Controller/TaskController.php index 94e356941..d90ddf08c 100644 --- a/Controller/TaskController.php +++ b/Controller/TaskController.php @@ -71,10 +71,10 @@ class TaskController extends Controller $em->flush(); - $this->addFlash('success', 'The transition is sucessfully applyed'); + $this->addFlash('success', 'The transition is sucessfully appliyed'); } else { - $this->addFlash('error', 'The transition could not be applyed'); + $this->addFlash('error', 'The transition could not be appliyed'); } return $this->redirect($request->query->get('return_path', $defaultReturnPath)); diff --git a/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php b/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php index d52e5484d..fb1a9e2aa 100644 --- a/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php +++ b/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php @@ -39,8 +39,17 @@ class TaskWorkflowDefinitionCompilerPass implements CompilerPassInterface $workflowManagerDefinition = $container->getDefinition(TaskWorkflowManager::class); foreach ($container->findTaggedServiceIds('chill_task.workflow_definition') as $id => $tags) { + // registering the definition to manager $workflowManagerDefinition ->addMethodCall('addDefinition', [new Reference($id)]); + // adding a listener for currentStatus changes + $definition = $container->getDefinition($id); + $workflowManagerDefinition + ->addTag('kernel.event_listener', [ + 'event' => sprintf('workflow.%s.entered', $definition->getClass()::getAssociatedWorkflowName()), + 'method' => 'onTaskStateEntered', + 'priority' => -255 + ]); } } } diff --git a/Entity/AbstractTask.php b/Entity/AbstractTask.php index d9d59c7b6..401891e12 100644 --- a/Entity/AbstractTask.php +++ b/Entity/AbstractTask.php @@ -80,6 +80,12 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface * @Assert\NotNull() */ private $circle; + + /** + * @var boolean + * @ORM\Column(name="closed", type="boolean", options={ "default"=false }) + */ + private $closed = false; /** * Set type @@ -229,6 +235,21 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface return $this->getCircle(); } - + /** + * @return bool + */ + public function isClosed(): bool + { + return $this->closed; + } + + /** + * + * @param bool $closed + */ + public function setClosed(bool $closed) + { + $this->closed = $closed; + } } diff --git a/Repository/SingleTaskRepository.php b/Repository/SingleTaskRepository.php index 67a26fcf6..648484e87 100644 --- a/Repository/SingleTaskRepository.php +++ b/Repository/SingleTaskRepository.php @@ -69,6 +69,8 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository * - `person` : filter by person associated with the task ; * - `date_status`: type of task. To choose between : * `ended`, `warning`, `current`, `not_started` + * - `is_closed`: boolean. Indicate if the tasks must be closed (true) or + * opened * * @param type $params * @param User $currentUser @@ -90,14 +92,18 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository { $this->buildACLQuery($qb, $currentUser); - if (\array_key_exists('person', $params)) { + if (\array_key_exists('person', $params) and !empty($params['person'])) { $qb->andWhere($qb->expr()->eq('st.person', ':person')); $qb->setParameter('person', $params['person']); } - if (\array_key_exists('date_status', $params)) { + if (\array_key_exists('date_status', $params) and !empty($params['date_status'])) { $this->addTypeFilter($qb, $params); } + + if (\array_key_exists('is_closed', $params)) { + $qb->andWhere($this->buildIsClosed($qb, !$params['is_closed'])); + } } protected function addTypeFilter(QueryBuilder $qb, $params) @@ -106,7 +112,9 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository switch ($params['date_status']) { case self::DATE_STATUS_ENDED: - $andWhere->add($this->buildNowIsAfterEndDate($qb)); + $andWhere + ->add($this->buildNowIsAfterEndDate($qb)) + ; break; case self::DATE_STATUS_WARNING: @@ -155,7 +163,7 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository } } - private function buildNowIsAfterWarningDate(QueryBuilder $qb, $negative = false) + private function buildNowIsAfterWarningDate(QueryBuilder $qb, bool $negative = false) { if ($negative === false) { return $qb->expr()->andX() @@ -181,7 +189,7 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository } } - private function buildNowIsAfterStartDate(QueryBuilder $qb, $negative = false) + private function buildNowIsAfterStartDate(QueryBuilder $qb, bool $negative = false) { if ($negative === false) { return $qb->expr()->orX() @@ -197,6 +205,15 @@ class SingleTaskRepository extends \Doctrine\ORM\EntityRepository } } + private function buildIsClosed(QueryBuilder $qb, bool $negative = false) + { + if ($negative === false) { + return $qb->expr()->eq('st.closed', "'TRUE'"); + } else { + return $qb->expr()->eq('st.closed', "'FALSE'"); + } + } + protected function buildACLQuery(QueryBuilder $qb, User $currentUser) { diff --git a/Resources/migrations/Version20180426093011.php b/Resources/migrations/Version20180426093011.php new file mode 100644 index 000000000..286d4e3d1 --- /dev/null +++ b/Resources/migrations/Version20180426093011.php @@ -0,0 +1,28 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_task.single_task ADD closed BOOLEAN DEFAULT \'false\' NOT NULL'); + $this->addSql('ALTER TABLE chill_task.recurring_task ADD closed BOOLEAN DEFAULT \'false\' NOT NULL'); + } + + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_task.recurring_task DROP closed'); + $this->addSql('ALTER TABLE chill_task.single_task DROP closed'); + } +} diff --git a/Resources/views/Task/index.html.twig b/Resources/views/Task/index.html.twig index e37535395..dbeae6786 100644 --- a/Resources/views/Task/index.html.twig +++ b/Resources/views/Task/index.html.twig @@ -100,7 +100,7 @@ {% else %}