]> Raphaël G. Git Repositories - blogbundle/blob - Controller/AbstractController.php
New structure
[blogbundle] / Controller / AbstractController.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys BlogBundle 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\BlogBundle\Controller;
13
14 use Doctrine\ORM\EntityManagerInterface;
15 use Doctrine\Persistence\ManagerRegistry;
16 use Psr\Log\LoggerInterface;
17 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
18 use Symfony\Bundle\SecurityBundle\Security;
19 use Symfony\Component\Asset\PackageInterface;
20 use Symfony\Component\DependencyInjection\ContainerInterface;
21 use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
22 use Symfony\Component\Filesystem\Filesystem;
23 use Symfony\Component\Form\FormFactoryInterface;
24 use Symfony\Component\HttpFoundation\Request;
25 use Symfony\Component\HttpFoundation\RequestStack;
26 use Symfony\Component\HttpFoundation\Response;
27 use Symfony\Component\Mailer\MailerInterface;
28 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
29 use Symfony\Component\Routing\RouterInterface;
30 use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
31 use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
32 use Symfony\Component\Security\Core\User\UserInterface;
33 use Symfony\Contracts\Service\ServiceSubscriberInterface;
34 use Symfony\Contracts\Translation\TranslatorInterface;
35 use Twig\Environment;
36
37 use Rapsys\BlogBundle\Entity\Dance;
38 use Rapsys\BlogBundle\Entity\Location;
39 use Rapsys\BlogBundle\Entity\Slot;
40 use Rapsys\BlogBundle\Entity\User;
41 use Rapsys\BlogBundle\RapsysBlogBundle;
42
43 use Rapsys\PackBundle\Util\FacebookUtil;
44 use Rapsys\PackBundle\Util\ImageUtil;
45 use Rapsys\PackBundle\Util\SluggerUtil;
46
47 /**
48 * Provides common features needed in controllers.
49 *
50 * {@inheritdoc}
51 */
52 abstract class AbstractController extends BaseAbstractController implements ServiceSubscriberInterface {
53 /**
54 * Config array
55 */
56 protected array $config;
57
58 /**
59 * Context array
60 */
61 protected array $context;
62
63 /**
64 * Limit integer
65 */
66 protected int $limit;
67
68 /**
69 * Locale string
70 */
71 protected string $locale;
72
73 /**
74 * Modified DateTime
75 */
76 protected \DateTime $modified;
77
78 /**
79 * Limit integer
80 */
81 protected int $page;
82
83 /**
84 * Route string
85 */
86 protected string $route;
87
88 /**
89 * Route params array
90 */
91 protected array $routeParams;
92
93 /**
94 * AuthorizationCheckerInterface instance
95 */
96 protected AuthorizationCheckerInterface $checker;
97
98 /**
99 * AccessDecisionManagerInterface instance
100 */
101 protected AccessDecisionManagerInterface $decision;
102
103 /**
104 * ManagerRegistry instance
105 */
106 protected ManagerRegistry $doctrine;
107
108 /**
109 * FacebookUtil instance
110 */
111 protected FacebookUtil $facebook;
112
113 /**
114 * FormFactoryInterface instance
115 */
116 protected FormFactoryInterface $factory;
117
118 /**
119 * Image util instance
120 */
121 protected ImageUtil $image;
122
123 /**
124 * MailerInterface instance
125 */
126 protected MailerInterface $mailer;
127
128 /**
129 * EntityManagerInterface instance
130 */
131 protected EntityManagerInterface $manager;
132
133 /**
134 * PackageInterface instance
135 */
136 protected PackageInterface $package;
137
138 /**
139 * Request instance
140 */
141 protected Request $request;
142
143 /**
144 * Router instance
145 */
146 protected RouterInterface $router;
147
148 /**
149 * Slugger util instance
150 */
151 protected SluggerUtil $slugger;
152
153 /**
154 * Security instance
155 */
156 protected Security $security;
157
158 /**
159 * RequestStack instance
160 */
161 protected RequestStack $stack;
162
163 /**
164 * Translator instance
165 */
166 protected TranslatorInterface $translator;
167
168 /**
169 * Twig\Environment instance
170 */
171 protected Environment $twig;
172
173 /**
174 * Abstract constructor
175 *
176 * @param AuthorizationCheckerInterface $checker The container instance
177 * @param ContainerInterface $container The container instance
178 * @param AccessDecisionManagerInterface $decision The decision instance
179 * @param ManagerRegistry $doctrine The doctrine instance
180 * @param FacebookUtil $facebook The facebook instance
181 * @param FormFactoryInterface $factory The factory instance
182 * @param ImageUtil $image The image instance
183 * @param MailerInterface $mailer The mailer instance
184 * @param EntityManagerInterface $manager The manager instance
185 * @param PackageInterface $package The package instance
186 * @param RouterInterface $router The router instance
187 * @param SluggerUtil $slugger The slugger instance
188 * @param Security $security The security instance
189 * @param RequestStack $stack The stack instance
190 * @param TranslatorInterface $translator The translator instance
191 * @param Environment $twig The twig environment instance
192 * @param integer $limit The page limit
193 *
194 * @TODO move all that stuff to setSlugger('@slugger') setters with a calls: [ setSlugger: [ '@slugger' ] ] to unbload classes ???
195 * @TODO add a calls: [ ..., prepare: ['@???'] ] that do all the logic that can't be done in constructor because various things are not available
196 */
197 public function __construct(AuthorizationCheckerInterface $checker, ContainerInterface $container, AccessDecisionManagerInterface $decision, ManagerRegistry $doctrine, FacebookUtil $facebook, FormFactoryInterface $factory, ImageUtil $image, MailerInterface $mailer, EntityManagerInterface $manager, PackageInterface $package, RouterInterface $router, SluggerUtil $slugger, Security $security, RequestStack $stack, TranslatorInterface $translator, Environment $twig, int $limit = 5) {
198 //Set checker
199 $this->checker = $checker;
200
201 //Retrieve config
202 $this->config = $container->getParameter(RapsysBlogBundle::getAlias());
203
204 //Set the container
205 $this->container = $container;
206
207 //Set decision
208 $this->decision = $decision;
209
210 //Set doctrine
211 $this->doctrine = $doctrine;
212
213 //Set facebook
214 $this->facebook = $facebook;
215
216 //Set factory
217 $this->factory = $factory;
218
219 //Set image
220 $this->image = $image;
221
222 //Set limit
223 $this->limit = $limit;
224
225 //Set mailer
226 $this->mailer = $mailer;
227
228 //Set manager
229 $this->manager = $manager;
230
231 //Set package
232 $this->package = $package;
233
234 //Set router
235 $this->router = $router;
236
237 //Set slugger
238 $this->slugger = $slugger;
239
240 //Set security
241 $this->security = $security;
242
243 //Set stack
244 $this->stack = $stack;
245
246 //Set translator
247 $this->translator = $translator;
248
249 //Set twig
250 $this->twig = $twig;
251
252 //Get main request
253 $this->request = $this->stack->getCurrentRequest();
254
255 //Get current locale
256 $this->locale = $this->request->getLocale();
257
258 //Set canonical
259 $canonical = null;
260
261 //Set alternates
262 $alternates = [];
263
264 //Get current page
265 $this->page = (int) $this->request->query->get('page');
266
267 //With negative page
268 if ($this->page < 0) {
269 $this->page = 0;
270 }
271
272 //Set route
273 //TODO: default to not found route ???
274 //TODO: when url not found, this attribute is not defined, how do we handle it ???
275 //XXX: do we generate a dummy default route by default ???
276 $this->route = $this->request->attributes->get('_route');
277
278 //Set route params
279 $this->routeParams = $this->request->attributes->get('_route_params');
280
281 //With route and routeParams
282 if ($this->route !== null && $this->routeParams !== null) {
283 //Set canonical
284 $canonical = $this->router->generate($this->route, $this->routeParams, UrlGeneratorInterface::ABSOLUTE_URL);
285
286 //Set alternates
287 $alternates = [];
288 }
289
290 //Set the context
291 $this->context = [
292 'head' => [
293 'alternates' => $alternates,
294 'canonical' => $canonical,
295 'icon' => $this->config['icon'],
296 'keywords' => null,
297 'locale' => str_replace('_', '-', $this->locale),
298 'logo' => [
299 'png' => $this->config['logo']['png'],
300 'svg' => $this->config['logo']['svg'],
301 'alt' => $this->translator->trans($this->config['logo']['alt'])
302 ],
303 'root' => $this->config['root'],
304 'site' => $this->translator->trans($this->config['title']),
305 'title' => null,
306 'facebook' => [
307 'og:type' => 'article',
308 'og:site_name' => $this->translator->trans($this->config['title']),
309 'og:url' => $canonical,
310 //TODO: review this value
311 'fb:app_id' => $this->config['facebook']['apps']
312 ],
313 'fbimage' => [
314 'texts' => [
315 $this->translator->trans($this->config['title']) => [
316 'font' => 'irishgrover',
317 'size' => 110
318 ]
319 ]
320 ]
321 ],
322 'contact' => [
323 'address' => $this->config['contact']['address'],
324 'name' => $this->translator->trans($this->config['contact']['name'])
325 ],
326 'copy' => [
327 'by' => $this->translator->trans($this->config['copy']['by']),
328 'link' => $this->config['copy']['link'],
329 'long' => $this->translator->trans($this->config['copy']['long']),
330 'short' => $this->translator->trans($this->config['copy']['short']),
331 'title' => $this->config['copy']['title']
332 ],
333 'forms' => [],
334 'description' => null,
335 'section' => null,
336 'title' => null
337 ];
338 }
339
340 /**
341 * Renders a view
342 *
343 * {@inheritdoc}
344 */
345 protected function render(string $view, array $parameters = [], Response $response = null): Response {
346 //Create response when null
347 $response ??= new Response();
348
349 //Without alternates
350 if (empty($parameters['head']['alternates'])) {
351 //Iterate on locales excluding current one
352 foreach($this->config['locales'] as $locale) {
353 //Set routeParams
354 $routeParams = $this->routeParams;
355
356 //With current locale
357 if ($locale !== $this->locale) {
358 //Set titles
359 $titles = [];
360
361 //Set route params locale
362 $routeParams['_locale'] = $locale;
363
364 //Unset slug
365 //XXX: slug is in current locale, better use a simple redirect for invalid slug than implement a hard to get translation here
366 unset($routeParams['slug']);
367
368 //Iterate on other locales
369 foreach(array_diff($this->config['locales'], [$locale]) as $other) {
370 //Set other locale title
371 $titles[$other] = $this->translator->trans($this->config['languages'][$locale], [], null, $other);
372 }
373
374 //Set locale locales context
375 $parameters['head']['alternates'][str_replace('_', '-', $locale)] = [
376 'absolute' => $this->router->generate($this->route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL),
377 'relative' => $this->router->generate($this->route, $routeParams),
378 'title' => implode('/', $titles),
379 'translated' => $this->translator->trans($this->config['languages'][$locale], [], null, $locale)
380 ];
381
382 //Add shorter locale
383 if (empty($parameters['head']['alternates'][$shortCurrent = substr($locale, 0, 2)])) {
384 //Set locale locales context
385 $parameters['head']['alternates'][$shortCurrent] = $parameters['head']['alternates'][str_replace('_', '-', $locale)];
386 }
387 //Add shorter locale
388 } elseif (empty($parameters['head']['alternates'][$shortCurrent = substr($locale, 0, 2)])) {
389 //Set titles
390 $titles = [];
391
392 //Set route params locale
393 $routeParams['_locale'] = $locale;
394
395 //Iterate on other locales
396 foreach(array_diff($this->config['locales'], [$locale]) as $other) {
397 //Set other locale title
398 $titles[$other] = $this->translator->trans($this->config['languages'][$locale], [], null, $other);
399 }
400
401 //Set locale locales context
402 $parameters['head']['alternates'][$shortCurrent] = [
403 'absolute' => $this->router->generate($this->route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL),
404 'relative' => $this->router->generate($this->route, $routeParams),
405 'title' => implode('/', $titles),
406 'translated' => $this->translator->trans($this->config['languages'][$locale], [], null, $locale)
407 ];
408 }
409 }
410 }
411
412 //With empty head title and section
413 if (empty($parameters['head']['title']) && !empty($parameters['section'])) {
414 //Set head title
415 $parameters['head']['title'] = implode(' - ', [$parameters['title'], $parameters['section'], $parameters['head']['site']]);
416 //With empty head title
417 } elseif (empty($parameters['head']['title'])) {
418 //Set head title
419 $parameters['head']['title'] = implode(' - ', [$parameters['title'], $parameters['head']['site']]);
420 }
421
422 //With empty head description and description
423 if (empty($parameters['head']['description']) && !empty($parameters['description'])) {
424 //Set head description
425 $parameters['head']['description'] = $parameters['description'];
426 }
427
428 //With empty facebook title and title
429 if (empty($parameters['head']['facebook']['og:title']) && !empty($parameters['title'])) {
430 //Set facebook title
431 $parameters['head']['facebook']['og:title'] = $parameters['title'];
432 }
433
434 //With empty facebook description and description
435 if (empty($parameters['head']['facebook']['og:description']) && !empty($parameters['description'])) {
436 //Set facebook description
437 $parameters['head']['facebook']['og:description'] = $parameters['description'];
438 }
439
440 //With locale
441 if (!empty($this->locale)) {
442 //Set facebook locale
443 $parameters['head']['facebook']['og:locale'] = $this->locale;
444
445 //With alternates
446 //XXX: locale change when fb_locale=xx_xx is provided is done in FacebookSubscriber
447 //XXX: see https://stackoverflow.com/questions/20827882/in-open-graph-markup-whats-the-use-of-oglocalealternate-without-the-locati
448 if (!empty($parameters['head']['alternates'])) {
449 //Iterate on alternates
450 foreach($parameters['head']['alternates'] as $lang => $alternate) {
451 if (strlen($lang) == 5) {
452 //Set facebook locale alternate
453 $parameters['head']['facebook']['og:locale:alternate'] = str_replace('-', '_', $lang);
454 }
455 }
456 }
457 }
458
459 //Without facebook image defined and texts
460 if (empty($parameters['head']['facebook']['og:image']) && !empty($this->request) && !empty($parameters['head']['fbimage']['texts']) && !empty($this->modified)) {
461 //Get facebook image
462 $parameters['head']['facebook'] += $this->facebook->getImage($this->request->getPathInfo(), $parameters['head']['fbimage']['texts'], $this->modified->getTimestamp());
463 }
464
465 //Call twig render method
466 $content = $this->twig->render($view, $parameters);
467
468 //Invalidate OK response on invalid form
469 if (200 === $response->getStatusCode()) {
470 foreach ($parameters as $v) {
471 if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) {
472 $response->setStatusCode(422);
473 break;
474 }
475 }
476 }
477
478 //Store content in response
479 $response->setContent($content);
480
481 //Return response
482 return $response;
483 }
484
485 /**
486 * {@inheritdoc}
487 *
488 * @see vendor/symfony/framework-bundle/Controller/AbstractController.php
489 */
490 public static function getSubscribedServices(): array {
491 //Return subscribed services
492 return [
493 'security.authorization_checker' => AuthorizationCheckerInterface::class,
494 'service_container' => ContainerInterface::class,
495 'rapsys_user.access_decision_manager' => AccessDecisionManagerInterface::class,
496 'doctrine' => ManagerRegistry::class,
497 'rapsys_pack.facebook_util' => FacebookUtil::class,
498 'form.factory' => FormFactoryInterface::class,
499 'rapsys_pack.image_util' => ImageUtil::class,
500 'mailer.mailer' => MailerInterface::class,
501 'doctrine.orm.default_entity_manager' => EntityManagerInterface::class,
502 'rapsys_pack.path_package' => PackageInterface::class,
503 'router' => RouterInterface::class,
504 'rapsys_pack.slugger_util' => SluggerUtil::class,
505 'security' => Security::class,
506 'stack' => RequestStack::class,
507 'translator' => TranslatorInterface::class,
508 'twig' => Environment::class,
509 ];
510 }
511 }