Passed
Push — cache ( c1ece6...c9eef1 )
by Arnaud
03:56
created

anonymous//src/Renderer/Twig.php$0   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 5
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 0
loc 5
rs 10
c 2
b 1
f 0
wmc 2
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
use Twig\Extra\Cache\CacheExtension;
27
28
/**
29
 * Class Twig.
30
 */
31
class Twig implements RendererInterface
32
{
33
    /** @var Builder */
34
    private $builder;
35
36
    /** @var \Twig\Environment */
37
    private $twig;
38
39
    /** @var Translator */
40
    private $translator = null;
41
42
    /** @var \Twig\Profiler\Profile */
43
    private $profile = null;
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function __construct(Builder $builder, $templatesPath)
49
    {
50
        $this->builder = $builder;
51
        // load layouts
52
        $loader = new \Twig\Loader\FilesystemLoader($templatesPath);
53
        // default options
54
        $loaderOptions = [
55
            'debug'            => $this->builder->isDebug(),
56
            'strict_variables' => true,
57
            'autoescape'       => false,
58
            'auto_reload'      => true,
59
            'cache'            => false,
60
        ];
61
        // use Twig cache?
62
        if ((bool) $this->builder->getConfig()->get('cache.templates.enabled')) {
63
            $loaderOptions = array_replace($loaderOptions, ['cache' => $this->builder->getConfig()->getCacheTemplatesPath()]);
64
        }
65
        // create the Twig instance
66
        $this->twig = new \Twig\Environment($loader, $loaderOptions);
67
        // set date format
68
        $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
69
            ->setDateFormat((string) $this->builder->getConfig()->get('date.format'));
70
        // set timezone
71
        if ($this->builder->getConfig()->has('date.timezone')) {
72
            $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
73
                ->setTimezone((string) $this->builder->getConfig()->get('date.timezone') ?? date_default_timezone_get());
74
        }
75
        /*
76
         * adds extensions
77
         */
78
        // Cecil core extension
79
        $this->twig->addExtension(new CoreExtension($this->builder));
80
        // required by `template_from_string()`
81
        $this->twig->addExtension(new \Twig\Extension\StringLoaderExtension());
82
        // l10n
83
        $this->translator = new Translator(
84
            $this->builder->getConfig()->getLanguageProperty('locale'),
85
            new MessageFormatter(new IdentityTranslator()),
86
            (bool) $this->builder->getConfig()->get('cache.templates.enabled') ? $this->builder->getConfig()->getCacheTranslationsPath() : null,
87
            $this->builder->isDebug()
88
        );
89
        if (\count($this->builder->getConfig()->getLanguages()) > 0) {
90
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
91
                $loader = \sprintf('Symfony\Component\Translation\Loader\%sFileLoader', ucfirst($format));
92
                if (class_exists($loader)) {
93
                    $this->translator->addLoader($format, new $loader());
94
                    $this->builder->getLogger()->debug(\sprintf('Translation loader for format "%s" found', $format));
95
                }
96
            }
97
            foreach ($this->builder->getConfig()->getLanguages() as $lang) {
98
                // internal
99
                $this->addTransResource($this->builder->getConfig()->getTranslationsInternalPath(), $lang['locale']);
100
                // themes
101
                if ($themes = $this->builder->getConfig()->getTheme()) {
102
                    foreach ($themes as $theme) {
103
                        $this->addTransResource($this->builder->getConfig()->getThemeDirPath($theme, 'translations'), $lang['locale']);
104
                    }
105
                }
106
                // site
107
                $this->addTransResource($this->builder->getConfig()->getTranslationsPath(), $lang['locale']);
108
            }
109
        }
110
        $this->twig->addExtension(new TranslationExtension($this->translator));
111
        // intl
112
        $this->twig->addExtension(new IntlExtension());
113
        if (\extension_loaded('intl')) {
114
            $this->builder->getLogger()->debug('PHP Intl extension is loaded');
115
        }
116
        // filters fallback
117
        $this->twig->registerUndefinedFilterCallback(function ($name) {
118
            switch ($name) {
119
                case 'localizeddate':
120
                    return new \Twig\TwigFilter($name, function (?\DateTime $value = null) {
121
                        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

121
                        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

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