From 9428d34e0422a8e781d6d7a03e6b5f73249efa4c 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.1 From 1f149a0823fee155e990cec1c716383c383be674 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.1 From 3cb6a3ec801d63645687f92c8aba932e2cd4008c 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.1 From 75d1b506489d9709b7f136942f470fc40603d467 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.1 From 2f4d90614dfa70536e2fc565d5b76bef4ecf3e80 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.1 From 2a3cdfda1bd84e24fd082e970c8a317267272f13 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.1 From 3b7129d4925b8ade812f3e2100da9640f1ebb49e 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.1 From efee50f8b43a22de0aedf05b9467080f2ca75b6a 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.1 From 1d8b24b18812d0115fd0259818075f7304b48e21 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.1 From ab1e0541f86db44abb3d8bc7a603b4fb1a0a9ffd 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.1 From cc0c3890e942bf2729415d6a4e998c1857b04bb0 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.1 From 8f64b0a6c30aa85b5306ca3eea00ab67a83b5fb2 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.1 From ef048fcc1c05cf5073e767ff65a5c24dabc640a8 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.1 From ebb56d5ee4c2cbfd468b7a2ee28fb2dae9ad1217 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.1 From a2fc5190735d03dfe95fe7bdbc023b2df268e2fb 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.1