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