diff --git a/src/Bundle/ChillTask/.gitignore b/src/Bundle/ChillTask/.gitignore new file mode 100644 index 000000000..57872d0f1 --- /dev/null +++ b/src/Bundle/ChillTask/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/src/Bundle/ChillTask/.gitlab-ci.yml b/src/Bundle/ChillTask/.gitlab-ci.yml new file mode 100644 index 000000000..e9826c3fb --- /dev/null +++ b/src/Bundle/ChillTask/.gitlab-ci.yml @@ -0,0 +1,25 @@ +.test_definition: &test_definition + services: + - chill/database:latest + before_script: + - composer config github-oauth.github.com $GITHUB_TOKEN + - composer install + - cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml + - php Resources/test/Fixtures/App/app/console --env=test cache:warmup + - php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction + - php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction + + +stages: + - deploy + +deploy-packagist: + stage: deploy + image: chill/ci-image:php-7.2 + before_script: + # test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set + - if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi + - if [ -z ${PACKAGIST_TOKEN+x} ]; then echo "Please set PACKAGIST_TOKEN variable"; exit -1; fi + script: + - STATUSCODE=$(curl -XPOST -H'content-type:application/json' "https://packagist.org/api/update-package?username=$PACKAGIST_USERNAME&apiToken=$PACKAGIST_TOKEN" -d"{\"repository\":{\"url\":\"$CI_PROJECT_URL.git\"}}" --silent --output /dev/stderr --write-out "%{http_code}") + - if [ $STATUSCODE = "202" ]; then exit 0; else exit $STATUSCODE; fi diff --git a/src/Bundle/ChillTask/CHANGELOG.md b/src/Bundle/ChillTask/CHANGELOG.md new file mode 100644 index 000000000..c4662ce9d --- /dev/null +++ b/src/Bundle/ChillTask/CHANGELOG.md @@ -0,0 +1,52 @@ +Version 1.5.1 +============= + +- fix bug in filter task form: allow to show the list of users, which was hidden when the user had access to multiple centers; +- add assignee in task list; +- fix some translation; +- add a filtering by center on list; +- add color in boxes for task statuses; + +Version 1.5.4 +============= + +- adding indexes on place event and task + +Version 1.5.5 +============= + +- Fix error on the "see more" link which was not showed +- Layout of the task list + +Version 1.5.6 +============= + +- fix: validation error on warning date interval is not shown +- add privacy events to task show / list; +- add privacy events to task edit / update; + +Version 1.5.7 +============== + +- fix error when showing task list without person in context (issue #3) ; + +Version 1.5.8 +============= + +- add returnPath to page Show and List for Single tasks ; + +Version 1.5.9 +============= + +- better exception description when task workflow is not found ; + +Version 1.5.10 +============== + +- load webpack config using a `configure` function ; + +Version 1.5.11 +============== + +- [task] fix loading of chill task list ; + diff --git a/src/Bundle/ChillTask/ChillTaskBundle.php b/src/Bundle/ChillTask/ChillTaskBundle.php new file mode 100644 index 000000000..acd8b8607 --- /dev/null +++ b/src/Bundle/ChillTask/ChillTaskBundle.php @@ -0,0 +1,36 @@ + + * + * 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; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Chill\TaskBundle\DependencyInjection\Compiler\TaskWorkflowDefinitionCompilerPass; + +/** + * + * + */ +class ChillTaskBundle extends Bundle +{ + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new TaskWorkflowDefinitionCompilerPass()); + } +} diff --git a/src/Bundle/ChillTask/Controller/SingleTaskController.php b/src/Bundle/ChillTask/Controller/SingleTaskController.php new file mode 100644 index 000000000..fd5e26e33 --- /dev/null +++ b/src/Bundle/ChillTask/Controller/SingleTaskController.php @@ -0,0 +1,604 @@ +eventDispatcher = $eventDispatcher; + } + + + /** + * @Route( + * "/{_locale}/task/single-task/new", + * name="chill_task_single_task_new" + * ) + */ + public function newAction( + Request $request, + TranslatorInterface $translator + ) { + + $task = (new SingleTask()) + ->setAssignee($this->getUser()) + ->setType('task_default') + ; + + if ($request->query->has('person_id')) { + $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) { + $this->createNotFoundException("Invalid person id"); + } + + $task->setPerson($person); + } + + $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); + + $this->eventDispatcher->dispatch(TaskEvent::PERSIST, new TaskEvent($task)); + + $em->flush(); + + $this->addFlash('success', $translator->trans("The task is created")); + + return $this->redirectToRoute('chill_task_singletask_list', [ + 'person_id' => $task->getPerson()->getId() + ]); + + } else { + $this->addFlash('error', $translator->trans("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::SHOW, $task, 'You are not ' + . 'allowed to view this task'); + + if (!$task) { + throw $this->createNotFoundException('Unable to find Task entity.'); + } + + $timeline = $this->get('chill.main.timeline_builder') + ->getTimelineHTML('task', array('task' => $task)); + + $event = new PrivacyEvent($person, array( + 'element_class' => SingleTask::class, + 'element_id' => $task->getId(), + 'action' => 'show' + )); + $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + + return $this->render('ChillTaskBundle:SingleTask:show.html.twig', array( + 'task' => $task, + 'timeline' => $timeline + )); + } + + + /** + * @Route( + * "/{_locale}/task/single-task/{id}/edit", + * name="chill_task_single_task_edit" + * ) + */ + public function editAction( + Request $request, + $id, + TranslatorInterface $translator + ) { + /* @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.'); + } + + $event = (new UIEvent('single-task', $task)) + ->setForm($this->setCreateForm($task, new Role(TaskVoter::UPDATE))) + ; + + $this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event); + + $form = $event->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted()) { + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($task); + + $em->flush(); + + $this->addFlash('success', $translator + ->trans("The task has been updated")); + + $event = new PrivacyEvent($person, array( + 'element_class' => SingleTask::class, + 'element_id' => $task->getId(), + 'action' => 'update' + )); + $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + + return $this->redirectToRoute( + 'chill_task_singletask_list', + $request->query->get('list_params', []) + ); + + } else { + $this->addFlash('error', $translator->trans("This form contains errors")); + } + } + + $this->eventDispatcher->dispatch(UIEvent::EDIT_PAGE, $event); + + if ($event->hasResponse()) { + return $event->getResponse(); + } + + $event = new PrivacyEvent($person, array( + 'element_class' => SingleTask::class, + 'element_id' => $task->getId(), + 'action' => 'edit' + )); + $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + + 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, + TranslatorInterface $translator + ) { + /* @var $taskRepository SingleTaskRepository */ + $taskRepository = $this->get('chill_task.single_task_repository'); + + $task = $taskRepository->find($id); + + if (!$task) { + throw $this->createNotFoundException('Unable to find Task entity.'); + } + + 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'); + + $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', $translator + ->trans("The task has been successfully removed.")); + + return $this->redirect($this->generateUrl( + 'chill_task_singletask_list', + $request->query->get('list_params', [ + 'person_id' => $person->getId() + ]))); + } + } + + + 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 setCreateForm(SingleTask $task, Role $role) + { + $form = $this->createForm(SingleTaskType::class, $task, [ + 'center' => $task->getCenter(), + 'role' => $role + ]); + + $form->add('submit', SubmitType::class); + + return $form; + } + + /** + * + * @return Response + * @Route( + * "/{_locale}/task/single-task/list/my", + * name="chill_task_single_my_tasks" + * ) + */ + public function myTasksAction(TranslatorInterface $translator) + { + return $this->redirectToRoute('chill_task_singletask_list', [ + 'user_id' => $this->getUser()->getId(), + 'hide_form' => true, + 'title' => $translator->trans('My tasks') + ]); + } + + /** + * + * Arguments: + * - user_id + * - scope_id + * - person_id + * - hide_form (hide the form to filter the tasks) + * - status: date state, amongst SingleTaskRepository::DATE_STATUSES, or 'closed' + * + * @Route( + * "/{_locale}/task/singletask/list", + * name="chill_task_singletask_list" + * ) + */ + public function listAction( + Request $request, + PaginatorFactory $paginatorFactory, + SingleTaskRepository $taskRepository, + PersonRepository $personRepository, + CenterRepository $centerRepository, + FormFactoryInterface $formFactory + ) { + /* @var $viewParams array The parameters for the view */ + /* @var $params array The parameters for the query */ + + $viewParams['person'] = null; + $params['person'] = null; + $viewParams['user'] = null; + $params['user'] = null; + $viewParams['center'] = null; + $params['types'] = null; + + // Get parameters from url + if (!empty($request->query->get('person_id', NULL))) { + $personId = $request->query->getInt('person_id'); + $person = $personRepository->find($personId); + + if ($person === null) { + throw $this->createNotFoundException("This person ' $personId ' does not exist."); + } + + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person); + + $viewParams['person'] = $person; + $params['person'] = $person; + } elseif (!empty($request->query->get('center_id', NULL))) { + $center = $centerRepository->find($request->query->getInt('center_id')); + + if ($center === null) { + throw $this->createNotFoundException('center not found'); + } + + $params['center'] = $center; + } + + if(!empty($request->query->get('types', []))) { + $types = $request->query->get('types', []); + if (count($types) > 0) { + $params['types'] = $types; + } + } + + if (!empty($request->query->get('user_id', null))) { + if ($request->query->get('user_id') === '_unassigned') { + $params['unassigned'] = true; + } else { + $userId = $request->query->getInt('user_id', null); + $user = $this->getDoctrine()->getManager() + ->getRepository('ChillMainBundle:User') + ->find($userId); + + if ($user === null) { + throw $this->createNotFoundException("This user ' $userId ' does not exist."); + } + + $viewParams['user'] = $user; + $params['user'] = $user; + } + } + + if (!empty($request->query->get('scope_id'))) { + + $scopeId = $request->query->getInt('scope_id', null); + $scope = $this->getDoctrine()->getManager() + ->getRepository('ChillMainBundle:Scope') + ->find($scopeId); + + if ($scope === null) { + throw $this->createNotFoundException("This scope' $scopeId 'does not exist."); + } + + $viewParams['scope'] = $scope; + $params['scope'] = $scope; + } + + // collect parameters for filter + $possibleStatuses = \array_merge(SingleTaskRepository::DATE_STATUSES, [ 'closed' ]); + $statuses = $request->query->get('status', $possibleStatuses); + + // 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; + + $tasks_count = 0; + + foreach($statuses as $status) { + if($request->query->has('status') + && FALSE === \in_array($status, $statuses)) { + continue; + } + + // 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_'.$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) + ; + + $tasks_count = $tasks_count + $count; + } + + // total number of tasks + $viewParams['tasks_count'] = $tasks_count; + + if ($viewParams['person'] !== null){ + $viewParams['layout'] = 'ChillPersonBundle::layout.html.twig'; + } else { + $viewParams['layout'] = 'ChillMainBundle::layout.html.twig'; + } + + // Form for filtering tasks + $form = $formFactory->createNamed(null, SingleTaskListType::class, null, [ + 'person' => $viewParams['person'], + 'method' => Request::METHOD_GET, + 'csrf_protection' => false, + 'add_type' => true + ]); + + $form->handleRequest($request); + + if (isset($person)) { + $event = new PrivacyEvent($person, array( + 'element_class' => SingleTask::class, + 'action' => 'list' + )); + $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); + } + + return $this->render('ChillTaskBundle:SingleTask:index.html.twig', + \array_merge($viewParams, [ 'form' => $form->createView() ])); + } + + + 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; + } + + /** + * Creates a form to delete a Task entity by id. + * + * @param mixed $id The entity id + * + * @return \Symfony\Component\Form\Form The form + */ + 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() + ; + } + +} diff --git a/src/Bundle/ChillTask/Controller/TaskController.php b/src/Bundle/ChillTask/Controller/TaskController.php new file mode 100644 index 000000000..3fc3f1dee --- /dev/null +++ b/src/Bundle/ChillTask/Controller/TaskController.php @@ -0,0 +1,142 @@ +find($taskId) + ; + $defaultReturnPath = $this->generateUrl( + 'chill_task_single_task_show', + [ + 'id' => $task->getId(), + 'list_params' => $request->query->get('list_params', []) + ]); + $defaultTemplate = '@ChillTask/SingleTask/transition.html.twig'; + break; + default: + return new Response("The type '$kind' is not implemented", + Response::HTTP_BAD_REQUEST); + } + + if (NULL === $task) { + throw $this->createNotFoundException("task with id '$taskId' and type " + . "'$type' does not exists"); + } + + $workflow = $registry->get($task); + + if (!$workflow->can($task, $transition)) { + throw $this->createAccessDeniedException('You are not allowed to apply this transition'); + } + $transitionInstance = \array_values( // array_values needed to reset keys (array_filter preserves keys) + \array_filter( + $workflow->getEnabledTransitions($task), + function(Transition $t) use ($transition) { + return $t->getName() === $transition; + } + ))[0]; + + $form = $this->createTransitionForm($task); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + + if ($workflow->can($task, $transition)) { + $workflow->apply($task, $transition); + + $em->flush(); + + $this->addFlash('success', $translator->trans('The transition is successfully applied')); + + } else { + $this->addFlash('error', $translator->trans('The transition could not be applied')); + } + + return $this->redirect($defaultReturnPath); + } else { + $event = (new UIEvent($kind, $task)) + ->setForm($form) + ->setTransition($transitionInstance) + ; + + $eventDispatcher->dispatch(UIEvent::SHOW_TRANSITION_PAGE, $event); + + if ($event->hasResponse()) { + return $event->getResponse(); + } else { + // we simply check that the user can see the task. Other ACL checks + // should be performed using `guard` events. + $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task); + + return $this->render($defaultTemplate, [ + 'task' => $task, + 'form' => $form->createView(), + 'transition' => $transitionInstance + ]); + } + } + } + + /** + * + * @param \Chill\TaskBundle\Controller\AbstractTask $task + * @return \Symfony\Component\Form\FormInterface + */ + protected function createTransitionForm(AbstractTask $task) + { + $builder = $this->createFormBuilder($task); + $builder->add('submit', SubmitType::class); + + return $builder->getForm(); + } +} diff --git a/src/Bundle/ChillTask/DataFixtures/ORM/LoadTaskACL.php b/src/Bundle/ChillTask/DataFixtures/ORM/LoadTaskACL.php new file mode 100644 index 000000000..a365f82d9 --- /dev/null +++ b/src/Bundle/ChillTask/DataFixtures/ORM/LoadTaskACL.php @@ -0,0 +1,91 @@ + + * + * 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 & Chill_TASK_TASK_DELETE 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); + $roleScopeDelete = (new RoleScope()) + ->setRole(TaskVoter::DELETE) + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeDelete); + + $manager->persist($roleScopeUpdate); + $manager->persist($roleScopeCreate); + $manager->persist($roleScopeDelete); + } + + } + + $manager->flush(); + } + +} diff --git a/src/Bundle/ChillTask/DependencyInjection/ChillTaskExtension.php b/src/Bundle/ChillTask/DependencyInjection/ChillTaskExtension.php new file mode 100644 index 000000000..3eda043a5 --- /dev/null +++ b/src/Bundle/ChillTask/DependencyInjection/ChillTaskExtension.php @@ -0,0 +1,103 @@ +processConfiguration($configuration, $configs); + + $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'); + $loader->load('services/workflow.yml'); + $loader->load('services/templating.yml'); + $loader->load('services/menu.yml'); + $loader->load('services/event.yml'); + $loader->load('services/timeline.yml'); + $loader->load('services/fixtures.yml'); + $loader->load('services/form.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' + ) + ) + )); + } + + protected function prependAuthorization(ContainerBuilder $container) + { + $container->prependExtensionConfig('security', array( + 'role_hierarchy' => array( + TaskVoter::UPDATE => [TaskVoter::SHOW], + TaskVoter::CREATE => [TaskVoter::SHOW] + ) + )); + } + + protected function prependWorkflows(ContainerBuilder $container) + { + $container->prependExtensionConfig('framework', [ + 'workflows' => [ + 'task_default' => [ + 'marking_store' => [ + 'type' => 'multiple_state', + 'arguments' => [ + 'currentStates' + ], + ], + 'type' => 'state_machine', + 'support_strategy' => TaskWorkflowManager::class, + 'places' => [ 'new', 'in_progress', 'closed', 'canceled'], + 'initial_place' => 'new', + 'transitions' => [ + 'start' => [ + 'from' => 'new', + 'to' => 'in_progress' + ], + 'close' => [ + 'from' => ['new', 'in_progress'], + 'to' => 'closed' + ], + 'cancel' => [ + 'from' => ['new', 'in_progress'], + 'to' => 'canceled' + ] + ] + ] + ] + ]); + } +} diff --git a/src/Bundle/ChillTask/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php b/src/Bundle/ChillTask/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php new file mode 100644 index 000000000..76e849d73 --- /dev/null +++ b/src/Bundle/ChillTask/DependencyInjection/Compiler/TaskWorkflowDefinitionCompilerPass.php @@ -0,0 +1,71 @@ + + * + * 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; +use Chill\TaskBundle\Templating\UI\CountNotificationTask; +use Chill\TaskBundle\Event\Lifecycle\TaskLifecycleEvent; + +/** + * + * + * @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); + $counterDefinition = $container->getDefinition(CountNotificationTask::class); + $lifecycleDefinition = $container->getDefinition(TaskLifecycleEvent::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 + ]); + $counterDefinition + ->addTag('kernel.event_listener', [ + 'event' => sprintf('workflow.%s.entered', $definition->getClass()::getAssociatedWorkflowName()), + 'method' => 'resetCacheOnNewStates', + 'priority' => 0 + ]); + $lifecycleDefinition + ->addTag('kernel.event_listener', [ + 'event' => sprintf('workflow.%s.transition', $definition->getClass()::getAssociatedWorkflowName()), + 'method' => 'onTransition', + 'priority' => 0 + ]); + } + } +} diff --git a/src/Bundle/ChillTask/DependencyInjection/Configuration.php b/src/Bundle/ChillTask/DependencyInjection/Configuration.php new file mode 100644 index 000000000..e0989cd69 --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Entity/AbstractTask.php b/src/Bundle/ChillTask/Entity/AbstractTask.php new file mode 100644 index 000000000..73e98c0dd --- /dev/null +++ b/src/Bundle/ChillTask/Entity/AbstractTask.php @@ -0,0 +1,260 @@ +type = (string) $type; + + return $this; + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set currentStates + * + * The current states are sorted in a single array, non associative. + * + * @param $currentStates + * + * @return AbstractTask + */ + public function setCurrentStates($currentStates) + { + $this->currentStates = \array_keys($currentStates); + + return $this; + } + + /** + * Get currentStates + * + * The states are returned as required by marking store format. + * + * @return array + */ + public function getCurrentStates() + { + return \array_fill_keys($this->currentStates, 1); + } + + /** + * Set title + * + * @param string $title + * + * @return AbstractTask + */ + public function setTitle($title) + { + $this->title = (string) $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 = (string) $description; + + return $this; + } + + /** + * Get description + * + * @return string + */ + public function getDescription() + { + 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 = null) + { + $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(); + } + + /** + * @return bool + */ + public function isClosed(): bool + { + return $this->closed; + } + + /** + * + * @param bool $closed + */ + public function setClosed(bool $closed) + { + $this->closed = $closed; + } +} + diff --git a/src/Bundle/ChillTask/Entity/RecurringTask.php b/src/Bundle/ChillTask/Entity/RecurringTask.php new file mode 100644 index 000000000..20bffab18 --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Entity/SingleTask.php b/src/Bundle/ChillTask/Entity/SingleTask.php new file mode 100644 index 000000000..03c904497 --- /dev/null +++ b/src/Bundle/ChillTask/Entity/SingleTask.php @@ -0,0 +1,234 @@ + this.getStartDate()", + * message="The start date must be before warning date" + * ) + */ + private $startDate; + + /** + * @var \DateTime + * + * @ORM\Column(name="end_date", type="date", nullable=true) + * @Assert\Date() + */ + private $endDate; + + /** + * @var \DateInterval + * and this.getEndDate() === null + * + * @ORM\Column(name="warning_interval", type="dateinterval", nullable=true) + * + * @Assert\Expression( + * "!(value != null and this.getEndDate() == null)", + * message="An end date is required if a warning interval is set" + * ) + * + * + */ + private $warningInterval; + + /** + * + * @var RecurringTask + * @ORM\ManyToOne( + * targetEntity="RecurringTask", + * inversedBy="singleTasks" + * ) + */ + private $recurringTask; + + /** + * + * @var \Doctrine\Common\Collections\Collection + * @ORM\OneToMany( + * targetEntity="\Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent", + * mappedBy="task", + * cascade={ "remove" } + * ) + */ + private $taskPlaceEvents; + + public function __construct() + { + $this->taskPlaceEvents = $events = new \Doctrine\Common\Collections\ArrayCollection; + + parent::__construct(); + } + + + /** + * Get id + * + * @return int + */ + public function getId() + { + return $this->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() + { + 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; + } + + function setRecurringTask(RecurringTask $recurringTask) + { + $this->recurringTask = $recurringTask; + } + + public function getTaskPlaceEvents(): Collection + { + return $this->taskPlaceEvents; + } + + public function setTaskPlaceEvents(Collection $taskPlaceEvents) + { + $this->taskPlaceEvents = $taskPlaceEvents; + + return $this; + } +} + diff --git a/src/Bundle/ChillTask/Entity/Task/AbstractTaskPlaceEvent.php b/src/Bundle/ChillTask/Entity/Task/AbstractTaskPlaceEvent.php new file mode 100644 index 000000000..91ce89a15 --- /dev/null +++ b/src/Bundle/ChillTask/Entity/Task/AbstractTaskPlaceEvent.php @@ -0,0 +1,154 @@ +datetime = new \DateTimeImmutable('now'); + } + + /** + * Get id. + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * Set datetime. + * + * @param datetime_immutable $datetime + * + * @return AbstractTaskPlaceEvent + */ + public function setDatetime($datetime) + { + $this->datetime = $datetime; + + return $this; + } + + /** + * Get datetime. + * + * @return datetime_immutable + */ + public function getDatetime() + { + return $this->datetime; + } + + /** + * Set transition. + * + * @param string $transition + * + * @return AbstractTaskPlaceEvent + */ + public function setTransition($transition) + { + $this->transition = $transition; + + return $this; + } + + /** + * Get transition. + * + * @return string + */ + public function getTransition() + { + return $this->transition; + } + + /** + * Set data. + * + * @param string $data + * + * @return AbstractTaskPlaceEvent + */ + public function setData($data) + { + $this->data = $data; + + return $this; + } + + /** + * Get data. + * + * @return string + */ + public function getData() + { + return $this->data; + } + + public function getAuthor(): User + { + return $this->author; + } + + public function setAuthor(User $author) + { + $this->author = $author; + + return $this; + } + + +} diff --git a/src/Bundle/ChillTask/Entity/Task/SingleTaskPlaceEvent.php b/src/Bundle/ChillTask/Entity/Task/SingleTaskPlaceEvent.php new file mode 100644 index 000000000..75dfd82d6 --- /dev/null +++ b/src/Bundle/ChillTask/Entity/Task/SingleTaskPlaceEvent.php @@ -0,0 +1,67 @@ + + * + * 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\Entity\Task; + +use Doctrine\ORM\Mapping as ORM; +use Chill\TaskBundle\Entity\SingleTask; + +/** + * + * + * @ORM\Table( + * "chill_task.single_task_place_event", + * indexes={ + * @ORM\Index( + * name="transition_task_date", + * columns={"task_id", "transition", "datetime"} + * ), + * @ORM\Index( + * name="transition_task", + * columns={"task_id", "transition"} + * ) + * }) + * @ORM\Entity() + * + * @author Julien Fastré + */ +class SingleTaskPlaceEvent extends AbstractTaskPlaceEvent +{ + /** + * + * @var SingleTask + * @ORM\ManyToOne( + * targetEntity="\Chill\TaskBundle\Entity\SingleTask", + * inversedBy="taskPlaceEvents" + * ) + */ + protected $task; + + public function getTask(): SingleTask + { + return $this->task; + } + + public function setTask(SingleTask $task) + { + $this->task = $task; + + return $this; + } + + +} diff --git a/src/Bundle/ChillTask/Event/Lifecycle/TaskLifecycleEvent.php b/src/Bundle/ChillTask/Event/Lifecycle/TaskLifecycleEvent.php new file mode 100644 index 000000000..74bd33cc9 --- /dev/null +++ b/src/Bundle/ChillTask/Event/Lifecycle/TaskLifecycleEvent.php @@ -0,0 +1,104 @@ + + * + * 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\Event\Lifecycle; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Chill\TaskBundle\Event\TaskEvent; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent; +use Symfony\Component\Workflow\Event\Event as WorkflowEvent; + +/** + * + * + * @author Julien Fastré + */ +class TaskLifecycleEvent implements EventSubscriberInterface +{ + /** + * + * @var TokenStorageInterface + */ + protected $tokenStorage; + + /** + * + * @var EntityManagerInterface + */ + protected $em; + + public function __construct( + TokenStorageInterface $tokenStorage, + EntityManagerInterface $em + ) { + $this->tokenStorage = $tokenStorage; + $this->em = $em; + } + + + public static function getSubscribedEvents(): array + { + return [ + TaskEvent::PERSIST => [ + 'onTaskPersist' + ] + ]; + } + + public function onTaskPersist(TaskEvent $e) + { + $task = $e->getTask(); + $user = $this->tokenStorage->getToken()->getUser(); + + $event = (new SingleTaskPlaceEvent()) + ->setTask($task) + ->setAuthor($user) + ->setTransition('_creation') + ->setData([ + 'new_states' => $task->getCurrentStates() + ]) + ; + + $task->getTaskPlaceEvents()->add($event); + + $this->em->persist($event); + } + + public function onTransition(WorkflowEvent $e) + { + $task = $e->getSubject(); + $user = $this->tokenStorage->getToken()->getUser(); + + $event = (new SingleTaskPlaceEvent()) + ->setTask($task) + ->setAuthor($user) + ->setTransition($e->getTransition()->getName()) + ->setData([ + 'old_states' => $e->getTransition()->getFroms(), + 'new_states' => $e->getTransition()->getTos(), + 'workflow' => $e->getWorkflowName() + ]) + ; + + $task->getTaskPlaceEvents()->add($event); + + $this->em->persist($event); + } + +} diff --git a/src/Bundle/ChillTask/Event/TaskEvent.php b/src/Bundle/ChillTask/Event/TaskEvent.php new file mode 100644 index 000000000..070118c4d --- /dev/null +++ b/src/Bundle/ChillTask/Event/TaskEvent.php @@ -0,0 +1,55 @@ + + * + * 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\Event; + +use Chill\TaskBundle\Entity\AbstractTask; +use Symfony\Component\EventDispatcher\Event; + +/** + * + * + * @author Julien Fastré + */ +class TaskEvent extends Event +{ + const PERSIST = 'chill_task.task_persist'; + + /** + * + * @var AbstractTask + */ + protected $task; + + public function __construct(AbstractTask $task) + { + $this->task = $task; + } + + public function getTask(): AbstractTask + { + return $this->task; + } + + public function setTask(AbstractTask $task) + { + $this->task = $task; + + return $this; + } +} + diff --git a/src/Bundle/ChillTask/Event/UI/UIEvent.php b/src/Bundle/ChillTask/Event/UI/UIEvent.php new file mode 100644 index 000000000..e041da542 --- /dev/null +++ b/src/Bundle/ChillTask/Event/UI/UIEvent.php @@ -0,0 +1,136 @@ + + */ +class UIEvent extends Event +{ + const SHOW_TRANSITION_PAGE = 'chill_task.show_transition_page'; + const EDIT_FORM = 'chill_task.edit_form'; + const EDIT_PAGE = 'chill_task.edit_page'; + + /** + * + * @var string + */ + protected $kind; + + /** + * + * @var AbstractTask + */ + protected $task; + + /** + * + * @var Response|null + */ + protected $response = null; + + /** + * + * @var FormInterface|null + */ + protected $form = null; + + /** + * @var Transition + */ + protected $transition = null; + + public function __construct($kind, AbstractTask $task) + { + $this->kind = $kind; + $this->task = $task; + } + + /** + * + * @return string + */ + public function getKind() + { + return $this->kind; + } + + /** + * + * @return AbstractTask + */ + public function getTask(): AbstractTask + { + return $this->task; + } + + /** + * + * @return FormInterface|null + */ + public function getForm() + { + return $this->form; + } + + public function setForm(FormInterface $form) + { + $this->form = $form; + + return $this; + } + + /** + * + * @return Transition + */ + public function getTransition() + { + return $this->transition; + } + + public function setTransition(Transition $transition) + { + $this->transition = $transition; + + return $this; + } + + /** + * + * @return Response + */ + public function getResponse(): Response + { + return $this->response; + } + + /** + * + * @param Response $response + * @return $this + */ + public function setResponse(Response $response) + { + $this->response = $response; + + return $this; + } + + public function hasResponse() + { + return $this->response instanceof Response; + } + + +} diff --git a/src/Bundle/ChillTask/Form/SingleTaskListType.php b/src/Bundle/ChillTask/Form/SingleTaskListType.php new file mode 100644 index 000000000..99e8ddb42 --- /dev/null +++ b/src/Bundle/ChillTask/Form/SingleTaskListType.php @@ -0,0 +1,295 @@ + + * + * 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 Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Chill\MainBundle\Entity\User; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Chill\TaskBundle\Repository\SingleTaskRepository; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\Security\Core\Role\Role; +use Chill\TaskBundle\Entity\SingleTask; +use Chill\PersonBundle\Form\Type\PickPersonType; +use Chill\PersonBundle\Entity\Person; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Chill\PersonBundle\Form\DataTransformer\PersonToIdTransformer; +use Chill\TaskBundle\Workflow\TaskWorkflowManager; + +/** + * + * + * @author Julien Fastré + */ +class SingleTaskListType extends AbstractType +{ + /** + * + * @var EntityManagerInterface + */ + protected $em; + + /** + * + * @var TokenStorageInterface + */ + protected $tokenStorage; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var TaskWorkflowManager + */ + protected $taskWorkflowManager; + + public function __construct( + EntityManagerInterface $em, + TokenStorageInterface $tokenStorage, + AuthorizationHelper $authorizationHelper, + TaskWorkflowManager $taskWorkflowManager + ) { + $this->em = $em; + $this->tokenStorage = $tokenStorage; + $this->authorizationHelper = $authorizationHelper; + $this->taskWorkflowManager = $taskWorkflowManager; + } + + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $statuses = [ + 'Tasks not started' => SingleTaskRepository::DATE_STATUS_NOT_STARTED, + 'Tasks with expired deadline' => SingleTaskRepository::DATE_STATUS_ENDED, + 'Tasks with warning deadline reached' => SingleTaskRepository::DATE_STATUS_WARNING, + 'Current tasks' => SingleTaskRepository::DATE_STATUS_CURRENT, + 'Closed tasks' => 'closed' + ]; + + $builder + ->add('user_id', ChoiceType::class, [ + 'choices' => $this->getUserChoices($options), + 'placeholder' => 'Any user', + 'required' => false, + 'label' => 'Assignee' + ]) + ; + + if ($options['add_status']) { + $builder + ->add('status', ChoiceType::class, [ + 'choices' => $statuses, + 'expanded' => true, + 'multiple' => true, + 'label' => 'status' + ]); + } + + if ($options['add_type']) { + $types = $this->getTaskTypesChoices($options); + + if (count($types) > 0) { + $builder->add('types', ChoiceType::class, [ + 'choices' => $types, + 'required' => false, + 'expanded' => true, + 'multiple' => true, + 'label' => 'Task types' + ]); + } + + } + + if ($options['person'] === null) { + $builder + ->add('person_id', PickPersonType::class, [ + 'centers' => $this->authorizationHelper + ->getReachableCenters( + $this->tokenStorage->getToken()->getUser(), + new Role(TaskVoter::SHOW) + ), + 'required' => false, + 'label' => 'Associated person' + ]) + ; + $reachablesCenters = $this->getReachablesCenters(); + if (count($reachablesCenters) > 1) { + $builder + ->add('center_id', EntityType::class, [ + 'class' => \Chill\MainBundle\Entity\Center::class, + 'choices' => $reachablesCenters, + 'label' => 'Center', + 'required' => false, + 'placeholder' => 'All centers' + ]); + } + } else { + // add a hidden field + $builder + ->add('person_id', HiddenType::class); + $builder->get('person_id') + ->addModelTransformer(new PersonToIdTransformer($this->em)) + ; + } + + } + + protected function getUserChoices($options) + { + $users = $this->getUsersAssigneedToTask($options); + $choices = \array_combine( + // get usernames + \array_map(function(User $user) { return $user->getUsername(); }, $users), + // get ids + \array_map(function(User $user) { return $user->getId(); }, $users) + ); + $choices['Unassigned'] = '_unassigned'; + + return $choices; + } + + protected function getTaskTypesChoices($options) + { + $qb = $this->em->createQueryBuilder(); + $user = $this->tokenStorage->getToken()->getUser(); + $role = new Role(TaskVoter::SHOW); + $centers = $this->authorizationHelper->getReachableCenters($user, $role); + + $qb->select('DISTINCT task.type AS type') + ->from(SingleTask::class, 'task') + ->join('task.person', 'person') + ; + + $i = 0; + $orCenters = $qb->expr()->orX(); + foreach($centers as $center) { + $circles = $this->authorizationHelper->getReachableCircles($user, $role, $center); + + if (count($circles) > 0) { + $andX = $qb->expr()->andX(); + $andX + ->add($qb->expr()->eq('person.center', ':center_'.$i)) + ->add($qb->expr()->in('task.circle', ':circles_'.$i)) + ; + $orCenters->add($andX); + + $qb + ->setParameter('center_'.$i, $center) + ->setParameter('circles_'.$i, $circles) + ; + $i++; + } + } + + if ($i > 0) { + $qb->where($orCenters); + } + + $types = $qb->getQuery()->getResult(); + + $choices = []; + + foreach ($types as $row) { + $fake = (new SingleTask())->setType($row['type']); + $label = $this->taskWorkflowManager->getWorkflowMetadata($fake, 'definition.name'); + $choices[$label] = $row['type']; + } + + return $choices; + } + + /** + * Return a list of user having a task assigned. + * + * @return User[] + */ + protected function getUsersAssigneedToTask($options) + { + $qb = $this->em->createQueryBuilder(); + $user = $this->tokenStorage->getToken()->getUser(); + $role = new Role(TaskVoter::SHOW); + $centers = $this->authorizationHelper->getReachableCenters($user, $role); + + $qb->select('DISTINCT user') + ->from(User::class, 'user') + ->join(SingleTask::class, 'task', \Doctrine\ORM\Query\Expr\Join::WITH, 'task.assignee = user') + ->join('task.person', 'person') + ->where("user.enabled = 'TRUE'") + ; + + if (NULL !== $options['person']) { + $qb + ->andWhere($qb->expr()->eq('task.person', ':person')) + ->setParameter('person', $options['person']) + ; + } + + $i = 0; + $circleCenterCond = $qb->expr()->orX(); + foreach ($centers as $center) { + $circles = $this->authorizationHelper->getReachableCircles($user, $role, $center); + // add condition about person and circle + $circleCenterCond->add( + $qb->expr()->andX() + ->add($qb->expr()->eq('person.center', ':center_'.$i)) + ->add($qb->expr()->in('task.circle', ':circles_'.$i)) + ); + + $qb->setParameter('center_'.$i, $center) + ->setParameter('circles_'.$i, $circles) + ; + // increase counter + $i++; + } + $qb->andWhere($circleCenterCond); + + return $qb->getQuery()->getResult(); + } + + protected function getReachablesCenters() + { + $user = $this->tokenStorage->getToken()->getUser(); + $role = new Role(TaskVoter::SHOW); + + return $this->authorizationHelper->getReachableCenters($user, $role); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefined('person') + ->setDefault('person', null) + ->setAllowedTypes('person', [Person::class, 'null']) + ->setDefined('add_status') + ->setDefault('add_status', false) + ->setAllowedTypes('add_status', ['bool']) + ->setDefined('add_type') + ->setDefault('add_type', false) + ->setAllowedTypes('add_type', ['bool']) + ; + } +} diff --git a/src/Bundle/ChillTask/Form/SingleTaskType.php b/src/Bundle/ChillTask/Form/SingleTaskType.php new file mode 100644 index 000000000..d215c2b75 --- /dev/null +++ b/src/Bundle/ChillTask/Form/SingleTaskType.php @@ -0,0 +1,80 @@ + + * + * 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 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; +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; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Chill\MainBundle\Form\Type\DateIntervalType; + +/** + * + * + * @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' => $options['role'], + 'placeholder' => 'Not assigned' + ]) + ->add('circle', ScopePickerType::class, [ + 'center' => $options['center'], + 'role' => $options['role'] + ]) + ->add('startDate', ChillDateType::class, [ + 'required' => false + ]) + ->add('endDate', ChillDateType::class, [ + 'required' => false + ]) + ->add('warningInterval', DateIntervalType::class, [ + 'required' => false + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired('center') + ->setAllowedTypes('center', [ Center::class ]) + ->setRequired('role') + ->setAllowedTypes('role', [ Role::class ]) + ; + } +} diff --git a/src/Bundle/ChillTask/Menu/PersonMenuBuilder.php b/src/Bundle/ChillTask/Menu/PersonMenuBuilder.php new file mode 100644 index 000000000..ee0afa497 --- /dev/null +++ b/src/Bundle/ChillTask/Menu/PersonMenuBuilder.php @@ -0,0 +1,80 @@ + + * + * 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\Menu; + +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Knp\Menu\MenuItem; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * + * + * @author Julien Fastré + */ +class PersonMenuBuilder implements LocalMenuBuilderInterface +{ + /** + * + * @var TranslatorInterface + */ + protected $translator; + + /** + * + * @var AuthorizationCheckerInterface + */ + protected $authorizationChecker; + + public function __construct( + AuthorizationCheckerInterface $authorizationChecker, + TranslatorInterface $translator) + { + $this->translator = $translator; + $this->authorizationChecker = $authorizationChecker; + } + + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + /* @var $person \Chill\PersonBundle\Entity\Person */ + $person = $parameters['person'] ?? null; + + if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $person)) { + $menu->addChild( + $this->translator->trans('Tasks'), [ + 'route' => 'chill_task_singletask_list', + 'routeParameters' => $menuId === 'person' ? + [ 'person_id' => $person->getId() ] + : + null, + ]) + ->setExtra('order', 400) + ; + if ($menuId === 'section') { + $menu->setExtra('icons', 'tasks'); + } + } + } + + public static function getMenuIds(): array + { + return ['person']; + } +} diff --git a/src/Bundle/ChillTask/Menu/SectionMenuBuilder.php b/src/Bundle/ChillTask/Menu/SectionMenuBuilder.php new file mode 100644 index 000000000..7ed84dbf5 --- /dev/null +++ b/src/Bundle/ChillTask/Menu/SectionMenuBuilder.php @@ -0,0 +1,78 @@ + + * + * 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\Menu; + +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Knp\Menu\MenuItem; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Chill\TaskBundle\Security\Authorization\TaskVoter; +use Symfony\Component\Translation\TranslatorInterface; + + +/** + * + * + */ +class SectionMenuBuilder implements LocalMenuBuilderInterface +{ + /** + * + * @var AuthorizationCheckerInterface + */ + public $authorizationChecker; + + /** + * + * @var TranslatorInterface + */ + public $translator; + + public function __construct( + AuthorizationCheckerInterface $authorizationChecker, + TranslatorInterface $translator + ) { + $this->authorizationChecker = $authorizationChecker; + $this->translator = $translator; + } + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + if (FALSE === $this->authorizationChecker->isGranted(TaskVoter::SHOW)) { + return; + } + + $menu->addChild( + $this->translator->trans("Tasks"), + [ + 'route' => 'chill_task_singletask_list', [ + 'routeParameters' => [ + 'hide_form' => false + ] + ]]) + ->setExtras([ + 'order'=> 50, + 'icon' => 'exclamation-triangle', + 'entryclass' => 'user_menu__entry--warning-entry' + ]); + } + + public static function getMenuIds(): array + { + return [ 'section' ]; + } +} diff --git a/src/Bundle/ChillTask/Menu/UserMenuBuilder.php b/src/Bundle/ChillTask/Menu/UserMenuBuilder.php new file mode 100644 index 000000000..e29239679 --- /dev/null +++ b/src/Bundle/ChillTask/Menu/UserMenuBuilder.php @@ -0,0 +1,142 @@ + + * + * 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\Menu; + +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Knp\Menu\MenuItem; +use Chill\TaskBundle\Templating\UI\CountNotificationTask; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\TaskBundle\Repository\SingleTaskRepository; +use Symfony\Component\Translation\TranslatorInterface; +use Chill\MainBundle\Entity\User; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Chill\TaskBundle\Security\Authorization\TaskVoter; + +/** + * + * + * @author Julien Fastré + */ +class UserMenuBuilder implements LocalMenuBuilderInterface +{ + + /** + * + * @var CountNotificationTask + */ + public $counter; + + /* + * @var TokenStorageInterface + */ + public $tokenStorage; + + /** + * + * @var TranslatorInterface + */ + public $translator; + + /** + * + * @var AuthorizationCheckerInterface + */ + public $authorizationChecker; + + public function __construct( + CountNotificationTask $counter, + TokenStorageInterface $tokenStorage, + TranslatorInterface $translator, + AuthorizationCheckerInterface $authorizationChecker + ) { + $this->counter = $counter; + $this->tokenStorage = $tokenStorage; + $this->translator = $translator; + $this->authorizationChecker = $authorizationChecker; + } + + public static function getMenuIds(): array + { + return [ 'user' ]; + } + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + if (FALSE === $this->authorizationChecker->isGranted(TaskVoter::SHOW)) { + return; + } + + $user = $this->tokenStorage->getToken()->getUser(); + $ended = $this->counter->countNotificationEnded($user); + $warning = $this->counter->countNotificationWarning($user); + + if ($ended > 0) { + $this->addItemInMenu( + $menu, + $user, + '%number% tasks over deadline', + 'My tasks over deadline', + SingleTaskRepository::DATE_STATUS_ENDED, + $ended, + -15); + } + + if ($warning > 0) { + $this->addItemInMenu( + $menu, + $user, + '%number% tasks near deadline', + 'My tasks near deadline', + SingleTaskRepository::DATE_STATUS_WARNING, + $warning, + -14); + } + + $menu->addChild("My tasks", [ + 'route' => 'chill_task_single_my_tasks' + ]) + ->setExtras([ + 'order' => -10, + 'icon' => 'tasks' + ]); + } + + protected function addItemInMenu(MenuItem $menu, User $u, $message, $title, $status, $number, $order) + { + if ($number > 0) { + $menu->addChild( + $this->translator->transChoice($message, $number), + [ + 'route' => 'chill_task_singletask_list', + 'routeParameters' => [ + 'user_id' => $u->getId(), + 'status' => [ + $status + ], + 'hide_form' => true, + 'title' => $this->translator->trans($title) + ] + ]) + ->setExtras([ + 'order'=> $order, + 'icon' => 'exclamation-triangle', + 'entryclass' => 'user_menu__entry--warning-entry' + ]); + } + } +} diff --git a/src/Bundle/ChillTask/Repository/AbstractTaskRepository.php b/src/Bundle/ChillTask/Repository/AbstractTaskRepository.php new file mode 100644 index 000000000..d5c7627c0 --- /dev/null +++ b/src/Bundle/ChillTask/Repository/AbstractTaskRepository.php @@ -0,0 +1,13 @@ +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 = null) + { + $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 ; + * - `user` : filter by user that created the task ; + * - `scope` : filter by scope 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 + * - `types`: string[] an array of task types + * + * @param type $params + * @param User $currentUser + * @param int $firstResult + * @param int $maxResults + * @return type + */ + public function findByParameters($params, User $currentUser, int $firstResult = 0, int $maxResults = 50) + { + $qb = $this->createQueryBuilder('st'); + + $this->buildQuery($qb, $params, $currentUser); + + $qb + ->setMaxResults($maxResults) + ->setFirstResult($firstResult) + ; + + return $qb + ->getQuery() + ->getResult() + ; + } + + protected function buildQuery(QueryBuilder $qb, $params, User $currentUser = null) + { + if (NULL !== $currentUser) { + $this->buildACLQuery($qb, $currentUser); + } + + if (\array_key_exists('person', $params) and !empty($params['person'])) { + $qb->andWhere($qb->expr()->eq('st.person', ':person')); + $qb->setParameter('person', $params['person']); + } elseif (\array_key_exists('center', $params)) { + if ($params['center'] instanceof Center) { + $qb->join('st.person', 'person'); + $qb->andWhere($qb->expr()->eq('person.center', ':center')); + $qb->setParameter('center', $params['center']); + } else { + throw new \UnexpectedValueException("params 'center' should be an instance of ".Center::class); + } + } + + if (\array_key_exists('unassigned', $params) and $params['unassigned'] === true) { + if (\array_key_exists('user', $params) and !empty($params['user'])) { + throw new \UnexpectedValueException("You should not require for " + . "unassigned tasks and tasks assigned to some user."); + } + + $qb->andWhere($qb->expr()->isNull('st.assignee')); + } + + if (\array_key_exists('user', $params) and !empty($params['user'])) { + $qb->andWhere($qb->expr()->eq('st.assignee', ':user')); + $qb->setParameter('user', $params['user']); + } + + if (\array_key_exists('scope', $params) and !empty($params['scope'])) { + $qb->andWhere($qb->expr()->eq('st.circle', ':scope')); + $qb->setParameter('scope', $params['scope']); + } + + if (\array_key_exists('types', $params) && $params['types'] !== NULL) { + if (count($params['types']) > 0) { + $qb->andWhere($qb->expr()->in('st.type', ':types')); + $qb->setParameter('types', $params['types']); + } + } + + 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) + { + $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()->lte('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, bool $negative = false) + { + if ($negative === false) { + return $qb->expr()->andX() + ->add($qb->expr()->lte( + $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, bool $negative = false) + { + if ($negative === false) { + return $qb->expr()->orX() + ->add($qb->expr()->lte('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')) + ; + } + } + + 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) + { + 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/src/Bundle/ChillTask/Resources/config/routing.yml b/src/Bundle/ChillTask/Resources/config/routing.yml new file mode 100644 index 000000000..53d5e4f81 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/routing.yml @@ -0,0 +1,3 @@ +chill_task_controllers: + resource: "@ChillTaskBundle/Controller" + type: annotation diff --git a/src/Bundle/ChillTask/Resources/config/services.yml b/src/Bundle/ChillTask/Resources/config/services.yml new file mode 100644 index 000000000..5779e425e --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services.yml @@ -0,0 +1,2 @@ +services: + diff --git a/src/Bundle/ChillTask/Resources/config/services/controller.yml b/src/Bundle/ChillTask/Resources/config/services/controller.yml new file mode 100644 index 000000000..bfeffa0d5 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/controller.yml @@ -0,0 +1,9 @@ +services: + Chill\TaskBundle\Controller\: + resource: '../../../Controller' + tags: ['controller.service_arguments'] + + Chill\TaskBundle\Controller\SingleTaskController: + arguments: + $eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface' + tags: ['controller.service_arguments'] diff --git a/src/Bundle/ChillTask/Resources/config/services/event.yml b/src/Bundle/ChillTask/Resources/config/services/event.yml new file mode 100644 index 000000000..385d062cc --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/event.yml @@ -0,0 +1,7 @@ +services: + Chill\TaskBundle\Event\Lifecycle\TaskLifecycleEvent: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + tags: + - { name: kernel.event_subscriber } diff --git a/src/Bundle/ChillTask/Resources/config/services/fixtures.yml b/src/Bundle/ChillTask/Resources/config/services/fixtures.yml new file mode 100644 index 000000000..38f9fa785 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/fixtures.yml @@ -0,0 +1,4 @@ +services: + Chill\TaskBundle\DataFixtures\ORM\: + resource: ../../../DataFixtures/ORM + tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillTask/Resources/config/services/form.yml b/src/Bundle/ChillTask/Resources/config/services/form.yml new file mode 100644 index 000000000..f82e4a562 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/form.yml @@ -0,0 +1,9 @@ +services: + Chill\TaskBundle\Form\SingleTaskListType: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' + $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + $taskWorkflowManager: '@Chill\TaskBundle\Workflow\TaskWorkflowManager' + tags: + - { name: form.type } diff --git a/src/Bundle/ChillTask/Resources/config/services/menu.yml b/src/Bundle/ChillTask/Resources/config/services/menu.yml new file mode 100644 index 000000000..b4ff7a421 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/menu.yml @@ -0,0 +1,23 @@ +services: + Chill\TaskBundle\Menu\UserMenuBuilder: + arguments: + $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + $counter: '@Chill\TaskBundle\Templating\UI\CountNotificationTask' + $translator: '@Symfony\Component\Translation\TranslatorInterface' + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + tags: + - { name: 'chill.menu_builder' } + + Chill\TaskBundle\Menu\PersonMenuBuilder: + arguments: + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + $translator: '@Symfony\Component\Translation\TranslatorInterface' + tags: + - { name: 'chill.menu_builder' } + + Chill\TaskBundle\Menu\SectionMenuBuilder: + arguments: + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + $translator: '@Symfony\Component\Translation\TranslatorInterface' + tags: + - { name: 'chill.menu_builder' } diff --git a/src/Bundle/ChillTask/Resources/config/services/repositories.yml b/src/Bundle/ChillTask/Resources/config/services/repositories.yml new file mode 100644 index 000000000..3cc867d96 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/repositories.yml @@ -0,0 +1,11 @@ +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" + Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository' diff --git a/src/Bundle/ChillTask/Resources/config/services/security.yml b/src/Bundle/ChillTask/Resources/config/services/security.yml new file mode 100644 index 000000000..e58fd19e3 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/security.yml @@ -0,0 +1,11 @@ +services: + chill_task.task_voter: + class: Chill\TaskBundle\Security\Authorization\TaskVoter + arguments: + - "@security.access.decision_manager" + - "@chill.main.security.authorization.helper" + - '@Symfony\Component\EventDispatcher\EventDispatcherInterface' + - "@logger" + tags: + - { name: security.voter } + - { name: chill.role } diff --git a/src/Bundle/ChillTask/Resources/config/services/templating.yml b/src/Bundle/ChillTask/Resources/config/services/templating.yml new file mode 100644 index 000000000..401bef6d5 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/templating.yml @@ -0,0 +1,13 @@ +services: + Chill\TaskBundle\Templating\TaskTwigExtension: + arguments: + $taskWorkflowManager: '@Chill\TaskBundle\Workflow\TaskWorkflowManager' + tags: + - { name: 'twig.extension' } + + Chill\TaskBundle\Templating\UI\CountNotificationTask: + arguments: + $singleTaskRepository: '@Chill\TaskBundle\Repository\SingleTaskRepository' + $cachePool: '@cache.user_data' + tags: + - { name: chill.count_notification.user } diff --git a/src/Bundle/ChillTask/Resources/config/services/timeline.yml b/src/Bundle/ChillTask/Resources/config/services/timeline.yml new file mode 100644 index 000000000..b00dd4ca7 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/timeline.yml @@ -0,0 +1,16 @@ +services: + Chill\TaskBundle\Timeline\TaskLifeCycleEventTimelineProvider: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $registry: '@Symfony\Component\Workflow\Registry' + $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' + $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + tags: + - { name: 'chill.timeline', context: 'person' } + + Chill\TaskBundle\Timeline\SingleTaskTaskLifeCycleEventTimelineProvider: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $registry: '@Symfony\Component\Workflow\Registry' + tags: + - { name: 'chill.timeline', context: 'task' } diff --git a/src/Bundle/ChillTask/Resources/config/services/workflow.yml b/src/Bundle/ChillTask/Resources/config/services/workflow.yml new file mode 100644 index 000000000..a80cf40dd --- /dev/null +++ b/src/Bundle/ChillTask/Resources/config/services/workflow.yml @@ -0,0 +1,12 @@ +services: + Chill\TaskBundle\Workflow\TaskWorkflowManager: ~ + + Chill\TaskBundle\Workflow\Definition\DefaultTaskDefinition: + tags: + - { 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/src/Bundle/ChillTask/Resources/migrations/Version20180413135614.php b/src/Bundle/ChillTask/Resources/migrations/Version20180413135614.php new file mode 100644 index 000000000..873f9b678 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/migrations/Version20180413135614.php @@ -0,0 +1,43 @@ +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/src/Bundle/ChillTask/Resources/migrations/Version20180413201023.php b/src/Bundle/ChillTask/Resources/migrations/Version20180413201023.php new file mode 100644 index 000000000..f068fe0e2 --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Resources/migrations/Version20180426093011.php b/src/Bundle/ChillTask/Resources/migrations/Version20180426093011.php new file mode 100644 index 000000000..286d4e3d1 --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Resources/migrations/Version20180502194119.php b/src/Bundle/ChillTask/Resources/migrations/Version20180502194119.php new file mode 100644 index 000000000..a07ad1089 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/migrations/Version20180502194119.php @@ -0,0 +1,34 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE SEQUENCE chill_task.single_task_place_event_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_task.single_task_place_event (id INT NOT NULL, author_id INT DEFAULT NULL, task_id INT DEFAULT NULL, datetime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, transition VARCHAR(255) NOT NULL, data JSONB NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_D459EBEEF675F31B ON chill_task.single_task_place_event (author_id)'); + $this->addSql('CREATE INDEX IDX_D459EBEE8DB60186 ON chill_task.single_task_place_event (task_id)'); + $this->addSql('COMMENT ON COLUMN chill_task.single_task_place_event.datetime IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_task.single_task_place_event ADD CONSTRAINT FK_D459EBEEF675F31B FOREIGN KEY (author_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_task.single_task_place_event ADD CONSTRAINT FK_D459EBEE8DB60186 FOREIGN KEY (task_id) REFERENCES chill_task.single_task (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP SEQUENCE chill_task.single_task_place_event_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_task.single_task_place_event'); + + } +} diff --git a/src/Bundle/ChillTask/Resources/migrations/Version20181113161925.php b/src/Bundle/ChillTask/Resources/migrations/Version20181113161925.php new file mode 100644 index 000000000..7b3ae95f4 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/migrations/Version20181113161925.php @@ -0,0 +1,29 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE INDEX transition_task_date ON chill_task.single_task_place_event (task_id, transition, datetime)'); + $this->addSql('CREATE INDEX transition_task ON chill_task.single_task_place_event (task_id, transition)'); + } + + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX transition_task_date'); + $this->addSql('DROP INDEX transition_task'); + + } +} diff --git a/src/Bundle/ChillTask/Resources/migrations/Version20181113164108.php b/src/Bundle/ChillTask/Resources/migrations/Version20181113164108.php new file mode 100644 index 000000000..48e22c597 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/migrations/Version20181113164108.php @@ -0,0 +1,28 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE INDEX by_type ON chill_task.single_task (type)'); + $this->addSql('CREATE INDEX by_current_state ON chill_task.single_task USING GIN (current_states)'); + } + + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX by_type'); + $this->addSql('DROP INDEX by_current_state'); + } +} diff --git a/src/Bundle/ChillTask/Resources/public/index.js b/src/Bundle/ChillTask/Resources/public/index.js new file mode 100644 index 000000000..ad7fb96c7 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/index.js @@ -0,0 +1,3 @@ +// this file loads all assets from the Chill task bundle +require('./sass/_task.scss'); + diff --git a/src/Bundle/ChillTask/Resources/public/sass/_task-list.scss b/src/Bundle/ChillTask/Resources/public/sass/_task-list.scss new file mode 100644 index 000000000..3350a4ace --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/sass/_task-list.scss @@ -0,0 +1,53 @@ +table.chill-task-list { + .chill-task-list__row > div { + margin-bottom: 0.50rem; + } + + .chill-task-list__row__title { + font-weight: bold; + font-size: 1.40rem; + } + + .chill-task-list__row__type { + font-variant: small-caps; + display: inline; + padding: 0.05rem .15rem; + font-size: 0.88rem; + font-weight: light; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border: 1px solid var(--chill-dark-gray); + color: var(--chill-dark-gray); + } + + .chill-task-list__row__person-for { + display: inline; + font-weight: bold; + } + + .chill-task-list__row__assignee { + display: inline; + + } + + .chill_task-list__row__assignee_by { + display: inline; + font-weight: bold; + } + + .chill-task-list__row__dates { + & > ul { + display: inline; + list-style: none; + + & > li { + display: inline; + margin-right: 0.25rem; + + } + } + } +} + diff --git a/src/Bundle/ChillTask/Resources/public/sass/_task-statuses.scss b/src/Bundle/ChillTask/Resources/public/sass/_task-statuses.scss new file mode 100644 index 000000000..5e371359e --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/sass/_task-statuses.scss @@ -0,0 +1,34 @@ +.task-status { + &.box { + font-variant: small-caps; + display: inline; + padding: .2em .6em .3em; + font-size: 0.88rem; + font-weight: bold; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + color: white; + } + + &.type-task_default { + // 'new', 'in_progress', 'closed', 'canceled' + &.place-new { + background-color: var(--chill-yellow); + } + + &.place-in_progress { + background-color: var(--chill-green); + } + + &.place-closed { + background-color: var(--chill-blue); + } + + &.place-canceled { + background-color: var(--chill-beige); + } + } +} diff --git a/src/Bundle/ChillTask/Resources/public/sass/_task.scss b/src/Bundle/ChillTask/Resources/public/sass/_task.scss new file mode 100644 index 000000000..4e668f3fe --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/sass/_task.scss @@ -0,0 +1,88 @@ +@import '../../../../main/Resources/public/sass/custom/config/colors'; +@import "_task-statuses.scss"; +@import "_task-list.scss"; + +div#single_task_warningInterval { + display: flex; + flex-direction: row; + justify-content: flex-end; + div.container { + display: flex; + flex-direction: row; + justify-content: flex-end; + & > div:first-child { + display:none; /* Hide the label container */ + } + & > div { + padding-right: 0; + width: 7em; + } + input[type="text"] { + height: 35px; + } + } +} + +.bt-task-label { + display: inline-flex; + margin-bottom: 0.2em; + font-size: 0.8em; + text-transform: capitalize; +} + +.bt-task { + display: inline-block; + height: 0; + width: 8px; +} + +.bt-task-exchange { + background: $chill-green; + color: #fff; +} +.bt-task-exchange:hover { + background: $chill-green-dark; + color: #fff; +} + +.bt-task-exchange::before { + @extend .bt-task; + width: 10px; + content: url(../svg/exchange-alt.svg); +} + +.bt-task-start::before { + @extend .bt-task; + content: url(../svg/play.svg); +} + +.bt-task-cancel::before { + @extend .bt-task; + content: url(../svg/archive.svg); +} + +.bt-task-close::before { + @extend .bt-task; + content: url(../svg/check.svg); +} + +.bt-dropdown { + position: relative; + display: inline-block; +} + +.bt-dropdown-content { + display: none; + position: absolute; + background-color: #f9f9f9; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; + a { + margin: 0.4em; + } +} + +.bt-dropdown:hover .bt-dropdown-content { + display: inline-block; + width: min-content; +} diff --git a/src/Bundle/ChillTask/Resources/public/svg/archive.svg b/src/Bundle/ChillTask/Resources/public/svg/archive.svg new file mode 100644 index 000000000..394dad2db --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/svg/archive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Bundle/ChillTask/Resources/public/svg/check.svg b/src/Bundle/ChillTask/Resources/public/svg/check.svg new file mode 100644 index 000000000..2ec27cf84 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/svg/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Bundle/ChillTask/Resources/public/svg/exchange-alt.svg b/src/Bundle/ChillTask/Resources/public/svg/exchange-alt.svg new file mode 100644 index 000000000..82ef60e98 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/svg/exchange-alt.svg @@ -0,0 +1,52 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/src/Bundle/ChillTask/Resources/public/svg/play.svg b/src/Bundle/ChillTask/Resources/public/svg/play.svg new file mode 100644 index 000000000..d7fa87f32 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/public/svg/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Bundle/ChillTask/Resources/translations/messages.fr.yml b/src/Bundle/ChillTask/Resources/translations/messages.fr.yml new file mode 100644 index 000000000..80d3065b1 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/translations/messages.fr.yml @@ -0,0 +1,105 @@ +Tasks: 'Tâches' +'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 d'échéance" +'Warning date': "Date d'avertissement" +'Warning interval': "Délai d'avertissement avant la date d'échéance" +'Unknown dates': 'Dates non spécifiées' +'N': '' +'Unit': '' +Task: Tâche +Details: Détails +Person: Personne +Scope: Cercle +Date: Date +Dates: Dates +User: Utilisateur +'Task list': 'Liste des tâches' +'Tasks with expired deadline': "Tâches avec une date d'échéance dépassée" +'Tasks with warning deadline reached': "Tâches avec une date d'avertissement atteinte" +'Current tasks': 'Tâches en cours' +'Closed tasks': Tâches terminées +'Tasks not started': 'Tâches non commencées' +'Task start date': 'Date de début' +'Task warning date': "Date d'avertissement" +'Task end date': "Date d'échéance" +'Start': 'Début' +'Warning': 'Avertissement' +'End': 'Échéance' +'Task type': 'Type' +'Task status': 'Statut' +'Edit the task': 'Modifier la tâche' +'Edit task': 'Modifier 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' +'Delete': 'Supprimer' +'Change task status': 'Changer le statut' +'Are you sure you want to remove the task about "%name%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche de "%name%"?' +'See more': 'Voir plus' +'Associated tasks': 'Tâches associées' +'My tasks': 'Mes tâches' +'No description': 'Pas de description' +'No dates specified': 'Dates non spécifiées' +'No one assignee': 'Aucune personne assignée' +'Task types': Types de tâches +Days: Jour(s) +Weeks: Semaine(s) +Months: Mois +Year: Année(s) +Filter the tasks: Filtrer les tâches +Filter: Filtrer +Any user: Tous les utilisateurs +Unassigned: Non assigné +Associated person: Personne associée +Default task: Tâche par défaut +Not assigned: Aucun utilisateur assigné +For person: Pour +By: Par + +# transitions - default task definition +'new': 'nouvelle' +'in_progress': 'en cours' +'closed': 'fermée' +'canceled': 'supprimée' +start: démarrer +close: clotûrer +cancel: annuler +Start_verb: Démarrer +Close_verb: Clotûrer +Set this task to cancel state: Marquer cette tâche comme annulée +'%user% has closed the task': %user% a fermé la tâche +'%user% has canceled the task': %user% a annulé la tâche +'%user% has started the task': %user% a commencé la tâche +'%user% has created the task': %user% a introduit la tâche +Are you sure you want to close this task ?: Êtes-vous sûrs de vouloir clotûrer cette tâche ? +Are you sure you want to cancel this task ?: Êtes-vous sûrs de vouloir annuler cette tâche ? +Are you sure you want to start this task ?: Êtes-vous sûrs de vouloir démarrer cette tâche ? + +#Flash messages +'The task is created': 'La tâche a été créée' +'There is no tasks.': Aucune tâche. +'The task has been successfully removed.': 'La tâche a bien été supprimée' +'This form contains errors': 'Ce formulaire contient des erreurs' +'The task has been updated': 'La tâche a été mise à jour' +'The transition is successfully applied': 'La transition a bien été effectuée' +'The transition could not be applied': "La transition n'a pas pu être appliquée" + +#widget +'%number% tasks over deadline': '{0} Aucune tâche dépassée|{1} Une tâche dépassée | ]1,Inf[ %count% tâches dépassées' +'%number% tasks near deadline': '{0} Aucune tâche en rappel|{1} Une tâche en rappel | ]1,Inf[ %count% tâches en rappel' + +#title +My tasks near deadline: Mes tâches à échéance proche +My tasks over deadline: Mes tâches à échéance dépassée + +#transition page +Apply transition on task %title%: Appliquer la transition sur la tâche %title% + +All centers: Tous les centres diff --git a/src/Bundle/ChillTask/Resources/translations/validators.fr.yml b/src/Bundle/ChillTask/Resources/translations/validators.fr.yml new file mode 100644 index 000000000..6bffe29ac --- /dev/null +++ b/src/Bundle/ChillTask/Resources/translations/validators.fr.yml @@ -0,0 +1,4 @@ +The start date must be before the end date: La date de début doit être avant la date d'échéance +This form contains errors: Le formulaire contient des erreurs +The start date must be before warning date: La date de début doit être avant le délai d'avertissement +An end date is required if a warning interval is set: Une date d'échéance est requise lorsqu'un délai de rappel est fixé diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/_list.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/_list.html.twig new file mode 100644 index 000000000..9f124d866 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/SingleTask/_list.html.twig @@ -0,0 +1,240 @@ +{% macro date_status(title, tasks, count, paginator, status, isSingleStatus, person, user) %} + {% if tasks|length > 0 %} +

{{ title|trans }}

+ + + + {% for task in tasks %} + + + + + {% endfor %} + +
+ +
+ {{ task.title }} +
+ + {% if person is null %} +
+ {{ 'For person'|trans }} : {{ task.person}} +
+ {% endif %} + +
+ {{ task_workflow_metadata(task, 'definition.name')|trans }} +
+ + + +
+ + {% for place in workflow_marked_places(task) %} + {{ place|trans }} + {% endfor %} + {% if task.assignee is not null %} +
{{ 'By'|trans }} : {{ task.assignee.username }}
+ {% endif %} +
+ + + {% if task.startDate is not null or task.warningDate is not null or task.endDate is not null %} +
+
    + {% 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 %} +
+
+ {% endif %} + +
+
    + {% if workflow_transitions(task)|length > 0 %} +
  • +
    +   +
    + {% for transition in workflow_transitions(task) %} + {{ task_workflow_metadata(task, 'transition.verb', transition)|trans }} + {% endfor %} +
    +
    +
  • + {% endif %} +
  • + +
  • + {% if is_granted('CHILL_TASK_TASK_UPDATE', task) %} +
  • + +
  • + {% endif %} + {% if is_granted('CHILL_TASK_TASK_DELETE', task) %} +
  • + +
  • + {% endif %} +
+
+ + {% if isSingleStatus %} + {% if tasks|length < paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + + + + {% else %} + + {% endif %} + + {% endif %} +{% endmacro %} + +{% import _self as helper %} + + +

{{ app.request.query.get('title', null)|escape('html')|default('Task list'|trans) }}

+ + {% if false == app.request.query.boolean('hide_form', false) %} +

{{ 'Filter the tasks'|trans }}

+ {{ form_start(form) }} + {{ form_row(form.user_id) }} + + {% if form.status is defined %} + {{ form_row(form.status) }} + {% endif %} + + {% if form.types is defined %} + {{ form_row(form.types) }} + {% endif %} + + {% if form.person_id is defined %} + {{ form_row(form.person_id) }} + {% endif %} + + {% if form.center_id is defined %} + {{ form_row(form.center_id) }} + {% endif %} + +
    +
  • + +
  • +
+ {{ form_end(form)}} + {% endif %} + + + + {% if tasks_count == 0 %} +

{{ "There is no tasks."|trans }}

+ {% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %} + + {% endif %} + {% else %} + + {% if false == app.request.query.boolean('hide_form', false) %} +

{{ 'Tasks'|trans }}

+ {% endif %} + + {% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %} + + {% endif %} + + {% 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, '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, '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, '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, 'not_started', isSingleStatus, person) }} + {% endif %} + + {% if single_task_closed_tasks is defined %} + {{ helper.date_status('Closed tasks', single_task_closed_tasks, single_task_closed_count, single_task_closed_paginator, 'closed', isSingleStatus, person) }} + {% endif %} + + {% if isSingleStatus == false %} + + {% endif %} + + {% endif %} diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/confirm_delete.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/confirm_delete.html.twig new file mode 100644 index 000000000..5e4fa4a0f --- /dev/null +++ b/src/Bundle/ChillTask/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_singletask_list', + 'cancel_parameters' : app.request.query.get('list_params', { } ), + 'form' : delete_form + } ) }} + +{% endblock %} diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/edit.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/edit.html.twig new file mode 100644 index 000000000..7f683d0a7 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/SingleTask/edit.html.twig @@ -0,0 +1,54 @@ +{# + * 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_end(form) }} + +{% endblock %} diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/index.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/index.html.twig new file mode 100644 index 000000000..f6509015f --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/SingleTask/index.html.twig @@ -0,0 +1,45 @@ +{# + * 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 layout %} + +{% set activeRouteKey = 'chill_task_single_task_new' %} + +{% block title %}{{ 'Task list'|trans }}{% endblock %} + +{% macro thead() %} + +{% endmacro %} + +{% macro row(task) %} + +{% endmacro %} + + +{# filter tasks #} + +{% if person is not null %} + {% block personcontent %} + {% include 'ChillTaskBundle:SingleTask:_list.html.twig' %} + {% endblock %} +{% else %} + {% block content %} +
+ {% include 'ChillTaskBundle:SingleTask:_list.html.twig' %} +
+ {% endblock %} +{% endif %} diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/new.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/new.html.twig new file mode 100644 index 000000000..1f054820c --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/SingleTask/new.html.twig @@ -0,0 +1,47 @@ +{# + * 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_start(form) }} + + {{ form_errors(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 %} diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/show.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/show.html.twig new file mode 100644 index 000000000..f19825cdd --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/SingleTask/show.html.twig @@ -0,0 +1,125 @@ +{# + * 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 }}

+ +

{{ task.title }} {% for place in workflow_marked_places(task) %} + {{ place|trans }} + {% endfor %}

+ +
+ +
{{ 'Description'|trans }}
+
+ {% if task.description is empty %} + {{"No description"|trans}} + {% else %} +
+ {{ task.description }} +
+ {% endif %} +
+ +
{{ 'Assignee'|trans }}
+
+ {% if task.assignee is null %} + {{"No one assignee"|trans}} + {% else %} + {{ task.assignee }} + {% endif %} +
+ +
{{ 'Scope'|trans }}
+
{{ task.scope.name|localize_translatable_string }}
+ +

{{"Dates"|trans}}

+ {% if task.startDate is null and task.endDate is null and task.warningDate is null %} +
+
{{"No dates specified"|trans}}
+ + {% else %} + {% if task.startDate is not null %} +
{{ 'Start'|trans }}
+
{{ task.startDate|localizeddate('long', 'none') }}
+ {% endif %} + + {% if task.endDate is not null %} +
{{ 'End'|trans }}
+
{{ task.endDate|localizeddate('long', 'none') }}
+ {% endif %} + + {% if task.warningDate is not null %} +
{{ 'Warning'|trans }}
+
{{ task.warningDate|localizeddate('long', 'none') }}
+ {% endif %} + + {% endif %} +
+ + {% if timeline is not null %} +

{{"Timeline"|trans}}

+ {{ timeline|raw }} + {% endif %} + + + +{% endblock %} diff --git a/src/Bundle/ChillTask/Resources/views/SingleTask/transition.html.twig b/src/Bundle/ChillTask/Resources/views/SingleTask/transition.html.twig new file mode 100644 index 000000000..7ba341cd9 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/SingleTask/transition.html.twig @@ -0,0 +1,34 @@ +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_task_list' %} +{% set person = task.person %} + +{% block title 'Remove task'|trans %} + +{% block personcontent %} + +

{{ 'Apply transition on task %title%'|trans({ '%title%': task.title } )|raw }}

+ + +{% if task_workflow_metadata(task, 'transition.sentence_confirmation', transition) is not empty %} +

{{ task_workflow_metadata(task, 'transition.sentence_confirmation', transition)|trans }}

+{% else %} +

{{ 'Are you sure to apply the transition %name% on this task ?'|trans({ '%name%': task_workflow_metadata(task, 'transition.name', transition)|default(transition.name)|trans }) }}

+{% endif %} + +{{ form_start(form) }} + +
    +
  • + + {{ 'Back to the list'|trans }} + +
  • +
  • + {{ form_widget(form.submit, { 'attr' : { 'class' : "sc-button bt-task-exchange green" }, 'label': task_workflow_metadata(task, 'transition.apply_transition_submit_label', transition)|default('apply')|trans } ) }} +
  • +
+ +{{ form_end(form) }} + +{% endblock %} diff --git a/src/Bundle/ChillTask/Resources/views/Timeline/single_task_transition_person_context.html.twig b/src/Bundle/ChillTask/Resources/views/Timeline/single_task_transition_person_context.html.twig new file mode 100644 index 000000000..4bd7f6176 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/Timeline/single_task_transition_person_context.html.twig @@ -0,0 +1,33 @@ +
+

+ {{ event.datetime|localizeddate('long', 'none') }} + / {{ 'Task'|trans }} / + {% if transition is not null %} + {{ task_workflow_metadata(event.task, 'transition.sentence', transition)|trans({ '%user%': event.author.username }) }} + {% else %} + {{ '%user% has created the task'|trans({ '%user%': event.author.username }) }} + {% endif %} +

+ +
+
+
{{ 'Title'|trans }}
+
{{ event.task.title }}
+ + {% if event.task.description is not empty %} +
{{ 'Description'|trans }}
+
+
+ {{ event.task.description }} +
+
+ {% endif %} + + {% if event.task.endDate is not empty %} +
{{ 'Task end date'|trans }}
+
{{ event.task.endDate|localizeddate('medium', 'none') }}
+ {% endif %} +
+
+ +
diff --git a/src/Bundle/ChillTask/Resources/views/Timeline/single_task_transition_task_context.html.twig b/src/Bundle/ChillTask/Resources/views/Timeline/single_task_transition_task_context.html.twig new file mode 100644 index 000000000..010023a29 --- /dev/null +++ b/src/Bundle/ChillTask/Resources/views/Timeline/single_task_transition_task_context.html.twig @@ -0,0 +1,11 @@ +
+

+ {{ event.datetime|localizeddate('long', 'short') }} + + {% if transition is not null %} + {{ task_workflow_metadata(event.task, 'transition.sentence', transition)|trans({ '%user%': event.author.username }) }} + {% else %} + {{ '%user% has created the task'|trans({ '%user%': event.author.username }) }} + {% endif %} +

+
diff --git a/src/Bundle/ChillTask/Security/Authorization/AuthorizationEvent.php b/src/Bundle/ChillTask/Security/Authorization/AuthorizationEvent.php new file mode 100644 index 000000000..38cda7563 --- /dev/null +++ b/src/Bundle/ChillTask/Security/Authorization/AuthorizationEvent.php @@ -0,0 +1,88 @@ + + */ +class AuthorizationEvent extends Event +{ + /** + * @var Chill\TaskBundle\Entity\AbstractTask|\Chill\PersonBundle\Entity\Person|null + */ + protected $subject; + + /** + * + * @var string + */ + protected $attribute; + + /** + * + * @var TokenInterface + */ + protected $token; + + /** + * + * @var bool + */ + protected $vote; + + const VOTE = 'chill_task.vote'; + + public function __construct( + $subject, + $attribute, + TokenInterface $token + ) { + $this->subject = $subject; + $this->attribute = $attribute; + $this->token = $token; + } + + public function getSubject() + { + return $this->subject; + } + + public function getAttribute() + { + return $this->attribute; + } + + public function getToken(): TokenInterface + { + return $this->token; + } + + public function getVote() + { + return $this->vote; + } + + public function setVote($vote) + { + $this->vote = $vote; + + return $this; + } + + public function hasVote() + { + return $this->vote !== NULL; + } + + public function removeVote() + { + $this->vote = NULL; + } +} diff --git a/src/Bundle/ChillTask/Security/Authorization/TaskVoter.php b/src/Bundle/ChillTask/Security/Authorization/TaskVoter.php new file mode 100644 index 000000000..8b8bce839 --- /dev/null +++ b/src/Bundle/ChillTask/Security/Authorization/TaskVoter.php @@ -0,0 +1,175 @@ + + * + * 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; +use Chill\PersonBundle\Entity\Person; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Chill\TaskBundle\Security\Authorization\AuthorizationEvent; + +/** + * + * + * @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 DELETE = 'CHILL_TASK_TASK_DELETE'; + + const ROLES = [ + self::CREATE, + self::UPDATE, + self::SHOW, + self::DELETE + ]; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var AccessDecisionManagerInterface + */ + protected $accessDecisionManager; + + /** + * + * @var LoggerInterface + */ + protected $logger; + + /** + * + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + public function __construct( + AccessDecisionManagerInterface $accessDecisionManager, + AuthorizationHelper $authorizationHelper, + EventDispatcherInterface $eventDispatcher, + LoggerInterface $logger + ) { + $this->accessDecisionManager = $accessDecisionManager; + $this->authorizationHelper = $authorizationHelper; + $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; + } + + public function supports($attribute, $subject) + { + return ($subject instanceof AbstractTask && in_array($attribute, self::ROLES)) + || + ($subject instanceof Person && \in_array($attribute, [ self::CREATE, self::SHOW ])) + || + (NULL === $subject && $attribute === self::SHOW ) + ; + } + + /** + * + * @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; + } + + $event = new AuthorizationEvent($subject, $attribute, $token); + + $this->eventDispatcher->dispatch(AuthorizationEvent::VOTE, $event); + + if ($event->hasVote()) { + + $this->logger->debug("The TaskVoter is overriding by " + .AuthorizationEvent::VOTE, [ + 'vote' => $event->getVote(), + 'task_id' => $subject->getId() + ]); + + return $event->getVote(); + } + + if ($subject instanceof AbstractTask) { + if ($subject->getPerson() === null) { + throw new \LogicException("You should associate a person with task " + . "in order to check autorizations"); + } + + $person = $subject->getPerson(); + } elseif ($subject instanceof Person) { + $person = $subject; + } else { + // subject is null. We check that at least one center is reachable + $centers = $this->authorizationHelper->getReachableCenters($token->getUser(), new Role($attribute)); + + return count($centers) > 0; + } + + if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) { + + 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 []; + } + +} diff --git a/src/Bundle/ChillTask/Templating/TaskTwigExtension.php b/src/Bundle/ChillTask/Templating/TaskTwigExtension.php new file mode 100644 index 000000000..fd799dd34 --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Templating/UI/CountNotificationTask.php b/src/Bundle/ChillTask/Templating/UI/CountNotificationTask.php new file mode 100644 index 000000000..e53b85fbe --- /dev/null +++ b/src/Bundle/ChillTask/Templating/UI/CountNotificationTask.php @@ -0,0 +1,129 @@ + + * + * 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\UI; + +use Chill\MainBundle\Templating\UI\NotificationCounterInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Chill\MainBundle\Entity\User; +use Chill\TaskBundle\Repository\SingleTaskRepository; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Workflow\Event\Event; + +/** + * + * + * @author Julien Fastré + */ +class CountNotificationTask implements NotificationCounterInterface +{ + /** + * + * @var SingleTaskRepository + */ + protected $singleTaskRepository; + + /** + * + * @var CacheItempPoolInterface + */ + protected $cachePool; + + const CACHE_KEY = 'chill_task.count_notifications.user.%d.%s'; + + public function __construct( + SingleTaskRepository $singleTaskRepository, + CacheItemPoolInterface $cachePool + ) { + $this->singleTaskRepository = $singleTaskRepository; + $this->cachePool = $cachePool; + } + + public function countNotification(UserInterface $u): int + { + return + $this->countNotificationEnded($u) + + $this->countNotificationWarning($u); + } + + public function countNotificationEnded(UserInterface $u): int + { + return $this->_countNotification($u, SingleTaskRepository::DATE_STATUS_ENDED); + } + + public function countNotificationWarning(UserInterface $u): int + { + return $this->_countNotification($u, SingleTaskRepository::DATE_STATUS_WARNING); + } + + protected function _countNotification(UserInterface $u, $status) + { + if (!$u instanceof User) { + return 0; + } + + $sumCache = $this->cachePool->getItem($this->getCacheKey($u, $status)); + + if ($sumCache->isHit()) { + return $sumCache->get(); + } + + $params = [ + 'user' => $u, + 'is_closed' => false + ]; + + $sum = $this->singleTaskRepository->countByParameters( + \array_merge($params, [ 'date_status' => $status ]) + ); + + $sumCache->set($sum); + $this->cachePool->save($sumCache); + + return $sum; + } + + public function addNotification(UserInterface $u): int + { + return $this->countNotification($u); + } + + public function resetCacheOnNewStates(Event $e) + { + /* @var $task \Chill\TaskBundle\Entity\SingleTask */ + $task = $e->getSubject(); + + if (NULL !== $task->getAssignee()) { + foreach ([ + SingleTaskRepository::DATE_STATUS_ENDED, + SingleTaskRepository::DATE_STATUS_WARNING + ] as $status) { + $key = $this->getCacheKey($task->getAssignee(), $status); + $sumCache = $this->cachePool->getItem($key); + + if ($sumCache->isHit()) { + $this->cachePool->deleteItem($key); + } + } + } + } + + private function getCacheKey(User $u, $status) + { + return sprintf(self::CACHE_KEY, $u->getId(), $status); + } +} diff --git a/src/Bundle/ChillTask/Tests/Controller/SingleTaskControllerTest.php b/src/Bundle/ChillTask/Tests/Controller/SingleTaskControllerTest.php new file mode 100644 index 000000000..d7337029d --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Tests/Controller/TaskControllerTest.php b/src/Bundle/ChillTask/Tests/Controller/TaskControllerTest.php new file mode 100644 index 000000000..18b980329 --- /dev/null +++ b/src/Bundle/ChillTask/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\Timeline; + +use Chill\MainBundle\Timeline\TimelineProviderInterface; +use Doctrine\ORM\EntityManagerInterface; +use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent; +use Chill\TaskBundle\Entity\SingleTask; +use Symfony\Component\Workflow\Registry; +use Symfony\Component\Workflow\Workflow; + +/** + * + * + * @author Julien Fastré + */ +class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderInterface +{ + /** + * + * @var EntityManagerInterface + */ + protected $em; + + /** + * + * @var Registry + */ + protected $registry; + + const TYPE = 'chill_task.transition'; + + public function __construct(EntityManagerInterface $em, Registry $registry) + { + $this->em = $em; + $this->registry = $registry; + } + + public function fetchQuery($context, $args) + { + if ($context !== 'task') { + throw new \LogicException(sprintf('%s is not able ' + . 'to render context %s', self::class, $context)); + } + + $metadata = $this->em + ->getClassMetadata(SingleTaskPlaceEvent::class); + $singleTaskMetadata = $this->em + ->getClassMetadata(SingleTask::class); + + return [ + 'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')), + 'type' => self::TYPE, + 'date' => $metadata->getColumnName('datetime'), + 'FROM' => sprintf('%s JOIN %s ON %s = %s', + sprintf('%s.%s', $metadata->getSchemaName(), $metadata->getTableName()), + sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), + $metadata->getAssociationMapping('task')['joinColumns'][0]['name'], + sprintf('%s.%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName(), $singleTaskMetadata->getColumnName('id')) + ), + 'WHERE' => sprintf('%s.%s = %d', + sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), + $singleTaskMetadata->getColumnName('id'), + $args['task']->getId() + ) + ]; + + } + + public function getEntities(array $ids) + { + $events = $this->em + ->getRepository(SingleTaskPlaceEvent::class) + ->findBy([ 'id' => $ids ]) + ; + + return \array_combine( + \array_map(function($e) { return $e->getId(); }, $events ), + $events + ); + } + + public function getEntityTemplate($entity, $context, array $args) + { + $workflow = $this->registry->get($entity->getTask(), $entity->getData['workflow']); + $transition = $this->getTransitionByName($entity->getTransition(), $workflow); + + return [ + 'template' => 'ChillTaskBundle:Timeline:single_task_transition_task_context.html.twig', + 'template_data' => [ + 'task' => $args['task'], + 'event' => $entity, + 'transition' => $transition + ] + ]; + } + + /** + * + * @param string $name + * @param Workflow $workflow + * @return \Symfony\Component\Workflow\Transition + */ + protected function getTransitionByName($name, Workflow $workflow) + { + foreach ($workflow->getDefinition()->getTransitions() as $transition) { + if ($transition->getName() === $name) { + return $transition; + } + } + } + + public function supportsType($type): bool + { + return $type === self::TYPE; + } +} diff --git a/src/Bundle/ChillTask/Timeline/TaskLifeCycleEventTimelineProvider.php b/src/Bundle/ChillTask/Timeline/TaskLifeCycleEventTimelineProvider.php new file mode 100644 index 000000000..ce2f99669 --- /dev/null +++ b/src/Bundle/ChillTask/Timeline/TaskLifeCycleEventTimelineProvider.php @@ -0,0 +1,171 @@ + + * + * 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\Timeline; + +use Chill\MainBundle\Timeline\TimelineProviderInterface; +use Doctrine\ORM\EntityManagerInterface; +use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent; +use Chill\TaskBundle\Entity\SingleTask; +use Symfony\Component\Workflow\Registry; +use Symfony\Component\Workflow\Workflow; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\ActivityBundle\Security\Authorization\ActivityVoter; +use Symfony\Component\Security\Core\Role\Role; + +/** + * + * + * @author Julien Fastré + */ +class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface +{ + /** + * + * @var EntityManagerInterface + */ + protected $em; + + /** + * + * @var Registry + */ + protected $registry; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var TokenStorageInterface + */ + protected $tokenStorage; + + const TYPE = 'chill_task.transition'; + + public function __construct( + EntityManagerInterface $em, + Registry $registry, + AuthorizationHelper $authorizationHelper, + TokenStorageInterface $tokenStorage + ) { + $this->em = $em; + $this->registry = $registry; + $this->authorizationHelper = $authorizationHelper; + $this->tokenStorage = $tokenStorage; + } + + public function fetchQuery($context, $args) + { + if ($context !== 'person') { + throw new \LogicException(sprintf('%s is not able ' + . 'to render context %s', self::class, $context)); + } + + $metadata = $this->em + ->getClassMetadata(SingleTaskPlaceEvent::class); + $singleTaskMetadata = $this->em + ->getClassMetadata(SingleTask::class); + $user = $this->tokenStorage->getToken()->getUser(); + $circles = $this->authorizationHelper->getReachableCircles( + $user, new Role(ActivityVoter::SEE_DETAILS), $args['person']->getCenter()); + + + if (count($circles) > 0) { + $circlesId = \array_map(function($c) { return $c->getId(); }, $circles); + $circleRestriction = sprintf('%s.%s.%s IN (%s)', + $singleTaskMetadata->getSchemaName(), // chill_task schema + $singleTaskMetadata->getTableName(), // single_task table name + $singleTaskMetadata->getAssociationMapping('circle')['joinColumns'][0]['name'], + \implode(', ', $circlesId) + ); + } else { + $circleRestriction = 'FALSE = TRUE'; + } + + + return [ + 'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')), + 'type' => self::TYPE, + 'date' => $metadata->getColumnName('datetime'), + 'FROM' => sprintf('%s JOIN %s ON %s = %s', + sprintf('%s.%s', $metadata->getSchemaName(), $metadata->getTableName()), + sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), + $metadata->getAssociationMapping('task')['joinColumns'][0]['name'], + sprintf('%s.%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName(), $singleTaskMetadata->getColumnName('id')) + ), + 'WHERE' => sprintf('%s.%s = %d and %s', + sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), + $singleTaskMetadata->getAssociationMapping('person')['joinColumns'][0]['name'], + $args['person']->getId(), + $circleRestriction + ) + ]; + } + + public function getEntities(array $ids) + { + $events = $this->em + ->getRepository(SingleTaskPlaceEvent::class) + ->findBy([ 'id' => $ids ]) + ; + + return \array_combine( + \array_map(function($e) { return $e->getId(); }, $events ), + $events + ); + } + + public function getEntityTemplate($entity, $context, array $args) + { + $workflow = $this->registry->get($entity->getTask(), $entity->getData['workflow']); + $transition = $this->getTransitionByName($entity->getTransition(), $workflow); + + return [ + 'template' => 'ChillTaskBundle:Timeline:single_task_transition_person_context.html.twig', + 'template_data' => [ + 'person' => $args['person'], + 'event' => $entity, + 'transition' => $transition + ] + ]; + } + + /** + * + * @param string $name + * @param Workflow $workflow + * @return \Symfony\Component\Workflow\Transition + */ + protected function getTransitionByName($name, Workflow $workflow) + { + foreach ($workflow->getDefinition()->getTransitions() as $transition) { + if ($transition->getName() === $name) { + return $transition; + } + } + } + + public function supportsType($type): bool + { + return $type === self::TYPE; + } +} diff --git a/src/Bundle/ChillTask/Workflow/Definition/DefaultTaskDefinition.php b/src/Bundle/ChillTask/Workflow/Definition/DefaultTaskDefinition.php new file mode 100644 index 000000000..64b753875 --- /dev/null +++ b/src/Bundle/ChillTask/Workflow/Definition/DefaultTaskDefinition.php @@ -0,0 +1,110 @@ + + * + * 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', + 'class' => 'sc-button bt-task-label bt-task-close', + 'sentence' => '%user% has closed the task', + 'sentence_confirmation' => 'Are you sure you want to close this task ?', + 'apply_transition_submit_label' => 'Close_verb' + ], + 'cancel' => [ + 'verb' => 'cancel', + 'class' => 'sc-button bt-task-label bt-task-cancel', + 'sentence' => '%user% has canceled the task', + 'sentence_confirmation' => 'Are you sure you want to cancel this task ?', + 'apply_transition_submit_label' => 'Set this task to cancel state' + ], + 'start' => [ + 'verb' => 'start', + 'class' => 'sc-button bt-task-label bt-task-start', + 'sentence' => '%user% has started the task', + 'sentence_confirmation' => 'Are you sure you want to start this task ?', + 'apply_transition_submit_label' => 'Start_verb' + ] + ]; + + const DEFINITION_METADATA = [ + 'name' => 'Default task' + ]; + + public function supports(AbstractTask $task) + { + + return $task instanceof SingleTask + && $task->getType() === 'task_default'; + } + + public static function getAssociatedWorkflowName() + { + return 'task_default'; + } + + public function getWorkflowMetadata( + AbstractTask $task, + 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); + case 'definition': + return self::DEFINITION_METADATA[$keys[1]] ?? $key; + default: + return $key; + } + } + + protected function getTransitionMetadata($key, Transition $transition) + { + if (!\array_key_exists($transition->getName(), self::TRANSITION_METADATA)) { + return $key; + } + + if (!\array_key_exists($key, self::TRANSITION_METADATA[$transition->getName()])) { + return $key; + } + + return self::TRANSITION_METADATA[$transition->getName()][$key]; + } + + public function isClosed(AbstractTask $task) + { + return \array_key_exists('closed', $task->getCurrentStates()) + || \array_key_exists('canceled', $task->getCurrentStates()); + } +} diff --git a/src/Bundle/ChillTask/Workflow/Event/DefaultTaskGuardEvent.php b/src/Bundle/ChillTask/Workflow/Event/DefaultTaskGuardEvent.php new file mode 100644 index 000000000..e4ceff327 --- /dev/null +++ b/src/Bundle/ChillTask/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); + } + } +} diff --git a/src/Bundle/ChillTask/Workflow/TaskWorkflowDefinition.php b/src/Bundle/ChillTask/Workflow/TaskWorkflowDefinition.php new file mode 100644 index 000000000..d49591a64 --- /dev/null +++ b/src/Bundle/ChillTask/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/src/Bundle/ChillTask/Workflow/TaskWorkflowManager.php b/src/Bundle/ChillTask/Workflow/TaskWorkflowManager.php new file mode 100644 index 000000000..cf34f4c66 --- /dev/null +++ b/src/Bundle/ChillTask/Workflow/TaskWorkflowManager.php @@ -0,0 +1,93 @@ + + * + * 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; +use Symfony\Component\Workflow\Event\Event; + +/** + * + * + * @author Julien Fastré + */ +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(\sprintf("No taskWorkflowDefinition supports this task type: %s (task id: %s).", $task->getType(), $task->getId())); + } + + return $definitions[0]; + } + + public function supports(Workflow $workflow, $subject): bool + { + if (!$subject instanceof AbstractTask) { + return false; + } + + return $workflow->getName() === $this + ->getTaskWorkflowDefinition($subject)->getAssociatedWorkflowName(); + } + + public function getWorkflowMetadata(AbstractTask $task, string $key, $metadataSubject = null, string $name = null) + { + return $this->getTaskWorkflowDefinition($task) + ->getWorkflowMetadata($task, $key, $metadataSubject); + } + + public function onTaskStateEntered(Event $e) + { + $task = $e->getSubject(); + + $definition = $this->getTaskWorkflowDefinition($task); + + $task->setClosed($definition->isClosed($task)); + } +} diff --git a/src/Bundle/ChillTask/chill.webpack.config.js b/src/Bundle/ChillTask/chill.webpack.config.js new file mode 100644 index 000000000..077d2c500 --- /dev/null +++ b/src/Bundle/ChillTask/chill.webpack.config.js @@ -0,0 +1,3 @@ +module.exports = function(encore, entries) { + entries.push(__dirname + '/Resources/public/index.js'); +}; \ No newline at end of file diff --git a/src/Bundle/ChillTask/composer.json b/src/Bundle/ChillTask/composer.json new file mode 100644 index 000000000..41540a946 --- /dev/null +++ b/src/Bundle/ChillTask/composer.json @@ -0,0 +1,41 @@ +{ + "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\\" : "" } + }, + "autoload-dev": { + "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] + }, + "require": { + "chill-project/person": "~1.5" + }, + "require-dev": { + "fzaninotto/faker": "^1.7", + "phpunit/phpunit": "^7.1" + }, + "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/src/Bundle/ChillTask/composer.lock b/src/Bundle/ChillTask/composer.lock new file mode 100644 index 000000000..2ad9ca212 --- /dev/null +++ b/src/Bundle/ChillTask/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": [] +}