Passed
Push — analysis-JG5MMn ( d0bd8f )
by Arnaud
88:41 queued 12s
created

Twig.php$0 ➔ render()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Cecil\Renderer;
15
16
use Cecil\Builder;
17
use Cecil\Exception\RuntimeException;
18
use Cecil\Renderer\Extension\Core as CoreExtension;
19
use Cecil\Util;
20
use Performing\TwigComponents\Configuration;
21
use Symfony\Bridge\Twig\Extension\TranslationExtension;
22
use Symfony\Component\Translation\Formatter\MessageFormatter;
23
use Symfony\Component\Translation\IdentityTranslator;
24
use Symfony\Component\Translation\Translator;
25
use Twig\Extra\Intl\IntlExtension;
26
27
use Twig\Extra\Cache\CacheExtension;
28
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
29
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
30
use Twig\Extra\Cache\CacheRuntime;
31
use Twig\RuntimeLoader\RuntimeLoaderInterface;
32
33
/**
34
 * Class Twig.
35
 */
36
class Twig implements RendererInterface
37
{
38
    /** @var Builder */
39
    private $builder;
40
41
    /** @var \Twig\Environment */
42
    private $twig;
43
44
    /** @var Translator */
45
    private $translator = null;
46
47
    /** @var \Twig\Profiler\Profile */
48
    private $profile = null;
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function __construct(Builder $builder, $templatesPath)
54
    {
55
        $this->builder = $builder;
56
        // load layouts
57
        $loader = new \Twig\Loader\FilesystemLoader($templatesPath);
58
        // default options
59
        $loaderOptions = [
60
            'debug'            => $this->builder->isDebug(),
61
            'strict_variables' => true,
62
            'autoescape'       => false,
63
            'auto_reload'      => true,
64
            'cache'            => false,
65
        ];
66
        // use Twig cache?
67
        if ((bool) $this->builder->getConfig()->get('cache.templates.enabled')) {
68
            $loaderOptions = array_replace($loaderOptions, ['cache' => $this->builder->getConfig()->getCacheTemplatesPath()]);
69
        }
70
        // create the Twig instance
71
        $this->twig = new \Twig\Environment($loader, $loaderOptions);
72
        // set date format
73
        $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
74
            ->setDateFormat((string) $this->builder->getConfig()->get('date.format'));
75
        // set timezone
76
        if ($this->builder->getConfig()->has('date.timezone')) {
77
            $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
78
                ->setTimezone((string) $this->builder->getConfig()->get('date.timezone') ?? date_default_timezone_get());
79
        }
80
        /*
81
         * adds extensions
82
         */
83
        // Cecil core extension
84
        $this->twig->addExtension(new CoreExtension($this->builder));
85
        // required by `template_from_string()`
86
        $this->twig->addExtension(new \Twig\Extension\StringLoaderExtension());
87
        // l10n
88
        $this->translator = new Translator(
89
            $this->builder->getConfig()->getLanguageProperty('locale'),
90
            new MessageFormatter(new IdentityTranslator()),
91
            (bool) $this->builder->getConfig()->get('cache.templates.enabled') ? $this->builder->getConfig()->getCacheTranslationsPath() : null,
92
            $this->builder->isDebug()
93
        );
94
        if (\count($this->builder->getConfig()->getLanguages()) > 0) {
95
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
96
                $loader = \sprintf('Symfony\Component\Translation\Loader\%sFileLoader', ucfirst($format));
97
                if (class_exists($loader)) {
98
                    $this->translator->addLoader($format, new $loader());
99
                    $this->builder->getLogger()->debug(\sprintf('Translation loader for format "%s" found', $format));
100
                }
101
            }
102
            foreach ($this->builder->getConfig()->getLanguages() as $lang) {
103
                // internal
104
                $this->addTransResource($this->builder->getConfig()->getTranslationsInternalPath(), $lang['locale']);
105
                // themes
106
                if ($themes = $this->builder->getConfig()->getTheme()) {
107
                    foreach ($themes as $theme) {
108
                        $this->addTransResource($this->builder->getConfig()->getThemeDirPath($theme, 'translations'), $lang['locale']);
109
                    }
110
                }
111
                // site
112
                $this->addTransResource($this->builder->getConfig()->getTranslationsPath(), $lang['locale']);
113
            }
114
        }
115
        $this->twig->addExtension(new TranslationExtension($this->translator));
116
        // intl
117
        $this->twig->addExtension(new IntlExtension());
118
        if (\extension_loaded('intl')) {
119
            $this->builder->getLogger()->debug('PHP Intl extension is loaded');
120
        }
121
        // filters fallback
122
        $this->twig->registerUndefinedFilterCallback(function ($name) {
123
            switch ($name) {
124
                case 'localizeddate':
125
                    return new \Twig\TwigFilter($name, function (?\DateTime $value = null) {
126
                        return date($this->builder->getConfig()->get('date.format'), $value->getTimestamp());
0 ignored issues
show
Bug introduced by
The method getTimestamp() does not exist on null. ( Ignorable by Annotation )

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

126
                        return date($this->builder->getConfig()->get('date.format'), $value->/** @scrutinizer ignore-call */ getTimestamp());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
It seems like $this->builder->getConfig()->get('date.format') can also be of type null; however, parameter $format of date() does only seem to accept string, 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

126
                        return date(/** @scrutinizer ignore-type */ $this->builder->getConfig()->get('date.format'), $value->getTimestamp());
Loading history...
127
                    });
128
            }
129
130
            return false;
131
        });
132
        // components
133
        Configuration::make($this->twig)
134
            ->setTemplatesPath($this->builder->getConfig()->get('layouts.components.dir') ?? 'components')
135
            ->setTemplatesExtension($this->builder->getConfig()->get('layouts.components.ext') ?? 'twig')
136
            ->useCustomTags()
137
            ->setup();
138
        // cache
139
        $this->twig->addExtension(new CacheExtension());
140
        $this->twig->addRuntimeLoader(new class () implements RuntimeLoaderInterface {
141
            public function load($class)
142
            {
143
                if (CacheRuntime::class === $class) {
144
                    return new CacheRuntime(new TagAwareAdapter(new FilesystemAdapter()));
145
                }
146
            }
147
        });
148
        // debug
149
        if ($this->builder->isDebug()) {
150
            // dump()
151
            $this->twig->addExtension(new \Twig\Extension\DebugExtension());
152
            // profiler
153
            $this->profile = new \Twig\Profiler\Profile();
154
            $this->twig->addExtension(new \Twig\Extension\ProfilerExtension($this->profile));
155
        }
156
        // loads custom extensions
157
        if ($this->builder->getConfig()->has('layouts.extensions')) {
158
            foreach ((array) $this->builder->getConfig()->get('layouts.extensions') as $name => $class) {
159
                try {
160
                    $this->twig->addExtension(new $class($this->builder));
161
                    $this->builder->getLogger()->debug(\sprintf('Twig extension "%s" added', $name));
162
                } catch (RuntimeException | \Error $e) {
163
                    $this->builder->getLogger()->error(\sprintf('Unable to add Twig extension "%s": %s', $name, $e->getMessage()));
164
                }
165
            }
166
        }
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function addGlobal(string $name, $value): void
173
    {
174
        $this->twig->addGlobal($name, $value);
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function render(string $template, array $variables): string
181
    {
182
        return $this->twig->render($template, $variables);
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function setLocale(string $locale): void
189
    {
190
        if (\extension_loaded('intl')) {
191
            \Locale::setDefault($locale);
192
        }
193
        $this->translator === null ?: $this->translator->setLocale($locale);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function addTransResource(string $translationsDir, string $locale): void
200
    {
201
        $locales = [$locale];
202
        // if locale is 'fr_FR', trying to load ['fr', 'fr_FR']
203
        if (\strlen($locale) > 2) {
204
            array_unshift($locales, substr($locale, 0, 2));
205
        }
206
        foreach ($locales as $locale) {
207
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
208
                $translationFile = Util::joinPath($translationsDir, \sprintf('messages.%s.%s', $locale, $format));
209
                if (Util\File::getFS()->exists($translationFile)) {
210
                    $this->translator->addResource($format, $translationFile, $locale);
211
                    $this->builder->getLogger()->debug(\sprintf('Translation file "%s" added', $translationFile));
212
                }
213
            }
214
        }
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function getDebugProfile(): ?\Twig\Profiler\Profile
221
    {
222
        return $this->profile;
223
    }
224
225
    /**
226
     * Returns the Twig instance.
227
     */
228
    public function getTwig(): \Twig\Environment
229
    {
230
        return $this->twig;
231
    }
232
}
233