]> Raphaël G. Git Repositories - airbundle/blob - Controller/AbstractController.php
Add captcha row
[airbundle] / Controller / AbstractController.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 Doctrine\ORM\EntityManagerInterface;
15 use Doctrine\Persistence\ManagerRegistry;
16
17 use Psr\Container\ContainerInterface;
18 use Psr\Log\LoggerInterface;
19
20 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
21 use Symfony\Bundle\SecurityBundle\Security;
22 use Symfony\Component\Asset\PackageInterface;
23 use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
24 use Symfony\Component\Filesystem\Filesystem;
25 use Symfony\Component\Form\FormFactoryInterface;
26 use Symfony\Component\HttpFoundation\Request;
27 use Symfony\Component\HttpFoundation\RequestStack;
28 use Symfony\Component\HttpFoundation\Response;
29 use Symfony\Component\Mailer\MailerInterface;
30 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
31 use Symfony\Component\Routing\RouterInterface;
32 use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
33 use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
34 use Symfony\Contracts\Service\ServiceSubscriberInterface;
35 use Symfony\Contracts\Translation\TranslatorInterface;
36
37 use Twig\Environment;
38
39 use Rapsys\AirBundle\Entity\Dance;
40 use Rapsys\AirBundle\Entity\Location;
41 use Rapsys\AirBundle\Entity\Slot;
42 use Rapsys\AirBundle\Entity\User;
43 use Rapsys\AirBundle\RapsysAirBundle;
44
45 use Rapsys\PackBundle\Util\FacebookUtil;
46 use Rapsys\PackBundle\Util\ImageUtil;
47 use Rapsys\PackBundle\Util\MapUtil;
48 use Rapsys\PackBundle\Util\SluggerUtil;
49
50 /**
51 * Provides common features needed in controllers.
52 *
53 * {@inheritdoc}
54 */
55 abstract class AbstractController extends BaseAbstractController implements ServiceSubscriberInterface {
56 /**
57 * Config array
58 */
59 protected array $config;
60
61 /**
62 * Context array
63 */
64 protected array $context;
65
66 /**
67 * Locale string
68 */
69 protected string $locale;
70
71 /**
72 * Modified DateTime
73 */
74 protected \DateTime $modified;
75
76 /**
77 * DatePeriod instance
78 */
79 protected \DatePeriod $period;
80
81 /**
82 * Request instance
83 */
84 protected Request $request;
85
86 /**
87 * Route string
88 */
89 protected string $route;
90
91 /**
92 * Route params array
93 */
94 protected array $routeParams;
95
96 /**
97 * Abstract constructor
98 *
99 * @param AuthorizationCheckerInterface $checker The container instance
100 * @param ContainerInterface $container The container instance
101 * @param AccessDecisionManagerInterface $decision The decision instance
102 * @param ManagerRegistry $doctrine The doctrine instance
103 * @param FacebookUtil $facebook The facebook instance
104 * @param FormFactoryInterface $factory The factory instance
105 * @param ImageUtil $image The image instance
106 * @param MailerInterface $mailer The mailer instance
107 * @param EntityManagerInterface $manager The manager instance
108 * @param MapUtil $map The map instance
109 * @param PackageInterface $package The package instance
110 * @param RouterInterface $router The router instance
111 * @param Security $security The security instance
112 * @param SluggerUtil $slugger The slugger instance
113 * @param RequestStack $stack The stack instance
114 * @param TranslatorInterface $translator The translator instance
115 * @param Environment $twig The twig environment instance
116 *
117 * @TODO move all that stuff to setSlugger('@slugger') setters with a calls: [ setSlugger: [ '@slugger' ] ] to unbload classes ???
118 * @TODO add a calls: [ ..., prepare: ['@???'] ] that do all the logic that can't be done in constructor because various things are not available
119 */
120 public function __construct(protected AuthorizationCheckerInterface $checker, protected ContainerInterface $container, protected AccessDecisionManagerInterface $decision, protected ManagerRegistry $doctrine, protected FacebookUtil $facebook, protected FormFactoryInterface $factory, protected ImageUtil $image, protected MailerInterface $mailer, protected EntityManagerInterface $manager, protected MapUtil $map, protected PackageInterface $package, protected RouterInterface $router, protected Security $security, protected SluggerUtil $slugger, protected RequestStack $stack, protected TranslatorInterface $translator, protected Environment $twig, protected int $limit = 5) {
121 //Retrieve config
122 $this->config = $container->getParameter(RapsysAirBundle::getAlias());
123
124 //Set period
125 $this->period = new \DatePeriod(
126 //Start from first monday of week
127 new \DateTime('Monday this week'),
128 //Iterate on each day
129 new \DateInterval('P1D'),
130 //End with next sunday and 4 weeks
131 //XXX: we can't use isGranted here as AuthenticatedVoter deny access because user is likely not authenticated yet :'(
132 new \DateTime('Monday this week + 2 week')
133 );
134
135 //Get main request
136 $this->request = $this->stack->getMainRequest();
137
138 //Get current locale
139 $this->locale = $this->request->getLocale();
140
141 //Set canonical
142 $canonical = null;
143
144 //Set alternates
145 $alternates = [];
146
147 //Set route
148 //TODO: default to not found route ???
149 //TODO: pour une url not found, cet attribut n'est pas défini, comment on fait ???
150 //XXX: on génère une route bidon par défaut ???
151 $this->route = $this->request->attributes->get('_route');
152
153 //Set route params
154 $this->routeParams = $this->request->attributes->get('_route_params');
155
156 //With route and routeParams
157 if ($this->route !== null && $this->routeParams !== null) {
158 //Set canonical
159 $canonical = $this->router->generate($this->route, $this->routeParams, UrlGeneratorInterface::ABSOLUTE_URL);
160
161 //Set alternates
162 $alternates = [
163 substr($this->locale, 0, 2) => [
164 'absolute' => $canonical
165 ]
166 ];
167 }
168
169 //Set the context
170 $this->context = [
171 'alternates' => $alternates,
172 'canonical' => $canonical,
173 'contact' => [
174 'address' => $this->config['contact']['address'],
175 'name' => $this->translator->trans($this->config['contact']['name'])
176 ],
177 'copy' => [
178 'by' => $this->translator->trans($this->config['copy']['by']),
179 'link' => $this->config['copy']['link'],
180 'long' => $this->translator->trans($this->config['copy']['long']),
181 'short' => $this->translator->trans($this->config['copy']['short']),
182 'title' => $this->config['copy']['title']
183 ],
184 'description' => null,
185 'donate' => $this->config['donate'],
186 'facebook' => [
187 'og:type' => 'article',
188 'og:site_name' => $title = $this->translator->trans($this->config['title']),
189 'og:url' => $canonical,
190 #'fb:admins' => $this->config['facebook']['admins'],
191 'fb:app_id' => $this->config['facebook']['apps']
192 ],
193 //XXX: TODO: only generate it when fb robot request the url ???
194 'fbimage' => [
195 'texts' => [
196 $title => [
197 'font' => 'irishgrover',
198 'size' => 110
199 ]
200 ]
201 ],
202 'icon' => $this->config['icon'],
203 'keywords' => null,
204 'locale' => str_replace('_', '-', $this->locale),
205 'logo' => $this->config['logo'],
206 'forms' => [],
207 'root' => $this->router->generate($this->config['root']),
208 'title' => [
209 'page' => null,
210 'section' => null,
211 'site' => $title
212 ]
213 ];
214 }
215
216 /**
217 * Renders a view
218 *
219 * {@inheritdoc}
220 */
221 protected function render(string $view, array $parameters = [], Response $response = null): Response {
222 //Create response when null
223 $response ??= new Response();
224
225 //Create application form for role_guest
226 if ($this->checker->isGranted('ROLE_GUEST')) {
227 //Without application form
228 if (empty($parameters['forms']['application'])) {
229 //Get favorites dances
230 $danceFavorites = $this->doctrine->getRepository(Dance::class)->findByUserId($this->security->getUser()->getId());
231
232 //Set dance default
233 $danceDefault = !empty($danceFavorites)?current($danceFavorites):null;
234
235 //Get favorites locations
236 $locationFavorites = $this->doctrine->getRepository(Location::class)->findByUserId($this->security->getUser()->getId());
237
238 //Set location default
239 $locationDefault = !empty($locationFavorites)?current($locationFavorites):null;
240
241 //With admin
242 if ($this->checker->isGranted('ROLE_ADMIN')) {
243 //Get dances
244 $dances = $this->doctrine->getRepository(Dance::class)->findAll();
245
246 //Get locations
247 $locations = $this->doctrine->getRepository(Location::class)->findAll();
248 //Without admin
249 } else {
250 //Restrict to favorite dances
251 $dances = $danceFavorites;
252
253 //Reset favorites
254 $danceFavorites = [];
255
256 //Restrict to favorite locations
257 $locations = $locationFavorites;
258
259 //Reset favorites
260 $locationFavorites = [];
261 }
262
263 //With session application dance id
264 if (!empty($parameters['session']['application']['dance']['id'])) {
265 //Iterate on each dance
266 foreach($dances as $dance) {
267 //Found dance
268 if ($dance->getId() == $parameters['session']['application']['dance']['id']) {
269 //Set dance as default
270 $danceDefault = $dance;
271
272 //Stop search
273 break;
274 }
275 }
276 }
277
278 //With session location id
279 //XXX: set in session controller
280 //TODO: with new findAll that key by id, it should be as simple as isset($locations[$id]) ?
281 if (!empty($parameters['session']['location']['id'])) {
282 //Iterate on each location
283 foreach($locations as $location) {
284 //Found location
285 if ($location->getId() == $parameters['session']['location']['id']) {
286 //Set location as default
287 $locationDefault = $location;
288
289 //Stop search
290 break;
291 }
292 }
293 }
294
295 //Create ApplicationType form
296 $application = $this->factory->create('Rapsys\AirBundle\Form\ApplicationType', null, [
297 //Set the action
298 'action' => $this->generateUrl('rapsysair_application_add'),
299 //Set the form attribute
300 'attr' => [ 'class' => 'col' ],
301 //Set dance choices
302 'dance_choices' => $dances,
303 //Set dance default
304 'dance_default' => $danceDefault,
305 //Set dance favorites
306 'dance_favorites' => $danceFavorites,
307 //Set location choices
308 'location_choices' => $locations,
309 //Set location default
310 'location_default' => $locationDefault,
311 //Set location favorites
312 'location_favorites' => $locationFavorites,
313 //With user
314 'user' => $this->checker->isGranted('ROLE_ADMIN'),
315 //Set user choices
316 'user_choices' => $this->doctrine->getRepository(User::class)->findChoicesAsArray(),
317 //Set default user to current
318 'user_default' => $this->security->getUser()->getId(),
319 //Set to session slot or evening by default
320 //XXX: default to Evening (3)
321 'slot_default' => $this->doctrine->getRepository(Slot::class)->findOneById($parameters['session']['slot']['id']??3)
322 ]);
323
324 //Add form to context
325 $parameters['forms']['application'] = $application->createView();
326 }
327 }/*
328 #XXX: removed because it fucks up the seo by displaying register and login form instead of content
329 #XXX: until we find a better way, removed !!!
330 //Create login form for anonymous
331 elseif (!$this->checker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
332 //Create LoginType form
333 $login = $this->factory->create('Rapsys\UserBundle\Form\LoginType', null, [
334 //Set the action
335 'action' => $this->generateUrl('rapsysuser_login'),
336 //Disable password repeated
337 'password_repeated' => false,
338 //Set the form attribute
339 'attr' => [ 'class' => 'col' ]
340 ]);
341
342 //Add form to context
343 $parameters['forms']['login'] = $login->createView();
344
345 //Set field
346 $field = [
347 //With mail
348 'mail' => true,
349 //Without civility
350 'civility' => false,
351 //Without pseudonym
352 'pseudonym' => false,
353 //Without forename
354 'forename' => false,
355 //Without surname
356 'surname' => false,
357 //Without password
358 'password' => false,
359 //Without slug
360 'slug' => false,
361 //Without phone
362 'phone' => false
363 ];
364
365 //Create RegisterType form
366 $register = $this->factory->create('Rapsys\AirBundle\Form\RegisterType', null, $field+[
367 //Set the action
368 'action' => $this->generateUrl(
369 'rapsysuser_register',
370 [
371 'mail' => $smail = $this->slugger->short(''),
372 'field' => $sfield = $this->slugger->serialize($field),
373 'hash' => $this->slugger->hash($smail.$sfield)
374 ]
375 ),
376 //Set the form attribute
377 'attr' => [ 'class' => 'col' ]
378 ]);
379
380 //Add form to context
381 $parameters['forms']['register'] = $register->createView();
382 }*/
383
384 //Without alternates
385 if (count($parameters['alternates']) <= 1) {
386 //Set routeParams
387 $routeParams = $this->routeParams;
388
389 //Iterate on locales excluding current one
390 foreach($this->config['locales'] as $locale) {
391 //With current locale
392 if ($locale !== $this->locale) {
393 //Set titles
394 $titles = [];
395
396 //Set route params locale
397 $routeParams['_locale'] = $locale;
398
399 //Iterate on other locales
400 foreach(array_diff($this->config['locales'], [$locale]) as $other) {
401 //Set other locale title
402 $titles[$other] = $this->translator->trans($this->config['languages'][$locale], [], null, $other);
403 }
404
405 //Set locale locales context
406 $parameters['alternates'][str_replace('_', '-', $locale)] = [
407 'absolute' => $this->router->generate($this->route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL),
408 'relative' => $this->router->generate($this->route, $routeParams),
409 'title' => implode('/', $titles),
410 'translated' => $this->translator->trans($this->config['languages'][$locale], [], null, $locale)
411 ];
412
413 //Add shorter locale
414 if (empty($parameters['alternates'][$shortCurrent = substr($locale, 0, 2)])) {
415 //Set locale locales context
416 $parameters['alternates'][$shortCurrent] = $parameters['alternates'][str_replace('_', '-', $locale)];
417 }
418 }
419 }
420 }
421
422 //With page infos and without facebook texts
423 if (count($parameters['fbimage']) <= 1 && isset($parameters['title']) && isset($this->route) && isset($this->routeParams)) {
424 //Append facebook image texts
425 $parameters['fbimage'] += [
426 'texts' => [
427 $parameters['title']['page'] => [
428 'font' => 'irishgrover',
429 'align' => 'left'
430 ]/*XXX: same problem as url, too long :'(,
431 $parameters['description'] => [
432 'align' => 'right',
433 'canonical' => true,
434 'font' => 'labelleaurore',
435 'size' => 50
436 ]*/
437 ]
438 ];
439
440 /*With short path info
441 We don't add this stupid url in image !!!
442 if (strlen($pathInfo = $this->router->generate($this->route, $this->routeParams)) <= 64) {
443 => [
444 'align' => 'right',
445 'canonical' => true,
446 'font' => 'labelleaurore',
447 'size' => 50
448 ]
449 }*/
450 }
451
452 //With empty locations link
453 if (empty($parameters['locations_link'])) {
454 //Set locations link
455 $parameters['locations_link'] = $this->router->generate('rapsysair_location');
456 }
457
458 //With empty locations title
459 if (empty($parameters['locations_title'])) {
460 //Set locations title
461 $parameters['locations_title'] = $this->translator->trans('Locations', [], null, $this->locale);
462 }
463
464 //With canonical
465 if (!empty($parameters['canonical'])) {
466 //Set facebook url
467 $parameters['facebook']['og:url'] = $parameters['canonical'];
468 }
469
470 //With empty facebook title and title
471 if (empty($parameters['facebook']['og:title']) && !empty($parameters['title'])) {
472 //Set facebook title
473 $parameters['facebook']['og:title'] = $parameters['title'];
474 }
475
476 //With empty facebook description and description
477 if (empty($parameters['facebook']['og:description']) && !empty($parameters['description'])) {
478 //Set facebook description
479 $parameters['facebook']['og:description'] = $parameters['description'];
480 }
481
482 //With locale
483 if (!empty($this->locale)) {
484 //Set facebook locale
485 $parameters['facebook']['og:locale'] = $this->locale;
486
487 //With alternates
488 //XXX: locale change when fb_locale=xx_xx is provided is done in FacebookSubscriber
489 //XXX: see https://stackoverflow.com/questions/20827882/in-open-graph-markup-whats-the-use-of-oglocalealternate-without-the-locati
490 if (!empty($parameters['alternates'])) {
491 //Iterate on alternates
492 foreach($parameters['alternates'] as $lang => $alternate) {
493 if (strlen($lang) == 5) {
494 //Set facebook locale alternate
495 $parameters['facebook']['og:locale:alternate'] = str_replace('-', '_', $lang);
496 }
497 }
498 }
499 }
500
501 //Without facebook image defined and texts
502 if (empty($parameters['facebook']['og:image']) && !empty($this->request) && !empty($parameters['fbimage']['texts']) && !empty($this->modified)) {
503 //Get facebook image
504 $parameters['facebook'] += $this->facebook->getImage($this->request->getPathInfo(), $parameters['fbimage']['texts'], $this->modified->getTimestamp());
505 }
506
507 //Call twig render method
508 $content = $this->twig->render($view, $parameters);
509
510 //Invalidate OK response on invalid form
511 if (200 === $response->getStatusCode()) {
512 foreach ($parameters as $v) {
513 if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) {
514 $response->setStatusCode(422);
515 break;
516 }
517 }
518 }
519
520 //Store content in response
521 $response->setContent($content);
522
523 //Return response
524 return $response;
525 }
526
527 /**
528 * {@inheritdoc}
529 *
530 * @see vendor/symfony/framework-bundle/Controller/AbstractController.php
531 */
532 public static function getSubscribedServices(): array {
533 //Return subscribed services
534 return [
535 'doctrine' => ManagerRegistry::class,
536 'doctrine.orm.default_entity_manager' => EntityManagerInterface::class,
537 'form.factory' => FormFactoryInterface::class,
538 'mailer.mailer' => MailerInterface::class,
539 'rapsysair.facebook_util' => FacebookUtil::class,
540 'rapsyspack.image_util' => ImageUtil::class,
541 'rapsyspack.map_util' => MapUtil::class,
542 'rapsyspack.path_package' => PackageInterface::class,
543 'rapsyspack.slugger_util' => SluggerUtil::class,
544 'rapsysuser.access_decision_manager' => AccessDecisionManagerInterface::class,
545 'request_stack' => RequestStack::class,
546 'router' => RouterInterface::class,
547 'security.authorization_checker' => AuthorizationCheckerInterface::class,
548 'service_container' => ContainerInterface::class,
549 'translator' => TranslatorInterface::class
550 ];
551 }
552 }