From 52ed7a1ca206b28536a7a91a2edab34a4d73af01 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Thu, 13 Oct 2022 15:19:07 +0200 Subject: [PATCH 01/16] New header layout New event layout --- Resources/views/session/index.html.twig | 31 ++++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Resources/views/session/index.html.twig b/Resources/views/session/index.html.twig index 9d6e2a6..9317b4c 100644 --- a/Resources/views/session/index.html.twig +++ b/Resources/views/session/index.html.twig @@ -1,7 +1,10 @@ {% extends '@RapsysAir/body.html.twig' %} {% block content %}
-

{{ section }}

+
+

{{ title }}

+

{{ description }}

+
{% if calendar is defined and calendar %}
@@ -12,20 +15,20 @@
    {% for session in day.sessions %}
  • - - {{ session.start|localizeddate('none', 'short') }} - {{ session.location }} - - {% if session.weather is defined and session.weather %} - {{ session.weather }} - {% endif %} - {{ session.slot }} - - {{ session.stop|localizeddate('none', 'short') }} - {% if session.pseudonym is defined and session.pseudonym %} - {{ session.pseudonym }} + + {{ session.start|intldate('none', 'short') }} + {{ session.location.title }} + {{ session.temperature.glyph }} + {{ session.stop|intldate('none', 'short') }} + {% if session.application.user.title is defined and session.application.user.title %}{{ session.application.user.title }}{% endif %} + {{ session.rain.glyph }} + {% if session.rate is defined and session.rate %} + {% if session.rate.rate is defined and session.rate.rate %}{{ session.rate.rate }} {% endif %}{{ session.rate.glyph }} + {% else %} + {% endif %} - {% if session.rate is defined %}{{ session.rate }} {% if session.hat is defined and session.hat %}🎩{% else %}€{% endif %}{% endif %} + {{ session.location.zipcode }} {{ session.location.city }} +
  • {% endfor %} -- 2.41.1 From 12df25845913a496e26ef0f7deef67b3dbdabc4f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Thu, 13 Oct 2022 15:20:00 +0200 Subject: [PATCH 02/16] New header layout Cleanup --- Resources/views/session/index.html.twig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Resources/views/session/index.html.twig b/Resources/views/session/index.html.twig index 9317b4c..59fc9e6 100644 --- a/Resources/views/session/index.html.twig +++ b/Resources/views/session/index.html.twig @@ -1,9 +1,8 @@ {% extends '@RapsysAir/body.html.twig' %} {% block content %} -
    +
    -

    {{ title }}

    -

    {{ description }}

    +

    {{ description }}

    {% if calendar is defined and calendar %} -- 2.41.1 From c99350a6b27d74c60076ed5661cfde2b3724cea6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Thu, 13 Oct 2022 15:20:45 +0200 Subject: [PATCH 03/16] New header layout New session layout --- Resources/views/session/view.html.twig | 327 +++++++++++++------------ 1 file changed, 176 insertions(+), 151 deletions(-) diff --git a/Resources/views/session/view.html.twig b/Resources/views/session/view.html.twig index 02df6db..aca1710 100644 --- a/Resources/views/session/view.html.twig +++ b/Resources/views/session/view.html.twig @@ -1,123 +1,165 @@ {% extends '@RapsysAir/body.html.twig' %} {% block content %} -
    +
    -

    - {{ session.title }} + {#

    {% if session.application.id is defined and session.application.id %} - {{ session.application.user.by }} + {{ session.application.dance.title ~ ' ' ~ session.id }} + {{ session.application.user.by }} + {% else %} + {{ title }} {% endif %} - {{ session.location.at }} -

    -

    {{ description }}

    + {{ session.location.at }} + #} +

    {{ description }}

    -
    +
    -

    {% trans %}Organizer{% endtrans %}

    +
    +

    {% trans %}Program{% endtrans %}

    +
    -
    {% trans %}Attributed to{% endtrans %}
    -
    - {% if session.application is null %} - {% trans %}None{% endtrans %} - {% else %} - {{ session.application.user.title }} - {% endif %} -
    +
    {% trans %}Date and schedule{% endtrans %}
    +
    {{ 'The %date% around %start% until %stop%'|trans({'%date%': session.start|intldate('long', 'none'), '%start%': session.start|intldate('none', 'medium'), '%stop%': session.stop|intldate('none', 'medium')}) }}
    - {% if session.snippet.description is defined and session.snippet.description %} -
    -
    {% trans %}Description{% endtrans %}
    -
    {{ session.snippet.description|striptags|markdown_to_html }}
    -
    - {% endif %} - {% if session.snippet.class is defined and session.snippet.class %} -
    -
    {% trans %}Class{% endtrans %}
    -
    {{ session.snippet.class|striptags|markdown_to_html }}
    -
    - {% endif %} - {% if session.snippet.contact is defined and session.snippet.contact %} -
    -
    {% trans %}Contact{% endtrans %}
    -
    {{ 'Send a message to %pseudonym%'|trans({'%pseudonym%': session.application.user.title}) }}
    -
    - {% endif %} - {% if session.snippet.donate is defined and session.snippet.donate %} -
    -
    {% trans %}Donate{% endtrans %}
    -
    {{ 'Donate to %pseudonym%'|trans({'%pseudonym%': session.application.user.title}) }}
    -
    - {% endif %} - {% if session.snippet.link is defined and session.snippet.link %} -
    -
    {% trans %}Link{% endtrans %}
    -
    {{ 'Link to %pseudonym%'|trans({'%pseudonym%': session.application.user.title}) }}
    -
    - {% endif %} - {% if session.snippet.profile is defined and session.snippet.profile %} -
    -
    {% trans %}Social network{% endtrans %}
    -
    {{ 'Consult %pseudonym% profile'|trans({'%pseudonym%': session.application.user.title}) }}
    -
    - {% endif %} - {% if session.snippet.rate is defined and session.snippet.rate %} - {% if session.snippet.hat is defined and session.snippet.hat %} + {% if session.application is defined and session.application %} + {% if session.application.dance is defined and session.application.dance %}
    -
    {% trans %}Contribution{% endtrans %}
    -
    {% if session.snippet.rate == 0 %}{% trans %}To the hat, to cover the costs: talc, electricity, bicycle, server{% endtrans %}{% else %}{{ 'To the hat, ideally %rate% €, to cover the costs: talc, electricity, bicycle, server'|trans({'%rate%': session.snippet.rate}) }}{% endif %}
    +
    {% trans %}Activity{% endtrans %}
    + {#
    {{ session.application.dance.title }}
    #} +
    {{ session.application.dance.title }}
    - {% else %} + {% endif %} + {% if session.application.user is defined and session.application.user %}
    -
    {% trans %}Rate{% endtrans %}
    -
    {% if session.snippet.rate == 0 %}{% trans %}Free{% endtrans %}{% else %}{{ session.snippet.rate }} €{% endif %}
    +
    {% trans %}Organizer{% endtrans %}
    +
    {{ session.application.user.title }}
    {% endif %} + {% if session.snippet is defined and session.snippet %} + {% if session.snippet.description is defined and session.snippet.description %} +
    +
    {% trans %}Description{% endtrans %}
    +
    {{ session.snippet.description|striptags|markdown_to_html }}
    +
    + {% endif %} + {% if session.snippet.class is defined and session.snippet.class %} +
    +
    {% trans %}Class{% endtrans %}
    +
    {{ session.snippet.class|striptags|markdown_to_html }}
    +
    + {% endif %} + {% if session.snippet.contact is defined and session.snippet.contact %} +
    +
    {% trans %}Contact{% endtrans %}
    +
    {{ 'Send a message to %pseudonym%'|trans({'%pseudonym%': session.application.user.title}) }}
    +
    + {% endif %} + {% if session.snippet.donate is defined and session.snippet.donate %} +
    +
    {% trans %}Donate{% endtrans %}
    +
    {{ 'Donate to %pseudonym%'|trans({'%pseudonym%': session.application.user.title}) }}
    +
    + {% endif %} + {% if session.snippet.link is defined and session.snippet.link %} +
    +
    {% trans %}Link{% endtrans %}
    +
    {{ 'Link to %pseudonym%'|trans({'%pseudonym%': session.application.user.title}) }}
    +
    + {% endif %} + {% if session.snippet.profile is defined and session.snippet.profile %} +
    +
    {% trans %}Social network{% endtrans %}
    +
    {{ 'Consult %pseudonym% profile'|trans({'%pseudonym%': session.application.user.title}) }}
    +
    + {% endif %} + {% if session.snippet.rate is defined and session.snippet.rate %} + {% if session.snippet.hat is defined and session.snippet.hat %} +
    +
    {% trans %}Contribution to costs{% endtrans %}
    +
    {% if session.snippet.rate == 0 %}{% trans %}To the hat, to cover: talc, electricity, bicycle, website, ...{% endtrans %}{% else %}{{ 'To the hat, ideally %rate% €, to cover: talc, electricity, bicycle, website, ...'|trans({'%rate%': session.snippet.rate}) }}{% endif %}
    +
    + {% else %} +
    +
    {% trans %}Contribution{% endtrans %}
    +
    {% if session.snippet.rate == 0 %}{% trans %}Free{% endtrans %}{% else %}{{ session.snippet.rate }} €{% endif %}
    +
    + {% endif %} + {% endif %} + {% endif %} + {% endif %} + {% if session.locked is defined and session.locked %} +
    +
    {% trans %}Locked{% endtrans %}
    +
    {{ session.locked|intldate('long', 'medium') }}
    +
    {% endif %} -
    -
    -

    {% trans %}Schedule{% endtrans %}

    -
    {% trans %}Date{% endtrans %}
    -
    {{ session.start|localizeddate('long', 'none') }}
    +
    {% trans %}Created{% endtrans %}
    +
    {{ session.created|intldate('long', 'medium') }}
    -
    {% trans %}Start{% endtrans %}
    -
    {{ session.start|localizeddate('none', 'medium') }}
    +
    {% trans %}Updated{% endtrans %}
    +
    {{ session.updated|intldate('long', 'medium') }}
    +
    +
    +
    +

    {% trans %}Location{% endtrans %}

    +
    -
    {% trans %}Stop{% endtrans %}
    -
    {{ session.stop|localizeddate('none', 'medium') }}
    +
    {% trans %}Place{% endtrans %}
    +
    {{ session.location.title }}
    -
    {% trans %}Length{% endtrans %}
    -
    {{ session.length|localizeddate('none', 'short', null, null, 'HH:mm') }}
    +
    {% trans %}Description{% endtrans %}
    +
    {{ session.location.description }}
    -
    {% trans %}Slot{% endtrans %}
    -
    {{ session.slot.title }}
    +
    {% trans %}Interiority{% endtrans %}
    +
    {% if session.location.indoor is defined and session.location.indoor%}{% trans %}Indoor{% endtrans %}{% else %}{% trans %}Outdoor{% endtrans %}{% endif %}
    -
    {% trans %}Locked{% endtrans %}
    +
    {% trans %}Address{% endtrans %}
    - {% if session.locked is null %} - {% trans %}None{% endtrans %} - {% else %} - {{ session.locked|localizeddate('long', 'medium') }} - {% endif %} + {{ session.location.address }} + {{ session.location.zipcode }} {{ session.location.city }}
    -
    {% trans %}Created{% endtrans %}
    -
    {{ session.created|localizeddate('long', 'medium') }}
    +
    {% trans %}GPS coordinates{% endtrans %}
    +
    + {{ session.location.latitude }},{{ session.location.longitude }} +
    -
    {% trans %}Updated{% endtrans %}
    -
    {{ session.updated|localizeddate('long', 'medium') }}
    +
    {% trans %}Maps{% endtrans %}
    +
    + Google Maps +
    +
    + OpenStreetMap +
    + {% if map is defined and map %} +
    +
    {% trans %}Access map{% endtrans %}
    +
    + +
    + {{ map.caption }} +
    {{ map.caption }}
    +
    +
    +
    +
    + {% endif %}
    -

    {% trans %}Weather{% endtrans %}

    +
    +

    {% trans %}Weather{% endtrans %}

    +
    {% if session.rainrisk is not null %}
    {% trans %}Rainrisk{% endtrans %}
    @@ -167,77 +209,60 @@
    {% endif %}
    -
    -

    {% trans %}Location{% endtrans %}

    -
    - {# infos #} -
    {% trans %}Location{% endtrans %}
    -
    {{ session.location.title }}
    -
    -
    - {# location #} -
    {% trans %}Address{% endtrans %}
    -
    - {{ session.location.address }} - {{ session.location.zipcode }} {{ session.location.city }} -
    -
    -
    -
    {% trans %}Maps{% endtrans %}
    -
    Google Maps
    -
    OpenStreetMap
    -
    -
    -
    {% trans %}Minimap{% endtrans %}
    -
    TODO: minimap
    -
    -
    -
    - {{ include('@RapsysAir/form/_toolbox.html.twig') }} -
    -
    -

    {% trans %}Candidates{% endtrans %}

    -
    - {% if session.applications is null %} + {% if is_granted('ROLE_GUEST') %}
    - {% trans %}None{% endtrans %} +
    +

    {% trans %}Candidates{% endtrans %}

    +
    +
    + {% if session.applications is defined and session.applications %} + {% for application in session.applications %} +
    + {% if application.user.id == 1 and application.user.title|slug == 'milonga-raphael' %} +

    {{ application.user.title }}

    + {% else %} +

    {{ application.user.title }}

    + {% endif %} +
    +
    {% trans %}Score{% endtrans %}
    +
    + {% if application.score is null %} + {% trans %}None{% endtrans %} + {% else %} + {{ application.score }}
    + {% endif %} +
    +
    +
    {% trans %}Created{% endtrans %}
    +
    {{ application.created|intldate('long', 'medium') }}
    +
    +
    +
    {% trans %}Updated{% endtrans %}
    +
    {{ application.updated|intldate('long', 'medium') }}
    +
    +
    +
    {% trans %}Canceled{% endtrans %}
    +
    + {% if application.canceled is null %} + {% trans %}None{% endtrans %} + {% else %} + {{ application.canceled|intldate('long', 'medium') }} + {% endif %} +
    +
    +
    + {% endfor %} + {% else %} +
    + {% trans %}None{% endtrans %} +
    + {% endif %} +
    - {% else %} - {% for application in session.applications %} -
    -

    {{ application.user.title }}

    -
    -
    {% trans %}Score{% endtrans %}
    -
    - {% if application.score is null %} - {% trans %}None{% endtrans %} - {% else %} - {{ application.score }}
    - {% endif %} -
    -
    -
    {% trans %}Created{% endtrans %}
    -
    {{ application.created|localizeddate('long', 'medium') }}
    -
    -
    -
    {% trans %}Updated{% endtrans %}
    -
    {{ application.updated|localizeddate('long', 'medium') }}
    -
    -
    -
    {% trans %}Canceled{% endtrans %}
    -
    - {% if application.canceled is null %} - {% trans %}None{% endtrans %} - {% else %} - {{ application.canceled|localizeddate('long', 'medium') }} - {% endif %} -
    -
    -
    - {% endfor %} {% endif %}
    -
    + {{ include('@RapsysAir/form/_toolbox.html.twig') }} +
    {{ include('@RapsysAir/default/_location.html.twig') }} {% endblock %} -- 2.41.1 From 77abd32258bffdf630dbea7b43646d8fd7387c92 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Thu, 13 Oct 2022 15:22:19 +0200 Subject: [PATCH 04/16] Cleanup --- Resources/views/session/view.html.twig | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Resources/views/session/view.html.twig b/Resources/views/session/view.html.twig index aca1710..9492fbe 100644 --- a/Resources/views/session/view.html.twig +++ b/Resources/views/session/view.html.twig @@ -1,16 +1,7 @@ {% extends '@RapsysAir/body.html.twig' %} {% block content %} -
    +
    - {#

    - {% if session.application.id is defined and session.application.id %} - {{ session.application.dance.title ~ ' ' ~ session.id }} - {{ session.application.user.by }} - {% else %} - {{ title }} - {% endif %} - {{ session.location.at }} -

    #}

    {{ description }}

    -- 2.41.1 From 372d8b766a4870ee718907cfe38ba5beef866fbf Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Thu, 13 Oct 2022 15:24:17 +0200 Subject: [PATCH 05/16] Update fixtures --- DataFixtures/AirFixtures.php | 59 ++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/DataFixtures/AirFixtures.php b/DataFixtures/AirFixtures.php index 8ccf572..7b3e3a3 100644 --- a/DataFixtures/AirFixtures.php +++ b/DataFixtures/AirFixtures.php @@ -2,47 +2,80 @@ namespace Rapsys\AirBundle\DataFixtures; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; + use Rapsys\AirBundle\Entity\Civility; use Rapsys\AirBundle\Entity\Group; use Rapsys\AirBundle\Entity\User; use Rapsys\AirBundle\Entity\Location; use Rapsys\AirBundle\Entity\Slot; -class AirFixtures extends \Doctrine\Bundle\FixturesBundle\Fixture implements \Symfony\Component\DependencyInjection\ContainerAwareInterface { +class AirFixtures extends Fixture implements ContainerAwareInterface { /** * @var ContainerInterface */ private $container; - public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null) { + /** + * @var UserPasswordHasherInterface + */ + private $hasher; + + public function setContainer(ContainerInterface $container = null) { $this->container = $container; + $this->hasher = $this->container->get('security.password_hasher_factory'); } /** * {@inheritDoc} */ - public function load(\Doctrine\Common\Persistence\ObjectManager $manager) { - $encoder = $this->container->get('security.password_encoder'); - + public function load(ObjectManager $manager) { //Civility tree $civilityTree = array( - 'Mr.' => 'Mister', - 'Mrs.' => 'Madam', - 'Ms.' => 'Miss' + 'Mister', + 'Madam', + 'Miss' ); //Create titles $civilitys = array(); - foreach($civilityTree as $shortData => $civilityData) { - $civility = new Title($civilityData); - $civility->setShort($shortData); + foreach($civilityTree as $civilityData) { + $civility = new Civility(); + $civility->setTitle($civilityData); $civility->setCreated(new \DateTime('now')); $civility->setUpdated(new \DateTime('now')); $manager->persist($civility); - $civilitys[$shortData] = $civility; + $civilitys[$civilityData] = $civility; unset($civility); } + //TODO: insert countries from https://raw.githubusercontent.com/raramuridesign/mysql-country-list/master/country-lists/mysql-country-list-detailed-info.sql + #CREATE TABLE `countries` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `code` varchar(2) NOT NULL, `alpha` varchar(3) NOT NULL, `title` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `code` (`code`), UNIQUE KEY `alpha` (`alpha`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + #insert into countries (code, alpha, title, created, updated) select countryCode, isoAlpha3, countryName, NOW(), NOW() FROM apps_countries_detailed ORDER BY countryCode ASC, isoAlpha3 ASC; + + //Dance tree + $danceTree = array( + 'Argentine Tango' => [ + 'Milonga', 'Class and milonga', 'Public class', 'Private class' + ] + ); + + //Create titles + $dances = array(); + foreach($danceTree as $danceTitle => $danceData) { + foreach($danceData as $danceType) { + $dance = new Dance($danceTitle, $danceType); + $dance->setCreated(new \DateTime('now')); + $dance->setUpdated(new \DateTime('now')); + $manager->persist($dance); + unset($dance); + } + } + //Group tree //XXX: ROLE_XXX is required by $groupTree = array( @@ -119,7 +152,7 @@ class AirFixtures extends \Doctrine\Bundle\FixturesBundle\Fixture implements \Sy $user->setForename($userData['forename']); $user->setSurname($userData['surname']); $user->setPhone($userData['phone']); - $user->setPassword($encoder->encodePassword($user, $userData['password'])); + $user->setPassword($this->hasher->hashPassword($user, $userData['password'])); $user->setCivility($civilitys[$userData['short']]); $user->addGroup($groups[$userData['group']]); $user->setCreated(new \DateTime('now')); -- 2.41.1 From bd5a79b1bc48da095aabd20b273a423fbd1ee485 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 07:34:44 +0200 Subject: [PATCH 06/16] Move tangoargentin export to json uri Migrate OrganizerController::view to DefaultController::userView Remove organizer class --- Resources/config/routes/rapsys_air.yaml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Resources/config/routes/rapsys_air.yaml b/Resources/config/routes/rapsys_air.yaml index 3549da5..05123d3 100644 --- a/Resources/config/routes/rapsys_air.yaml +++ b/Resources/config/routes/rapsys_air.yaml @@ -130,9 +130,8 @@ rapsys_air_session: rapsys_air_session_tangoargentin: path: - en_gb: '/en/session/tangoargentin' - fr_fr: '/seance/tangoargentin' - format: json + en_gb: '/en/session/tangoargentin.{!_format?json}' + fr_fr: '/seance/tangoargentin.{!_format?json}' controller: Rapsys\AirBundle\Controller\SessionController::tangoargentin methods: GET @@ -168,16 +167,14 @@ rapsys_air_user_view: path: en_gb: '/en/user/{id<\d+>}/{user<[\w-]+>?}' fr_fr: '/utilisateur/{id<\d+>}/{user<[\w-]+>?}' - #TODO: changer pour DefaultController::organizer ou DefaultController::user et dropper cette classe useless - controller: Rapsys\AirBundle\Controller\OrganizerController::view + controller: Rapsys\AirBundle\Controller\DefaultController::userView methods: GET|POST rapsys_air_user_milongaraphael: path: en_gb: '/en/milonga-raphael' fr_fr: '/milonga-raphael' - #TODO: changer pour DefaultController::organizer ou DefaultController::user et dropper cette classe useless - controller: Rapsys\AirBundle\Controller\OrganizerController::view + controller: Rapsys\AirBundle\Controller\DefaultController::userView defaults: # default parameters id: 1 -- 2.41.1 From 2b4888e4a7a40ec60ec9c876c54d4eab6dbb4c32 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 07:36:57 +0200 Subject: [PATCH 07/16] Add default locations_link and locations_title --- Controller/AbstractController.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Controller/AbstractController.php b/Controller/AbstractController.php index 8a10b87..f06178d 100644 --- a/Controller/AbstractController.php +++ b/Controller/AbstractController.php @@ -509,6 +509,18 @@ abstract class AbstractController extends BaseAbstractController implements Serv }*/ } + //With empty locations link + if (empty($parameters['locations_link'])) { + //Set locations link + $parameters['locations_link'] = $this->router->generate('rapsys_air_location'); + } + + //With empty locations title + if (empty($parameters['locations_title'])) { + //Set locations title + $parameters['locations_title'] = $this->translator->trans('Locations', [], null, $this->locale); + } + //With canonical if (!empty($parameters['canonical'])) { //Set facebook url -- 2.41.1 From bc62694c7f6cd91a1d9564be415bed8d877c94a5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 07:38:56 +0200 Subject: [PATCH 08/16] Load all locations on cities view Update getMultiMap to new api Follow to new findAllByPeriodAsCalendarArray for old function findAllByPeriodAsArray Redirect permanently to right named location Update to new location tree Overide locations_link and locations_description on location view to city Cleanup --- Controller/LocationController.php | 50 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Controller/LocationController.php b/Controller/LocationController.php index 33c2050..8838d69 100644 --- a/Controller/LocationController.php +++ b/Controller/LocationController.php @@ -30,7 +30,7 @@ class LocationController extends DefaultController { */ public function cities(Request $request): Response { //Add cities - $this->context['cities'] = $this->doctrine->getRepository(Location::class)->findCitiesAsArray($this->period); + $this->context['cities'] = $this->doctrine->getRepository(Location::class)->findCitiesAsArray($this->period, 0); //Add dances $this->context['dances'] = $this->doctrine->getRepository(Dance::class)->findNamesAsArray(); @@ -44,8 +44,7 @@ class LocationController extends DefaultController { //Add city multi foreach($this->context['cities'] as $id => $city) { //Add city multi - #$this->osm->getMultiImage($city['link'], $city['osm'], $this->modified->getTimestamp(), $city['latitude'], $city['longitude'], $city['locations'], $this->osm->getMultiZoom($city['latitude'], $city['longitude'], $city['locations'], 16)); - $this->context['cities'][$id]['multimap'] = $this->map->getMultiMap($city['multimap'], $this->modified->getTimestamp(), $city['latitude'], $city['longitude'], $city['locations'], $this->map->getMultiZoom($city['latitude'], $city['longitude'], $city['locations'])); + $this->context['cities'][$id]['multimap'] = $this->map->getMultiMap($city['multimap'], $this->modified->getTimestamp(), $city['locations']); } //With logged user @@ -129,7 +128,7 @@ class LocationController extends DefaultController { } //Add calendar - $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsArray($this->period, $request->getLocale(), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), floatval($latitude), floatval($longitude)); + $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), floatval($latitude), floatval($longitude)); //Set dances $this->context['dances'] = []; @@ -147,7 +146,7 @@ class LocationController extends DefaultController { } //Add locations - $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllByLatitudeLongitudeAsArray(floatval($latitude), floatval($longitude), $this->period); + $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllByLatitudeLongitudeAsArray(floatval($latitude), floatval($longitude), $this->period, 0); //Set modified //XXX: dance modified is already computed inside calendar modified @@ -183,7 +182,7 @@ class LocationController extends DefaultController { } //Add multi - $this->context['multimap'] = $this->map->getMultiMap($this->context['city']['multimap'], $this->modified->getTimestamp(), $latitude, $longitude, $this->context['locations'], $this->map->getMultiZoom($latitude, $longitude, $this->context['locations'])); + $this->context['multimap'] = $this->map->getMultiMap($this->context['city']['multimap'], $this->modified->getTimestamp(), $this->context['locations']); //Set keywords $this->context['keywords'] = [ @@ -270,20 +269,8 @@ class LocationController extends DefaultController { } } - //Set latitudes - $latitudes = array_map(function ($v) { return $v['latitude']; }, $this->context['locations']); - - //Set latitude - $latitude = round(array_sum($latitudes)/count($latitudes), 6); - - //Set longitudes - $longitudes = array_map(function ($v) { return $v['longitude']; }, $this->context['locations']); - - //Set longitude - $longitude = round(array_sum($longitudes)/count($longitudes), 6); - //Add multi map - $this->context['multimap'] = $this->map->getMultiMap($this->translator->trans('Libre Air locations sector map'), $this->modified->getTimestamp(), $latitude, $longitude, $this->context['locations'], $this->map->getMultiZoom($latitude, $longitude, $this->context['locations'])); + $this->context['multimap'] = $this->map->getMultiMap($this->translator->trans('Libre Air locations sector map'), $this->modified->getTimestamp(), $this->context['locations']); //Set title $this->context['title'] = $this->translator->trans('Libre Air locations'); @@ -399,18 +386,25 @@ class LocationController extends DefaultController { * * @param Request $request The request instance * @param int $id The location id + * @param ?string $location The location slug * * @return Response The rendered view */ - public function view(Request $request, int $id): Response { + public function view(Request $request, int $id, ?string $location): Response { //Without location if (empty($this->context['location'] = $this->doctrine->getRepository(Location::class)->findOneByIdAsArray($id, $this->locale))) { //Throw 404 throw $this->createNotFoundException($this->translator->trans('Unable to find location: %id%', ['%id%' => $id])); } + //With invalid slug + if ($location !== $this->context['location']['slug']) { + //Redirect on correctly spelled location + return $this->redirectToRoute('rapsys_air_location_view', ['id' => $this->context['location']['id'], 'location' => $this->context['location']['slug']], Response::HTTP_MOVED_PERMANENTLY); + } + //Fetch calendar - $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsArray($this->period, $this->locale, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), $this->context['location']['latitude'], $this->context['location']['longitude']); + $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), $this->context['location']['latitude'], $this->context['location']['longitude']); //Set dances $this->context['dances'] = []; @@ -464,12 +458,12 @@ class LocationController extends DefaultController { } //Add multi map - $this->context['multimap'] = $this->map->getMultiMap($this->context['location']['multimap'], $this->modified->getTimestamp(), $this->context['location']['latitude'], $this->context['location']['longitude'], $this->context['locations'], $this->map->getMultiZoom($this->context['location']['latitude'], $this->context['location']['longitude'], $this->context['locations'])); + $this->context['multimap'] = $this->map->getMultiMap($this->context['location']['multimap'], $this->modified->getTimestamp(), $this->context['locations']); //Set keywords $this->context['keywords'] = [ $this->context['location']['title'], - $this->context['location']['city'], + $this->context['location']['city']['title'], $this->translator->trans($this->context['location']['indoor']?'Indoor':'Outdoor'), $this->translator->trans('Calendar'), $this->translator->trans('Libre Air') @@ -497,11 +491,17 @@ class LocationController extends DefaultController { $this->context['title'] = $this->translator->trans('Dance %location%', ['%location%' => $this->context['location']['atin']]); //Set description - $this->context['description'] = $this->translator->trans('Indoor and outdoor dance calendar %location%', [ '%location%' => $this->context['location']['at'] ]); + $this->context['description'] = $this->translator->trans('Indoor and outdoor dance calendar %location%', ['%location%' => $this->context['location']['at']]); } //Set locations description - $this->context['locations_description'] = $this->translator->trans('Libre Air location list %location%', ['%location%' => $this->context['location']['atin']]); + $this->context['locations_description'] = $this->translator->trans('Libre Air location list %location% %city%', ['%location%' => $this->context['location']['around'], '%city%' => $this->context['location']['city']['in']]); + + //Set locations link + $this->context['locations_link'] = $this->context['location']['city']['link']; + + //Set locations title + $this->context['locations_title'] = $this->context['location']['city']['title'].' ('.$this->context['location']['city']['id'].')'; //Set alternates $this->context['alternates'] += $this->context['location']['alternates']; -- 2.41.1 From e223215d4451312944232cbd8e69f0a6784fb51a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 07:44:31 +0200 Subject: [PATCH 09/16] Pass extra locale to repository factory Add types Cleanup --- Factory/RepositoryFactory.php | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Factory/RepositoryFactory.php b/Factory/RepositoryFactory.php index 0c754ea..21e672b 100644 --- a/Factory/RepositoryFactory.php +++ b/Factory/RepositoryFactory.php @@ -28,35 +28,42 @@ final class RepositoryFactory implements RepositoryFactoryInterface { * * @var ObjectRepository[] */ - private $repositoryList = []; + private array $repositoryList = []; /** * The list of languages * * @var string[] */ - private $languages = []; + private array $languages = []; + + /** + * The current locale + * + * @var string + */ + private string $locale; /** * The RouterInterface instance * * @var RouterInterface */ - private $router; + private RouterInterface $router; /** * The SluggerUtil instance * * @var SluggerUtil */ - private $slugger; + private SluggerUtil $slugger; /** * The TranslatorInterface instance * * @var TranslatorInterface */ - private $translator; + private TranslatorInterface $translator; /** * Initializes a new RepositoryFactory instance @@ -65,8 +72,9 @@ final class RepositoryFactory implements RepositoryFactoryInterface { * @param SluggerUtil $slugger The SluggerUtil instance * @param TranslatorInterface $translator The TranslatorInterface instance * @param array $languages The languages list + * @param string $locale The current locale */ - public function __construct(RouterInterface $router, SluggerUtil $slugger, TranslatorInterface $translator, array $languages) { + public function __construct(RouterInterface $router, SluggerUtil $slugger, TranslatorInterface $translator, array $languages, string $locale) { //Set router $this->router = $router; @@ -78,6 +86,9 @@ final class RepositoryFactory implements RepositoryFactoryInterface { //Set languages $this->languages = $languages; + + //Set locale + $this->locale = $locale; } /** @@ -112,6 +123,6 @@ final class RepositoryFactory implements RepositoryFactoryInterface { //Return repository class instance //XXX: router, slugger, translator and languages arguments will be ignored by default - return new $repositoryClass($entityManager, $metadata, $this->router, $this->slugger, $this->translator, $this->languages); + return new $repositoryClass($entityManager, $metadata, $this->router, $this->slugger, $this->translator, $this->languages, $this->locale); } } -- 2.41.1 From 752e48619db81da242495044882a5f3da81b7878 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 07:49:34 +0200 Subject: [PATCH 10/16] Fix comment Cleanup --- Factory/RepositoryFactory.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Factory/RepositoryFactory.php b/Factory/RepositoryFactory.php index 21e672b..f64a4fb 100644 --- a/Factory/RepositoryFactory.php +++ b/Factory/RepositoryFactory.php @@ -26,14 +26,14 @@ final class RepositoryFactory implements RepositoryFactoryInterface { /** * The list of EntityRepository instances * - * @var ObjectRepository[] + * @var array */ private array $repositoryList = []; /** * The list of languages * - * @var string[] + * @var array */ private array $languages = []; @@ -94,7 +94,7 @@ final class RepositoryFactory implements RepositoryFactoryInterface { /** * {@inheritdoc} */ - public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository { + public function getRepository(EntityManagerInterface $entityManager, mixed $entityName): ObjectRepository { //Set repository hash $repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_hash($entityManager); @@ -122,7 +122,7 @@ final class RepositoryFactory implements RepositoryFactoryInterface { $repositoryClass = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); //Return repository class instance - //XXX: router, slugger, translator and languages arguments will be ignored by default + //XXX: router, slugger, translator, languages and locale arguments will be ignored by default return new $repositoryClass($entityManager, $metadata, $this->router, $this->slugger, $this->translator, $this->languages, $this->locale); } } -- 2.41.1 From 1a35f270bc2cd8cee255d9553699b85145f04f0b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 07:51:06 +0200 Subject: [PATCH 11/16] Add php strict Add Response return type Cleanup --- Handler/AccessDeniedHandler.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Handler/AccessDeniedHandler.php b/Handler/AccessDeniedHandler.php index b742db0..13727c9 100644 --- a/Handler/AccessDeniedHandler.php +++ b/Handler/AccessDeniedHandler.php @@ -1,8 +1,18 @@ - + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Rapsys\AirBundle\Handler; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; @@ -16,7 +26,7 @@ class AccessDeniedHandler extends AbstractController implements AccessDeniedHand /** * {@inheritdoc} */ - public function handle(Request $request, AccessDeniedException $exception) { + public function handle(Request $request, AccessDeniedException $exception): Response { //Set title $this->context['title'] = $this->translator->trans('Access denied'); -- 2.41.1 From faae833b04055eb8e32208bde8aa3a319a9b1ccd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 08:59:54 +0200 Subject: [PATCH 12/16] Drop mailer interface Drop doctrine interface New user view New index view Cleanup --- Controller/DefaultController.php | 635 +++++++++++++++++++------------ 1 file changed, 399 insertions(+), 236 deletions(-) diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php index e55d6e8..cfe5f58 100644 --- a/Controller/DefaultController.php +++ b/Controller/DefaultController.php @@ -12,15 +12,18 @@ namespace Rapsys\AirBundle\Controller; use Symfony\Bridge\Twig\Mime\TemplatedEmail; +use Symfony\Component\Filesystem\Exception\IOExceptionInterface; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; -use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Address; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Rapsys\AirBundle\Entity\Civility; +use Rapsys\AirBundle\Entity\Dance; use Rapsys\AirBundle\Entity\Location; use Rapsys\AirBundle\Entity\Session; use Rapsys\AirBundle\Entity\Snippet; @@ -68,11 +71,10 @@ class DefaultController extends AbstractController { * @desc Send a contact mail to configured contact * * @param Request $request The request instance - * @param MailerInterface $mailer The mailer instance * * @return Response The rendered view or redirection */ - public function contact(Request $request, MailerInterface $mailer): Response { + public function contact(Request $request): Response { //Set page $this->context['title'] = $this->translator->trans('Contact'); @@ -88,9 +90,21 @@ class DefaultController extends AbstractController { $this->translator->trans('calendar') ]; + //Set data + $data = []; + + //With user + if ($user = $this->getUser()) { + //Set data + $data = [ + 'name' => $user->getRecipientName(), + 'mail' => $user->getMail() + ]; + } + //Create the form according to the FormType created previously. //And give the proper parameters - $form = $this->createForm('Rapsys\AirBundle\Form\ContactType', null, [ + $form = $this->createForm('Rapsys\AirBundle\Form\ContactType', $data, [ 'action' => $this->generateUrl('rapsys_air_contact'), 'method' => 'POST' ]); @@ -99,7 +113,7 @@ class DefaultController extends AbstractController { // Refill the fields in case the form is not valid. $form->handleRequest($request); - if ($form->isValid()) { + if ($form->isSubmitted() && $form->isValid()) { //Get data $data = $form->getData(); @@ -128,7 +142,7 @@ class DefaultController extends AbstractController { //XXX: mail delivery may silently fail try { //Send message - $mailer->send($message); + $this->mailer->send($message); //Redirect on the same route with sent=1 to cleanup form return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params')); @@ -155,11 +169,10 @@ class DefaultController extends AbstractController { * @desc Generate a dispute document * * @param Request $request The request instance - * @param MailerInterface $mailer The mailer instance * * @return Response The rendered view or redirection */ - public function dispute(Request $request, MailerInterface $mailer): Response { + public function dispute(Request $request): Response { //Prevent non-guest to access here $this->denyAccessUnlessGranted('ROLE_USER', null, $this->translator->trans('Unable to access this page without role %role%!', ['%role%' => $this->translator->trans('User')])); @@ -250,7 +263,7 @@ class DefaultController extends AbstractController { # //XXX: mail delivery may silently fail # try { # //Send message -# $mailer->send($message); +# $this->mailer->send($message); # # //Redirect on the same route with sent=1 to cleanup form # return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params')); @@ -274,65 +287,118 @@ class DefaultController extends AbstractController { /** * The index page * - * @desc Display all granted sessions with an application or login form + * Display session calendar * * @param Request $request The request instance * @return Response The rendered view */ public function index(Request $request): Response { - //Fetch doctrine - $doctrine = $this->getDoctrine(); + //Add cities + $this->context['cities'] = $this->doctrine->getRepository(Location::class)->findCitiesAsArray($this->period); - //Set page - $this->context['title'] = $this->translator->trans('Argentine Tango in Paris'); + //Add calendar + $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED')); - //Set description - $this->context['description'] = $this->translator->trans('Outdoor Argentine Tango session calendar in Paris'); + //Add dances + $this->context['dances'] = $this->doctrine->getRepository(Dance::class)->findNamesAsArray(); - //Set keywords - $this->context['keywords'] = [ - $this->translator->trans('Argentine Tango'), - $this->translator->trans('Paris'), - $this->translator->trans('outdoor'), - $this->translator->trans('calendar'), - $this->translator->trans('Libre Air') - ]; + //Set modified + $this->modified = max(array_map(function ($v) { return $v['modified']; }, array_merge($this->context['calendar'], $this->context['cities'], $this->context['dances']))); - //Set facebook type - //XXX: only valid for home page - $this->context['facebook']['metas']['og:type'] = 'website'; + //Create response + $response = new Response(); + + //With logged user + if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + //Set last modified + $response->setLastModified(new \DateTime('-1 year')); + + //Set as private + $response->setPrivate(); + //Without logged user + } else { + //Set etag + //XXX: only for public to force revalidation by last modified + $response->setEtag(md5(serialize(array_merge($this->context['calendar'], $this->context['cities'], $this->context['dances'])))); + + //Set last modified + $response->setLastModified($this->modified); + + //Set as public + $response->setPublic(); + + //Without role and modification + if ($response->isNotModified($request)) { + //Return 304 response + return $response; + } + } + + //With cities + if (!empty($this->context['cities'])) { + //Set locations + $locations = []; + + //Iterate on each cities + foreach($this->context['cities'] as $city) { + //Iterate on each locations + foreach($city['locations'] as $location) { + //Add location + $locations[$location['id']] = $location; + } + } + + //Add multi + $this->context['multimap'] = $this->map->getMultiMap($this->translator->trans('Libre Air cities sector map'), $this->modified->getTimestamp(), $locations); + + //Set cities + $cities = array_map(function ($v) { return $v['in']; }, $this->context['cities']); + + //Set dances + $dances = array_map(function ($v) { return $v['name']; }, $this->context['dances']); + } else { + //Set cities + $cities = []; - //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( - $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week' + //Set dances + $dances = []; + } + + //Set keywords + //TODO: use splice instead of that shit !!! + //TODO: handle smartly indoor and outdoor !!! + $this->context['keywords'] = array_values( + array_merge( + $dances, + $cities, + [ + $this->translator->trans('indoor'), + $this->translator->trans('outdoor'), + $this->translator->trans('calendar'), + $this->translator->trans('Libre Air') + ] ) ); - //Fetch calendar - $calendar = $doctrine->getRepository(Session::class)->fetchCalendarByDatePeriod($this->translator, $period, null, $request->get('session'), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), $request->getLocale()); + //Get textual cities + $cities = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($cities, 0, -1))], array_slice($cities, -1)), 'strlen')); - //Fetch locations - //XXX: we want to display all active locations anyway - $locations = $doctrine->getRepository(Location::class)->findTranslatedSortedByPeriod($this->translator, $period); + //Get textual dances + $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen')); - //Render the view - return $this->render('@RapsysAir/default/index.html.twig', ['calendar' => $calendar, 'locations' => $locations]+$this->context); + //Set title + $this->context['title'] = $this->translator->trans('%dances% %cities%', ['%dances%' => $dances, '%cities%' => $cities]); - //Set Cache-Control must-revalidate directive - //TODO: add a javascript forced refresh after 1h ? or header refresh ? - #$response->setPublic(true); - #$response->setMaxAge(300); - #$response->mustRevalidate(); - ##$response->setCache(['public' => true, 'max_age' => 300]); + //Set description + //TODO: handle french translation when city start with a A, change à in en ! + $this->context['description'] = $this->translator->trans('%dances% indoor and outdoor calendar %cities%', ['%dances%' => $dances, '%cities%' => $cities]); - //Return the response - #return $response; + //Set facebook type + //XXX: only valid for home page + $this->context['facebook']['metas']['og:type'] = 'website'; + + //Render the view + return $this->render('@RapsysAir/default/index.html.twig', $this->context, $response); } /** @@ -445,9 +511,6 @@ class DefaultController extends AbstractController { * @return Response The rendered view */ public function userIndex(Request $request): Response { - //Fetch doctrine - $doctrine = $this->getDoctrine(); - //With admin role if ($this->isGranted('ROLE_ADMIN')) { //Set section @@ -476,19 +539,7 @@ class DefaultController extends AbstractController { $title = $this->translator->trans($this->config['site']['title']).' - '.$section; //Fetch users - $users = $doctrine->getRepository(User::class)->findUserGroupedByTranslatedGroup($this->translator); - - //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( - $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week' - ) - ); + $users = $this->doctrine->getRepository(User::class)->findIndexByGroupId(); //With admin role if ($this->isGranted('ROLE_ADMIN')) { @@ -500,12 +551,8 @@ class DefaultController extends AbstractController { $this->context['users'] = $users[$this->translator->trans('Senior')]; } - //Fetch locations - //XXX: we want to display all active locations anyway - $locations = $doctrine->getRepository(Location::class)->findTranslatedSortedByPeriod($this->translator, $period); - //Render the view - return $this->render('@RapsysAir/user/index.html.twig', ['title' => $title, 'section' => $section, 'locations' => $locations]+$this->context); + return $this->render('@RapsysAir/user/index.html.twig', ['title' => $title, 'section' => $section]+$this->context); } /** @@ -518,218 +565,334 @@ class DefaultController extends AbstractController { * * @return Response The rendered view */ - public function userView(Request $request, $id): Response { - //Fetch doctrine - $doctrine = $this->getDoctrine(); - - //Fetch user - if (empty($user = $doctrine->getRepository(User::class)->findOneById($id))) { - throw $this->createNotFoundException($this->translator->trans('Unable to find user: %id%', ['%id%' => $id])); + public function userView(Request $request, int $id, ?string $user): Response { + //Get user + if (empty($this->context['user'] = $this->doctrine->getRepository(User::class)->findOneByIdAsArray($id, $this->locale))) { + //Throw not found + throw new NotFoundHttpException($this->translator->trans('Unable to find user: %id%', ['%id%' => $id])); } - //Get user token - $token = new UsernamePasswordToken($user, null, 'none', $user->getRoles()); - - //Check if guest - $isGuest = $this->get('rapsys_user.access_decision_manager')->decide($token, ['ROLE_GUEST']); + //Create token + $token = new AnonymousToken('', $this->context['user']['mail'], $this->context['user']['roles']); //Prevent access when not admin, user is not guest and not currently logged user - if (!$this->isGranted('ROLE_ADMIN') && empty($isGuest) && $user != $this->getUser()) { - throw $this->createAccessDeniedException($this->translator->trans('Unable to access user: %id%', ['%id%' => $id])); + if (!($isAdmin = $this->isGranted('ROLE_ADMIN')) && !($isGuest = $this->decision->decide($token, ['ROLE_GUEST']))) { + //Throw access denied + throw new AccessDeniedException($this->translator->trans('Unable to access user: %id%', ['%id%' => $id])); } - //Set section - $section = $user->getPseudonym(); + //With invalid user slug + if ($this->context['user']['slug'] !== $user) { + //Redirect to cleaned url + return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]); + } - //Set title - $title = $this->translator->trans($this->config['site']['title']).' - '.$section; + //Fetch calendar + $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), null, null, $id); - //Set description - $this->context['description'] = $this->translator->trans('%pseudonym% outdoor Argentine Tango session calendar', [ '%pseudonym%' => $user->getPseudonym() ]); + //Get locations at less than 2 km + $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllByUserIdAsArray($id, $this->period, 2); - //Set keywords - $this->context['keywords'] = [ - $user->getPseudonym(), - $this->translator->trans('outdoor'), - $this->translator->trans('Argentine Tango'), - $this->translator->trans('calendar') - ]; + //Set ats + $ats = []; - //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( - $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week' - ) - ); - - //Fetch calendar - //TODO: highlight with current session route parameter - $calendar = $doctrine->getRepository(Session::class)->fetchUserCalendarByDatePeriod($this->translator, $period, $isGuest?$id:null, $request->get('session'), $request->getLocale()); - - //Fetch locations - //XXX: we want to display all active locations anyway - $locations = $doctrine->getRepository(Location::class)->findTranslatedSortedByPeriod($this->translator, $period, $id); - - //Create user form for admin or current user - if ($this->isGranted('ROLE_ADMIN') || $user == $this->getUser()) { - //Create SnippetType form - $userForm = $this->createForm('Rapsys\AirBundle\Form\RegisterType', $user, [ - //Set action - 'action' => $this->generateUrl('rapsys_air_user_view', ['id' => $id]), - //Set the form attribute - 'attr' => [ 'class' => 'col' ], - //Set civility class - 'civility_class' => Civility::class, - //Disable mail - 'mail' => $this->isGranted('ROLE_ADMIN'), - //Disable password - 'password' => false - ]); - - //Init user to context - $this->context['forms']['user'] = $userForm->createView(); - - //Check if submitted - if ($request->isMethod('POST')) { - //Refill the fields in case the form is not valid. - $userForm->handleRequest($request); - - //Handle invalid form - if (!$userForm->isSubmitted() || !$userForm->isValid()) { - //Render the view - return $this->render('@RapsysAir/user/view.html.twig', ['id' => $id, 'title' => $title, 'section' => $section, 'calendar' => $calendar, 'locations' => $locations]+$this->context); - } + //Set dances + $dances = []; - //Get data - $data = $userForm->getData(); + //Set indoors + $indoors = []; - //Get manager - $manager = $doctrine->getManager(); + //Set ins + $ins = []; - //Queue snippet save - $manager->persist($data); + //Set insides + $insides = []; - //Flush to get the ids - $manager->flush(); + //Set locations + $locations = []; - //Add notice - $this->addFlash('notice', $this->translator->trans('User %id% updated', ['%id%' => $id])); + //Set types + $types = []; - //Extract and process referer - if ($referer = $request->headers->get('referer')) { - //Create referer request instance - $req = Request::create($referer); + //Iterate on each calendar + foreach($this->context['calendar'] as $date => $calendar) { + //Iterate on each session + foreach($calendar['sessions'] as $sessionId => $session) { + //Add dance + $dances[$session['application']['dance']['name']] = $session['application']['dance']['name']; - //Get referer path - $path = $req->getPathInfo(); + //Add types + $types[$session['application']['dance']['type']] = lcfirst($session['application']['dance']['type']); - //Get referer query string - $query = $req->getQueryString(); + //Add indoors + $indoors[$session['location']['indoor']?'indoor':'outdoor'] = $this->translator->trans($session['location']['indoor']?'indoor':'outdoor'); - //Remove script name - $path = str_replace($request->getScriptName(), '', $path); + //Add insides + $insides[$session['location']['indoor']?'inside':'outside'] = $this->translator->trans($session['location']['indoor']?'inside':'outside'); - //Try with referer path - try { - //Save old context - $oldContext = $this->router->getContext(); + //Add ats + $ats[$session['location']['id']] = $session['location']['at']; - //Force clean context - //XXX: prevent MethodNotAllowedException because current context method is POST in onevendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php+42 - $this->router->setContext(new RequestContext()); + //Add ins + $ins[$session['location']['id']] = $session['location']['in']; - //Retrieve route matching path - $route = $this->router->match($path); + //Session with application user id + if (!empty($session['application']['user']['id']) && $session['application']['user']['id'] == $id) { + //Add location + $locations[$session['location']['id']] = $session['location']; + } + } + } - //Reset context - $this->router->setContext($oldContext); + //Set modified + //XXX: dance modified is already computed inside calendar modified + $this->modified = max(array_merge([$this->context['user']['modified']], array_map(function ($v) { return $v['modified']; }, array_merge($this->context['calendar'], $this->context['locations'])))); - //Clear old context - unset($oldContext); + //Create response + $response = new Response(); - //Extract name - $name = $route['_route']; + //With logged user + if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + //Set last modified + $response->setLastModified(new \DateTime('-1 year')); - //Remove route and controller from route defaults - unset($route['_route'], $route['_controller']); + //Set as private + $response->setPrivate(); + //Without logged user + } else { + //Set etag + //XXX: only for public to force revalidation by last modified + $response->setEtag(md5(serialize(array_merge($this->context['user'], $this->context['calendar'], $this->context['locations'])))); - //Check if user view route - if ($name == 'rapsys_air_user_view' && !empty($route['id'])) { - //Replace id - $route['id'] = $data->getId(); - //Other routes - } else { - //Set user - $route['user'] = $data->getId(); - } + //Set last modified + $response->setLastModified($this->modified); - //Generate url - return $this->redirectToRoute($name, $route); - //No route matched - } catch(MethodNotAllowedException|ResourceNotFoundException $e) { - //Unset referer to fallback to default route - unset($referer); - } - } + //Set as public + $response->setPublic(); - //Redirect to cleanup the form - return $this->redirectToRoute('rapsys_air', ['user' => $data->getId()]); + //Without role and modification + if ($response->isNotModified($request)) { + //Return 304 response + return $response; } } - //Create snippet forms for role_guest - if ($this->isGranted('ROLE_ADMIN') || ($this->isGranted('ROLE_GUEST') && $user == $this->getUser())) { - //Fetch all user snippet - $snippets = $doctrine->getRepository(Snippet::class)->findByLocaleUserId($request->getLocale(), $id); + //Add multi map + $this->context['multimap'] = $this->map->getMultiMap($this->context['user']['multimap'], $this->modified->getTimestamp(), $this->context['locations']); + + //Set keywords + $this->context['keywords'] = [ + $this->context['user']['pseudonym'], + $this->translator->trans('calendar'), + $this->translator->trans('Libre Air') + ]; - //Rekey by location id - $snippets = array_reduce($snippets, function($carry, $item){$carry[$item->getLocation()->getId()] = $item; return $carry;}, []); + //Set cities + $cities = array_unique(array_map(function ($v) { return $v['city']; }, $locations)); - //Init snippets to context - $this->context['forms']['snippets'] = []; + //Set titles + $titles = array_map(function ($v) { return $v['title']; }, $locations); - //Iterate on locations - foreach($locations as $locationId => $location) { - //Init snippet - $snippet = new Snippet(); + //Insert dances in keywords + array_splice($this->context['keywords'], 1, 0, array_merge($types, $dances, $indoors, $insides, $titles, $cities)); + + //Deduplicate ins + $ins = array_unique($ins); + + //Get textual dances + $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen')); + + //Get textual types + $types = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($types, 0, -1))], array_slice($types, -1)), 'strlen')); + + //Get textual indoors + $indoors = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($indoors, 0, -1))], array_slice($indoors, -1)), 'strlen')); + + //Get textual ats + $ats = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($ats, 0, -1))], array_slice($ats, -1)), 'strlen')); + + //Get textual ins + $ins = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($ins, 0, -1))], array_slice($ins, -1)), 'strlen')); + + //Set title + $this->context['title'] = $this->translator->trans('%pseudonym% organizer', ['%pseudonym%' => $this->context['user']['pseudonym']]); + + //With locations + if (!empty($locations)) { + //Set description + $this->context['description'] = ucfirst($this->translator->trans('%dances% %types% %indoors% calendar %ats% %ins% %pseudonym%', ['%dances%' => $dances, '%types%' => $types, '%indoors%' => $indoors, '%ats%' => $ats, '%ins%' => $ins, '%pseudonym%' => $this->translator->trans('by %pseudonym%', ['%pseudonym%' => $this->context['user']['pseudonym']])])); + //Without locations + } else { + //Set description + $this->context['description'] = $this->translator->trans('%pseudonym% calendar', ['%pseudonym%' => $this->context['user']['pseudonym']]); + } - //Set default locale - $snippet->setLocale($request->getLocale()); + //Set user description + $this->context['locations_description'] = $this->translator->trans('Libre Air %pseudonym% location list', ['%pseudonym%' => $this->translator->trans('by %pseudonym%', ['%pseudonym%' => $this->context['user']['pseudonym']])]); - //Set default user - $snippet->setUser($user); + //Set alternates + $this->context['alternates'] += $this->context['user']['alternates']; - //Set default location - $snippet->setLocation($doctrine->getRepository(Location::class)->findOneById($locationId)); + //Create snippet forms for role_guest + //TODO: optimize this call + if ($isAdmin || $isGuest && $this->getUser() && $this->context['user']['id'] == $this->getUser()->getId()) { + //Fetch all user snippet + $snippets = $this->doctrine->getRepository(Snippet::class)->findByUserIdLocaleIndexByLocationId($id, $this->locale); + //Get user + $user = $this->doctrine->getRepository(User::class)->findOneById($id); + + //Iterate on locations + foreach($this->context['locations'] as $locationId => $location) { //With existing snippet - if (!empty($snippets[$locationId])) { - $snippet = $snippets[$locationId]; - $action = $this->generateUrl('rapsys_air_snippet_edit', ['id' => $snippet->getId()]); - //Without snippet + if (isset($snippets[$location['id']])) { + //Set existing in current + $current = $snippets[$location['id']]; + //Without existing snippet } else { - $action = $this->generateUrl('rapsys_air_snippet_add', ['location' => $locationId]); + //Init snippet + $current = new Snippet(); + + //Set default locale + $current->setLocale($this->locale); + + //Set default user + $current->setUser($user); + + //Set default location + $current->setLocation($this->doctrine->getRepository(Location::class)->findOneById($location['id'])); } //Create SnippetType form - $form = $this->container->get('form.factory')->createNamed('snipped_'.$request->getLocale().'_'.$locationId, 'Rapsys\AirBundle\Form\SnippetType', $snippet, [ - //Set the action - 'action' => $action, - //Set the form attribute - 'attr' => [] - ]); + $form = $this->factory->createNamed( + //Set form id + 'snippet_'.$locationId.'_'.$id.'_'.$this->locale, + //Set form type + 'Rapsys\AirBundle\Form\SnippetType', + //Set form data + $current + ); + + //Refill the fields in case of invalid form + $form->handleRequest($request); + + //Handle submitted and valid form + //TODO: add a delete snippet ? + if ($form->isSubmitted() && $form->isValid()) { + //Get snippet + $snippet = $form->getData(); + + //Queue snippet save + $this->manager->persist($snippet); + + //Flush to get the ids + $this->manager->flush(); + + //Add notice + $this->addFlash('notice', $this->translator->trans('Snippet for %user% %location% updated', ['%location%' => $location['at'], '%user%' => $this->context['user']['pseudonym']])); + + //Redirect to cleaned url + return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]); + } //Add form to context $this->context['forms']['snippets'][$locationId] = $form->createView(); + + //With location user source image + if (($isFile = is_file($source = $this->config['path'].'/location/'.$location['id'].'/'.$id.'.png')) && ($mtime = stat($source)['mtime'])) { + //Set location image + $this->context['locations'][$locationId]['image'] = $this->image->getThumb($location['miniature'], $mtime, $source); + } + + //Create ImageType form + $form = $this->factory->createNamed( + //Set form id + 'image_'.$locationId.'_'.$id, + //Set form type + 'Rapsys\AirBundle\Form\ImageType', + //Set form data + [ + //Set location + 'location' => $location['id'], + //Set user + 'user' => $id + ], + //Set form attributes + [ + //Enable delete with image + 'delete' => isset($this->context['locations'][$locationId]['image']) + ] + ); + + //Refill the fields in case of invalid form + $form->handleRequest($request); + + //Handle submitted and valid form + if ($form->isSubmitted() && $form->isValid()) { + //With delete + if ($form->has('delete') && $form->get('delete')->isClicked()) { + //With source and mtime + if ($isFile && !empty($source) && !empty($mtime)) { + //Clear thumb + $this->image->remove($mtime, $source); + + //Unlink file + unlink($this->config['path'].'/location/'.$location['id'].'/'.$id.'.png'); + + //Add notice + $this->addFlash('notice', $this->translator->trans('Image for %user% %location% deleted', ['%location%' => $location['at'], '%user%' => $this->context['user']['pseudonym']])); + + //Redirect to cleaned url + return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]); + } + } + + //With image + if ($image = $form->get('image')->getData()) { + //Check source path + if (!is_dir($dir = dirname($source))) { + //Create filesystem object + $filesystem = new Filesystem(); + + try { + //Create dir + //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) + $filesystem->mkdir($dir, 0775); + } catch (IOExceptionInterface $e) { + //Throw error + throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); + } + } + + //Set source + $source = realpath($dir).'/'.basename($source); + + //Create imagick object + $imagick = new \Imagick(); + + //Read image + $imagick->readImage($image->getRealPath()); + + //Save image + if (!$imagick->writeImage($source)) { + //Throw error + throw new \Exception(sprintf('Unable to write image "%s"', $source)); + } + + //Add notice + $this->addFlash('notice', $this->translator->trans('Image for %user% %location% updated', ['%location%' => $location['at'], '%user%' => $this->context['user']['pseudonym']])); + + //Redirect to cleaned url + return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]); + } + } + + //Add form to context + $this->context['forms']['images'][$locationId] = $form->createView(); } } //Render the view - return $this->render('@RapsysAir/user/view.html.twig', ['id' => $id, 'title' => $title, 'section' => $section, 'calendar' => $calendar, 'locations' => $locations]+$this->context); + return $this->render('@RapsysAir/user/view.html.twig', ['id' => $id]+$this->context); } } -- 2.41.1 From 38a1161f61672fe53bb0aabc4c6a700789973c4e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Mon, 24 Oct 2022 10:02:19 +0200 Subject: [PATCH 13/16] Update css and logo timestamp --- Resources/views/body.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/views/body.html.twig b/Resources/views/body.html.twig index 3d3abc3..10630ff 100644 --- a/Resources/views/body.html.twig +++ b/Resources/views/body.html.twig @@ -32,7 +32,7 @@ {# stylesheet '//fonts.googleapis.com/css?family=Irish+Grover' '//fonts.googleapis.com/css?family=La+Belle+Aurore' '@RapsysAirBundle/Resources/public/css/{reset,screen}.css' #} {% stopwatch 'stylesheet' %} {% stylesheet '@rapsys_air_bundle/css/{reset,droidsans,lemon,notoemoji,screen}.css' %} - + {% endstylesheet %} {% endstopwatch %} {% if canonical is defined and canonical %} @@ -68,7 +68,7 @@