Passed
Pull Request — master (#1013)
by lee
07:38
created

PagesRender::process()   F

Complexity

Conditions 16
Paths 737

Size

Total Lines 116
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 58
CRAP Score 16.3198

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 16
eloc 65
c 5
b 0
f 0
nc 737
nop 0
dl 0
loc 116
ccs 58
cts 65
cp 0.8923
crap 16.3198
rs 1.7652

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
 * This file is part of the Cecil/Cecil package.
4
 *
5
 * Copyright (c) Arnaud Ligny <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Cecil\Step;
12
13
use Cecil\Builder;
14
use Cecil\Collection\Page\Page;
15
use Cecil\Collection\Page\PrefixSuffix;
16
use Cecil\Exception\Exception;
17
use Cecil\Renderer\Layout;
18
use Cecil\Renderer\Site;
19
use Cecil\Renderer\Twig;
20
use Cecil\Util;
21
22
/**
23
 * Pages rendering.
24
 */
25
class PagesRender extends AbstractStep
26
{
27
    /**
28
     * {@inheritdoc}
29
     */
30 1
    public function getName(): string
31
    {
32 1
        return 'Rendering pages';
33
    }
34
35
    /**
36
     * {@inheritdoc}
37
     *
38
     * @throws Exception
39
     */
40 1
    public function init($options)
41
    {
42 1
        if (!is_dir($this->config->getLayoutsPath()) && !$this->config->hasTheme()) {
43
            $message = sprintf(
44
                "'%s' is not a valid layouts directory",
45
                $this->config->getLayoutsPath()
46
            );
47
            $this->builder->getLogger()->debug($message);
48
        }
49
50 1
        $this->canProcess = true;
51 1
    }
52
53
    /**
54
     * {@inheritdoc}
55
     *
56
     * @throws Exception
57
     */
58 1
    public function process()
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
        // collects published pages
67
        /** @var Page $page */
68
        $filteredPages = $this->builder->getPages()->filter(function (Page $page) {
69 1
            return !empty($page->getVariable('published'));
70 1
        });
71 1
        $max = count($filteredPages);
72
73
        // renders each page
74 1
        $count = 0;
75
        /** @var Page $page */
76 1
        foreach ($filteredPages as $page) {
77 1
            $count++;
78 1
            $rendered = [];
79
80
            // i18n
81 1
            $pageLang = $page->getVariable('language');
82 1
            $locale = $this->config->getLanguageProperty('locale', $pageLang);
83
            // the PHP Intl extension is needed to use localized date
84 1
            if (extension_loaded('intl')) {
85 1
                \Locale::setDefault($locale);
86
            }
87
            // the PHP Gettext extension is needed to use translation
88 1
            if (extension_loaded('gettext')) {
89 1
                $localePath = realpath(Util::joinFile($this->config->getSourceDir(), 'locale'));
90 1
                $domain = 'messages';
91 1
                putenv("LC_ALL=$locale");
92 1
                putenv("LANGUAGE=$locale");
93 1
                setlocale(LC_ALL, "$locale.UTF-8");
94 1
                bindtextdomain($domain, $localePath);
95
            }
96
97
            // global site variables
98 1
            $this->builder->getRenderer()->addGlobal('site', new Site($this->builder, $pageLang));
99
100
            // get Page's output formats
101 1
            $formats = $this->getOutputFormats($page);
102 1
            $page->setVariable('output', $formats);
103
104
            // excluded format(s)?
105 1
            foreach ($formats as $key => $format) {
106 1
                if ($exclude = $this->config->getOutputFormatProperty($format, 'exclude')) {
107
                    // ie:
108
                    //   formats:
109
                    //     atom:
110
                    //       [...]
111
                    //       exclude: [paginated]
112 1
                    if (!is_array($exclude)) {
113
                        $exclude = [$exclude];
114
                    }
115 1
                    foreach ($exclude as $variable) {
116 1
                        if ($page->hasVariable($variable)) {
117 1
                            unset($formats[$key]);
118
                        }
119
                    }
120
                }
121
            }
122
123
            // get and set alternates links
124 1
            $page->setVariable('alternates', $this->getAlternates($formats));
125
126
            // renders each output format
127 1
            foreach ($formats as $format) {
128
                // search for the template
129 1
                $layout = Layout::finder($page, $format, $this->config);
130
                // renders with Twig
131
                try {
132 1
                    $deprecations = [];
133
                    set_error_handler(function ($type, $msg) use (&$deprecations) {
134 1
                        if (E_USER_DEPRECATED === $type) {
135 1
                            $deprecations[] = $msg;
136
                        }
137 1
                    });
138 1
                    $output = $this->builder->getRenderer()->render($layout['file'], ['page' => $page]);
139 1
                    foreach ($deprecations as $value) {
140 1
                        $this->builder->getLogger()->warning($value);
141
                    }
142 1
                    $output = $this->postProcessOutput($output, $page, $format);
143 1
                    $rendered[$format]['output'] = $output;
144 1
                    $rendered[$format]['template']['scope'] = $layout['scope'];
145 1
                    $rendered[$format]['template']['file'] = $layout['file'];
146
                    // profiler
147 1
                    if (getenv('CECIL_DEBUG') == 'true') {
148 1
                        $dumper = new \Twig\Profiler\Dumper\HtmlDumper();
149 1
                        file_put_contents(
150 1
                            Util::joinFile($this->config->getOutputPath(), '_debug_twig_profile.html'),
151 1
                            $dumper->dump($this->builder->getRenderer()->profile)
152
                        );
153
                    }
154
                } catch (\Twig\Error\Error $e) {
155
                    throw new Exception(sprintf(
156
                        'Template "%s"%s (for page "%s"): %s',
157
                        $e->getSourceContext()->getPath(),
158
                        $e->getTemplateLine() >= 0 ? sprintf(' line %s', $e->getTemplateLine()) : '',
159
                        $page->getId(),
160 1
                        $e->getMessage()
161
                    ));
162
                }
163
            }
164 1
            $page->setVariable('rendered', $rendered);
165 1
            $this->builder->getPages()->replace($page->getId(), $page);
166
167 1
            $templates = array_column($rendered, 'template');
168 1
            $message = sprintf(
169 1
                '%s [%s]',
170 1
                ($page->getId() ?: 'index'),
171 1
                Util::combineArrayToString($templates, 'scope', 'file')
172
            );
173 1
            $this->builder->getLogger()->info($message, ['progress' => [$count, $max]]);
174
        }
175 1
    }
176
177
    /**
178
     * Returns an array of layouts directories.
179
     *
180
     * @return array
181
     */
182 1
    protected function getAllLayoutsPaths(): array
183
    {
184 1
        $paths = [];
185
186
        // layouts/
187 1
        if (is_dir($this->config->getLayoutsPath())) {
188 1
            $paths[] = $this->config->getLayoutsPath();
189
        }
190
        // <theme>/layouts/
191 1
        if ($this->config->hasTheme()) {
192 1
            $themes = $this->config->getTheme();
193 1
            foreach ($themes as $theme) {
194 1
                $paths[] = $this->config->getThemeDirPath($theme);
195
            }
196
        }
197
        // resources/layouts/
198 1
        if (is_dir($this->config->getInternalLayoutsPath())) {
199 1
            $paths[] = $this->config->getInternalLayoutsPath();
200
        }
201
202 1
        return $paths;
203
    }
204
205
    /**
206
     * Adds global variables.
207
     */
208 1
    protected function addGlobals()
209
    {
210 1
        $this->builder->getRenderer()->addGlobal('cecil', [
211 1
            'url'       => sprintf('https://cecil.app/#%s', Builder::getVersion()),
212 1
            'version'   => Builder::getVersion(),
213 1
            'poweredby' => sprintf('Cecil v%s', Builder::getVersion()),
214
        ]);
215 1
    }
216
217
    /**
218
     * Get available output formats.
219
     *
220
     * @param Page $page
221
     *
222
     * @return array
223
     */
224 1
    protected function getOutputFormats(Page $page): array
225
    {
226
        // Get available output formats for the page type.
227
        // ie:
228
        //   page: [html, json]
229 1
        $formats = $this->config->get('output.pagetypeformats.'.$page->getType());
230
231 1
        if (empty($formats)) {
232
            throw new Exception('Configuration key "pagetypeformats" can\'t be empty.');
233
        }
234
235 1
        if (!\is_array($formats)) {
236
            $formats = [$formats];
237
        }
238
239
        // Get page output format(s).
240
        // ie:
241
        //   output: txt
242 1
        if ($page->getVariable('output')) {
243 1
            $formats = $page->getVariable('output');
244 1
            if (!\is_array($formats)) {
245 1
                $formats = [$formats];
246
            }
247
        }
248
249 1
        return $formats;
250
    }
251
252
    /**
253
     * Get alternates.
254
     *
255
     * @param array $formats
256
     *
257
     * @return array
258
     */
259 1
    protected function getAlternates(array $formats): array
260
    {
261 1
        $alternates = [];
262
263 1
        if (count($formats) > 1 || in_array('html', $formats)) {
264 1
            foreach ($formats as $format) {
265 1
                $format == 'html' ? $rel = 'canonical' : $rel = 'alternate';
266 1
                $alternates[] = [
267 1
                    'rel'    => $rel,
268 1
                    'type'   => $this->config->getOutputFormatProperty($format, 'mediatype'),
269 1
                    'title'  => strtoupper($format),
270 1
                    'format' => $format,
271
                ];
272
            }
273
        }
274
275 1
        return $alternates;
276
    }
277
278
    /**
279
     * Apply post rendering on output.
280
     *
281
     * @param string $rendered
282
     * @param Page   $page
283
     * @param string $format
284
     *
285
     * @return string
286
     */
287 1
    private function postProcessOutput(string $rendered, Page $page, string $format): string
288
    {
289
        switch ($format) {
290 1
            case 'html':
291
                // add generator meta tag
292 1
                if (!preg_match('/<meta name="generator".*/i', $rendered)) {
293 1
                    $meta = \sprintf('<meta name="generator" content="Cecil %s" />', Builder::getVersion());
294
                    $rendered = preg_replace_callback('/([[:blank:]]+)(<\/head>)/i', function ($matches) use ($meta) {
295 1
                        return str_repeat($matches[1], 2).$meta."\n".$matches[1].$matches[2];
296 1
                    }, $rendered);
297
                }
298
                // replace excerpt or break tag by HTML anchor
299
                // https://regex101.com/r/Xl7d5I/3
300 1
                $pattern = '(.*)(<!--[[:blank:]]?(excerpt|break)[[:blank:]]?-->)(.*)';
301 1
                $replacement = '$1<span id="more"></span>$4';
302 1
                $rendered = preg_replace('/'.$pattern.'/is', $replacement, $rendered);
303
        }
304
305
        // replace internal link to *.md files with the right URL
306
        // https://regex101.com/r/dZ02zO/5
307 1
        $replace = 'href="../%s/%s"';
308 1
        if (empty($page->getFolder())) {
309 1
            $replace = 'href="%s/%s"';
310
        }
311 1
        $rendered = preg_replace_callback(
312 1
            '/href="([A-Za-z0-9_\.\-\/]+)\.md(\#[A-Za-z0-9\-]+)?"/is',
313
            function ($matches) use ($replace) {
314 1
                return \sprintf($replace, Page::slugify(PrefixSuffix::sub($matches[1])), $matches[2] ?? '');
315 1
            },
316 1
            $rendered
317
        );
318
319 1
        return $rendered;
320
    }
321
}
322