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