Passed
Pull Request — master (#1676)
by Arnaud
05:45
created

Render::process()   F

Complexity

Conditions 17
Paths 849

Size

Total Lines 127
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 66
CRAP Score 18.8357

Importance

Changes 0
Metric Value
cc 17
eloc 71
c 0
b 0
f 0
nc 849
nop 0
dl 0
loc 127
ccs 66
cts 81
cp 0.8148
crap 18.8357
rs 1.2597

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