Completed
Pull Request — master (#20)
by Vladimir
02:14
created

PageManager::compilePageView()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 7.5412

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 12
nc 8
nop 2
dl 0
loc 21
ccs 8
cts 15
cp 0.5333
crap 7.5412
rs 8.7624
c 1
b 0
f 0
1
<?php
2
3
namespace allejo\stakx\Manager;
4
5
use allejo\stakx\Exception\TrackedItemNotFoundException;
6
use allejo\stakx\Object\ContentItem;
7
use allejo\stakx\Object\DynamicPageView;
8
use allejo\stakx\Object\PageView;
9
use allejo\stakx\Object\RepeaterPageView;
10
use allejo\stakx\System\FileExplorer;
11
use allejo\stakx\System\Folder;
12
use Twig_Error_Syntax;
13
use Twig_Template;
14
15
/**
16
 * This class is responsible for handling all of the PageViews within a website.
17
 *
18
 * PageManager will parse all available dynamic and static PageViews. After, dynamic PageViews will be prepared by
19
 * setting the appropriate values for each ContentItem such as permalinks. Lastly, this class will compile all of the
20
 * PageViews and write them to the target directory.
21
 *
22
 * @package allejo\stakx\Manager
23
 */
24
class PageManager extends TrackingManager
25
{
26
    /**
27
     * The relative (to the stakx project) file path to the redirect template
28
     *
29
     * @var string|bool
30
     */
31
    private $redirectTemplate;
32
33
    /**
34
     * @var ContentItem[][]
35
     */
36
    private $collections;
37
38
    /**
39
     * @var Folder
40
     */
41
    private $targetDir;
42
43
    private $siteMenu;
44
45
    /**
46
     * @var \Twig_Environment
47
     */
48
    private $twig;
49
50
    /**
51
     * PageManager constructor
52
     */
53 1
    public function __construct()
54
    {
55 1
        parent::__construct();
56
57 1
        $this->siteMenu = array();
58 1
    }
59
60
    public function setCollections (&$collections)
61
    {
62
        if (empty($collections)) { return; }
63
64
        $this->collections = &$collections;
65
    }
66
67 1
    public function setRedirectTemplate ($filePath)
68 1
    {
69 1
        $this->redirectTemplate = $filePath;
70
    }
71
72
    /**
73
     * @param Folder $folder The relative target directory as specified from the configuration file
74
     */
75 1
    public function setTargetFolder (&$folder)
76
    {
77 1
        $this->targetDir = &$folder;
78 1
    }
79
80 1
    public function configureTwig ($configuration, $options)
81
    {
82 1
        $twig = new TwigManager();
83 1
        $twig->configureTwig($configuration, $options);
84
85 1
        $this->twig = TwigManager::getInstance();
86 1
    }
87
88
    /**
89
     * An array representing the website's menu structure with children and grandchildren made from static PageViews
90
     *
91
     * @return array
92
     */
93
    public function getSiteMenu ()
94
    {
95
        return $this->siteMenu;
96
    }
97
98
    /**
99
     * Go through all of the PageView directories and create a respective PageView for each and classify them as a
100
     * dynamic or static PageView.
101
     *
102
     * @param $pageViewFolders
103
     */
104 1
    public function parsePageViews ($pageViewFolders)
105
    {
106 1
        if (empty($pageViewFolders)) { return; }
107
108
        /**
109
         * The name of the folder where PageViews are located
110
         *
111
         * @var $pageViewFolder string
112
         */
113 1
        foreach ($pageViewFolders as $pageViewFolderName)
114
        {
115 1
            $pageViewFolder = $this->fs->absolutePath($pageViewFolderName);
116
117 1
            if (!$this->fs->exists($pageViewFolder))
118 1
            {
119
                continue;
120 1
            }
121
122
            // @TODO Replace this with a regular expression or have wildcard support
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
123 1
            $this->scanTrackableItems($pageViewFolder, array(
124 1
                'refresh' => false,
125
                'fileExplorer' => FileExplorer::INCLUDE_ONLY_FILES
126 1
            ), array('.html', '.twig'));
127 1
            $this->saveFolderDefinition($pageViewFolderName);
128 1
        }
129 1
    }
130
131
    /**
132
     * Compile dynamic and static PageViews
133
     */
134 1
    public function compileAll ()
135
    {
136 1
        foreach (array_keys($this->trackedItemsFlattened) as $filePath)
137
        {
138 1
            $this->compileFromFilePath($filePath);
139 1
        }
140 1
    }
141
142
    public function compileSome ($filter = array())
143
    {
144
        /** @var PageView $pageView */
145
        foreach ($this->trackedItemsFlattened as $pageView)
146
        {
147
            if ($pageView->hasTwigDependency($filter['namespace'], $filter['dependency']))
148
            {
149
                $this->compilePageView($pageView);
150
            }
151
        }
152
    }
153
154
    /**
155
     * @param ContentItem $contentItem
156
     */
157
    public function compileContentItem (&$contentItem)
158
    {
159
        $pageView = $contentItem->getPageView();
160
161
        // This ContentItem doesn't have an individual PageView dedicated to displaying this item
162
        if (is_null($pageView))
163
        {
164
            return;
165
        }
166
167
        $template = $this->createTemplate($pageView);
168
        $contentItem->evaluateFrontMatter(
169
            $pageView->getFrontMatter(false)
170
        );
171
172
        $output = $template->render(array(
173
            'this' => $contentItem
174
        ));
175
176
        $this->targetDir->writeFile($contentItem->getTargetFile(), $output);
177
    }
178
179
    /**
180
     * Add a new ContentItem to the respective parent PageView of the ContentItem
181
     *
182
     * @param ContentItem $contentItem
183
     */
184
    public function updatePageView ($contentItem)
185
    {
186
        /** @var PageView $pageView */
187
        foreach ($this->trackedItems['dynamic'] as &$pageView)
188
        {
189
            $fm = $pageView->getFrontMatter(false);
190
191
            if ($fm['collection'] == $contentItem->getCollection())
192
            {
193
                $pageView->addContentItem($contentItem);
194
            }
195
        }
196
    }
197
198
    /**
199
     * Update an existing Twig variable that's injected globally
200
     *
201
     * @param string $variable
202
     * @param string $value
203
     */
204
    public function updateTwigVariable ($variable, $value)
205
    {
206
        $this->twig->addGlobal($variable, $value);
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function refreshItem($filePath)
213
    {
214
        $this->compileFromFilePath($filePath, true);
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220 1
    protected function handleTrackableItem($filePath, $options = array())
221
    {
222 1
        $pageView  = PageView::create($filePath);
223 1
        $namespace = $pageView->getType();
224
225 1
        if ($namespace == PageView::DYNAMIC_TYPE)
226 1
        {
227
            $frontMatter = $pageView->getFrontMatter(false);
228
            $collection = $frontMatter['collection'];
229
230
            foreach ($this->collections[$collection] as &$item)
231
            {
232
                $item->evaluateFrontMatter($frontMatter);
233
                $pageView->addContentItem($item);
234
            }
235
        }
236
237 1
        $this->addObjectToTracker($pageView, $pageView->getRelativeFilePath(), $namespace);
238 1
        $this->saveTrackerOptions($pageView->getRelativeFilePath(), array(
239
            'viewType' => $namespace
240 1
        ));
241
242 1
        if ($namespace == PageView::STATIC_TYPE)
243 1
        {
244
            $this->addToSiteMenu($pageView);
245
        }
246 1
    }
247
248
    /**
249
     * Compile a given PageView
250
     *
251
     * @param string $filePath The file path to the PageView to compile
252
     * @param bool   $refresh  When set to true, the PageView will reread its contents
253
     *
254
     * @throws \Exception
255
     */
256 1
    private function compileFromFilePath ($filePath, $refresh = false)
257
    {
258 1
        if (!$this->isTracked($filePath))
259 1
        {
260
            throw new TrackedItemNotFoundException('PageView not found');
261
        }
262
263
        /** @var PageView $pageView */
264 1
        $pageView = &$this->trackedItemsFlattened[$filePath];
265
266 1
        $this->compilePageView($pageView, $refresh);
267
268
        // Create redirect files as needed
269 1
        if ($pageView->getType() != PageView::REPEATER_TYPE)
270 1
        {
271
            foreach ($pageView->getRedirects() as $redirect)
0 ignored issues
show
Bug introduced by
The expression $pageView->getRedirects() of type null|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
272
            {
273
                $redirectPageView = PageView::createRedirect(
274
                    $redirect,
275
                    $pageView->getPermalink(),
276 1
                    $this->redirectTemplate
277
                );
278
279
                $this->compilePageView($redirectPageView);
280
            }
281
        }
282 1
    }
283
284
    /**
285
     * @param DynamicPageView|PageView|RepeaterPageView $pageView
286
     * @param bool                                      $refresh
287
     */
288 1
    private function compilePageView ($pageView, $refresh = false)
289
    {
290
        if ($refresh)
291 1
        {
292
            $pageView->refreshFileContent();
293
        }
294
295 1
        switch ($pageView->getType())
296
        {
297 1
            case PageView::REPEATER_TYPE:
298 1
                $this->compileRepeaterPageView($pageView);
0 ignored issues
show
Compatibility introduced by
$pageView of type object<allejo\stakx\Object\PageView> is not a sub-type of object<allejo\stakx\Object\RepeaterPageView>. It seems like you assume a child class of the class allejo\stakx\Object\PageView to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
299 1
                break;
300
301
            case PageView::DYNAMIC_TYPE:
302
                $this->compileDynamicPageView($pageView);
303
                break;
304
305
            case PageView::STATIC_TYPE:
306
                $this->compileStaticPageView($pageView);
307 1
        }
308 1
    }
309
310
    /**
311
     * @param RepeaterPageView $pageView
312
     */
313 1
    private function compileRepeaterPageView (&$pageView)
314
    {
315 1
        $template = $this->createTemplate($pageView);
316
317 1
        foreach ($pageView->getRepeaterPermalinks() as $permalink)
318
        {
319 1
            $pageView->bump();
320
321 1
            $frontMatter = array_merge(
322 1
                $pageView->getFrontMatter(),
323
                array(
324 1
                    'permalink' => $permalink->getEvaluated(),
325 1
                    'iterators' => $permalink->getIterators()
326 1
                )
327 1
            );
328
329 1
            $output = $template->render(array(
330
                'this' => $frontMatter
331 1
            ));
332
333 1
            $this->output->notice("Writing repeater file: {file}", array('file' => $pageView->getTargetFile()));
334 1
            $this->targetDir->writeFile($pageView->getTargetFile(), $output);
335 1
        }
336 1
    }
337
338
    /**
339
     * @param PageView $pageView
340
     */
341
    private function compileDynamicPageView (&$pageView)
342
    {
343
        $template = $this->createTemplate($pageView);
344
345
        $pageViewFrontMatter = $pageView->getFrontMatter(false);
346
        $collection = $pageViewFrontMatter['collection'];
347
348
        /** @var ContentItem $contentItem */
349
        foreach ($this->collections[$collection] as &$contentItem)
350
        {
351
            $output = $template->render(array(
352
                'this' => $contentItem
353
            ));
354
355
            $this->output->notice("Writing file: {file}", array('file' => $contentItem->getTargetFile()));
356
            $this->targetDir->writeFile($contentItem->getTargetFile(), $output);
357
        }
358
    }
359
360
    /**
361
     * @param PageView $pageView
362
     */
363
    private function compileStaticPageView (&$pageView)
364
    {
365
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
366
367
        $template = $this->createTemplate($pageView);
368
        $output = $template->render(array(
369
            'this' => $pageView->getFrontMatter()
370
        ));
371
372
        $this->output->notice("Writing file: {file}", array('file' => $pageView->getTargetFile()));
373
        $this->targetDir->writeFile($pageView->getTargetFile(), $output);
374
    }
375
376
    /**
377
     * Add a static PageView to the menu array. Dynamic PageViews are not added to the menu
378
     *
379
     * @param PageView $pageView
380
     */
381
    private function addToSiteMenu (&$pageView)
382
    {
383
        $frontMatter = $pageView->getFrontMatter();
384
385
        if (!array_key_exists('permalink', $frontMatter) ||
386
            (array_key_exists('menu', $frontMatter) && !$frontMatter['menu']))
387
        {
388
            return;
389
        }
390
391
        $url = $pageView->getPermalink();
392
        $root = &$this->siteMenu;
393
        $permalink = trim($url, DIRECTORY_SEPARATOR);
394
        $dirs = explode(DIRECTORY_SEPARATOR, $permalink);
395
396
        while (count($dirs) > 0)
397
        {
398
            $name = array_shift($dirs);
399
            $name = (!empty($name)) ? $name : '.';
400
401
            if (!is_null($name) && count($dirs) == 0)
402
            {
403
                $children = array();
404
405
                if (array_key_exists($name, $root) && is_array($root[$name]))
406
                {
407
                    $children = $root[$name]['children'];
408
                }
409
410
                $root[$name] = &$pageView;
411
                $root = &$root[$name]->getChildren();
412
413
                if (!empty($children))
414
                {
415
                    $root = $children;
416
                }
417
            }
418
            else
419
            {
420
                $root[$name]['children'] = array();
421
                $root = &$root[$name]['children'];
422
            }
423
        }
424
    }
425
426
    /**
427
     * @param PageView $pageView
428
     *
429
     * @return Twig_Template
430
     * @throws Twig_Error_Syntax
431
     */
432 1
    private function createTemplate ($pageView)
433
    {
434
        try
435
        {
436 1
            return $this->twig->createTemplate($pageView->getContent());
437
        }
438
        catch (Twig_Error_Syntax $e)
439
        {
440
            $e->setTemplateName($pageView->getRelativeFilePath());
441
442
            throw $e;
443
        }
444
    }
445
}