1 <?php
declare(strict_types
=1);
4 * This file is part of the Rapsys BlogBundle package.
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Rapsys\BlogBundle\Controller
;
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
;
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
;
43 use Rapsys\PackBundle\Util\FacebookUtil
;
44 use Rapsys\PackBundle\Util\ImageUtil
;
45 use Rapsys\PackBundle\Util\SluggerUtil
;
48 * Provides common features needed in controllers.
52 abstract class AbstractController
extends BaseAbstractController
implements ServiceSubscriberInterface
{
56 protected array $config;
66 protected array $context;
76 protected string $locale;
81 protected \DateTime
$modified;
91 protected string $route;
96 protected array $routeParams;
99 * AuthorizationCheckerInterface instance
101 protected AuthorizationCheckerInterface
$checker;
104 * AccessDecisionManagerInterface instance
106 protected AccessDecisionManagerInterface
$decision;
109 * ManagerRegistry instance
111 protected ManagerRegistry
$doctrine;
114 * FacebookUtil instance
116 protected FacebookUtil
$facebook;
119 * FormFactoryInterface instance
121 protected FormFactoryInterface
$factory;
124 * Image util instance
126 protected ImageUtil
$image;
129 * MailerInterface instance
131 protected MailerInterface
$mailer;
134 * EntityManagerInterface instance
136 protected EntityManagerInterface
$manager;
139 * PackageInterface instance
141 protected PackageInterface
$package;
146 protected Request
$request;
151 protected RouterInterface
$router;
154 * Slugger util instance
156 protected SluggerUtil
$slugger;
161 protected Security
$security;
164 * RequestStack instance
166 protected RequestStack
$stack;
169 * Translator instance
171 protected TranslatorInterface
$translator;
174 * Twig\Environment instance
176 protected Environment
$twig;
179 * Abstract constructor
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
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
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) {
204 $this->checker
= $checker;
207 $this->config
= $container->getParameter(RapsysBlogBundle
::getAlias());
210 $this->container
= $container;
213 $this->decision
= $decision;
216 $this->doctrine
= $doctrine;
219 $this->facebook
= $facebook;
222 $this->factory
= $factory;
225 $this->image
= $image;
228 $this->limit
= $limit;
231 $this->mailer
= $mailer;
234 $this->manager
= $manager;
237 $this->package
= $package;
240 $this->router
= $router;
243 $this->slugger
= $slugger;
246 $this->security
= $security;
249 $this->stack
= $stack;
252 $this->translator
= $translator;
258 $this->request
= $this->stack
->getCurrentRequest();
261 $this->locale
= $this->request
->getLocale();
270 $this->page
= (int) $this->request
->query
->get('page');
273 if ($this->page
< 0) {
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');
284 $this->routeParams
= $this->request
->attributes
->get('_route_params');
286 //With route and routeParams
287 if ($this->route
!== null && $this->routeParams
!== null) {
289 $canonical = $this->router
->generate($this->route
, $this->routeParams
, UrlGeneratorInterface
::ABSOLUTE_URL
);
297 'alternates' => $alternates,
298 'canonical' => $canonical,
300 'address' => $this->config
['contact']['address'],
301 'name' => $this->translator
->trans($this->config
['contact']['name'])
304 'by' => $this->translator
->trans($this->config
['copy']['by']),
305 'link' => $this->config
['copy']['link'],
306 'long' => $this->translator
->trans($this->config
['copy']['long']),
307 'short' => $this->translator
->trans($this->config
['copy']['short']),
308 'title' => $this->config
['copy']['title']
310 'description' => null,
311 'donate' => $this->config
['donate'],
313 'og:type' => 'article',
314 'og:site_name' => $title = $this->translator
->trans($this->config
['title']),
315 'og:url' => $canonical,
316 #'fb:admins' => $this->config['facebook']['admins'],
317 'fb:app_id' => $this->config
['facebook']['apps']
319 //XXX: TODO: only generate it when fb robot request the url ???
323 //'font' => 'irishgrover',
329 'icon' => $this->config
['icon'],
331 'locale' => str_replace('_', '-', $this->locale
),
333 # 'png' => $this->config['logo']['png'],
334 # 'svg' => $this->config['logo']['svg'],
335 # 'alt' => $this->translator->trans($this->config['logo']['alt'])
337 'logo' => $this->config
['logo'],
340 'root' => $this->config
['root'],
354 protected function render(string $view, array $parameters = [], Response
$response = null): Response
{
355 //Create response when null
356 $response ??= new Response();
359 if (count($parameters['alternates']) <= 1) {
360 //Iterate on locales excluding current one
361 foreach($this->config
['locales'] as $locale) {
363 $routeParams = $this->routeParams
;
365 //With current locale
366 if ($locale !== $this->locale
) {
370 //Set route params locale
371 $routeParams['_locale'] = $locale;
374 //XXX: slug is in current locale, better use a simple redirect for invalid slug than implement a hard to get translation here
375 unset($routeParams['slug']);
377 //Iterate on other locales
378 foreach(array_diff($this->config
['locales'], [$locale]) as $other) {
379 //Set other locale title
380 $titles[$other] = $this->translator
->trans($this->config
['languages'][$locale], [], null, $other);
383 //Set locale locales context
384 $parameters['alternates'][str_replace('_', '-', $locale)] = [
385 'absolute' => $this->router
->generate($this->route
, $routeParams, UrlGeneratorInterface
::ABSOLUTE_URL
),
386 'relative' => $this->router
->generate($this->route
, $routeParams),
387 'title' => implode('/', $titles),
388 'translated' => $this->translator
->trans($this->config
['languages'][$locale], [], null, $locale)
392 if (empty($parameters['alternates'][$shortCurrent = substr($locale, 0, 2)])) {
393 //Set locale locales context
394 $parameters['alternates'][$shortCurrent] = $parameters['alternates'][str_replace('_', '-', $locale)];
397 } elseif (empty($parameters['alternates'][$shortCurrent = substr($locale, 0, 2)])) {
401 //Set route params locale
402 $routeParams['_locale'] = $locale;
404 //Iterate on other locales
405 foreach(array_diff($this->config
['locales'], [$locale]) as $other) {
406 //Set other locale title
407 $titles[$other] = $this->translator
->trans($this->config
['languages'][$locale], [], null, $other);
410 //Set locale locales context
411 $parameters['alternates'][$shortCurrent] = [
412 'absolute' => $this->router
->generate($this->route
, $routeParams, UrlGeneratorInterface
::ABSOLUTE_URL
),
413 'relative' => $this->router
->generate($this->route
, $routeParams),
414 'title' => implode('/', $titles),
415 'translated' => $this->translator
->trans($this->config
['languages'][$locale], [], null, $locale)
422 if (!empty($parameters['canonical'])) {
424 $parameters['facebook']['og:url'] = $parameters['canonical'];
427 //With empty facebook title and title
428 if (empty($parameters['facebook']['og:title']) && !empty($parameters['title']['page'])) {
430 $parameters['facebook']['og:title'] = $parameters['title']['page'];
433 //With empty facebook description and description
434 if (empty($parameters['facebook']['og:description']) && !empty($parameters['description'])) {
435 //Set facebook description
436 $parameters['facebook']['og:description'] = $parameters['description'];
440 if (!empty($this->locale
)) {
441 //Set facebook locale
442 $parameters['facebook']['og:locale'] = $this->locale
;
445 //XXX: locale change when fb_locale=xx_xx is provided is done in FacebookSubscriber
446 //XXX: see https://stackoverflow.com/questions/20827882/in-open-graph-markup-whats-the-use-of-oglocalealternate-without-the-locati
447 if (!empty($parameters['alternates'])) {
448 //Iterate on alternates
449 foreach($parameters['alternates'] as $lang => $alternate) {
450 if (strlen($lang) == 5) {
451 //Set facebook locale alternate
452 $parameters['facebook']['og:locale:alternate'] = str_replace('-', '_', $lang);
458 //Without facebook image defined and texts
459 if (empty($parameters['facebook']['og:image']) && !empty($this->request
) && !empty($parameters['fbimage']['texts']) && !empty($this->modified
)) {
461 //XXX: decode getPathInfo (see https://github.com/symfony/symfony/issues/2579)
462 $parameters['facebook'] +
= $this->image
->getFacebook(urldecode($this->request
->getPathInfo()), $parameters['fbimage']['texts'], $this->modified
->getTimestamp());
466 if (!empty($this->count
)) {
468 if ($this->page
> 0) {
470 $parameters['prev'] = $this->generateUrl($this->request
->get('_route'), ['page' => $this->page
- 1]+
$this->request
->get('_route_params'));
474 if ($this->count
> ($this->page +
1) * $this->limit
) {
476 $parameters['next'] = $this->generateUrl($this->request
->get('_route'), ['page' => $this->page +
1]+
$this->request
->get('_route_params'));
480 //Call twig render method
481 $content = $this->twig
->render($view, $parameters);
483 //Invalidate OK response on invalid form
484 if (200 === $response->getStatusCode()) {
485 foreach ($parameters as $v) {
486 if ($v instanceof FormInterface
&& $v->isSubmitted() && !$v->isValid()) {
487 $response->setStatusCode(422);
493 //Store content in response
494 $response->setContent($content);
503 * @see vendor/symfony/framework-bundle/Controller/AbstractController.php
505 public static function getSubscribedServices(): array {
506 //Return subscribed services
508 'security.authorization_checker' => AuthorizationCheckerInterface
::class,
509 'service_container' => ContainerInterface
::class,
510 'rapsys_user.access_decision_manager' => AccessDecisionManagerInterface
::class,
511 'doctrine' => ManagerRegistry
::class,
512 'rapsys_pack.facebook_util' => FacebookUtil
::class,
513 'form.factory' => FormFactoryInterface
::class,
514 'rapsys_pack.image_util' => ImageUtil
::class,
515 'mailer.mailer' => MailerInterface
::class,
516 'doctrine.orm.default_entity_manager' => EntityManagerInterface
::class,
517 'rapsys_pack.path_package' => PackageInterface
::class,
518 'router' => RouterInterface
::class,
519 'rapsys_pack.slugger_util' => SluggerUtil
::class,
520 'security' => Security
::class,
521 'stack' => RequestStack
::class,
522 'translator' => TranslatorInterface
::class,
523 'twig' => Environment
::class,