Passed
Push — extensions ( 04127d...07f253 )
by Arnaud
03:03
created

Render   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Test Coverage

Coverage 90.27%

Importance

Changes 9
Bugs 4 Features 1
Metric Value
eloc 115
c 9
b 4
f 1
dl 0
loc 262
rs 9.1199
ccs 102
cts 113
cp 0.9027
wmc 41

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A init() 0 8 3
A getAlternates() 0 17 5
A addGlobals() 0 6 1
A getOutputFormats() 0 30 5
A getAllLayoutsPaths() 0 21 5
A getTranslations() 0 11 5
F process() 0 120 16

How to fix   Complexity   

Complex Class

Complex classes like Render often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Render, and based on these observations, apply Extract Interface, too.

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\Step\Pages;
15
16
use Cecil\Builder;
17
use Cecil\Collection\Page\Collection;
18
use Cecil\Collection\Page\Page;
19
use Cecil\Collection\Page\PrefixSuffix;
20
use Cecil\Exception\RuntimeException;
21
use Cecil\Renderer\Layout;
22
use Cecil\Renderer\Site;
23
use Cecil\Renderer\Twig;
24
use Cecil\Step\AbstractStep;
25
use Cecil\Util;
26
27
/**
28
 * Pages rendering.
29
 */
30
class Render extends AbstractStep
31
{
32
    /**
33
     * {@inheritdoc}
34 1
     */
35
    public function getName(): string
36 1
    {
37
        return 'Rendering pages';
38
    }
39
40
    /**
41
     * {@inheritdoc}
42 1
     */
43
    public function init(array $options): void
44 1
    {
45
        if (!is_dir($this->config->getLayoutsPath()) && !$this->config->hasTheme()) {
46
            $message = \sprintf("'%s' is not a valid layouts directory", $this->config->getLayoutsPath());
47
            $this->builder->getLogger()->debug($message);
48
        }
49 1
50
        $this->canProcess = true;
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     *
56
     * @throws RuntimeException
57 1
     */
58
    public function process(): void
59
    {
60 1
        // prepares renderer
61
        $this->builder->setRenderer(new Twig($this->builder, $this->getAllLayoutsPaths()));
62
63 1
        // adds global variables
64
        $this->addGlobals();
65
66
        /** @var Collection $pages */
67 1
        $pages = $this->builder->getPages()
68 1
            // published only
69
            ->filter(function (Page $page) {
70 1
                return (bool) $page->getVariable('published');
71
            })
72
            // enrichs some variables
73 1
            ->map(function (Page $page) {
74
                $formats = $this->getOutputFormats($page);
75 1
                // output formats
76 1
                $page->setVariable('output', $formats);
77 1
                // alternates formats
78
                $page->setVariable('alternates', $this->getAlternates($formats));
79
                // translations
80 1
                $page->setVariable('translations', $this->getTranslations($page));
81 1
82 1
                return $page;
83
            });
84
        $total = count($pages);
85 1
86
        // renders each page
87
        $count = 0;
88 1
        /** @var Page $page */
89 1
        foreach ($pages as $page) {
90
            $count++;
91
            $rendered = [];
92 1
93 1
            // l10n
94
            $language = $page->getVariable('language', $this->config->getLanguageDefault());
95
            $locale = $this->config->getLanguageProperty('locale', $language);
96
            $this->builder->getRenderer()->setLocale($locale);
97
98
            // global site variables
99 1
            $this->builder->getRenderer()->addGlobal('site', new Site($this->builder, $language));
100
101
            // excluded format(s)?
102 1
            $formats = (array) $page->getVariable('output');
103 1
            foreach ($formats as $key => $format) {
104 1
                if ($exclude = $this->config->getOutputFormatProperty($format, 'exclude')) {
105
                    // ie:
106
                    //   formats:
107
                    //     atom:
108
                    //       [...]
109
                    //       exclude: [paginated]
110
                    if (!is_array($exclude)) {
111 1
                        $exclude = [$exclude];
112
                    }
113 1
                    foreach ($exclude as $variable) {
114
                        if ($page->hasVariable($variable)) {
115
                            unset($formats[$key]);
116 1
                        }
117
                    }
118 1
                }
119
            }
120
121 1
            // renders each output format
122 1
            foreach ($formats as $format) {
123 1
                // search for the template
124 1
                $layout = Layout::finder($page, $format, $this->config);
125
                // renders with Twig
126
                try {
127 1
                    $deprecations = [];
128 1
                    set_error_handler(function ($type, $msg) use (&$deprecations) {
129 1
                        if (E_USER_DEPRECATED === $type) {
130
                            $deprecations[] = $msg;
131 1
                        }
132 1
                    });
133 1
                    $output = $this->builder->getRenderer()->render($layout['file'], ['page' => $page]);
134 1
                    foreach ($deprecations as $value) {
135
                        $this->builder->getLogger()->warning($value);
136 1
                    }
137 1
                    Util::autoload($this->builder, 'postprocessors');
138 1
                    foreach ($this->config->get('output.postprocessors') as $processor) {
139 1
                        $output = (new $processor($this->builder))->process($page, $output, $format);
140 1
                    }
141
                    $rendered[$format] = [
142
                        'output'   => $output,
143
                        'template' => [
144
                            'scope' => $layout['scope'],
145
                            'file'  => $layout['file'],
146
                        ]
147
                    ];
148
                    $page->addRendered($rendered);
149
                    // profiler
150
                    if ($this->builder->isDebug()) {
151
                        $dumper = new \Twig\Profiler\Dumper\HtmlDumper();
152
                        file_put_contents(
153
                            Util::joinFile($this->config->getOutputPath(), '_debug_twig_profile.html'),
154
                            $dumper->dump($this->builder->getRenderer()->getDebugProfile())
155 1
                        );
156 1
                    }
157
                } catch (\Twig\Error\Error $e) {
158 1
                    $template = !empty($e->getSourceContext()->getPath()) ? $e->getSourceContext()->getPath() : $e->getSourceContext()->getName();
159 1
160
                    throw new RuntimeException(\sprintf(
161 1
                        'Template "%s%s" (page: %s): %s',
162 1
                        $template,
163
                        $e->getTemplateLine() >= 0 ? \sprintf(':%s', $e->getTemplateLine()) : '',
164 1
                        $page->getId(),
165
                        $e->getMessage()
166
                    ));
167
                }
168
            }
169
            $this->builder->getPages()->replace($page->getId(), $page);
170
171 1
            $templates = array_column($rendered, 'template');
172
            $message = \sprintf(
173 1
                'Page "%s" rendered with template(s) "%s"',
174
                ($page->getId() ?: 'index'),
175
                Util\Str::combineArrayToString($templates, 'scope', 'file')
176 1
            );
177 1
            $this->builder->getLogger()->info($message, ['progress' => [$count, $total]]);
178
        }
179
    }
180 1
181 1
    /**
182 1
     * Returns an array of layouts directories.
183 1
     */
184
    protected function getAllLayoutsPaths(): array
185
    {
186
        $paths = [];
187 1
188 1
        // layouts/
189
        if (is_dir($this->config->getLayoutsPath())) {
190
            $paths[] = $this->config->getLayoutsPath();
191 1
        }
192
        // <theme>/layouts/
193
        if ($this->config->hasTheme()) {
194
            $themes = $this->config->getTheme();
195
            foreach ($themes as $theme) {
196
                $paths[] = $this->config->getThemeDirPath($theme);
197 1
            }
198
        }
199 1
        // resources/layouts/
200 1
        if (is_dir($this->config->getInternalLayoutsPath())) {
201 1
            $paths[] = $this->config->getInternalLayoutsPath();
202 1
        }
203
204
        return $paths;
205
    }
206
207
    /**
208
     * Adds global variables.
209
     */
210
    protected function addGlobals()
211 1
    {
212
        $this->builder->getRenderer()->addGlobal('cecil', [
213
            'url'       => \sprintf('https://cecil.app/#%s', Builder::getVersion()),
214
            'version'   => Builder::getVersion(),
215
            'poweredby' => \sprintf('Cecil v%s', Builder::getVersion()),
216 1
        ]);
217
    }
218 1
219
    /**
220
     * Get available output formats.
221
     *
222 1
     * @throws RuntimeException
223
     */
224
    protected function getOutputFormats(Page $page): array
225
    {
226
        // Get page output format(s) if defined.
227
        // ie:
228
        // ```yaml
229 1
        // output: txt
230 1
        // ```
231 1
        if ($page->getVariable('output')) {
232 1
            $formats = $page->getVariable('output');
233
            if (!\is_array($formats)) {
234
                $formats = [$formats];
235
            }
236 1
237
            return $formats;
238
        }
239
240
        // Get available output formats for the page type.
241
        // ie:
242 1
        // ```yaml
243
        // page: [html, json]
244 1
        // ```
245
        $formats = $this->config->get('output.pagetypeformats.' . $page->getType());
246 1
        if (empty($formats)) {
247 1
            throw new RuntimeException('Configuration key "pagetypeformats" can\'t be empty.');
248 1
        }
249 1
        if (!\is_array($formats)) {
250
            $formats = [$formats];
251 1
        }
252 1
253
        return $formats;
254
    }
255
256
    /**
257
     * Get alternates.
258 1
     */
259
    protected function getAlternates(array $formats): array
260
    {
261
        $alternates = [];
262
263
        if (count($formats) > 1 || in_array('html', $formats)) {
264 1
            foreach ($formats as $format) {
265
                $format == 'html' ? $rel = 'canonical' : $rel = 'alternate';
266
                $alternates[] = [
267 1
                    'rel'    => $rel,
268 1
                    'type'   => $this->config->getOutputFormatProperty($format, 'mediatype'),
269 1
                    'title'  => strtoupper($format),
270 1
                    'format' => $format,
271 1
                ];
272 1
            }
273
        }
274
275 1
        return $alternates;
276
    }
277
278
    /**
279
     * Returns the collection of translated pages for a given page.
280
     */
281 1
    protected function getTranslations(Page $refPage): \Cecil\Collection\Page\Collection
282
    {
283
        $pages = $this->builder->getPages()->filter(function (Page $page) use ($refPage) {
284 1
            return $page->getId() !== $refPage->getId()
285
                && $page->getVariable('langref') == $refPage->getVariable('langref')
286 1
                && $page->getType() == $refPage->getType()
287 1
                && !empty($page->getVariable('published'))
288 1
                && !$page->getVariable('paginated');
289 1
        });
290
291
        return $pages;
292
    }
293
}
294