vendor/netzmacht/contao-toolkit/src/Controller/AbstractFragmentController.php line 110

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Netzmacht\Contao\Toolkit\Controller;
  4. use Contao\CoreBundle\Fragment\FragmentOptionsAwareInterface;
  5. use Contao\Model;
  6. use Contao\StringUtil;
  7. use Netzmacht\Contao\Toolkit\Response\ResponseTagger;
  8. use Netzmacht\Contao\Toolkit\Routing\RequestScopeMatcher;
  9. use Netzmacht\Contao\Toolkit\View\Template\TemplateRenderer;
  10. use Symfony\Component\DependencyInjection\Container;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use function array_pad;
  14. use function array_unshift;
  15. use function implode;
  16. use function is_array;
  17. use function ltrim;
  18. use function sprintf;
  19. use function strpos;
  20. use function strrchr;
  21. use function substr;
  22. use function trim;
  23. /**
  24.  * This class a the base class for the base fragment controller provided by the Toolkit.
  25.  *
  26.  * @template TModel of Model
  27.  */
  28. abstract class AbstractFragmentController implements FragmentOptionsAwareInterface
  29. {
  30.     /**
  31.      * Template renderer.
  32.      *
  33.      * @var TemplateRenderer
  34.      */
  35.     private $templateRenderer;
  36.     /**
  37.      * Request scope matcher.
  38.      *
  39.      * @var RequestScopeMatcher
  40.      */
  41.     protected $scopeMatcher;
  42.     /**
  43.      * The http request response tagger.
  44.      *
  45.      * @var ResponseTagger
  46.      */
  47.     private $responseTagger;
  48.     /**
  49.      * Fragment options.
  50.      *
  51.      * @var array<string,mixed>
  52.      */
  53.     protected $options = [];
  54.     /**
  55.      * @param TemplateRenderer    $templateRenderer The template renderer.
  56.      * @param RequestScopeMatcher $scopeMatcher     The scope matcher.
  57.      * @param ResponseTagger      $responseTagger   The http request response tagger.
  58.      */
  59.     public function __construct(
  60.         TemplateRenderer $templateRenderer,
  61.         RequestScopeMatcher $scopeMatcher,
  62.         ResponseTagger $responseTagger
  63.     ) {
  64.         $this->templateRenderer $templateRenderer;
  65.         $this->scopeMatcher     $scopeMatcher;
  66.         $this->responseTagger   $responseTagger;
  67.     }
  68.     /**
  69.      * Set the fragment options.
  70.      *
  71.      * @param array<string,mixed> $options The fragment options.
  72.      *
  73.      * @psalm-suppress MoreSpecificImplementedParamType
  74.      */
  75.     public function setFragmentOptions(array $options): void
  76.     {
  77.         $this->options $options;
  78.     }
  79.     /**
  80.      * Generate the response for a fragment.
  81.      *
  82.      * This method executed the preGenerate() and postGenerate() method which might also return a response and intercept
  83.      * the default rendering.
  84.      *
  85.      * @param Request           $request The given request.
  86.      * @param Model             $model   The related model providing the configuration.
  87.      * @param string            $section The section in which the fragment is rendered.
  88.      * @param list<string>|null $classes Additional classes.
  89.      * @psalm-param TModel $model
  90.      */
  91.     protected function generate(Request $requestModel $modelstring $section, ?array $classes null): Response
  92.     {
  93.         $response $this->preGenerate($request$model$section$classes);
  94.         if ($response !== null) {
  95.             return $response;
  96.         }
  97.         $data     $this->prepareDefaultTemplateData($model$section$classes);
  98.         $data     $this->prepareTemplateData($data$request$model);
  99.         $buffer   $this->render($this->getTemplateName($model), $data);
  100.         $response $this->postGenerate($buffer$data$request$model);
  101.         if ($response !== null) {
  102.             return $response;
  103.         }
  104.         $this->tagResponse(sprintf('contao.db.%s.%s'$model::getTable(), $model->id));
  105.         return new Response($buffer);
  106.     }
  107.     /**
  108.      * Prepare the default template data.
  109.      *
  110.      * @param Model             $model   The related model providing the configuration.
  111.      * @param string            $section The section in which the fragment is rendered.
  112.      * @param list<string>|null $classes Additional classes.
  113.      * @psalm-param TModel $model
  114.      *
  115.      * @return array<string,mixed>
  116.      */
  117.     private function prepareDefaultTemplateData(Model $modelstring $section, ?array $classes null): array
  118.     {
  119.         $data    $model->row();
  120.         $cssID   array_pad(StringUtil::deserialize($data['cssID'], true), 2'');
  121.         $classes $classes ?: [];
  122.         if ($cssID[1] !== '') {
  123.             array_unshift($classes$cssID[1]);
  124.         }
  125.         $data['inColumn'] = $section;
  126.         $data['cssID']    = $cssID[0] !== '' ' id="' $cssID[0] . '"' '';
  127.         $data['class']    = trim($this->getTemplateName($model) . ' ' implode(' '$classes));
  128.         $headline StringUtil::deserialize($data['headline']);
  129.         if (is_array($headline)) {
  130.             $data['headline'] = $headline['value'];
  131.             $data['hl']       = $headline['unit'];
  132.         } else {
  133.             $data['headline'] = $headline;
  134.             $data['hl']       = 'h1';
  135.         }
  136.         return $data;
  137.     }
  138.     /**
  139.      * Prepare the template data.
  140.      *
  141.      * The method has to extend the existing data and return the modified one as return value.
  142.      *
  143.      * @param array<string,mixed> $data    The parsed template data.
  144.      * @param Request             $request The current request.
  145.      * @param Model               $model   The model containing the configuration.
  146.      * @psalm-param TModel $model
  147.      *
  148.      * @return array<string,mixed>
  149.      *
  150.      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  151.      */
  152.     protected function prepareTemplateData(array $dataRequest $requestModel $model): array
  153.     {
  154.         return $data;
  155.     }
  156.     /**
  157.      * Pre generate is called first before any logic is called in the generate method.
  158.      *
  159.      * Use it to intercept the default behaviour.
  160.      *
  161.      * @param Request           $request The given request.
  162.      * @param Model             $model   The related model providing the configuration.
  163.      * @param string            $section The section in which the fragment is rendered.
  164.      * @param list<string>|null $classes Additional classes.
  165.      * @psalm-param TModel $model
  166.      *
  167.      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  168.      */
  169.     protected function preGenerate(Request $requestModel $modelstring $section, ?array $classes null): ?Response
  170.     {
  171.         return null;
  172.     }
  173.     /**
  174.      * Post generate is called after the default rendering is done.
  175.      *
  176.      * Return a custom response if you want to modify the output.
  177.      *
  178.      * @param string              $buffer  The generated output.
  179.      * @param array<string,mixed> $data    The parsed data.
  180.      * @param Request             $request The given request.
  181.      * @param Model               $model   The related model providing the configuration.
  182.      * @psalm-param TModel $model
  183.      *
  184.      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  185.      */
  186.     protected function postGenerate(string $buffer, array $dataRequest $requestModel $model): ?Response
  187.     {
  188.         return null;
  189.     }
  190.     /**
  191.      * Render a template and return the result as string.
  192.      *
  193.      * @param string              $templateName The template name.
  194.      * @param array<string,mixed> $data         The template data.
  195.      */
  196.     protected function render(string $templateName, array $data): string
  197.     {
  198.         if (
  199.             substr($templateName, -5) !== '.twig'
  200.             && strpos($templateName'toolkit:') !== 0
  201.             && strpos($templateName'fe:') !== 0
  202.             && strpos($templateName'be:') !== 0
  203.         ) {
  204.             $templateName 'fe:' $templateName;
  205.         }
  206.         return $this->templateRenderer->render($templateName$data);
  207.     }
  208.     /**
  209.      * Render a template and return the result as response.
  210.      *
  211.      * @param string              $templateName The template name.
  212.      * @param array<string,mixed> $data         The template data.
  213.      */
  214.     protected function renderResponse(string $templateName, array $data): Response
  215.     {
  216.         return new Response($this->render($templateName$data));
  217.     }
  218.     /**
  219.      * Get the template name from the fragment options and or the provided model.
  220.      *
  221.      * @param Model $model The model containing the fragment configuration.
  222.      * @psalm-param TModel $model
  223.      */
  224.     protected function getTemplateName(Model $model): string
  225.     {
  226.         if ($model->customTpl && ! $this->scopeMatcher->isBackendRequest()) {
  227.             return $model->customTpl;
  228.         }
  229.         if (isset($this->options['template'])) {
  230.             return $this->options['template'];
  231.         }
  232.         return $this->getFallbackTemplateName($model);
  233.     }
  234.     /**
  235.      * Get the type of the fragment.
  236.      */
  237.     protected function getType(): string
  238.     {
  239.         if (isset($this->options['type'])) {
  240.             return $this->options['type'];
  241.         }
  242.         $className ltrim(strrchr(static::class, '\\'), '\\');
  243.         if (substr($className, -10) === 'Controller') {
  244.             $className substr($className0, -10);
  245.         }
  246.         return Container::underscore($className);
  247.     }
  248.     /**
  249.      * Check if the request is a backend request.
  250.      *
  251.      * @param Request $request The current request.
  252.      */
  253.     protected function isBackendRequest(Request $request): bool
  254.     {
  255.         return $this->scopeMatcher->isBackendRequest($request);
  256.     }
  257.     /**
  258.      * Get the fallback template name.
  259.      *
  260.      * @param Model $model The model containing the fragment configuration.
  261.      * @psalm-param TModel $model
  262.      */
  263.     abstract protected function getFallbackTemplateName(Model $model): string;
  264.     /**
  265.      * Tag the current response.
  266.      *
  267.      * @param string ...$tags The list of tags.
  268.      */
  269.     protected function tagResponse(string ...$tags): void
  270.     {
  271.         $this->responseTagger->addTags($tags);
  272.     }
  273. }