From 8a87bfd0ad180ec916476db52960bcacb31cc3ee Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:35:53 +0100 Subject: [PATCH 01/16] Add recover form directly on login page when it failed Remove header block now moved in twig form template Use fragment instead of form Cleanup --- Resources/views/location/index.html.twig | 33 +++++++----------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/Resources/views/location/index.html.twig b/Resources/views/location/index.html.twig index 5863c76..ca51431 100644 --- a/Resources/views/location/index.html.twig +++ b/Resources/views/location/index.html.twig @@ -1,9 +1,8 @@ {% extends '@RapsysAir/body.html.twig' %} {% block content %}
-TODO:

{% trans %}Dashboard{% endtrans %}

-
+
{% if calendar is defined and calendar %} @@ -30,29 +29,17 @@ TODO:
{% endif %} -
- {{ form_start(form) }} - -
{{ form_errors(form) }}
- + {# Display application or login form #} + {% if is_granted('ROLE_GUEST') %}
- {{ form_row(form.location) }} - - {{ form_row(form.date) }} - - {{ form_row(form.slot) }} + {{ include('@RapsysAir/form/_application.html.twig') }}
- - {{ form_row(form.submit) }} - - {# Render CSRF token etc .#} -
- {{ form_rest(form) }} -
- - {{ form_end(form) }} -
-
+ {% elseif not is_granted('IS_AUTHENTICATED_REMEMBERED') %} +
+ {{ include('@RapsysAir/form/_login.html.twig') }} +
+ {% endif %} +
{# dump(calendar) #} {% endblock %} -- 2.41.3 From 1476978ece976e1992e3914abff25e0822af90be Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:36:52 +0100 Subject: [PATCH 02/16] Fix root route name Use body as base template --- Resources/views/security/denied.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/views/security/denied.html.twig b/Resources/views/security/denied.html.twig index 4dbd719..51e5824 100644 --- a/Resources/views/security/denied.html.twig +++ b/Resources/views/security/denied.html.twig @@ -1,7 +1,7 @@ -{% extends '@RapsysAir/base.html.twig' %} +{% extends '@RapsysAir/body.html.twig' %} {% block content %}
-

{{ section }}

+

{{ section }}

{% if message %}{{ message }}{% else %}{% trans %}Access denied{% endtrans %}{% endif %}

{% endblock %} -- 2.41.3 From c5576c3388507075d79944120d610e9692d91da6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:38:11 +0100 Subject: [PATCH 03/16] Add recover and login form fragments --- Resources/views/form/_application.html.twig | 20 ++++++++++++++++++++ Resources/views/form/_login.html.twig | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 Resources/views/form/_application.html.twig create mode 100644 Resources/views/form/_login.html.twig diff --git a/Resources/views/form/_application.html.twig b/Resources/views/form/_application.html.twig new file mode 100644 index 0000000..b165790 --- /dev/null +++ b/Resources/views/form/_application.html.twig @@ -0,0 +1,20 @@ +{{ form_start(application) }} +
+ {% if application.user is defined %} + {{ form_row(application.user) }} + {% endif %} + + {{ form_row(application.location) }} + + {{ form_row(application.date) }} + + {{ form_row(application.slot) }} +
+ + {{ form_row(application.submit) }} + + {# Render CSRF token etc .#} +
+ {{ form_rest(application) }} +
+{{ form_end(application) }} diff --git a/Resources/views/form/_login.html.twig b/Resources/views/form/_login.html.twig new file mode 100644 index 0000000..61d55de --- /dev/null +++ b/Resources/views/form/_login.html.twig @@ -0,0 +1,16 @@ +{{ form_start(login) }} + +
+ {{ form_row(login.mail) }} + + {{ form_row(login.password) }} +
+ + {{ form_row(login.submit) }} + + {# Render CSRF token etc .#} +
+ {{ form_rest(login) }} +
+ +{{ form_end(login) }} -- 2.41.3 From 2ef6650cb5125638bf482a78adb7edd0522eaf8a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:39:11 +0100 Subject: [PATCH 04/16] Add error controller --- Controller/ErrorController.php | 40 +++++++++++++++++++++++++++++++++ Resources/views/error.html.twig | 8 +++++++ 2 files changed, 48 insertions(+) create mode 100644 Controller/ErrorController.php create mode 100644 Resources/views/error.html.twig diff --git a/Controller/ErrorController.php b/Controller/ErrorController.php new file mode 100644 index 0000000..eb53209 --- /dev/null +++ b/Controller/ErrorController.php @@ -0,0 +1,40 @@ +getStatusCode().' '.$this->translator->trans($exception->getStatusText()); + + //Set title + $title = $section.' - '.$this->translator->trans($this->config['site']['title']); + + //Set the message + $message = $exception->getMessage(); + + //Set the trace + $trace = $exception->getAsString(); + + //Render template + return $this->render( + '@RapsysAir/error.html.twig', + ['title' => $title, 'section' => $section, 'message' => $message, 'trace' => $trace]+$this->context + ); + } +} diff --git a/Resources/views/error.html.twig b/Resources/views/error.html.twig new file mode 100644 index 0000000..8dd4a69 --- /dev/null +++ b/Resources/views/error.html.twig @@ -0,0 +1,8 @@ +{% extends '@RapsysAir/body.html.twig' %} +{% block content %} +
+

{{ section }}

+

{% if message %}{{ message }}{% else %}{% trans %}Error{% endtrans %}{% endif %}

+ {% if app.environment == 'dev' and trace is defined %}
{{ trace }}
{% endif %} +
+{% endblock %} -- 2.41.3 From 7dc96408477ff4f0b4a965ef46b364e81faa97a9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:39:50 +0100 Subject: [PATCH 05/16] Add logout success handler that redirect on referer when possible --- Security/LogoutSuccessHandler.php | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Security/LogoutSuccessHandler.php diff --git a/Security/LogoutSuccessHandler.php b/Security/LogoutSuccessHandler.php new file mode 100644 index 0000000..de4d960 --- /dev/null +++ b/Security/LogoutSuccessHandler.php @@ -0,0 +1,110 @@ +router = $router; + } + + /** + * {@inheritdoc} + */ + public function onLogoutSuccess(Request $request) { + //Retrieve logout route + $logout = $request->get('_route'); + + //Extract and process referer + if ($referer = $request->headers->get('referer')) { + //Create referer request instance + $req = Request::create($referer); + + //Get referer path + $path = $req->getPathInfo(); + + //Get referer query string + $query = $req->getQueryString(); + + //Remove script name + $path = str_replace($request->getScriptName(), '', $path); + + //Try with referer path + try { + //Retrieve route matching path + $route = $this->router->match($path); + + //Verify that it differ from current one + if (($name = $route['_route']) == $logout) { + throw new ResourceNotFoundException('Identical referer and logout route'); + } + + //Remove route and controller from route defaults + unset($route['_route'], $route['_controller']); + + //Generate url + $url = $this->router->generate($name, $route); + //No route matched + } catch(ResourceNotFoundException $e) { + //Unset referer to fallback to default route + unset($referer); + } + } + + //Referer empty or unusable + if (empty($referer)) { + //Try with / path + try { + //Retrieve route matching / + $route = $this->router->match('/'); + + //Verify that it differ from current one + if (($name = $route['_route']) == $logout) { + throw new ResourceNotFoundException('Identical referer and logout route'); + } + + //Remove route and controller from route defaults + unset($route['_route'], $route['_controller']); + + //Generate url + $url = $this->router->generate($name, $route); + //Get first route from route collection if / path was not matched + } catch(ResourceNotFoundException $e) { + //Fetch all routes + //XXX: this method regenerate the Routing cache making apps very slow + //XXX: see https://github.com/symfony/symfony-docs/issues/6710 + //XXX: it should be fine to call it without referer and a / route + foreach($this->router->getRouteCollection() as $name => $route) { + //Return on first public route excluding logout one + if (!empty($name) && $name[0] != '_' && $name != $logout) { + break; + } + } + + //Bail out if no route found + if (!isset($name) || !isset($route)) { + throw new \RuntimeException('Unable to retrieve default route'); + } + + //Retrieve route defaults + $defaults = $route->getDefaults(); + + //Remove route and controller from route defaults + unset($defaults['_route'], $defaults['_controller']); + + //Generate url + $url = $this->router->generate($name, $defaults); + } + } + + //Return redirect response + return new RedirectResponse($url, 302); + } +} -- 2.41.3 From f1d7d268283d0e1ab68af19f7ae55826da59c992 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:40:32 +0100 Subject: [PATCH 06/16] Add user controller squeleton --- Controller/UserController.php | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Controller/UserController.php diff --git a/Controller/UserController.php b/Controller/UserController.php new file mode 100644 index 0000000..7f31086 --- /dev/null +++ b/Controller/UserController.php @@ -0,0 +1,7 @@ + Date: Wed, 11 Dec 2019 05:41:03 +0100 Subject: [PATCH 07/16] Add application controller --- Controller/ApplicationController.php | 338 +++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 Controller/ApplicationController.php diff --git a/Controller/ApplicationController.php b/Controller/ApplicationController.php new file mode 100644 index 0000000..cf7e7ce --- /dev/null +++ b/Controller/ApplicationController.php @@ -0,0 +1,338 @@ +denyAccessUnlessGranted('ROLE_GUEST', null, $this->translator->trans('Unable to access this page without role %role%!', ['%role%' => $this->translator->trans('Guest')])); + + //Create ApplicationType form + $form = $this->createForm('Rapsys\AirBundle\Form\ApplicationType', null, [ + //Set the action + 'action' => $this->generateUrl('rapsys_air_application_add'), + //Set the form attribute + #'attr' => [ 'class' => 'col' ], + //Set admin + 'admin' => $this->isGranted('ROLE_ADMIN'), + //Set default user to current + 'user' => $this->getUser()->getId(), + //Set default slot to evening + //XXX: default to Evening (3) + 'slot' => $this->getDoctrine()->getRepository(Slot::class)->findOneById(3) + ]); + + //Reject non post requests + if (!$request->isMethod('POST')) { + throw new \RuntimeException('Request method MUST be POST'); + } + + //Refill the fields in case of invalid form + $form->handleRequest($request); + + //Handle invalid form + if (!$form->isValid()) { + //Set section + $section = $this->translator->trans('Application Add'); + + //Set title + $title = $section.' - '.$this->translator->trans($this->config['site']['title']); + + //Render the view + return $this->render('@RapsysAir/application/add.html.twig', ['title' => $title, 'section' => $section, 'form' => $form]+$this->context); + } + + //Get doctrine + $doctrine = $this->getDoctrine(); + + //Get manager + $manager = $doctrine->getManager(); + + //Get data + $data = $form->getData(); + + //Protect session fetching + try { + //Fetch session + $session = $doctrine->getRepository(Session::class)->findOneByLocationSlotDate($data['location'], $data['slot'], $data['date']); + //Catch no session case + } catch (\Doctrine\ORM\NoResultException $e) { + //Create the session + $session = new Session(); + $session->setLocation($data['location']); + $session->setDate($data['date']); + $session->setSlot($data['slot']); + $session->setCreated(new \DateTime('now')); + $session->setUpdated(new \DateTime('now')); + + //Queue session save + $manager->persist($session); + + //Flush to get the ids + #$manager->flush(); + + $this->addFlash('notice', $this->translator->trans('Session on %date% %location% %slot% created', ['%location%' => $this->translator->trans('at '.$data['location']), '%slot%' => $this->translator->trans('the '.strtolower($data['slot'])), '%date%' => $data['date']->format('Y-m-d')])); + } + + //Set user + $user = $this->getUser(); + + //Replace with requested user for admin + if ($this->isGranted('ROLE_ADMIN') && !empty($data['user'])) { + $user = $this->getDoctrine()->getRepository(User::class)->findOneById($data['user']); + } + + //Protect application fetching + try { + //Retrieve application + $application = $doctrine->getRepository(Application::class)->findOneBySessionUser($session, $user); + + //Add notice in flash message + //TODO: set warning about application already exists bla bla bla... + #$this->addFlash('notice', $this->translator->trans('Application request the %date% for %location% on the slot %slot% saved', ['%location%' => $data['location']->getTitle(), '%slot%' => $data['slot']->getTitle(), '%date%' => $data['date']->format('Y-m-d')])); + + //Add error message to mail field + #$form->get('slot')->addError(new FormError($this->translator->trans('Application already exists'))); + + //TODO: redirect anyway on uri with application highlighted + //Catch no application and session without identifier (not persisted&flushed) cases + } catch (\Doctrine\ORM\NoResultException|\Doctrine\ORM\ORMInvalidArgumentException $e) { + //Create the application + $application = new Application(); + $application->setSession($session); + $application->setUser($user); + $application->setCreated(new \DateTime('now')); + $application->setUpdated(new \DateTime('now')); + + //Refresh session updated field + $session->setUpdated(new \DateTime('now')); + + //Queue session save + $manager->persist($session); + + //Queue application save + $manager->persist($application); + + //Flush to get the ids + #$manager->flush(); + + //Add notice in flash message + $this->addFlash('notice', $this->translator->trans('Application on %date% %location% %slot% created', ['%location%' => $this->translator->trans('at '.$data['location']), '%slot%' => $this->translator->trans('the '.strtolower($data['slot'])), '%date%' => $data['date']->format('Y-m-d')])); + } + + //Try unshort return field + if ( + !empty($data['return']) && + ($unshort = $this->slugger->unshort($data['return'])) && + ($route = json_decode($unshort, true)) !== null + ) { + $return = $this->generateUrl($route['_route'], ['session' => $session->getId()?:1]+$route['_route_params']); + } + + //XXX: Debug + header('Content-Type: text/plain'); + + //Extract and process referer + if ($referer = $request->headers->get('referer')) { + //Create referer request instance + $req = Request::create($referer); + + //Get referer path + $path = $req->getPathInfo(); + + //Get referer query string + $query = $req->getQueryString(); + + //Remove script name + $path = str_replace($request->getScriptName(), '', $path); + + //Try with referer path + try { + var_dump($this->router); + exit; + var_dump($path = '/location'); + var_dump($this->router->match($path)); + var_dump($path = '/fr/emplacement'); + exit; + var_dump($this->router->match()); + exit; + var_dump($path); + var_dump($query); + exit; + //Retrieve route matching path + $route = $this->router->match($path); + var_dump($route); + exit; + + //Verify that it differ from current one + if (($name = $route['_route']) == $logout) { + throw new ResourceNotFoundException('Identical referer and logout route'); + } + + //Remove route and controller from route defaults + unset($route['_route'], $route['_controller']); + + //Generate url + $url = $this->router->generate($name, $route); + //No route matched + } catch(ResourceNotFoundException $e) { + //Unset referer to fallback to default route + unset($referer); + } + } + var_dump($request->headers->get('referer')); + #var_dump($request->get('_route')); + + var_dump($return); + exit; + + //Fetch slugger helper + $slugger = $this->get('rapsys.slugger'); + + var_dump($short = $slugger->short(json_encode(['_route' => $request->get('_route'), '_route_params' => $request->get('_route_params')]))); + $short[12] = 'T'; + var_dump($ret = json_decode($slugger->unshort($short), true)); + var_dump($ret); + var_dump($this->generateUrl($ret['_route'], $ret['_route_params'])); + #var_dump(json_decode($slugger->unshort($data['return']))); + #var_dump($application->getId()); + exit; + + //Init application + $application = false; + + //Protect application fetching + try { + //TODO: handle admin case where we provide a user in extra + $application = $doctrine->getRepository(Application::class)->findOneBySessionUser($session, $this->getUser()); + + //Add error message to mail field + $form->get('slot')->addError(new FormError($this->translator->trans('Application already exists'))); + //Catch no application cases + //XXX: combine these catch when php 7.1 is available + } catch (\Doctrine\ORM\NoResultException $e) { + //Catch invalid argument because session is not already persisted + } catch(\Doctrine\ORM\ORMInvalidArgumentException $e) { + } + + //Create new application if none found + if (!$application) { + //Create the application + $application = new Application(); + $application->setSession($session); + //TODO: handle admin case where we provide a user in extra + $application->setUser($this->getUser()); + $application->setCreated(new \DateTime('now')); + $application->setUpdated(new \DateTime('now')); + $manager->persist($application); + + //Flush to get the ids + $manager->flush(); + + //Add notice in flash message + $this->addFlash('notice', $this->translator->trans('Application request the %date% for %location% on the slot %slot% saved', ['%location%' => $data['location']->getTitle(), '%slot%' => $data['slot']->getTitle(), '%date%' => $data['date']->format('Y-m-d')])); + + //Redirect to cleanup the form + return $this->redirectToRoute('rapsys_air_admin'); + } + } + + function test(Request $request) { + + //Compute period + $period = new \DatePeriod( + //Start from first monday of week + new \DateTime('Monday this week'), + //Iterate on each day + new \DateInterval('P1D'), + //End with next sunday and 4 weeks + new \DateTime('Monday this week + 5 week') + ); + + //Fetch sessions + $sessions = $doctrine->getRepository(Session::class)->findAllByDatePeriod($period); + + //Init calendar + $calendar = []; + + //Init month + $month = null; + + //Iterate on each day + foreach($period as $date) { + //Init day in calendar + $calendar[$Ymd = $date->format('Ymd')] = [ + 'title' => $date->format('d'), + 'class' => [], + 'sessions' => [] + ]; + //Append month for first day of month + if ($month != $date->format('m')) { + $month = $date->format('m'); + $calendar[$Ymd]['title'] .= '/'.$month; + } + //Deal with today + if ($date->format('U') == ($today = strtotime('today'))) { + $calendar[$Ymd]['title'] .= '/'.$month; + $calendar[$Ymd]['current'] = true; + $calendar[$Ymd]['class'][] = 'current'; + } + //Disable passed days + if ($date->format('U') < $today) { + $calendar[$Ymd]['disabled'] = true; + $calendar[$Ymd]['class'][] = 'disabled'; + } + //Set next month days + if ($date->format('m') > date('m')) { + $calendar[$Ymd]['next'] = true; + $calendar[$Ymd]['class'][] = 'next'; + } + //Iterate on each session to find the one of the day + foreach($sessions as $session) { + if (($sessionYmd = $session->getDate()->format('Ymd')) == $Ymd) { + //Count number of application + $count = count($session->getApplications()); + + //Compute classes + $class = []; + if ($session->getApplication()) { + $class[] = 'granted'; + } elseif ($count == 0) { + $class[] = 'orphaned'; + } elseif ($count > 1) { + $class[] = 'disputed'; + } else { + $class[] = 'pending'; + } + + //Add the session + $calendar[$Ymd]['sessions'][$session->getSlot()->getId().$session->getLocation()->getId()] = [ + 'id' => $session->getId(), + 'title' => ($count > 1?'['.$count.'] ':'').$session->getSlot()->getTitle().' '.$session->getLocation()->getTitle(), + 'class' => $class + ]; + } + } + + //Sort sessions + ksort($calendar[$Ymd]['sessions']); + } + } +} -- 2.41.3 From 9b5aab485913580c7cdf414f4d8157ae6aa4c4b4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:41:49 +0100 Subject: [PATCH 08/16] Add Slot repository Add function to retrieve slot listing with translated title Add User repository Add function to retrieve user with translated title in a translated group keyed tree --- Repository/SlotRepository.php | 60 ++++++++++++++++++++ Repository/UserRepository.php | 103 ++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 Repository/SlotRepository.php create mode 100644 Repository/UserRepository.php diff --git a/Repository/SlotRepository.php b/Repository/SlotRepository.php new file mode 100644 index 0000000..b54fd5a --- /dev/null +++ b/Repository/SlotRepository.php @@ -0,0 +1,60 @@ +getEntityManager(); + + //Get quote strategy + $qs = $em->getConfiguration()->getQuoteStrategy(); + $dp = $em->getConnection()->getDatabasePlatform(); + + //Set the request from quoted table name + //XXX: this allow to make this code table name independent + $req = 'SELECT s.id, s.title FROM '.$qs->getTableName($em->getClassMetadata('RapsysAirBundle:Slot'), $dp).' AS s'; + + //Get result set mapping instance + //XXX: DEBUG: see ../blog.orig/src/Rapsys/BlogBundle/Repository/ArticleRepository.php + $rsm = new ResultSetMapping(); + + //Declare all fields + //XXX: see vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Types.php + //addScalarResult($sqlColName, $resColName, $type = 'string'); + $rsm->addScalarResult('id', 'id', 'integer') + ->addScalarResult('title', 'title', 'string') + ->addIndexByScalar('id'); + + //Fetch result + $res = $em + ->createNativeQuery($req, $rsm) + ->getResult(); + + //Init return + $ret = []; + + //Process result + foreach($res as $data) { + //Get translated slot + $slot = $translator->trans($data['title']); + //Set data + //XXX: ChoiceType use display string as key + $ret[$slot] = $data['id']; + } + + //Send result + return $ret; + } +} diff --git a/Repository/UserRepository.php b/Repository/UserRepository.php new file mode 100644 index 0000000..cc51ea0 --- /dev/null +++ b/Repository/UserRepository.php @@ -0,0 +1,103 @@ +getEntityManager(); + + //Get quote strategy + $qs = $em->getConfiguration()->getQuoteStrategy(); + $dp = $em->getConnection()->getDatabasePlatform(); + + //Get quoted table names + //XXX: this allow to make this code table name independent + $tables = [ + 'RapsysAirBundle:GroupUser' => $qs->getJoinTableName($em->getClassMetadata('RapsysAirBundle:User')->getAssociationMapping('groups'), $em->getClassMetadata('RapsysAirBundle:User'), $dp), + 'RapsysAirBundle:Group' => $qs->getTableName($em->getClassMetadata('RapsysAirBundle:Group'), $dp), + 'RapsysAirBundle:Title' => $qs->getTableName($em->getClassMetadata('RapsysAirBundle:Title'), $dp), + 'RapsysAirBundle:User' => $qs->getTableName($em->getClassMetadata('RapsysAirBundle:User'), $dp) + ]; + + //Set the request + $req = 'SELECT a.id, a.forename, a.surname, a.t_id, a.t_short, a.t_title, a.g_id, a.g_title FROM ( + SELECT u.id, u.forename, u.surname, t.id AS t_id, t.short AS t_short, t.title AS t_title, g.id AS g_id, g.title AS g_title + FROM RapsysAirBundle:User AS u + JOIN RapsysAirBundle:Title AS t ON (t.id = u.title_id) + LEFT JOIN RapsysAirBundle:GroupUser AS gu ON (gu.user_id = u.id) + LEFT JOIN RapsysAirBundle:Group AS g ON (g.id = gu.group_id) + ORDER BY g.id DESC, NULL LIMIT '.PHP_INT_MAX.' + ) AS a GROUP BY a.id ORDER BY NULL'; + + //Replace bundle entity name by table name + $req = str_replace(array_keys($tables), array_values($tables), $req); + + //Get result set mapping instance + //XXX: DEBUG: see ../blog.orig/src/Rapsys/BlogBundle/Repository/ArticleRepository.php + $rsm = new ResultSetMapping(); + + /*XXX: we don't want a result set for our request + $rsm->addEntityResult('RapsysAirBundle:User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'forename', 'forename'); + $rsm->addFieldResult('u', 'surname', 'surname'); + $rsm->addFieldResult('u', 't_id', 'title_id'); + $rsm->addJoinedEntityResult('RapsysAirBundle:Title', 't', 'u', 'title'); + $rsm->addFieldResult('t', 't_id', 'id'); + $rsm->addFieldResult('t', 't_title', 'title'); + $rsm->addJoinedEntityResult('RapsysAirBundle:Group', 'g', 'u', 'groups'); + $rsm->addFieldResult('g', 'g_id', 'id'); + $rsm->addFieldResult('g', 'g_title', 'title');*/ + + //Declare all fields + //XXX: see vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Types.php + //addScalarResult($sqlColName, $resColName, $type = 'string'); + $rsm->addScalarResult('id', 'id', 'integer') + ->addScalarResult('forename', 'forename', 'string') + ->addScalarResult('surname', 'surname', 'string') + ->addScalarResult('t_id', 't_id', 'integer') + ->addScalarResult('t_short', 't_short', 'string') + ->addScalarResult('t_title', 't_title', 'string') + ->addScalarResult('g_id', 'g_id', 'integer') + ->addScalarResult('g_title', 'g_title', 'string') + ->addIndexByScalar('id'); + + //Fetch result + $res = $em + ->createNativeQuery($req, $rsm) + ->getResult(); + + //Init return + $ret = []; + + //Process result + foreach($res as $data) { + //Get translated group + $group = $translator->trans($data['g_title']?:'ROLE_USER'); + //Get translated title + $title = $translator->trans($data['t_short']); + //Init group subarray + if (!isset($ret[$group])) { + $ret[$group] = []; + } + //Set data + //XXX: ChoiceType use display string as key + $ret[$group][$title.' '.$data['forename'].' '.$data['surname']] = $data['id']; + } + + //Send result + return $ret; + } +} -- 2.41.3 From 7b9fde20d38a687d0cf80256fddcb54e8f62071c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:46:06 +0100 Subject: [PATCH 09/16] Disable unused Title repository --- Resources/config/doctrine/Title.orm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/config/doctrine/Title.orm.yml b/Resources/config/doctrine/Title.orm.yml index 8652f06..481707d 100644 --- a/Resources/config/doctrine/Title.orm.yml +++ b/Resources/config/doctrine/Title.orm.yml @@ -1,6 +1,6 @@ Rapsys\AirBundle\Entity\Title: type: entity - repositoryClass: Rapsys\AirBundle\Repository\TitleRepository + #repositoryClass: Rapsys\AirBundle\Repository\TitleRepository table: titles fields: short: -- 2.41.3 From fbe2745dd0e334a1228865bcc240009e39d85228 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:49:36 +0100 Subject: [PATCH 10/16] Fix base route Add translated routes Rename policy in regulation --- Resources/config/routes/rapsys_air.yaml | 58 +++++++++++++++++++------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/Resources/config/routes/rapsys_air.yaml b/Resources/config/routes/rapsys_air.yaml index 518cc02..333451e 100644 --- a/Resources/config/routes/rapsys_air.yaml +++ b/Resources/config/routes/rapsys_air.yaml @@ -1,34 +1,64 @@ -rapsys_air_index: - path: / +#https://symfony.com/doc/current/routing.html#localized-routes-i18n +#SCRUD: index, add, edit, delete, view +rapsys_air: + path: + en: '/' + fr: '/fr' controller: Rapsys\AirBundle\Controller\DefaultController::index methods: GET rapsys_air_contact: - path: /contact + path: + en: '/contact' + fr: '/fr/contact' controller: Rapsys\AirBundle\Controller\DefaultController::contact methods: GET|POST -rapsys_air_policy: - path: /policy - controller: Rapsys\AirBundle\Controller\DefaultController::policy +rapsys_air_regulation: + path: + en: '/regulation' + fr: '/fr/reglement' + controller: Rapsys\AirBundle\Controller\DefaultController::regulation methods: GET rapsys_air_location: - path: /locations + path: + en: '/location' + fr: '/fr/emplacement' controller: Rapsys\AirBundle\Controller\LocationController::index methods: GET -rapsys_air_location_show: - path: /location/{id<\d+>} - controller: Rapsys\AirBundle\Controller\LocationController::show +rapsys_air_location_view: + path: + en: '/location/{id<\d+>}' + fr: '/fr/emplacement/{id<\d+>}' + controller: Rapsys\AirBundle\Controller\LocationController::view methods: GET +rapsys_air_application_add: + path: + en: '/application' + fr: '/fr/reservation' + controller: Rapsys\AirBundle\Controller\ApplicationController::add + methods: POST + +rapsys_air_application_edit: + path: + en: '/application/{id<\d+>}' + fr: '/fr/reservation/{id<\d+>}' + controller: Rapsys\AirBundle\Controller\ApplicationController::edit + methods: POST + rapsys_air_user: - path: /users + path: + en: '/user' + fr: '/fr/utilisateur' controller: Rapsys\AirBundle\Controller\UserController::index methods: GET -rapsys_air_user_show: - path: /user/{id<\d+>} - controller: Rapsys\AirBundle\Controller\UserController::show +rapsys_air_user_view: + path: + en: '/user/{id<\d+>}' + fr: '/fr/utilisateur/{id<\d+>}' + controller: Rapsys\AirBundle\Controller\UserController::view methods: GET -- 2.41.3 From 7ba714d41ccf386ab9370defa62f164741e56417 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:50:54 +0100 Subject: [PATCH 11/16] Fix base route Use referer in login form Add logout success handler Fix services arguments Add Application, User and Error controller definition Add ApplicationType service definition Add Logout success handler service definition Fix Access denied success handler service definition Follow pack_package service renaming --- Resources/config/packages/rapsys_air.yaml | 55 ++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/Resources/config/packages/rapsys_air.yaml b/Resources/config/packages/rapsys_air.yaml index 92abe3f..a4a9066 100644 --- a/Resources/config/packages/rapsys_air.yaml +++ b/Resources/config/packages/rapsys_air.yaml @@ -19,7 +19,7 @@ rapsys_user: #Route replacement route: index: - name: 'rapsys_air_index' + name: 'rapsys_air' #Contact replacement contact: name: '%rapsys_air.contact.name%' @@ -151,8 +151,9 @@ security: #Set form login #XXX: https://symfony.com/doc/current/security/form_login_setup.html + #TODO: https://symfony.com/doc/current/security/guard_authentication.html form_login: - use_referer: false + use_referer: true login_path: rapsys_user_login check_path: rapsys_user_login username_parameter: 'login[mail]' @@ -160,8 +161,10 @@ security: #Set logout route logout: - path: /logout - target: / + path: rapsys_user_logout + #XXX: see https://symfony.com/doc/current/security.html#logging-out + success_handler: Rapsys\AirBundle\Security\LogoutSuccessHandler + #target: / #Set custom access denied handler access_denied_handler: Rapsys\AirBundle\Security\AccessDeniedHandler @@ -173,10 +176,12 @@ security: ROLE_SENIOR: [ 'ROLE_USER', 'ROLE_GUEST', 'ROLE_REGULAR' ] ROLE_ADMIN: [ 'ROLE_USER', 'ROLE_GUEST', 'ROLE_REGULAR', 'ROLE_SENIOR' ] -##Framework configuration -#XXX: don't use that shit, it breaks assets._default_package url generation +#Framework configuration #framework: +# error_controller: Rapsys\AirBundle\Controller\ErrorController::show +# # #Assets configuration +# XXX: don't use that shit, it breaks assets._default_package url generation # assets: # #Set default base path # #base_path: '/bundles/%%s' @@ -206,33 +211,51 @@ services: tags: [ 'twig.extension' ] #Register default controller Rapsys\AirBundle\Controller\DefaultController: - arguments: [ '@service_container', '@translator', '@router' ] + arguments: [ '@service_container', '@router', '@rapsys.slugger', '@translator' ] tags: [ 'controller.service_arguments' ] #Register location controller Rapsys\AirBundle\Controller\LocationController: - arguments: [ '@service_container', '@translator', '@router' ] + arguments: [ '@service_container', '@router', '@rapsys.slugger', '@translator' ] + tags: [ 'controller.service_arguments' ] + #Register application controller + Rapsys\AirBundle\Controller\ApplicationController: + arguments: [ '@service_container', '@router', '@rapsys.slugger', '@translator' ] + tags: [ 'controller.service_arguments' ] + #Register user controller + Rapsys\AirBundle\Controller\UserController: + arguments: [ '@service_container', '@router', '@rapsys.slugger', '@translator' ] + tags: [ 'controller.service_arguments' ] + #Register error controller + Rapsys\AirBundle\Controller\ErrorController: + arguments: [ '@service_container', '@router', '@rapsys.slugger', '@translator' ] tags: [ 'controller.service_arguments' ] #Register access denied handler Rapsys\AirBundle\Security\AccessDeniedHandler: - arguments: [ '@service_container', '@translator', '@twig' ] + arguments: [ '@service_container', '@twig', '@router', '@translator' ] + #Register logout success handler + Rapsys\AirBundle\Security\LogoutSuccessHandler: + arguments: [ '@router' ] #Register air fixtures Rapsys\AirBundle\DataFixtures\AirFixtures: tags: [ 'doctrine.fixture.orm' ] #Replace assets packages assets.packages: - class: Symfony\Component\Asset\Packages - arguments: [ '@assets.pack_package' ] + class: 'Symfony\Component\Asset\Packages' + arguments: [ '@rapsys.pack_package' ] # #Set version strategy # assets.static_version_strategy: # class: Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy # arguments: [ 'x', '%%s?v=%%s' ] + Rapsys\AirBundle\Form\ApplicationType: + arguments: [ '@doctrine', '@translator' ] + tags: [ 'form.type' ] #Twig Configuration twig: -# #Enforce debug -# debug: true -# auto_reload: ~ -# cache: false + #Enforce debug + debug: true + auto_reload: ~ + cache: false #Fix form layout for css #XXX: @RapsysAir is a shortcut to vendor/rapsys/airbundle/Resources/views directory here form_theme: [ '@RapsysAir/form/form_div_layout.html.twig' ] @@ -241,3 +264,5 @@ twig: #Required by email.image(site_logo) directive #XXX: Allow twig to resolve @RapsysAir/png/logo.png in vendor/rapsys/airbundle/Resources/public/png/logo.png '%kernel.project_dir%/vendor/rapsys/airbundle/Resources/public': 'RapsysAir' + #Override default exception controller + #exception_controller: Rapsys\AirBundle\Controller\ErrorController::preview -- 2.41.3 From b8a4818439c3b078853d12c566462e767e1ebc5d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 05:57:48 +0100 Subject: [PATCH 12/16] Disable twig debug --- Resources/config/packages/rapsys_air.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Resources/config/packages/rapsys_air.yaml b/Resources/config/packages/rapsys_air.yaml index a4a9066..518a2cc 100644 --- a/Resources/config/packages/rapsys_air.yaml +++ b/Resources/config/packages/rapsys_air.yaml @@ -253,9 +253,9 @@ services: #Twig Configuration twig: #Enforce debug - debug: true - auto_reload: ~ - cache: false + #debug: true + #auto_reload: ~ + #cache: false #Fix form layout for css #XXX: @RapsysAir is a shortcut to vendor/rapsys/airbundle/Resources/views directory here form_theme: [ '@RapsysAir/form/form_div_layout.html.twig' ] -- 2.41.3 From 48ac8640c9a6bf908a754022eb149202ba3af8f8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 09:22:22 +0100 Subject: [PATCH 13/16] Remove _canonical_route from _route_params --- Security/LogoutSuccessHandler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Security/LogoutSuccessHandler.php b/Security/LogoutSuccessHandler.php index de4d960..3ac33f7 100644 --- a/Security/LogoutSuccessHandler.php +++ b/Security/LogoutSuccessHandler.php @@ -47,7 +47,7 @@ class LogoutSuccessHandler implements LogoutSuccessHandlerInterface { } //Remove route and controller from route defaults - unset($route['_route'], $route['_controller']); + unset($route['_route'], $route['_controller'], $route['_canonical_route']); //Generate url $url = $this->router->generate($name, $route); @@ -71,7 +71,7 @@ class LogoutSuccessHandler implements LogoutSuccessHandlerInterface { } //Remove route and controller from route defaults - unset($route['_route'], $route['_controller']); + unset($route['_route'], $route['_controller'], $route['_canonical_route']); //Generate url $url = $this->router->generate($name, $route); @@ -97,7 +97,7 @@ class LogoutSuccessHandler implements LogoutSuccessHandlerInterface { $defaults = $route->getDefaults(); //Remove route and controller from route defaults - unset($defaults['_route'], $defaults['_controller']); + unset($defaults['_route'], $defaults['_controller'], $defaults['_canonical_route']); //Generate url $url = $this->router->generate($name, $defaults); -- 2.41.3 From 5a1c2978daafdf0ffed02ee261668d4f6d3342a7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 09:23:53 +0100 Subject: [PATCH 14/16] Add warning class --- Resources/public/css/screen.css | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Resources/public/css/screen.css b/Resources/public/css/screen.css index 271e7d6..953d83f 100644 --- a/Resources/public/css/screen.css +++ b/Resources/public/css/screen.css @@ -137,6 +137,8 @@ body { /* Message */ .notice::before, .notice::after, +.warning::before, +.warning::after, .error::before, .error::after { content: "⚠"; @@ -146,6 +148,7 @@ body { } .notice, +.warning, .error { display: flex; flex-direction: row; @@ -173,11 +176,26 @@ body { color: #3333c3; } +.warning::before, +.warning::after { + /*XXX: see https://www.fileformat.info/info/unicode/char/2139/fontsupport.htm + * DejaVu Sans/FreeSans/FreeSerif/Linux Libertine/Symbola/Unifont*/ + content: "?"; + /*content: "⌘";*/ +} + +.warning { + border-color: #c39333; + background-color: #f9c333; + color: #936333; +} + .flash { margin: 0 .5rem .5rem; } p.notice, +p.warning, p.error { margin-bottom: .5rem; } -- 2.41.3 From d458958d9f2bff86eef468ce0502bc281aab8c8e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 11 Dec 2019 09:25:27 +0100 Subject: [PATCH 15/16] Set slot as nullable Set default user to 1 --- Form/ApplicationType.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Form/ApplicationType.php b/Form/ApplicationType.php index 1107ac6..3e6f097 100644 --- a/Form/ApplicationType.php +++ b/Form/ApplicationType.php @@ -59,10 +59,11 @@ class ApplicationType extends AbstractType { * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(['error_bubbling' => true, 'admin' => false, 'return' => '', 'slot' => 3, 'user' => 0]); + //XXX: 1 should be the first user + $resolver->setDefaults(['error_bubbling' => true, 'admin' => false, 'return' => '', 'slot' => null, 'user' => 1]); $resolver->setAllowedTypes('admin', 'boolean'); $resolver->setAllowedTypes('return', 'string'); - $resolver->setAllowedTypes('slot', Slot::class); + $resolver->setAllowedTypes('slot', [Slot::class, 'null']); $resolver->setAllowedTypes('user', 'integer'); } -- 2.41.3 From 33c1d5701a7dc0e6e54fc0fd016782811d1d4f95 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Wed, 14 Oct 2020 03:50:48 +0200 Subject: [PATCH 16/16] Add session controller with view function --- Controller/SessionController.php | 150 +++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Controller/SessionController.php diff --git a/Controller/SessionController.php b/Controller/SessionController.php new file mode 100644 index 0000000..c184f4d --- /dev/null +++ b/Controller/SessionController.php @@ -0,0 +1,150 @@ +getDoctrine(); + + //Fetch session + //TODO: genereate a custom request to fetch everything in a single request ??? + $session = $doctrine->getRepository(Session::class)->findOneById($id); + + //Fetch session + //TODO: genereate a custom request to fetch everything in a single request ??? + $location = $session->getLocation(); #$doctrine->getRepository(Location::class)->findOneBySession($session); + + //Set section + //TODO: replace with $session['location']['title'] + $section = $this->translator->trans($location); + + //Set title + $title = $this->translator->trans('Session %id%', ['%id%' => $id]).' - '.$section.' - '.$this->translator->trans($this->config['site']['title']); + + //Init context + $context = []; + + //Create application form for role_guest + if ($this->isGranted('ROLE_GUEST')) { + //Create ApplicationType form + $application = $this->createForm('Rapsys\AirBundle\Form\ApplicationType', null, [ + //Set the action + 'action' => $this->generateUrl('rapsys_air_application_add'), + //Set the form attribute + 'attr' => [ 'class' => 'col' ], + //Set admin + 'admin' => $this->isGranted('ROLE_ADMIN'), + //Set default user to current + 'user' => $this->getUser()->getId(), + //Set default slot to evening + //XXX: default to Evening (3) + 'slot' => $this->getDoctrine()->getRepository(Slot::class)->findOneById(3) + ]); + + //Add form to context + $context['application'] = $application->createView(); + //Create login form for anonymous + } elseif (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + //Create ApplicationType form + $login = $this->createForm('Rapsys\UserBundle\Form\LoginType', null, [ + //Set the action + 'action' => $this->generateUrl('rapsys_user_login'), + //Set the form attribute + 'attr' => [ 'class' => 'col' ] + ]); + + //Add form to context + $context['login'] = $login->createView(); + } + + //Extract session date + $sessionDate = $session->getDate(); + + //Init session begin and end + $sessionBegin = $sessionEnd = null; + + //Check session begin and end availability + if (($sessionBegin = $session->getBegin()) && ($sessionLength = $session->getLength())) { + $sessionBegin = new \DateTime($sessionDate->format('Y-m-d')."\t".$sessionBegin->format('H:i:sP')); + #$sessionEnd = (new \DateTime($sessionDate->format('Y-m-d')."\t".$sessionBegin->format('H:i:sP')))->add(new \DateInterval($sessionLength->format('\P\TH\Hi\Ms\S'))); + $sessionEnd = (clone $sessionBegin)->add(new \DateInterval($sessionLength->format('\P\TH\Hi\Ms\S'))); + } + + //Add session in context + $context['session'] = [ + 'id' => ($sessionId = $session->getId()), + 'date' => $sessionDate, + 'begin' => $sessionBegin, + 'end' => $sessionEnd, + #'begin' => new \DateTime($session->getDate()->format('Y-m-d')."\t".$session->getBegin()->format('H:i:sP')), + #'end' => (new \DateTime($session->getDate()->format('Y-m-d')."\t".$session->getBegin()->format('H:i:sP')))->add(new \DateInterval($session->getLength()->format('\P\TH\Hi\Ms\S'))), + #'length' => $session->getLength(), + 'created' => $session->getCreated(), + 'updated' => $session->getUpdated(), + 'title' => $this->translator->trans('Session %id%', ['%id%' => $sessionId]), + 'application' => null, + 'location' => [ + 'id' => ($location = $session->getLocation())->getId(), + 'title' => $this->translator->trans($location), + ], + 'slot' => [ + 'id' => ($slot = $session->getSlot())->getId(), + 'title' => $this->translator->trans($slot), + ], + 'applications' => null, + ]; + + if ($application = $session->getApplication()) { + $context['session']['application'] = [ + 'user' => [ + 'id' => ($user = $application->getUser())->getId(), + 'title' => (string) $user->getPseudonym(), + ], + 'id' => ($applicationId = $application->getId()), + 'title' => $this->translator->trans('Application %id%', [ '%id%' => $applicationId ]), + ]; + } + + if ($applications = $session->getApplications()) { + $context['session']['applications'] = []; + foreach($applications as $application) { + $context['session']['applications'][$applicationId = $application->getId()] = [ + 'user' => null, + 'created' => $application->getCreated(), + 'updated' => $application->getUpdated(), + ]; + if ($user = $application->getUser()) { + $context['session']['applications'][$applicationId]['user'] = [ + 'id' => $user->getId(), + 'title' => (string) $user->getPseudonym(), + ]; + } + } + } + + //Add location in context + #$context['location'] = [ + # 'id' => $location->getId(), + # 'title' => $location->getTitle(), + #]; + + //Render the view + return $this->render('@RapsysAir/session/view.html.twig', ['title' => $title, 'section' => $section]+$context+$this->context); + } +} -- 2.41.3