]> Raphaël G. Git Repositories - airbundle/blob - Controller/LocationController.php
Php strict
[airbundle] / Controller / LocationController.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys AirBundle package.
5 *
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Rapsys\AirBundle\Controller;
13
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;
20
21 use Rapsys\AirBundle\Entity\Dance;
22 use Rapsys\AirBundle\Entity\Location;
23 use Rapsys\AirBundle\Entity\Session;
24 use Rapsys\AirBundle\Entity\Slot;
25
26 /**
27 * {@inheritdoc}
28 */
29 class LocationController extends DefaultController {
30 /**
31 * List all cities
32 *
33 * @param Request $request The request instance
34 * @return Response The rendered view
35 */
36 public function cities(Request $request): Response {
37 //Add cities
38 $this->context['cities'] = $this->doctrine->getRepository(Location::class)->findCitiesAsArray($this->period);
39
40 //Add dances
41 $this->context['dances'] = $this->doctrine->getRepository(Dance::class)->findNamesAsArray();
42
43 //Create response
44 $response = new Response();
45
46 //Set modified
47 $this->modified = max(array_map(function ($v) { return $v['modified']; }, array_merge($this->context['cities'], $this->context['dances'])));
48
49 //Add city multi
50 foreach($this->context['cities'] as $id => $city) {
51 //Add city multi
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']));
54 }
55
56 //With logged user
57 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
58 //Set last modified
59 $response->setLastModified(new \DateTime('-1 year'));
60
61 //Set as private
62 $response->setPrivate();
63 //Without logged user
64 } else {
65 //Set etag
66 //XXX: only for public to force revalidation by last modified
67 $response->setEtag(md5(serialize(array_merge($this->context['cities'], $this->context['dances']))));
68
69 //Set last modified
70 $response->setLastModified($this->modified);
71
72 //Set as public
73 $response->setPublic();
74
75 //Without role and modification
76 if ($response->isNotModified($request)) {
77 //Return 304 response
78 return $response;
79 }
80 }
81
82 //Set section
83 $this->context['title'] = $this->translator->trans('Libre Air cities');
84
85 //Set description
86 $this->context['description'] = $this->translator->trans('Libre Air city list');
87
88 //Set cities
89 $cities = array_map(function ($v) { return $v['in']; }, $this->context['cities']);
90
91 //Set dances
92 $dances = array_map(function ($v) { return $v['name']; }, $this->context['dances']);
93
94 //Set indoors
95 $indoors = array_reduce($this->context['cities'], function ($c, $v) { return array_merge($c, $v['indoors']); }, []);
96
97 //Set keywords
98 $this->context['keywords'] = array_values(
99 array_merge(
100 [
101 $this->translator->trans('Cities'),
102 $this->translator->trans('City list'),
103 $this->translator->trans('Listing'),
104 ],
105 $cities,
106 $indoors,
107 [
108 $this->translator->trans('calendar'),
109 $this->translator->trans('Libre Air')
110 ]
111 )
112 );
113
114 //Render the view
115 return $this->render('@RapsysAir/location/cities.html.twig', $this->context, $response);
116 }
117
118 /**
119 * Display city
120 *
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
124 *
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
129 */
130 public function city(Request $request, float $latitude, float $longitude, string $city): Response {
131 //Get city
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]));
134 }
135
136 //Add calendar
137 $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsArray($this->period, $request->getLocale(), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), floatval($latitude), floatval($longitude));
138
139 //Set dances
140 $this->context['dances'] = [];
141
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'])) {
148 //Add dance
149 $this->context['dances'][$session['application']['dance']['id']] = $session['application']['dance'];
150 }
151 }
152 }
153
154 //Add locations
155 $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllByLatitudeLongitudeAsArray(floatval($latitude), floatval($longitude), $this->period);
156
157 //Set modified
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']))));
160
161 //Create response
162 $response = new Response();
163
164 //With logged user
165 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
166 //Set last modified
167 $response->setLastModified(new \DateTime('-1 year'));
168
169 //Set as private
170 $response->setPrivate();
171 //Without logged user
172 } else {
173 //Set etag
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']))));
176
177 //Set last modified
178 $response->setLastModified($this->modified);
179
180 //Set as public
181 $response->setPublic();
182
183 //Without role and modification
184 if ($response->isNotModified($request)) {
185 //Return 304 response
186 return $response;
187 }
188 }
189
190 //Add multi
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']));
192
193 //Set keywords
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')
200 ];
201
202 //With context dances
203 if (!empty($this->context['dances'])) {
204 //Set dances
205 $dances = array_map(function ($v) { return $v['name']; }, $this->context['dances']);
206
207 //Insert dances in keywords
208 array_splice($this->context['keywords'], 1, 0, $dances);
209
210 //Get textual dances
211 $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
212
213 //Set title
214 $this->context['title'] = $this->translator->trans('%dances% %city%', ['%dances%' => $dances, '%city%' => $this->context['city']['in']]);
215
216 //Set description
217 $this->context['description'] = $this->translator->trans('%dances% indoor and outdoor calendar %city%', ['%dances%' => $dances, '%city%' => $this->context['city']['in']]);
218 } else {
219 //Set title
220 $this->context['title'] = $this->translator->trans('Dance %city%', ['%city%' => $this->context['city']['in']]);
221
222 //Set description
223 $this->context['description'] = $this->translator->trans('Indoor and outdoor dance calendar %city%', ['%city%' => $this->context['city']['in']]);
224 }
225
226 //Set locations description
227 $this->context['locations_description'] = $this->translator->trans('Libre Air location list %city%', ['%city%' => $this->context['city']['in']]);
228
229 //Render the view
230 return $this->render('@RapsysAir/location/city.html.twig', $this->context, $response);
231 }
232
233 /**
234 * List all locations
235 *
236 * @desc Display all locations
237 *
238 * @param Request $request The request instance
239 *
240 * @return Response The rendered view
241 */
242 public function index(Request $request): Response {
243 //Get locations
244 $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllAsArray($this->period);
245
246 //Set modified
247 $this->modified = max(array_map(function ($v) { return $v['updated']; }, $this->context['locations']));
248
249 //Create response
250 $response = new Response();
251
252 //With logged user
253 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
254 //Set last modified
255 $response->setLastModified(new \DateTime('-1 year'));
256
257 //Set as private
258 $response->setPrivate();
259 //Without logged user
260 } else {
261 //Set etag
262 //XXX: only for public to force revalidation by last modified
263 $response->setEtag(md5(serialize($this->context['locations'])));
264
265 //Set last modified
266 $response->setLastModified($this->modified);
267
268 //Set as public
269 $response->setPublic();
270
271 //Without role and modification
272 if ($response->isNotModified($request)) {
273 //Return 304 response
274 return $response;
275 }
276 }
277
278 //Set latitudes
279 $latitudes = array_map(function ($v) { return $v['latitude']; }, $this->context['locations']);
280
281 //Set latitude
282 $latitude = round(array_sum($latitudes)/count($latitudes), 6);
283
284 //Set longitudes
285 $longitudes = array_map(function ($v) { return $v['longitude']; }, $this->context['locations']);
286
287 //Set longitude
288 $longitude = round(array_sum($longitudes)/count($longitudes), 6);
289
290 //Add multi map
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']));
292
293 //Set title
294 $this->context['title'] = $this->translator->trans('Libre Air locations');
295
296 //Set description
297 $this->context['description'] = $this->translator->trans('Libre Air location list');
298
299 //Set keywords
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')
305 ];
306
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();
311
312 //Init locations to context
313 $this->context['forms']['locations'] = [];
314
315 //Iterate on locations
316 foreach($this->context['locations'] as $id => $location) {
317 //Create LocationType form
318 $form = $this->factory->createNamed(
319 //Set form id
320 'locations_'.$id,
321 //Set form type
322 'Rapsys\AirBundle\Form\LocationType',
323 //Set form data
324 $locations[$location['id']],
325 //Set the form attributes
326 ['attr' => []]
327 );
328
329 //Refill the fields in case of invalid form
330 $form->handleRequest($request);
331
332 //Handle valid form
333 if ($form->isSubmitted() && $form->isValid()) {
334 //Get data
335 $data = $form->getData();
336
337 //Set updated
338 $data->setUpdated(new \DateTime('now'));
339
340 //Queue location save
341 $this->manager->persist($data);
342
343 //Flush to get the ids
344 $this->manager->flush();
345
346 //Add notice
347 $this->addFlash('notice', $this->translator->trans('Location %id% updated', ['%id%' => $location['id']]));
348
349 //Redirect to cleanup the form
350 return $this->redirectToRoute('rapsys_air_location', ['location' => $location['id']]);
351 }
352
353 //Add form to context
354 $this->context['forms']['locations'][$id] = $form->createView();
355 }
356
357 //Create LocationType form
358 $form = $this->factory->createNamed(
359 //Set form id
360 'locations',
361 //Set form type
362 'Rapsys\AirBundle\Form\LocationType',
363 //Set form data
364 new Location(),
365 //Set the form attributes
366 ['attr' => ['class' => 'col']]
367 );
368
369 //Refill the fields in case of invalid form
370 $form->handleRequest($request);
371
372 //Handle valid form
373 if ($form->isSubmitted() && $form->isValid()) {
374 //Get data
375 $data = $form->getData();
376
377 //Queue location save
378 $this->manager->persist($data);
379
380 //Flush to get the ids
381 $this->manager->flush();
382
383 //Add notice
384 $this->addFlash('notice', $this->translator->trans('Location created'));
385
386 //Redirect to cleanup the form
387 return $this->redirectToRoute('rapsys_air_location', ['location' => $data->getId()]);
388 }
389
390 //Add form to context
391 $this->context['forms']['location'] = $form->createView();
392 }
393
394 //Render the view
395 return $this->render('@RapsysAir/location/index.html.twig', $this->context);
396 }
397
398 /**
399 * List all sessions for the location
400 *
401 * Display all sessions for the location with an application or login form
402 *
403 * @TODO: add location edit form ???
404 *
405 * @param Request $request The request instance
406 * @param int $id The location id
407 *
408 * @return Response The rendered view
409 */
410 public function view(Request $request, int $id): Response {
411 //Without location
412 if (empty($this->context['location'] = $this->doctrine->getRepository(Location::class)->findOneByIdAsArray($id, $this->locale))) {
413 //Throw 404
414 throw $this->createNotFoundException($this->translator->trans('Unable to find location: %id%', ['%id%' => $id]));
415 }
416
417 //Fetch calendar
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']);
419
420 //Set dances
421 $this->context['dances'] = [];
422
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'])) {
429 //Add dance
430 $this->context['dances'][$session['application']['dance']['id']] = $session['application']['dance'];
431 }
432 }
433 }
434
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);
437
438 //Set modified
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']))));
441
442 //Create response
443 $response = new Response();
444
445 //With logged user
446 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
447 //Set last modified
448 $response->setLastModified(new \DateTime('-1 year'));
449
450 //Set as private
451 $response->setPrivate();
452 //Without logged user
453 } else {
454 //Set etag
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']))));
457
458 //Set last modified
459 $response->setLastModified($this->modified);
460
461 //Set as public
462 $response->setPublic();
463
464 //Without role and modification
465 if ($response->isNotModified($request)) {
466 //Return 304 response
467 return $response;
468 }
469 }
470
471 //Add multi map
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']));
473
474 //Set keywords
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')
481 ];
482
483 //With dances
484 if (!empty($this->context['dances'])) {
485 //Set dances
486 $dances = array_map(function ($v) { return $v['name']; }, $this->context['dances']);
487
488 //Insert dances in keywords
489 array_splice($this->context['keywords'], 2, 0, $dances);
490
491 //Get textual dances
492 $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
493
494 //Set title
495 $this->context['title'] = $this->translator->trans('%dances% %location%', ['%dances%' => $dances, '%location%' => $this->context['location']['atin']]);
496
497 //Set description
498 $this->context['description'] = $this->translator->trans('%dances% indoor and outdoor calendar %location%', ['%dances%' => $dances, '%location%' => $this->context['location']['at']]);
499 //Without dances
500 } else {
501 //Set title
502 $this->context['title'] = $this->translator->trans('Dance %location%', ['%location%' => $this->context['location']['atin']]);
503
504 //Set description
505 $this->context['description'] = $this->translator->trans('Indoor and outdoor dance calendar %location%', [ '%location%' => $this->context['location']['at'] ]);
506 }
507
508 //Set locations description
509 $this->context['locations_description'] = $this->translator->trans('Libre Air location list %location%', ['%location%' => $this->context['location']['atin']]);
510
511 //Set alternates
512 $this->context['alternates'] += $this->context['location']['alternates'];
513
514 //Render the view
515 return $this->render('@RapsysAir/location/view.html.twig', $this->context, $response);
516 }
517 }