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

PageManager::compileRepeaterPageView()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 1
dl 0
loc 24
ccs 16
cts 16
cp 1
crap 2
rs 8.9713
c 0
b 0
f 0
1
<?php
2
3
namespace allejo\stakx\Manager;
4
5
use allejo\stakx\Exception\TrackedItemNotFoundException;
6
use allejo\stakx\FrontMatter\ExpandedValue;
7
use allejo\stakx\Object\ContentItem;
8
use allejo\stakx\Object\DynamicPageView;
9
use allejo\stakx\Object\PageView;
10
use allejo\stakx\Object\RepeaterPageView;
11
use allejo\stakx\System\FileExplorer;
12
use allejo\stakx\System\Folder;
13
use Twig_Error_Syntax;
14
use Twig_Template;
15
16
/**
17
 * This class is responsible for handling all of the PageViews within a website.
18
 *
19
 * PageManager will parse all available dynamic and static PageViews. After, dynamic PageViews will be prepared by
20
 * setting the appropriate values for each ContentItem such as permalinks. Lastly, this class will compile all of the
21
 * PageViews and write them to the target directory.
22
 *
23
 * @package allejo\stakx\Manager
24
 */
25
class PageManager extends TrackingManager
26
{
27
    /**
28
     * The relative (to the stakx project) file path to the redirect template
29
     *
30
     * @var string|bool
31
     */
32
    private $redirectTemplate;
33
34
    /**
35
     * @var ContentItem[][]
36
     */
37
    private $collections;
38
39
    /**
40
     * @var Folder
41
     */
42
    private $targetDir;
43
44
    private $siteMenu;
45
46
    /**
47
     * @var \Twig_Environment
48
     */
49
    private $twig;
50
51
    /**
52
     * PageManager constructor
53
     */
54 1
    public function __construct()
55
    {
56 1
        parent::__construct();
57
58 1
        $this->siteMenu = array();
59 1
    }
60
61
    public function setCollections (&$collections)
62
    {
63
        if (empty($collections)) { return; }
64
65
        $this->collections = &$collections;
66
    }
67
68 1
    public function setRedirectTemplate ($filePath)
69 1
    {
70
        $this->redirectTemplate = $filePath;
71
    }
72
73
    /**
74
     * @param Folder $folder The relative target directory as specified from the configuration file
75
     */
76 1
    public function setTargetFolder (&$folder)
77
    {
78 1
        $this->targetDir = &$folder;
79 1
    }
80
81 1
    public function configureTwig ($configuration, $options)
82
    {
83 1
        $twig = new TwigManager();
84 1
        $twig->configureTwig($configuration, $options);
85
86 1
        $this->twig = TwigManager::getInstance();
87 1
    }
88
89
    /**
90
     * An array representing the website's menu structure with children and grandchildren made from static PageViews
91
     *
92
     * @return array
93
     */
94
    public function getSiteMenu ()
95
    {
96
        return $this->siteMenu;
97
    }
98
99
    /**
100
     * Go through all of the PageView directories and create a respective PageView for each and classify them as a
101
     * dynamic or static PageView.
102
     *
103
     * @param $pageViewFolders
104
     */
105 1
    public function parsePageViews ($pageViewFolders)
106
    {
107 1
        if (empty($pageViewFolders)) { return; }
108
109
        /**
110
         * The name of the folder where PageViews are located
111
         *
112
         * @var $pageViewFolder string
113
         */
114 1
        foreach ($pageViewFolders as $pageViewFolderName)
115
        {
116 1
            $pageViewFolder = $this->fs->absolutePath($pageViewFolderName);
117
118 1
            if (!$this->fs->exists($pageViewFolder))
119 1
            {
120 1
                continue;
121
            }
122
123
            // @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...
124 1
            $this->scanTrackableItems($pageViewFolder, array(
125 1
                'refresh' => false,
126
                'fileExplorer' => FileExplorer::INCLUDE_ONLY_FILES
127 1
            ), array('.html', '.twig'));
128 1
            $this->saveFolderDefinition($pageViewFolderName);
129 1
        }
130 1
    }
131
132
    /**
133
     * Compile dynamic and static PageViews
134
     */
135 1
    public function compileAll ()
136
    {
137 1
        foreach (array_keys($this->trackedItemsFlattened) as $filePath)
138
        {
139 1
            $this->compileFromFilePath($filePath);
140 1
        }
141 1
    }
142
143
    public function compileSome ($filter = array())
144
    {
145
        /** @var PageView $pageView */
146
        foreach ($this->trackedItemsFlattened as $pageView)
147
        {
148
            if ($pageView->hasTwigDependency($filter['namespace'], $filter['dependency']))
149
            {
150
                $this->compilePageView($pageView);
151
            }
152
        }
153
    }
154
155
    /**
156
     * @param ContentItem $contentItem
157
     */
158
    public function compileContentItem (&$contentItem)
159
    {
160
        $pageView = $contentItem->getPageView();
161
162
        // This ContentItem doesn't have an individual PageView dedicated to displaying this item
163
        if (is_null($pageView))
164
        {
165
            return;
166
        }
167
168
        $template = $this->createTemplate($pageView);
169
        $contentItem->evaluateFrontMatter(
170
            $pageView->getFrontMatter(false)
171
        );
172
173
        $output = $template->render(array(
174
            'this' => $contentItem
175
        ));
176
177
        $this->targetDir->writeFile($contentItem->getTargetFile(), $output);
178
    }
179
180
    /**
181
     * Add a new ContentItem to the respective parent PageView of the ContentItem
182
     *
183
     * @param ContentItem $contentItem
184
     */
185
    public function updatePageView ($contentItem)
186
    {
187
        /** @var DynamicPageView $pageView */
188
        foreach ($this->trackedItems['dynamic'] as &$pageView)
189
        {
190
            $fm = $pageView->getFrontMatter(false);
191
192
            if ($fm['collection'] == $contentItem->getCollection())
193
            {
194
                $pageView->addContentItem($contentItem);
195
            }
196
        }
197
    }
198
199
    /**
200
     * Update an existing Twig variable that's injected globally
201
     *
202
     * @param string $variable
203
     * @param string $value
204
     */
205
    public function updateTwigVariable ($variable, $value)
206
    {
207
        $this->twig->addGlobal($variable, $value);
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function refreshItem($filePath)
214
    {
215
        $this->compileFromFilePath($filePath, true);
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 1
    protected function handleTrackableItem($filePath, $options = array())
222
    {
223 1
        $pageView  = PageView::create($filePath);
224 1
        $namespace = $pageView->getType();
225
226 1
        if ($namespace == PageView::DYNAMIC_TYPE)
227 1
        {
228
            $frontMatter = $pageView->getFrontMatter(false);
229
            $collection = $frontMatter['collection'];
230
231
            foreach ($this->collections[$collection] as &$item)
232
            {
233
                $item->evaluateFrontMatter($frontMatter);
234
                $pageView->addContentItem($item);
235
            }
236
        }
237
238 1
        $this->addObjectToTracker($pageView, $pageView->getRelativeFilePath(), $namespace);
239 1
        $this->saveTrackerOptions($pageView->getRelativeFilePath(), array(
240
            'viewType' => $namespace
241 1
        ));
242
243 1
        if ($namespace == PageView::STATIC_TYPE)
244 1
        {
245
            $this->addToSiteMenu($pageView);
246
        }
247 1
    }
248
249
    /**
250
     * Compile a given PageView
251
     *
252
     * @param string $filePath The file path to the PageView to compile
253
     * @param bool   $refresh  When set to true, the PageView will reread its contents
254
     *
255
     * @throws \Exception
256
     */
257 1
    private function compileFromFilePath ($filePath, $refresh = false)
258
    {
259 1
        if (!$this->isTracked($filePath))
260 1
        {
261
            throw new TrackedItemNotFoundException('PageView not found');
262
        }
263
264
        /** @var PageView $pageView */
265 1
        $pageView = &$this->trackedItemsFlattened[$filePath];
266
267 1
        $this->compilePageView($pageView, $refresh);
268 1
    }
269
270
    /**
271
     * @param DynamicPageView|PageView|RepeaterPageView $pageView
272
     * @param bool                                      $refresh
273
     */
274 1
    private function compilePageView ($pageView, $refresh = false)
275
    {
276 1
        if ($refresh)
277 1
        {
278
            $pageView->refreshFileContent();
279
        }
280
281 1
        switch ($pageView->getType())
282
        {
283 1
            case PageView::REPEATER_TYPE:
284 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...
285 1
                $this->compileExpandedRedirects($pageView);
286 1
                break;
287
288
            case PageView::DYNAMIC_TYPE:
289
                $this->compileDynamicPageView($pageView);
290
                $this->compileNormalRedirects($pageView);
291
                break;
292
293
            case PageView::STATIC_TYPE:
294
                $this->compileStaticPageView($pageView);
295
                $this->compileNormalRedirects($pageView);
296
                break;
297 1
        }
298 1
    }
299
300
    /**
301
     * @param RepeaterPageView $pageView
302
     */
303 1
    private function compileRepeaterPageView (&$pageView)
304
    {
305 1
        $template = $this->createTemplate($pageView);
306
307 1
        foreach ($pageView->getRepeaterPermalinks() as $permalink)
0 ignored issues
show
Bug introduced by
The expression $pageView->getRepeaterPermalinks() of type object<allejo\stakx\FrontMatter\ExpandedValue> is not traversable.
Loading history...
308
        {
309 1
            $pageView->bumpPermalink();
310
311 1
            $frontMatter = array_merge(
312 1
                $pageView->getFrontMatter(),
313
                array(
314 1
                    'permalink' => $permalink->getEvaluated(),
315 1
                    'iterators' => $permalink->getIterators()
316 1
                )
317 1
            );
318
319 1
            $output = $template->render(array(
320
                'this' => $frontMatter
321 1
            ));
322
323 1
            $this->output->notice("Writing repeater file: {file}", array('file' => $pageView->getTargetFile()));
324 1
            $this->targetDir->writeFile($pageView->getTargetFile(), $output);
325 1
        }
326 1
    }
327
328
    /**
329
     * @param PageView $pageView
330
     */
331
    private function compileDynamicPageView (&$pageView)
332
    {
333
        $template = $this->createTemplate($pageView);
334
335
        $pageViewFrontMatter = $pageView->getFrontMatter(false);
336
        $collection = $pageViewFrontMatter['collection'];
337
338
        /** @var ContentItem $contentItem */
339
        foreach ($this->collections[$collection] as &$contentItem)
340
        {
341
            $output = $template->render(array(
342
                'this' => $contentItem
343
            ));
344
345
            $this->output->notice("Writing file: {file}", array('file' => $contentItem->getTargetFile()));
346
            $this->targetDir->writeFile($contentItem->getTargetFile(), $output);
347
        }
348
    }
349
350
    /**
351
     * @param PageView $pageView
352
     */
353
    private function compileStaticPageView (&$pageView)
354
    {
355
        $this->twig->addGlobal('__currentTemplate', $pageView->getFilePath());
356
357
        $template = $this->createTemplate($pageView);
358
        $output = $template->render(array(
359
            'this' => $pageView->getFrontMatter()
360
        ));
361
362
        $this->output->notice("Writing file: {file}", array('file' => $pageView->getTargetFile()));
363
        $this->targetDir->writeFile($pageView->getTargetFile(), $output);
364
    }
365
366
    /**
367
     * @param DynamicPageView|PageView $pageView
368
     */
369
    private function compileNormalRedirects (&$pageView)
370
    {
371
        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...
372
        {
373
            $redirectPageView = PageView::createRedirect(
374
                $redirect,
375
                $pageView->getPermalink(),
376
                $this->redirectTemplate
377
            );
378
379
            $this->compilePageView($redirectPageView);
380
        }
381
    }
382
383
    /**
384
     * @param RepeaterPageView $pageView
385
     */
386 1
    private function compileExpandedRedirects (&$pageView)
387
    {
388 1
        $permalinks = $pageView->getRepeaterPermalinks();
389
390 1
        foreach ($pageView->getRepeaterRedirects() as $repeaterRedirect)
391
        {
392
            /**
393
             * @var int           $index
394
             * @var ExpandedValue $redirect
395
             */
396
            foreach ($repeaterRedirect as $index => $redirect)
0 ignored issues
show
Bug introduced by
The expression $repeaterRedirect of type object<allejo\stakx\FrontMatter\ExpandedValue> is not traversable.
Loading history...
397
            {
398
                $redirectPageView = PageView::createRedirect(
399
                    $redirect->getEvaluated(),
400
                    $permalinks[$index]->getEvaluated(),
401
                    $this->redirectTemplate
402
                );
403
404
                $this->compilePageView($redirectPageView);
405
            }
406 1
        }
407 1
    }
408
409
    /**
410
     * Add a static PageView to the menu array. Dynamic PageViews are not added to the menu
411
     *
412
     * @param PageView $pageView
413
     */
414
    private function addToSiteMenu (&$pageView)
415
    {
416
        $frontMatter = $pageView->getFrontMatter();
417
418
        if (!array_key_exists('permalink', $frontMatter) ||
419
            (array_key_exists('menu', $frontMatter) && !$frontMatter['menu']))
420
        {
421
            return;
422
        }
423
424
        $url = $pageView->getPermalink();
425
        $root = &$this->siteMenu;
426
        $permalink = trim($url, DIRECTORY_SEPARATOR);
427
        $dirs = explode(DIRECTORY_SEPARATOR, $permalink);
428
429
        while (count($dirs) > 0)
430
        {
431
            $name = array_shift($dirs);
432
            $name = (!empty($name)) ? $name : '.';
433
434
            if (!is_null($name) && count($dirs) == 0)
435
            {
436
                $children = array();
437
438
                if (array_key_exists($name, $root) && is_array($root[$name]))
439
                {
440
                    $children = $root[$name]['children'];
441
                }
442
443
                $root[$name] = &$pageView;
444
                $root = &$root[$name]->getChildren();
445
446
                if (!empty($children))
447
                {
448
                    $root = $children;
449
                }
450
            }
451
            else
452
            {
453
                $root[$name]['children'] = array();
454
                $root = &$root[$name]['children'];
455
            }
456
        }
457
    }
458
459
    /**
460
     * @param PageView $pageView
461
     *
462
     * @return Twig_Template
463
     * @throws Twig_Error_Syntax
464
     */
465 1
    private function createTemplate ($pageView)
466
    {
467
        try
468
        {
469 1
            return $this->twig->createTemplate($pageView->getContent());
470
        }
471
        catch (Twig_Error_Syntax $e)
472
        {
473
            $e->setTemplateName($pageView->getRelativeFilePath());
474
475
            throw $e;
476
        }
477
    }
478
}