Passed
Pull Request — master (#1676)
by Arnaud
09:49 queued 03:17
created

Render::process()   F

Complexity

Conditions 17
Paths 881

Size

Total Lines 127
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 69
CRAP Score 17.9387

Importance

Changes 6
Bugs 1 Features 1
Metric Value
cc 17
eloc 71
c 6
b 1
f 1
nc 881
nop 0
dl 0
loc 127
ccs 69
cts 81
cp 0.8519
crap 17.9387
rs 1.2152

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
        /** @var Page $page */
89 1
        foreach ($pages as $page) {
90 1
            $count++;
91 1
            $rendered = [];
92
93
            // l10n
94 1
            $language = $page->getVariable('language', $this->config->getLanguageDefault());
95 1
            $locale = $this->config->getLanguageProperty('locale', $language);
96 1
            $this->builder->getRenderer()->setLocale($locale);
97
98
            // global site variables
99 1
            $this->builder->getRenderer()->addGlobal('site', new Site($this->builder, $language));
100
101
            // global config raw variables
102 1
            $this->builder->getRenderer()->addGlobal('config', new Config($this->builder, $language));
103
104
            // excluded format(s)?
105 1
            $formats = (array) $page->getVariable('output');
106 1
            foreach ($formats as $key => $format) {
107 1
                if ($exclude = $this->config->getOutputFormatProperty($format, 'exclude')) {
108
                    // ie:
109
                    //   formats:
110
                    //     atom:
111
                    //       [...]
112
                    //       exclude: [paginated]
113 1
                    if (!is_array($exclude)) {
114
                        $exclude = [$exclude];
115
                    }
116 1
                    foreach ($exclude as $variable) {
117 1
                        if ($page->hasVariable($variable)) {
118 1
                            unset($formats[$key]);
119
                        }
120
                    }
121
                }
122
            }
123
124
            // renders each output format
125 1
            foreach ($formats as $format) {
126
                // search for the template
127 1
                $layout = Layout::finder($page, $format, $this->config);
128
                // renders with Twig
129
                try {
130 1
                    $deprecations = [];
131 1
                    set_error_handler(function ($type, $msg) use (&$deprecations) {
132 1
                        if (E_USER_DEPRECATED === $type) {
133 1
                            $deprecations[] = $msg;
134
                        }
135 1
                    });
136 1
                    $output = $this->builder->getRenderer()->render($layout['file'], ['page' => $page]);
137 1
                    foreach ($deprecations as $value) {
138 1
                        $this->builder->getLogger()->warning($value);
139
                    }
140 1
                    Util::autoload($this->builder, 'postprocessors');
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