Issues (281)

Branch: master

src/Backend/Core/Engine/TwigTemplate.php (5 issues)

1
<?php
2
3
namespace Backend\Core\Engine;
4
5
use Backend\Core\Language\Language as BL;
6
use Common\Core\Twig\BaseTwigTemplate;
7
use Common\Core\Twig\Extensions\TwigFilters;
8
use Frontend\Core\Engine\FormExtension;
9
use ReflectionClass;
10
use Symfony\Bridge\Twig\AppVariable;
11
use Symfony\Bridge\Twig\Extension\FormExtension as SymfonyFormExtension;
12
use Symfony\Bridge\Twig\Extension\TranslationExtension;
13
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
14
use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator;
15
use Symfony\Component\DependencyInjection\ContainerInterface;
16
use Symfony\Component\Form\FormRenderer;
17
use Twig\Environment;
18
use Twig\Extension\DebugExtension;
19
use Twig\Loader\FilesystemLoader;
20
use Twig\RuntimeLoader\FactoryRuntimeLoader;
21
22
/**
23
 * This is a twig template wrapper
24
 * that glues spoon libraries and code standards with twig.
25
 */
26
class TwigTemplate extends BaseTwigTemplate
27
{
28
    /**
29
     * The constructor will store the instance in the reference, preset some settings and map the custom modifiers.
30
     *
31
     * @param bool $addToReference Should the instance be added into the reference.
32
     */
33 131
    public function __construct(bool $addToReference = true)
34
    {
35 131
        $container = Model::getContainer();
36 131
        $this->debugMode = $container->getParameter('kernel.debug');
37
38 131
        parent::__construct(
39 131
            $this->buildTwigEnvironmentForTheBackend(),
40 131
            $container->get('templating.name_parser.public'),
0 ignored issues
show
It seems like $container->get('templating.name_parser.public') can also be of type null; however, parameter $parser of Symfony\Bundle\TwigBundl...igEngine::__construct() does only seem to accept Symfony\Component\Templa...lateNameParserInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

40
            /** @scrutinizer ignore-type */ $container->get('templating.name_parser.public'),
Loading history...
41 131
            new TemplateLocator($container->get('file_locator.public'), $container->getParameter('kernel.cache_dir'))
0 ignored issues
show
It seems like $container->get('file_locator.public') can also be of type null; however, parameter $locator of Symfony\Bundle\Framework...eLocator::__construct() does only seem to accept Symfony\Component\Config\FileLocatorInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

41
            new TemplateLocator(/** @scrutinizer ignore-type */ $container->get('file_locator.public'), $container->getParameter('kernel.cache_dir'))
Loading history...
Deprecated Code introduced by
The class Symfony\Bundle\Framework...\Loader\TemplateLocator has been deprecated: since version 4.3, to be removed in 5.0; use Twig instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

41
            /** @scrutinizer ignore-deprecated */ new TemplateLocator($container->get('file_locator.public'), $container->getParameter('kernel.cache_dir'))
Loading history...
42
        );
43
44 131
        if ($addToReference) {
45 131
            $container->set('template', $this);
46
        }
47
48 131
        $this->forkSettings = $container->get('fork.settings');
49 131
        if ($this->debugMode) {
50 131
            $this->environment->enableAutoReload();
51 131
            $this->environment->setCache(false);
52 131
            $this->environment->addExtension(new DebugExtension());
53
        }
54 131
        $this->language = BL::getWorkingLanguage();
55 131
        $this->connectSymfonyForms();
56 131
        $this->connectSymfonyTranslator();
57 131
        $this->connectSpoonForm();
58 131
        TwigFilters::addFilters($this->environment, 'Backend');
59 131
        $this->autoloadMissingTaggedExtensions($container);
60 131
    }
61
62
    /**
63
     * Fetch the parsed content from this template.
64
     *
65
     * @param string $template The location of the template file, used to display this template.
66
     *
67
     * @return string The actual parsed content after executing this template.
68
     */
69 71
    public function getContent(string $template): string
70
    {
71 71
        $this->parseUserDefinedConstants();
72 71
        $this->parseAuthenticationSettingsForTheAuthenticatedUser();
0 ignored issues
show
Deprecated Code introduced by
The function Backend\Core\Engine\Twig...rTheAuthenticatedUser() has been deprecated: This is a very inaccurate way since it doesn't include the goduser permissions and the always allowed settings into account ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

72
        /** @scrutinizer ignore-deprecated */ $this->parseAuthenticationSettingsForTheAuthenticatedUser();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
73 71
        $this->parseAuthenticatedUser();
74 71
        $this->parseDebug();
75 71
        $this->parseTranslations();
76 71
        $this->parseVars();
77 71
        $this->startGlobals($this->environment);
78
79 71
        return $this->render(str_replace(BACKEND_MODULES_PATH, '', $template), $this->variables);
80
    }
81
82
    /**
83
     * @return Environment
84
     */
85 131
    private function buildTwigEnvironmentForTheBackend(): Environment
86
    {
87
        // path to TwigBridge library so we can locate the form theme files.
88 131
        $appVariableReflection = new ReflectionClass(AppVariable::class);
89 131
        $vendorTwigBridgeDir = dirname($appVariableReflection->getFileName());
90
91
        // render the compiled File
92 131
        $loader = new FilesystemLoader(
93
            [
94 131
                BACKEND_MODULES_PATH,
95 131
                BACKEND_CORE_PATH,
96 131
                $vendorTwigBridgeDir . '/Resources/views/Form',
97
            ]
98
        );
99
100 131
        return new Environment(
101 131
            $loader,
102
            [
103 131
                'cache' => Model::getContainer()->getParameter('kernel.cache_dir') . '/twig',
104 131
                'debug' => $this->debugMode,
105
            ]
106
        );
107
    }
108
109 131
    private function connectSymfonyForms(): void
110
    {
111 131
        $rendererEngine = new TwigRendererEngine(
112
            [
113 131
                'Layout/Templates/FormLayout.html.twig',
114
                'MediaLibrary/Resources/views/FormLayout.html.twig',
115
            ],
116 131
            $this->environment
117
        );
118 131
        $csrfTokenManager = Model::get('security.csrf.token_manager');
119 131
        $this->environment->addRuntimeLoader(
120 131
            new FactoryRuntimeLoader(
121
                [
122
                    FormRenderer::class => function () use ($rendererEngine, $csrfTokenManager): FormRenderer {
123 6
                        return new FormRenderer($rendererEngine, $csrfTokenManager);
124 131
                    },
125
                ]
126
            )
127
        );
128
129 131
        if (!$this->environment->hasExtension(SymfonyFormExtension::class)) {
130 131
            $this->environment->addExtension(new SymfonyFormExtension());
131
        }
132 131
    }
133
134 131
    private function connectSymfonyTranslator(): void
135
    {
136 131
        $this->environment->addExtension(new TranslationExtension(Model::get('translator')));
137 131
    }
138
139 131
    private function connectSpoonForm(): void
140
    {
141 131
        new FormExtension($this->environment);
142 131
    }
143
144 71
    private function parseUserDefinedConstants(): void
145
    {
146
        // get all defined constants
147 71
        $constants = get_defined_constants(true);
148
149
        // we should only assign constants if there are constants to assign
150 71
        if (!empty($constants['user'])) {
151 71
            $this->assignArray($constants['user']);
152
        }
153
154
        // we use some abbreviations and common terms, these should also be assigned
155 71
        $this->assign('LANGUAGE', BL::getWorkingLanguage());
156
157
        // check on url object
158 71
        if (Model::getContainer()->has('url')) {
159 71
            $url = Model::get('url');
160
161 71
            if ($url instanceof Url) {
162
                // assign the current module
163 71
                $this->assign('MODULE', $url->getModule());
164
165
                // assign the current action
166 71
                if ($url->getAction() !== '') {
167 71
                    $this->assign('ACTION', $url->getAction());
168
                }
169
170 71
                if ($url->getModule() === 'Core') {
171
                    $this->assign(
172
                        'BACKEND_MODULE_PATH',
173
                        BACKEND_PATH . '/' . $url->getModule()
174
                    );
175
                } else {
176 71
                    $this->assign(
177 71
                        'BACKEND_MODULE_PATH',
178 71
                        BACKEND_MODULES_PATH . '/' . $url->getModule()
179
                    );
180
                }
181
            }
182
        }
183
184
        // is the user object filled?
185 71
        if (Authentication::getUser()->isAuthenticated()) {
186
            // assign the authenticated users secret key
187 38
            $this->assign('SECRET_KEY', Authentication::getUser()->getSecretKey());
188
189
            // assign the authenticated users preferred interface language
190 38
            $this->assign('INTERFACE_LANGUAGE', (string) Authentication::getUser()->getSetting('interface_language'));
191
        }
192
193
        // assign some variable constants (such as site-title)
194 71
        $this->assign(
195 71
            'SITE_TITLE',
196 71
            Model::get('fork.settings')->get('Core', 'site_title_' . BL::getWorkingLanguage(), SITE_DEFAULT_TITLE)
197
        );
198 71
    }
199
200 71
    private function parseAuthenticatedUser(): void
201
    {
202
        // check if the current user is authenticated
203 71
        if (Authentication::getUser()->isAuthenticated()) {
204
            // show stuff that only should be visible if authenticated
205 38
            $this->assign('isAuthenticated', true);
206
207
            // get authenticated user-settings
208 38
            $settings = (array) Authentication::getUser()->getSettings();
209
210 38
            foreach ($settings as $key => $setting) {
211 38
                $this->assign('authenticatedUser' . \SpoonFilter::toCamelCase($key), $setting ?? '');
212
            }
213
214
            // check if this action is allowed
215 38
            if (Authentication::isAllowedAction('Edit', 'Users')) {
216
                // assign special vars
217 37
                $this->assign(
218 37
                    'authenticatedUserEditUrl',
219 37
                    Model::createUrlForAction(
220 37
                        'Edit',
221 37
                        'Users',
222 37
                        null,
223 37
                        ['id' => Authentication::getUser()->getUserId()]
224
                    )
225
                );
226
            }
227
        }
228 71
    }
229
230
    /**
231
     * @deprecated This is a very inaccurate way since it doesn't include the goduser permissions and the always allowed settings into account
232
     */
233 71
    private function parseAuthenticationSettingsForTheAuthenticatedUser(): void
234
    {
235
        // loop actions and assign to template
236 71
        foreach (Authentication::getAllowedActions() as $module => $allowedActions) {
0 ignored issues
show
Deprecated Code introduced by
The function Backend\Core\Engine\Auth...on::getAllowedActions() has been deprecated: this will become a private method in Fork 6 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

236
        foreach (/** @scrutinizer ignore-deprecated */ Authentication::getAllowedActions() as $module => $allowedActions) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
237 38
            foreach ($allowedActions as $action => $level) {
238 38
                if ($level !== 7) {
239
                    continue;
240
                }
241
242 38
                $this->assign(
243 38
                    'show' . \SpoonFilter::toCamelCase($module, '_') . \SpoonFilter::toCamelCase(
244 38
                        $action,
245 38
                        '_'
246
                    ),
247 38
                    true
248
                );
249
            }
250
        }
251 71
    }
252
253 71
    private function parseDebug(): void
254
    {
255 71
        $this->assign('debug', Model::getContainer()->getParameter('kernel.debug'));
256
257 71
        if ($this->debugMode === true && !$this->environment->hasExtension(DebugExtension::class)) {
258
            $this->environment->addExtension(new DebugExtension());
259
        }
260 71
    }
261
262 71
    private function parseLabels(array $labels, string $module, string $key): void
263
    {
264 71
        $realLabels = $this->prefixArrayKeys('Core', $labels['Core']);
265
266 71
        if (array_key_exists($module, $labels)) {
267 38
            $realLabels = array_merge($realLabels, $labels[$module]);
268
        }
269
270 71
        if ($this->addSlashes) {
271
            $realLabels = array_map('addslashes', $realLabels);
272
        }
273
274
        // just so the dump is nicely sorted
275 71
        ksort($realLabels);
276
277 71
        $this->assignArray($realLabels, $key);
278 71
    }
279
280 71
    private function prefixArrayKeys(string $prefix, array $array): array
281
    {
282 71
        return array_combine(
283 71
            array_map(
284
                function ($key) use ($prefix) {
285 71
                    return $prefix . \SpoonFilter::ucfirst($key);
286 71
                },
287 71
                array_keys($array)
288
            ),
289 71
            $array
290
        );
291
    }
292
293 71
    private function parseTranslations(): void
294
    {
295 71
        $currentModule = BL::getCurrentModule();
296 71
        $this->parseLabels(BL::getErrors(), $currentModule, 'err');
297 71
        $this->parseLabels(BL::getLabels(), $currentModule, 'lbl');
298 71
        $this->parseLabels(BL::getMessages(), $currentModule, 'msg');
299
300 71
        $interfaceLanguage = BL::getInterfaceLanguage();
301 71
        $this->assignArray($this->prefixArrayKeys('locMonthLong', \SpoonLocale::getMonths($interfaceLanguage, false)));
302 71
        $this->assignArray($this->prefixArrayKeys('locMonthShort', \SpoonLocale::getMonths($interfaceLanguage, true)));
303 71
        $this->assignArray($this->prefixArrayKeys('locDayLong', \SpoonLocale::getWeekDays($interfaceLanguage, false)));
304 71
        $this->assignArray($this->prefixArrayKeys('locDayShort', \SpoonLocale::getWeekDays($interfaceLanguage, true)));
305 71
    }
306
307 71
    private function parseVars(): void
308
    {
309 71
        $this->assign('var', '');
310 71
        $this->assign('timestamp', time());
311 71
        $this->assign('fork_csrf_token', Model::getToken());
312 71
        $this->addBodyClassAndId();
313 71
        $this->parseNavigation();
314
315 71
        foreach ($this->forms as $form) {
316 67
            if ($form->isSubmitted() && !$form->isCorrect()) {
317 4
                $this->assign('form_error', true);
318 67
                break;
319
            }
320
        }
321
322 71
        $this->assign('cookies', Model::getRequest()->cookies->all());
323 71
    }
324
325 71
    private function parseNavigation(): void
326
    {
327 71
        if (!Model::has('navigation')) {
328
            return;
329
        }
330
331 71
        $navigation = Model::get('navigation');
332 71
        if ($navigation instanceof Navigation) {
333 71
            $navigation->parse($this);
334
        }
335 71
    }
336
337 71
    private function addBodyClassAndId(): void
338
    {
339 71
        if (!Model::getContainer()->has('url')) {
340
            return;
341
        }
342
343 71
        $url = Model::get('url');
344
345 71
        if (!$url instanceof Url) {
346
            return;
347
        }
348
349 71
        $this->assign('bodyID', \SpoonFilter::toCamelCase($url->getModule(), '_', true));
350 71
        $bodyClass = \SpoonFilter::toCamelCase($url->getModule() . '_' . $url->getAction(), '_', true);
351 71
        if (in_array(mb_strtolower($url->getAction()), ['add', 'edit'], true)) {
352 10
            $bodyClass = $url->getModule() . 'AddEdit';
353
        }
354 71
        $this->assign('bodyClass', $bodyClass);
355 71
    }
356
357 131
    private function autoloadMissingTaggedExtensions(ContainerInterface $container): void
358
    {
359 131
        foreach ($container->get('twig')->getExtensions() as $id => $extension) {
360 131
            if (!$this->environment->hasExtension($id)) {
361 131
                $this->environment->addExtension($extension);
362
            }
363
        }
364 131
    }
365
}
366