1 <?php
declare(strict_types
=1);
4 * This file is part of the Rapsys AirBundle package.
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Rapsys\AirBundle\Controller
;
14 use Symfony\Component\HttpFoundation\Request
;
15 use Symfony\Component\HttpFoundation\Response
;
16 use Symfony\Component\Routing\Exception\MethodNotAllowedException
;
17 use Symfony\Component\Routing\Exception\ResourceNotFoundException
;
18 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
;
19 use Symfony\Component\Routing\RequestContext
;
21 use Rapsys\AirBundle\Entity\Dance
;
22 use Rapsys\AirBundle\Entity\Location
;
23 use Rapsys\AirBundle\Entity\Session
;
24 use Rapsys\AirBundle\Entity\Slot
;
29 class LocationController
extends DefaultController
{
33 * @param Request $request The request instance
34 * @return Response The rendered view
36 public function cities(Request
$request): Response
{
38 $this->context
['cities'] = $this->doctrine
->getRepository(Location
::class)->findCitiesAsArray($this->period
);
41 $this->context
['dances'] = $this->doctrine
->getRepository(Dance
::class)->findNamesAsArray();
44 $response = new Response();
47 $this->modified
= max(array_map(function ($v) { return $v
['modified']; }, array_merge($this->context
['cities'], $this->context
['dances'])));
50 foreach($this->context
['cities'] as $id => $city) {
52 #$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));
53 $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']));
57 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
59 $response->setLastModified(new \
DateTime('-1 year'));
62 $response->setPrivate();
66 //XXX: only for public to force revalidation by last modified
67 $response->setEtag(md5(serialize(array_merge($this->context
['cities'], $this->context
['dances']))));
70 $response->setLastModified($this->modified
);
73 $response->setPublic();
75 //Without role and modification
76 if ($response->isNotModified($request)) {
83 $this->context
['title'] = $this->translator
->trans('Libre Air cities');
86 $this->context
['description'] = $this->translator
->trans('Libre Air city list');
89 $cities = array_map(function ($v) { return $v
['in']; }, $this->context
['cities']);
92 $dances = array_map(function ($v) { return $v
['name']; }, $this->context
['dances']);
95 $indoors = array_reduce($this->context
['cities'], function ($c, $v) { return array_merge($c
, $v
['indoors']); }, []);
98 $this->context
['keywords'] = array_values(
101 $this->translator
->trans('Cities'),
102 $this->translator
->trans('City list'),
103 $this->translator
->trans('Listing'),
108 $this->translator
->trans('calendar'),
109 $this->translator
->trans('Libre Air')
115 return $this->render('@RapsysAir/location/cities.html.twig', $this->context
, $response);
121 * @todo XXX: TODO: add <link rel="prev|next" for sessions or classes ? />
122 * @todo XXX: TODO: like described in: https://www.alsacreations.com/article/lire/1400-attribut-rel-relations.html#xnf-rel-attribute
123 * @todo XXX: TODO: or here: http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions
125 * @param Request $request The request instance
126 * @param float $latitude The city latitude
127 * @param float $longitude The city longitude
128 * @return Response The rendered view
130 public function city(Request
$request, float $latitude, float $longitude, string $city): Response
{
132 if (!($this->context
['city'] = $this->doctrine
->getRepository(Location
::class)->findCityByLatitudeLongitudeAsArray(floatval($latitude), floatval($longitude)))) {
133 throw $this->createNotFoundException($this->translator
->trans('Unable to find city: %latitude%,%longitude%', ['%latitude%' => $latitude, '%longitude%' => $longitude]));
137 $this->context
['calendar'] = $this->doctrine
->getRepository(Session
::class)->findAllByPeriodAsArray($this->period
, $request->getLocale(), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), floatval($latitude), floatval($longitude));
140 $this->context
['dances'] = [];
142 //Iterate on each calendar
143 foreach($this->context
['calendar'] as $date => $calendar) {
144 //Iterate on each session
145 foreach($calendar['sessions'] as $sessionId => $session) {
146 //Session with application dance
147 if (!empty($session['application']['dance'])) {
149 $this->context
['dances'][$session['application']['dance']['id']] = $session['application']['dance'];
155 $this->context
['locations'] = $this->doctrine
->getRepository(Location
::class)->findAllByLatitudeLongitudeAsArray(floatval($latitude), floatval($longitude), $this->period
);
158 //XXX: dance modified is already computed inside calendar modified
159 $this->modified
= max(array_merge([$this->context
['city']['updated']], array_map(function ($v) { return $v
['modified']; }, array_merge($this->context
['calendar'], $this->context
['locations']))));
162 $response = new Response();
165 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
167 $response->setLastModified(new \
DateTime('-1 year'));
170 $response->setPrivate();
171 //Without logged user
174 //XXX: only for public to force revalidation by last modified
175 $response->setEtag(md5(serialize(array_merge($this->context
['city'], $this->context
['dances'], $this->context
['calendar'], $this->context
['locations']))));
178 $response->setLastModified($this->modified
);
181 $response->setPublic();
183 //Without role and modification
184 if ($response->isNotModified($request)) {
185 //Return 304 response
191 $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']));
194 $this->context
['keywords'] = [
195 $this->context
['city']['city'],
196 $this->translator
->trans('Indoor'),
197 $this->translator
->trans('Outdoor'),
198 $this->translator
->trans('Calendar'),
199 $this->translator
->trans('Libre Air')
202 //With context dances
203 if (!empty($this->context
['dances'])) {
205 $dances = array_map(function ($v) { return $v
['name']; }, $this->context
['dances']);
207 //Insert dances in keywords
208 array_splice($this->context
['keywords'], 1, 0, $dances);
211 $dances = implode($this->translator
->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
214 $this->context
['title'] = $this->translator
->trans('%dances% %city%', ['%dances%' => $dances, '%city%' => $this->context
['city']['in']]);
217 $this->context
['description'] = $this->translator
->trans('%dances% indoor and outdoor calendar %city%', ['%dances%' => $dances, '%city%' => $this->context
['city']['in']]);
220 $this->context
['title'] = $this->translator
->trans('Dance %city%', ['%city%' => $this->context
['city']['in']]);
223 $this->context
['description'] = $this->translator
->trans('Indoor and outdoor dance calendar %city%', ['%city%' => $this->context
['city']['in']]);
226 //Set locations description
227 $this->context
['locations_description'] = $this->translator
->trans('Libre Air location list %city%', ['%city%' => $this->context
['city']['in']]);
230 return $this->render('@RapsysAir/location/city.html.twig', $this->context
, $response);
236 * @desc Display all locations
238 * @param Request $request The request instance
240 * @return Response The rendered view
242 public function index(Request
$request): Response
{
244 $this->context
['locations'] = $this->doctrine
->getRepository(Location
::class)->findAllAsArray($this->period
);
247 $this->modified
= max(array_map(function ($v) { return $v
['updated']; }, $this->context
['locations']));
250 $response = new Response();
253 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
255 $response->setLastModified(new \
DateTime('-1 year'));
258 $response->setPrivate();
259 //Without logged user
262 //XXX: only for public to force revalidation by last modified
263 $response->setEtag(md5(serialize($this->context
['locations'])));
266 $response->setLastModified($this->modified
);
269 $response->setPublic();
271 //Without role and modification
272 if ($response->isNotModified($request)) {
273 //Return 304 response
279 $latitudes = array_map(function ($v) { return $v
['latitude']; }, $this->context
['locations']);
282 $latitude = round(array_sum($latitudes)/count($latitudes), 6);
285 $longitudes = array_map(function ($v) { return $v
['longitude']; }, $this->context
['locations']);
288 $longitude = round(array_sum($longitudes)/count($longitudes), 6);
291 $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']));
294 $this->context
['title'] = $this->translator
->trans('Libre Air locations');
297 $this->context
['description'] = $this->translator
->trans('Libre Air location list');
300 $this->context
['keywords'] = [
301 $this->translator
->trans('locations'),
302 $this->translator
->trans('location list'),
303 $this->translator
->trans('listing'),
304 $this->translator
->trans('Libre Air')
307 //Create location forms for role_admin
308 if ($this->isGranted('ROLE_ADMIN')) {
309 //Fetch all locations
310 $locations = $this->doctrine
->getRepository(Location
::class)->findAll();
312 //Init locations to context
313 $this->context
['forms']['locations'] = [];
315 //Iterate on locations
316 foreach($this->context
['locations'] as $id => $location) {
317 //Create LocationType form
318 $form = $this->factory
->createNamed(
322 'Rapsys\AirBundle\Form\LocationType',
324 $locations[$location['id']],
325 //Set the form attributes
329 //Refill the fields in case of invalid form
330 $form->handleRequest($request);
333 if ($form->isSubmitted() && $form->isValid()) {
335 $data = $form->getData();
338 $data->setUpdated(new \
DateTime('now'));
340 //Queue location save
341 $this->manager
->persist($data);
343 //Flush to get the ids
344 $this->manager
->flush();
347 $this->addFlash('notice', $this->translator
->trans('Location %id% updated', ['%id%' => $location['id']]));
349 //Redirect to cleanup the form
350 return $this->redirectToRoute('rapsys_air_location', ['location' => $location['id']]);
353 //Add form to context
354 $this->context
['forms']['locations'][$id] = $form->createView();
357 //Create LocationType form
358 $form = $this->factory
->createNamed(
362 'Rapsys\AirBundle\Form\LocationType',
365 //Set the form attributes
366 ['attr' => ['class' => 'col']]
369 //Refill the fields in case of invalid form
370 $form->handleRequest($request);
373 if ($form->isSubmitted() && $form->isValid()) {
375 $data = $form->getData();
377 //Queue location save
378 $this->manager
->persist($data);
380 //Flush to get the ids
381 $this->manager
->flush();
384 $this->addFlash('notice', $this->translator
->trans('Location created'));
386 //Redirect to cleanup the form
387 return $this->redirectToRoute('rapsys_air_location', ['location' => $data->getId()]);
390 //Add form to context
391 $this->context
['forms']['location'] = $form->createView();
395 return $this->render('@RapsysAir/location/index.html.twig', $this->context
);
399 * List all sessions for the location
401 * Display all sessions for the location with an application or login form
403 * @TODO: add location edit form ???
405 * @param Request $request The request instance
406 * @param int $id The location id
408 * @return Response The rendered view
410 public function view(Request
$request, int $id): Response
{
412 if (empty($this->context
['location'] = $this->doctrine
->getRepository(Location
::class)->findOneByIdAsArray($id, $this->locale
))) {
414 throw $this->createNotFoundException($this->translator
->trans('Unable to find location: %id%', ['%id%' => $id]));
418 $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']);
421 $this->context
['dances'] = [];
423 //Iterate on each calendar
424 foreach($this->context
['calendar'] as $date => $calendar) {
425 //Iterate on each session
426 foreach($calendar['sessions'] as $sessionId => $session) {
427 //Session with application dance
428 if (!empty($session['application']['dance'])) {
430 $this->context
['dances'][$session['application']['dance']['id']] = $session['application']['dance'];
435 //Get locations at less than 2 km
436 $this->context
['locations'] = $this->doctrine
->getRepository(Location
::class)->findAllByLatitudeLongitudeAsArray($this->context
['location']['latitude'], $this->context
['location']['longitude'], $this->period
, 2);
439 //XXX: dance modified is already computed inside calendar modified
440 $this->modified
= max(array_merge([$this->context
['location']['updated']], array_map(function ($v) { return $v
['modified']; }, array_merge($this->context
['calendar'], $this->context
['locations']))));
443 $response = new Response();
446 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
448 $response->setLastModified(new \
DateTime('-1 year'));
451 $response->setPrivate();
452 //Without logged user
455 //XXX: only for public to force revalidation by last modified
456 $response->setEtag(md5(serialize(array_merge($this->context
['location'], $this->context
['calendar'], $this->context
['locations']))));
459 $response->setLastModified($this->modified
);
462 $response->setPublic();
464 //Without role and modification
465 if ($response->isNotModified($request)) {
466 //Return 304 response
472 $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']));
475 $this->context
['keywords'] = [
476 $this->context
['location']['title'],
477 $this->context
['location']['city'],
478 $this->translator
->trans($this->context
['location']['indoor']?'Indoor':'Outdoor'),
479 $this->translator
->trans('Calendar'),
480 $this->translator
->trans('Libre Air')
484 if (!empty($this->context
['dances'])) {
486 $dances = array_map(function ($v) { return $v
['name']; }, $this->context
['dances']);
488 //Insert dances in keywords
489 array_splice($this->context
['keywords'], 2, 0, $dances);
492 $dances = implode($this->translator
->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
495 $this->context
['title'] = $this->translator
->trans('%dances% %location%', ['%dances%' => $dances, '%location%' => $this->context
['location']['atin']]);
498 $this->context
['description'] = $this->translator
->trans('%dances% indoor and outdoor calendar %location%', ['%dances%' => $dances, '%location%' => $this->context
['location']['at']]);
502 $this->context
['title'] = $this->translator
->trans('Dance %location%', ['%location%' => $this->context
['location']['atin']]);
505 $this->context
['description'] = $this->translator
->trans('Indoor and outdoor dance calendar %location%', [ '%location%' => $this->context
['location']['at'] ]);
508 //Set locations description
509 $this->context
['locations_description'] = $this->translator
->trans('Libre Air location list %location%', ['%location%' => $this->context
['location']['atin']]);
512 $this->context
['alternates'] +
= $this->context
['location']['alternates'];
515 return $this->render('@RapsysAir/location/view.html.twig', $this->context
, $response);