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

Render::process()   F

Complexity

Conditions 16
Paths 601

Size

Total Lines 120
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 16.427

Importance

Changes 6
Bugs 2 Features 1
Metric Value
cc 16
eloc 67
c 6
b 2
f 1
nc 601
nop 0
dl 0
loc 120
rs 1.9541
ccs 52
cts 59
cp 0.8814
crap 16.427

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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