Passed
Pull Request — master (#1704)
by Arnaud
11:53 queued 04:45
created

Render::process()   F

Complexity

Conditions 19
Paths 1515

Size

Total Lines 135
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 74
CRAP Score 19.98

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 19
eloc 76
c 3
b 0
f 0
nc 1515
nop 0
dl 0
loc 135
ccs 74
cts 86
cp 0.8605
crap 19.98
rs 0.3499

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\Page;
18
use Cecil\Collection\Page\Type;
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
        // prepares pages collections
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
            // enriched variables
73 1
            ->map(function (Page $page) {
74 1
                $formats = $this->getOutputFormats($page);
75
                // alternates formats
76 1
                $page->setVariable('output', $formats);
77 1
                $page->setVariable('alternates', $this->getAlternates($formats));
78
                // translations
79 1
                $page->setVariable('translations', $this->getTranslations($page));
80
                // parent
81 1
                if (($parent = $this->findParent($page)) !== null) {
82 1
                    $page->setParent($parent);
83
                }
84
85 1
                return $page;
86 1
            });
87
88
        // loads post processors
89 1
        $postprocessors = [];
90 1
        foreach ($this->config->get('output.postprocessors') as $name => $postprocessor) {
91 1
            if (!class_exists($postprocessor)) {
92 1
                $this->builder->getLogger()->error(sprintf('Unable to load output post processor "%s"', $postprocessor));
93 1
                break;
94
            }
95 1
            $postprocessors[] = new $postprocessor($this->builder);
96 1
            $this->builder->getLogger()->debug(sprintf('Output post processor "%s" loaded', $name));
97
        }
98
99
        // renders each page
100 1
        $total = \count($pages);
101 1
        $count = 0;
102 1
        foreach ($pages as $page) {
103 1
            $count++;
104 1
            $rendered = [];
105
106
            // l10n
107 1
            $language = $page->getVariable('language', $this->config->getLanguageDefault());
108 1
            $locale = $this->config->getLanguageProperty('locale', $language);
109 1
            $this->builder->getRenderer()->setLocale($locale);
110
111
            // global site variables
112 1
            $this->builder->getRenderer()->addGlobal('site', new Site($this->builder, $language));
113
114
            // global config variables
115 1
            $this->builder->getRenderer()->addGlobal('config', new Config($this->builder, $language));
116
117
            // excluded format(s)?
118 1
            $formats = (array) $page->getVariable('output');
119 1
            foreach ($formats as $key => $format) {
120 1
                if ($exclude = $this->config->getOutputFormatProperty($format, 'exclude')) {
121
                    // ie:
122
                    //   formats:
123
                    //     atom:
124
                    //       [...]
125
                    //       exclude: [paginated]
126 1
                    if (!\is_array($exclude)) {
127
                        $exclude = [$exclude];
128
                    }
129 1
                    foreach ($exclude as $variable) {
130 1
                        if ($page->hasVariable($variable)) {
131 1
                            unset($formats[$key]);
132
                        }
133
                    }
134
                }
135
            }
136
137
            // renders each output format
138 1
            foreach ($formats as $format) {
139
                // search for the template
140 1
                $layout = Layout::finder($page, $format, $this->config);
141
                // renders with Twig
142
                try {
143 1
                    $deprecations = [];
144 1
                    set_error_handler(function ($type, $msg) use (&$deprecations) {
145 1
                        if (E_USER_DEPRECATED === $type) {
146
                            $deprecations[] = $msg;
147
                        }
148 1
                    });
149 1
                    $output = $this->builder->getRenderer()->render($layout['file'], ['page' => $page]);
150 1
                    foreach ($deprecations as $value) {
151
                        $this->builder->getLogger()->warning($value);
152
                    }
153 1
                    foreach ($postprocessors as $postprocessor) {
154 1
                        $output = $postprocessor->process($page, $output, $format);
155
                    }
156 1
                    $rendered[$format] = [
157 1
                        'output'   => $output,
158 1
                        'template' => [
159 1
                            'scope' => $layout['scope'],
160 1
                            'file'  => $layout['file'],
161 1
                        ],
162 1
                    ];
163 1
                    $page->addRendered($rendered);
164
                    // profiler
165 1
                    if ($this->builder->isDebug()) {
166 1
                        $dumper = new \Twig\Profiler\Dumper\HtmlDumper();
167 1
                        file_put_contents(
168 1
                            Util::joinFile($this->config->getOutputPath(), '_debug_twig_profile.html'),
169 1
                            $dumper->dump($this->builder->getRenderer()->getDebugProfile())
170 1
                        );
171
                    }
172
                } catch (\Twig\Error\Error $e) {
173
                    $template = !empty($e->getSourceContext()->getPath()) ? $e->getSourceContext()->getPath() : $e->getSourceContext()->getName();
174
175
                    throw new RuntimeException(sprintf(
176
                        'Template "%s%s" (page: %s): %s',
177
                        $template,
178
                        $e->getTemplateLine() >= 0 ? sprintf(':%s', $e->getTemplateLine()) : '',
179
                        $page->getId(),
180
                        $e->getMessage()
181
                    ));
182
                }
183
            }
184 1
            $this->builder->getPages()->replace($page->getId(), $page);
185
186 1
            $templates = array_column($rendered, 'template');
187 1
            $message = sprintf(
188 1
                'Page "%s" rendered with [%s]',
189 1
                $page->getId() ?: 'index',
190 1
                Util\Str::combineArrayToString($templates, 'scope', 'file')
191 1
            );
192 1
            $this->builder->getLogger()->info($message, ['progress' => [$count, $total]]);
193
        }
194
    }
195
196
    /**
197
     * Returns an array of layouts directories.
198
     */
199 1
    protected function getAllLayoutsPaths(): array
200
    {
201 1
        $paths = [];
202
203
        // layouts/
204 1
        if (is_dir($this->config->getLayoutsPath())) {
205 1
            $paths[] = $this->config->getLayoutsPath();
206
        }
207
        // <theme>/layouts/
208 1
        if ($this->config->hasTheme()) {
209 1
            $themes = $this->config->getTheme();
210 1
            foreach ($themes as $theme) {
211 1
                $paths[] = $this->config->getThemeDirPath($theme);
212
            }
213
        }
214
        // resources/layouts/
215 1
        if (is_dir($this->config->getLayoutsInternalPath())) {
216 1
            $paths[] = $this->config->getLayoutsInternalPath();
217
        }
218
219 1
        return $paths;
220
    }
221
222
    /**
223
     * Adds global variables.
224
     */
225 1
    protected function addGlobals()
226
    {
227 1
        $this->builder->getRenderer()->addGlobal('cecil', [
228 1
            'url'       => sprintf('https://cecil.app/#%s', Builder::getVersion()),
229 1
            'version'   => Builder::getVersion(),
230 1
            'poweredby' => sprintf('Cecil v%s', Builder::getVersion()),
231 1
        ]);
232
    }
233
234
    /**
235
     * Get available output formats.
236
     *
237
     * @throws RuntimeException
238
     */
239 1
    protected function getOutputFormats(Page $page): array
240
    {
241
        // Get page output format(s) if defined.
242
        // ie:
243
        // ```yaml
244
        // output: txt
245
        // ```
246 1
        if ($page->getVariable('output')) {
247 1
            $formats = $page->getVariable('output');
248 1
            if (!\is_array($formats)) {
249 1
                $formats = [$formats];
250
            }
251
252 1
            return $formats;
253
        }
254
255
        // Get available output formats for the page type.
256
        // ie:
257
        // ```yaml
258
        // page: [html, json]
259
        // ```
260 1
        $formats = $this->config->get('output.pagetypeformats.' . $page->getType());
261 1
        if (empty($formats)) {
262
            throw new RuntimeException('Configuration key "pagetypeformats" can\'t be empty.');
263
        }
264 1
        if (!\is_array($formats)) {
265
            $formats = [$formats];
266
        }
267
268 1
        return $formats;
269
    }
270
271
    /**
272
     * Get alternates.
273
     */
274 1
    protected function getAlternates(array $formats): array
275
    {
276 1
        $alternates = [];
277
278 1
        if (\count($formats) > 1 || \in_array('html', $formats)) {
279 1
            foreach ($formats as $format) {
280 1
                $format == 'html' ? $rel = 'canonical' : $rel = 'alternate';
281 1
                $alternates[] = [
282 1
                    'rel'    => $rel,
283 1
                    'type'   => $this->config->getOutputFormatProperty($format, 'mediatype'),
284 1
                    'title'  => strtoupper($format),
285 1
                    'format' => $format,
286 1
                ];
287
            }
288
        }
289
290 1
        return $alternates;
291
    }
292
293
    /**
294
     * Returns the collection of translated pages for a given page.
295
     */
296 1
    protected function getTranslations(Page $refPage): \Cecil\Collection\Page\Collection
297
    {
298 1
        $pages = $this->builder->getPages()->filter(function (Page $page) use ($refPage) {
299 1
            return $page->getId() !== $refPage->getId()
300 1
                && $page->getVariable('langref') == $refPage->getVariable('langref')
301 1
                && $page->getType() == $refPage->getType()
302 1
                && !empty($page->getVariable('published'))
303 1
                && !$page->getVariable('paginated');
304 1
        });
305
306 1
        return $pages;
307
    }
308
309
    /**
310
     * Find page parent or null.
311
     */
312 1
    protected function findParent(Page $page): ?Page
313
    {
314 1
        $parent = null;
315 1
        $langPrefix = '';
316 1
        if ($page->getVariable('language') !== null && $page->getVariable('language') != $this->config->getLanguageDefault()) {
317 1
            $langPrefix = $page->getVariable('language') . "/";
318
        }
319
        // home page by default
320 1
        if ($page->getType() !== Type::HOMEPAGE->value) {
321 1
            $parent = $this->builder->getPages()->get($langPrefix . 'index');
322
        }
323
        // recursive folder search
324 1
        $folderAsArray = explode('/', (string) $page->getFolder());
325 1
        while (\count($folderAsArray) >= 1 && !empty($folderAsArray[0])) {
326 1
            $parentFolder = implode('/', $folderAsArray);
327 1
            if ($this->builder->getPages()->has($langPrefix . $parentFolder) && $this->builder->getPages()->get($langPrefix . $parentFolder)->getId() !== $page->getId()) {
328 1
                $parent = $this->builder->getPages()->get($langPrefix . $parentFolder);
329 1
                break;
330
            }
331 1
            array_pop($folderAsArray);
332
        }
333
334 1
        return $parent;
335
    }
336
}
337