Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.89% covered (success)
91.89%
34 / 37
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CustomPageController
91.89% covered (success)
91.89%
34 / 37
50.00% covered (danger)
50.00%
1 / 2
10.05
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 show
91.67% covered (success)
91.67%
33 / 36
0.00% covered (danger)
0.00%
0 / 1
9.05
1<?php
2
3/**
4 * Custom Page Controller
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-01-15
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Frontend;
21
22use phpMyFAQ\Core\Exception;
23use phpMyFAQ\CustomPage;
24use phpMyFAQ\Entity\SeoEntity;
25use phpMyFAQ\Enums\SeoType;
26use phpMyFAQ\Seo;
27use phpMyFAQ\Strings;
28use Symfony\Component\HttpFoundation\Request;
29use Symfony\Component\HttpFoundation\Response;
30use Symfony\Component\Routing\Attribute\Route;
31use Twig\Error\LoaderError;
32
33final class CustomPageController extends AbstractFrontController
34{
35    public function __construct(
36        private readonly CustomPage $customPage,
37    ) {
38        parent::__construct();
39    }
40
41    /**
42     * Displays a custom page by its slug
43     *
44     * @throws Exception|LoaderError|\Exception
45     */
46    #[Route(path: '/page/{slug}.html', name: 'public.page', requirements: ['slug' => '[a-z0-9\-_]+'], methods: ['GET'])]
47    public function show(Request $request): Response
48    {
49        $slug = (string) $request->attributes->get('slug', '');
50
51        if ($slug === '') {
52            return $this->render('404.twig', [
53                ...$this->getHeader($request),
54            ]);
55        }
56
57        $language = $this->configuration->getLanguage()->getLanguage();
58
59        // Get page by slug
60        $pageEntity = $this->customPage->getBySlug($slug, $language);
61
62        // If not found or not active, show 404
63        if ($pageEntity === null || !$pageEntity->isActive()) {
64            return $this->render('404.twig', [
65                ...$this->getHeader($request),
66            ]);
67        }
68
69        // Get SEO metadata
70        $seo = new Seo($this->configuration);
71        $seoQueryEntity = new SeoEntity();
72        $seoQueryEntity
73            ->setSeoType(SeoType::PAGE)
74            ->setReferenceId($pageEntity->getId())
75            ->setReferenceLanguage($language);
76
77        try {
78            $seoEntity = $seo->get($seoQueryEntity);
79            $seoTitle = $seoEntity->getTitle();
80            $metaTitle = $seoTitle !== '' && $seoTitle !== '0' ? $seoTitle : $pageEntity->getPageTitle();
81
82            $seoDescription = $seoEntity->getDescription();
83            $metaDescription = $seoDescription !== '' && $seoDescription !== '0' ? $seoDescription : '';
84        } catch (Exception $e) {
85            // SEO data aren't found, use page defaults
86            $metaTitle = $pageEntity->getPageTitle();
87            $metaDescription = '';
88        }
89
90        return $this->render('custom-page.twig', [
91            ...$this->getHeader($request),
92            'pageTitle' => Strings::htmlentities($pageEntity->getPageTitle()),
93            'pageContent' => $pageEntity->getContent(),
94            'authorName' => Strings::htmlentities($pageEntity->getAuthorName()),
95            'authorEmail' => $pageEntity->getAuthorEmail(),
96            'pageCreated' => $pageEntity->getCreated()->format('Y-m-d H:i:s'),
97            'pageUpdated' => $pageEntity->getUpdated()?->format('Y-m-d H:i:s'),
98            'metaTitle' => $metaTitle,
99            'metaDescription' => $metaDescription,
100        ]);
101    }
102}