From c7d84bc01ab357783cfc548fedba566ead9a8fbc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Fri, 10 Nov 2023 12:51:40 +0100 Subject: [PATCH] Add abstract controller --- Controller/AbstractController.php | 459 ++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 Controller/AbstractController.php diff --git a/Controller/AbstractController.php b/Controller/AbstractController.php new file mode 100644 index 0000000..0bf792a --- /dev/null +++ b/Controller/AbstractController.php @@ -0,0 +1,459 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Rapsys\BlogBundle\Controller; + +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Filesystem\Exception\IOExceptionInterface; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Twig\Environment; + +use Rapsys\BlogBundle\Entity\Dance; +use Rapsys\BlogBundle\Entity\Location; +use Rapsys\BlogBundle\Entity\Slot; +use Rapsys\BlogBundle\Entity\User; +use Rapsys\BlogBundle\RapsysBlogBundle; + +use Rapsys\PackBundle\Util\FacebookUtil; +use Rapsys\PackBundle\Util\ImageUtil; +use Rapsys\PackBundle\Util\SluggerUtil; + +/** + * Provides common features needed in controllers. + * + * {@inheritdoc} + */ +abstract class AbstractController extends BaseAbstractController implements ServiceSubscriberInterface { + ///AuthorizationCheckerInterface instance + protected AuthorizationCheckerInterface $checker; + + ///Config array + protected array $config; + + ///Context array + protected array $context; + + ///AccessDecisionManagerInterface instance + protected AccessDecisionManagerInterface $decision; + + ///ManagerRegistry instance + protected ManagerRegistry $doctrine; + + ///FacebookUtil instance + protected FacebookUtil $facebook; + + ///FormFactoryInterface instance + protected FormFactoryInterface $factory; + + ///Image util instance + protected ImageUtil $image; + + ///Limit integer + protected int $limit; + + ///Locale string + protected string $locale; + + ///MailerInterface instance + protected MailerInterface $mailer; + + ///EntityManagerInterface instance + protected EntityManagerInterface $manager; + + ///Modified DateTime + protected \DateTime $modified; + + ///PackageInterface instance + protected PackageInterface $package; + + ///Request instance + protected Request $request; + + ///Route string + protected string $route; + + ///Route params array + protected array $routeParams; + + ///Router instance + protected RouterInterface $router; + + ///Slugger util instance + protected SluggerUtil $slugger; + + ///RequestStack instance + protected RequestStack $stack; + + ///Translator instance + protected TranslatorInterface $translator; + + ///Twig\Environment instance + protected Environment $twig; + + /** + * Abstract constructor + * + * @param AuthorizationCheckerInterface $checker The container instance + * @param ContainerInterface $container The container instance + * @param AccessDecisionManagerInterface $decision The decision instance + * @param ManagerRegistry $doctrine The doctrine instance + * @param FacebookUtil $facebook The facebook instance + * @param FormFactoryInterface $factory The factory instance + * @param ImageUtil $image The image instance + * @param MailerInterface $mailer The mailer instance + * @param EntityManagerInterface $manager The manager instance + * @param PackageInterface $package The package instance + * @param RouterInterface $router The router instance + * @param SluggerUtil $slugger The slugger instance + * @param RequestStack $stack The stack instance + * @param TranslatorInterface $translator The translator instance + * @param Environment $twig The twig environment instance + * @param integer $limit The page limit + * + * @TODO move all that stuff to setSlugger('@slugger') setters with a calls: [ setSlugger: [ '@slugger' ] ] to unbload classes ??? + * @TODO add a calls: [ ..., prepare: ['@???'] ] that do all the logic that can't be done in constructor because various things are not available + */ + 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) { + //Set checker + $this->checker = $checker; + + //Retrieve config + $this->config = $container->getParameter(RapsysBlogBundle::getAlias()); + + //Set the container + $this->container = $container; + + //Set decision + $this->decision = $decision; + + //Set doctrine + $this->doctrine = $doctrine; + + //Set facebook + $this->facebook = $facebook; + + //Set factory + $this->factory = $factory; + + //Set image + $this->image = $image; + + //Set limit + $this->limit = $limit; + + //Set mailer + $this->mailer = $mailer; + + //Set manager + $this->manager = $manager; + + //Set package + $this->package = $package; + + //Set router + $this->router = $router; + + //Set slugger + $this->slugger = $slugger; + + //Set stack + $this->stack = $stack; + + //Set translator + $this->translator = $translator; + + //Set twig + $this->twig = $twig; + + //Get main request + $this->request = $this->stack->getMainRequest(); + + //Get current locale + $this->locale = $this->request->getLocale(); + + //Set canonical + $canonical = null; + + //Set alternates + $alternates = []; + + //Set route + //TODO: default to not found route ??? + //TODO: when url not found, this attribute is not defined, how do we handle it ??? + //XXX: do we generate a dummy default route by default ??? + $this->route = $this->request->attributes->get('_route'); + + //Set route params + $this->routeParams = $this->request->attributes->get('_route_params'); + + //With route and routeParams + if ($this->route !== null && $this->routeParams !== null) { + //Set canonical + $canonical = $this->router->generate($this->route, $this->routeParams, UrlGeneratorInterface::ABSOLUTE_URL); + + //Set alternates + $alternates = []; + } + + //Set the context + $this->context = [ + //TODO: review the structure + #'title' => $this->translator->trans($this->config['title']), + #'og:site_name' => $this->translator->trans($this->config['title']), + #'site' => [ + # 'donate' => $this->config['donate'], + # 'title' => $title = $this->translator->trans($this->config['site']['title']), + #], + 'head' => [ + 'alternates' => $alternates, + 'canonical' => $canonical, + 'icon' => $this->config['icon'], + 'keywords' => null, + 'locale' => str_replace('_', '-', $this->locale), + 'logo' => [ + 'png' => $this->config['logo']['png'], + 'svg' => $this->config['logo']['svg'], + 'alt' => $this->translator->trans($this->config['logo']['alt']) + ], + 'root' => $this->config['root'], + 'title' => null, + 'facebook' => [ + 'og:type' => 'article', + 'og:site_name' => $this->translator->trans($this->config['title']), + 'og:url' => $canonical, + //TODO: review this value + 'fb:app_id' => $this->config['facebook']['apps'] + ], + 'fbimage' => [ + 'texts' => [ + $this->translator->trans($this->config['title']) => [ + 'font' => 'irishgrover', + 'size' => 110 + ] + ] + ] + ], + 'contact' => [ + 'name' => $this->translator->trans($this->config['contact']['name']), + 'mail' => $this->config['contact']['mail'] + ], + 'copy' => [ + 'by' => $this->translator->trans($this->config['copy']['by']), + 'link' => $this->config['copy']['link'], + 'long' => $this->translator->trans($this->config['copy']['long']), + 'short' => $this->translator->trans($this->config['copy']['short']), + 'title' => $this->config['copy']['title'] + ], + 'forms' => [], + 'title' => null, + 'description' => null, + 'section' => null, + 'site' => [ + 'icon' => $this->config['icon'], + 'logo' => [ + 'png' => $this->config['logo']['png'], + 'svg' => $this->config['logo']['svg'], + 'alt' => $this->translator->trans($this->config['logo']['alt']) + ], + 'path' => $this->config['path'], + 'root' => $this->config['root'], + 'title' => $this->translator->trans($this->config['title']) + ] + ]; + } + + /** + * Renders a view + * + * {@inheritdoc} + */ + protected function render(string $view, array $parameters = [], Response $response = null): Response { + //Create response when null + $response ??= new Response(); + + //Without alternates + if (empty($parameters['head']['alternates'])) { + //Iterate on locales excluding current one + foreach($this->config['locales'] as $locale) { + //Set routeParams + $routeParams = $this->routeParams; + + //With current locale + if ($locale !== $this->locale) { + //Set titles + $titles = []; + + //Set route params locale + $routeParams['_locale'] = $locale; + + //Unset slug + //XXX: slug is in current locale, better use a simple redirect for invalid slug than implement a hard to get translation here + unset($routeParams['slug']); + + //Iterate on other locales + foreach(array_diff($this->config['locales'], [$locale]) as $other) { + //Set other locale title + $titles[$other] = $this->translator->trans($this->config['languages'][$locale], [], null, $other); + } + + //Set locale locales context + $parameters['head']['alternates'][str_replace('_', '-', $locale)] = [ + 'absolute' => $this->router->generate($this->route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL), + 'relative' => $this->router->generate($this->route, $routeParams), + 'title' => implode('/', $titles), + 'translated' => $this->translator->trans($this->config['languages'][$locale], [], null, $locale) + ]; + + //Add shorter locale + if (empty($parameters['head']['alternates'][$shortCurrent = substr($locale, 0, 2)])) { + //Set locale locales context + $parameters['head']['alternates'][$shortCurrent] = $parameters['head']['alternates'][str_replace('_', '-', $locale)]; + } + //Add shorter locale + } elseif (empty($parameters['head']['alternates'][$shortCurrent = substr($locale, 0, 2)])) { + //Set titles + $titles = []; + + //Set route params locale + $routeParams['_locale'] = $locale; + + //Iterate on other locales + foreach(array_diff($this->config['locales'], [$locale]) as $other) { + //Set other locale title + $titles[$other] = $this->translator->trans($this->config['languages'][$locale], [], null, $other); + } + + //Set locale locales context + $parameters['head']['alternates'][$shortCurrent] = [ + 'absolute' => $this->router->generate($this->route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL), + 'relative' => $this->router->generate($this->route, $routeParams), + 'title' => implode('/', $titles), + 'translated' => $this->translator->trans($this->config['languages'][$locale], [], null, $locale) + ]; + } + } + } + + //With empty head title and section + if (empty($parameters['head']['title']) && !empty($parameters['section'])) { + //Set head title + $parameters['head']['title'] = implode(' - ', [$parameters['title'], $parameters['section'], $this->translator->trans($this->config['title'])]); + //With empty head title + } elseif (empty($parameters['head']['title'])) { + //Set head title + $parameters['head']['title'] = implode(' - ', [$parameters['title'], $this->translator->trans($this->config['title'])]); + } + + //With empty head description and description + if (empty($parameters['head']['description']) && !empty($parameters['description'])) { + //Set head description + $parameters['head']['description'] = $parameters['description']; + } + + //With empty facebook title and title + if (empty($parameters['head']['facebook']['og:title']) && !empty($parameters['title'])) { + //Set facebook title + $parameters['head']['facebook']['og:title'] = $parameters['title']; + } + + //With empty facebook description and description + if (empty($parameters['head']['facebook']['og:description']) && !empty($parameters['description'])) { + //Set facebook description + $parameters['head']['facebook']['og:description'] = $parameters['description']; + } + + //With locale + if (!empty($this->locale)) { + //Set facebook locale + $parameters['head']['facebook']['og:locale'] = $this->locale; + + //With alternates + //XXX: locale change when fb_locale=xx_xx is provided is done in FacebookSubscriber + //XXX: see https://stackoverflow.com/questions/20827882/in-open-graph-markup-whats-the-use-of-oglocalealternate-without-the-locati + if (!empty($parameters['head']['alternates'])) { + //Iterate on alternates + foreach($parameters['head']['alternates'] as $lang => $alternate) { + if (strlen($lang) == 5) { + //Set facebook locale alternate + $parameters['head']['facebook']['og:locale:alternate'] = str_replace('-', '_', $lang); + } + } + } + } + + //Without facebook image defined and texts + if (empty($parameters['head']['facebook']['og:image']) && !empty($this->request) && !empty($parameters['head']['fbimage']['texts']) && !empty($this->modified)) { + //Get facebook image + $parameters['head']['facebook'] += $this->facebook->getImage($this->request->getPathInfo(), $parameters['head']['fbimage']['texts'], $this->modified->getTimestamp()); + } + + //Call twig render method + $content = $this->twig->render($view, $parameters); + + //Invalidate OK response on invalid form + if (200 === $response->getStatusCode()) { + foreach ($parameters as $v) { + if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + break; + } + } + } + + //Store content in response + $response->setContent($content); + + //Return response + return $response; + } + + /** + * {@inheritdoc} + * + * @see vendor/symfony/framework-bundle/Controller/AbstractController.php + */ + public static function getSubscribedServices(): array { + //Return subscribed services + return [ + 'security.authorization_checker' => AuthorizationCheckerInterface::class, + 'service_container' => ContainerInterface::class, + 'rapsys_user.access_decision_manager' => AccessDecisionManagerInterface::class, + 'doctrine' => ManagerRegistry::class, + 'rapsys_pack.facebook_util' => FacebookUtil::class, + 'form.factory' => FormFactoryInterface::class, + 'rapsys_pack.image_util' => ImageUtil::class, + 'mailer.mailer' => MailerInterface::class, + 'doctrine.orm.default_entity_manager' => EntityManagerInterface::class, + 'rapsys_pack.path_package' => PackageInterface::class, + 'router' => RouterInterface::class, + 'rapsys_pack.slugger_util' => SluggerUtil::class, + 'request_stack' => RequestStack::class, + 'translator' => TranslatorInterface::class, + 'twig' => Environment::class, + ]; + } +} -- 2.41.1