Completed
Push — master ( a7a1f4...c2b5cc )
by Vladimir
02:39
created

Website::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx;
9
10
use allejo\stakx\Command\BuildableCommand;
11
use allejo\stakx\Core\StakxLogger;
12
use allejo\stakx\Exception\FileAwareException;
13
use allejo\stakx\Manager\AssetManager;
14
use allejo\stakx\Manager\CollectionManager;
15
use allejo\stakx\Manager\DataManager;
16
use allejo\stakx\Manager\MenuManager;
17
use allejo\stakx\Manager\PageManager;
18
use allejo\stakx\Manager\ThemeManager;
19
use allejo\stakx\Manager\TwigManager;
20
use allejo\stakx\System\FileExplorer;
21
use allejo\stakx\System\Filesystem;
22
use allejo\stakx\System\Folder;
23
use allejo\stakx\Twig\StakxTwigTextProfiler;
24
use Kwf\FileWatcher\Event\AbstractEvent;
25
use Kwf\FileWatcher\Event\Create;
26
use Kwf\FileWatcher\Event\Modify;
27
use Kwf\FileWatcher\Event\Move;
28
use Kwf\FileWatcher\Watcher;
29
use Symfony\Component\Console\Output\OutputInterface;
30
31
class Website
32
{
33
    /**
34
     * The location of where the compiled website will be written to.
35
     *
36
     * @var Folder
37
     */
38
    private $outputDirectory;
39
40
    /**
41
     * The main configuration to be used to build the specified website.
42
     *
43
     * @var Configuration
44
     */
45
    private $configuration;
46
47
    /**
48
     * When set to true, the Stakx website will be built without a configuration file.
49
     *
50
     * @var bool
51
     */
52
    private $confLess;
53
54
    /**
55
     * @var StakxLogger
56
     */
57
    private $output;
58
59
    /**
60
     * @var AssetManager
61
     */
62
    private $am;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $am. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
63
64
    /**
65
     * @var CollectionManager
66
     */
67
    private $cm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $cm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
68
69
    /**
70
     * @var DataManager
71
     */
72
    private $dm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
73
74
    /**
75
     * @var Filesystem
76
     */
77
    private $fs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
78
79
    /** @var MenuManager */
80
    private $mm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $mm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
81
82
    /**
83
     * @var PageManager
84
     */
85
    private $pm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $pm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
86
87
    /**
88
     * @var ThemeManager
89
     */
90
    private $tm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $tm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
91
92
    /** @var Compiler */
93
    private $compiler;
94
95
    /**
96
     * Website constructor.
97
     *
98
     * @param OutputInterface $output
99
     */
100
    public function __construct(OutputInterface $output)
101
    {
102
        $this->output = new StakxLogger($output);
103
        $this->cm = new CollectionManager();
104
        $this->dm = new DataManager();
105
        $this->mm = new MenuManager();
106
        $this->pm = new PageManager();
107
        $this->fs = new Filesystem();
108
    }
109
110
    /**
111
     * Compile the website.
112
     *
113
     * @param bool $tracking Whether or not to keep track of files as they're compiled to save time in 'watch'
114
     */
115
    public function build($tracking = false)
116
    {
117
        Service::setParameter(BuildableCommand::WATCHING, $tracking);
118
119
        // Configure the environment
120
        $this->createFolderStructure();
121
122
        // Our output directory
123
        $this->outputDirectory = new Folder($this->getConfiguration()->getTargetFolder());
124
        $this->outputDirectory->setTargetDirectory($this->getConfiguration()->getBaseUrl());
125
126
        // Parse DataItems
127
        $this->dm->setLogger($this->output);
128
        $this->dm->enableTracking($tracking);
129
        $this->dm->parseDataItems($this->getConfiguration()->getDataFolders());
130
        $this->dm->parseDataSets($this->getConfiguration()->getDataSets());
131
132
        // Prepare Collections
133
        $this->cm->setLogger($this->output);
134
        $this->cm->enableTracking($tracking);
135
        $this->cm->parseCollections($this->getConfiguration()->getCollectionsFolders());
136
137
        // Handle PageViews
138
        $this->pm->setLogger($this->output);
139
        $this->pm->enableTracking($tracking);
140
        $this->pm->setCollections($this->cm->getCollections());
141
        $this->pm->setDatasets($this->dm->getDataItems());
142
        $this->pm->parsePageViews($this->getConfiguration()->getPageViewFolders());
143
144
        // Handle the site's menu
145
        $this->mm->setLogger($this->output);
146
        $this->mm->buildFromPageViews($this->pm->getStaticPageViews());
147
148
        // Configure our Twig environment
149
        $theme = $this->configuration->getTheme();
150
        $twigEnv = new TwigManager();
151
        $twigEnv->configureTwig($this->getConfiguration(), array(
152
            'safe'    => Service::getParameter(BuildableCommand::SAFE_MODE),
153
            'globals' => array(
154
                array('name' => 'site', 'value' => $this->getConfiguration()->getConfiguration()),
155
                array('name' => 'collections', 'value' => $this->cm->getJailedCollections()),
156
                array('name' => 'menu', 'value' => $this->mm->getSiteMenu()),
157
                array('name' => 'pages', 'value' => $this->pm->getJailedStaticPageViews()),
158
                array('name' => 'data', 'value' => $this->dm->getJailedDataItems()),
159
            ),
160
        ));
161
162
        $profiler = null;
163
164
        if (Service::getParameter(BuildableCommand::BUILD_PROFILE))
165
        {
166
            $profiler = new \Twig_Profiler_Profile();
167
            TwigManager::getInstance()->addExtension(new \Twig_Extension_Profiler($profiler));
168
        }
169
170
        // Compile everything
171
        $this->compiler = new Compiler();
172
        $this->compiler->setLogger($this->output);
173
        $this->compiler->setRedirectTemplate($this->getConfiguration()->getRedirectTemplate());
174
        $this->compiler->setPageViews($this->pm->getPageViews(), $this->pm->getPageViewsFlattened());
175
        $this->compiler->setTargetFolder($this->outputDirectory);
176
        $this->compiler->setThemeName($theme);
177
        $this->compiler->compileAll();
178
179
        if (Service::getParameter(BuildableCommand::BUILD_PROFILE))
180
        {
181
            $dumper = new StakxTwigTextProfiler();
182
            $dumper->setTemplateMappings($this->compiler->getTemplateMappings());
183
            $text = $dumper->dump($profiler);
0 ignored issues
show
Bug introduced by
It seems like $profiler defined by null on line 162 can be null; however, Twig_Profiler_Dumper_Text::dump() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
184
            $this->output->writeln($text);
185
        }
186
187
        // At this point, we are looking at static files to copy over meaning we need to ignore all of the files that
188
        // make up the source of a stakx website
189
        $assetsToIgnore = array_merge(
190
            Configuration::$stakxSourceFiles,
191
            $this->getConfiguration()->getExcludes()
192
        );
193
194
        //
195
        // Theme Management
196
        //
197
        if (!is_null($theme))
198
        {
199
            $this->output->notice("Looking for '${theme}' theme...");
200
201
            $this->tm = new ThemeManager($theme);
202
            $this->tm->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
203
            $this->tm->setLogger($this->output);
204
            $this->tm->enableTracking($tracking);
205
            $this->tm->setFolder($this->outputDirectory);
206
            $this->tm->copyFiles();
207
        }
208
209
        //
210
        // Static file management
211
        //
212
        $this->am = new AssetManager();
213
        $this->am->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
214
        $this->am->setLogger($this->output);
215
        $this->am->setFolder($this->outputDirectory);
216
        $this->am->enableTracking($tracking);
217
        $this->am->copyFiles();
218
    }
219
220
    public function watch()
221
    {
222
        $this->output->writeln('Building website...');
223
        $this->build(true);
224
        $this->output->writeln(sprintf('Watching %s', getcwd()));
225
226
        $exclusions = array_merge($this->getConfiguration()->getExcludes(), array(
227
            $this->getConfiguration()->getTargetFolder()
228
        ));
229
        $fileExplorer = FileExplorer::create(
230
            getcwd(), $exclusions, $this->getConfiguration()->getIncludes()
231
        );
232
233
        $newWatcher = Watcher::create(getcwd());
234
        $newWatcher
235
            ->setLogger($this->output)
236
            ->setExcludePatterns(array_merge(
237
                $exclusions, FileExplorer::$vcsPatterns, array(Configuration::CACHE_FOLDER)
238
            ))
239
            ->setIterator($fileExplorer->getExplorer())
240
            ->addListener(Create::NAME, function ($e) { $this->watchListenerFunction($e); })
241
            ->addListener(Modify::NAME, function ($e) { $this->watchListenerFunction($e); })
242
            ->addListener(Move::NAME,   function ($e) { $this->watchListenerFunction($e); })
243
        ;
244
245
        $this->output->writeln('Watch started successfully');
246
247
        $newWatcher->start();
248
    }
249
250
    private function watchListenerFunction(AbstractEvent $event)
251
    {
252
        $filePath = $this->fs->getRelativePath($event->filename);
253
254
        try
255
        {
256
            switch ($event::getEventName())
257
            {
258
                case Create::NAME:
259
                    $this->creationWatcher($filePath);
260
                    break;
261
262
                case Modify::NAME:
263
                    $this->modificationWatcher($filePath);
264
                    break;
265
266
                case Move::NAME:
267
                    $newFile = $this->fs->getRelativePath($event->destFilename);
0 ignored issues
show
Bug introduced by
The property destFilename does not seem to exist. Did you mean filename?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
268
269
                    $this->deletionWatcher($filePath);
0 ignored issues
show
Unused Code introduced by
The call to the method allejo\stakx\Website::deletionWatcher() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
270
                    $this->creationWatcher($newFile);
271
                    break;
272
            }
273
        }
274
        catch (FileAwareException $e)
275
        {
276
            $this->output->writeln(sprintf("Your website failed to build with the following error in file '%s': %s",
277
                $e->getPath(),
278
                $e->getMessage()
279
            ));
280
        }
281
        catch (\Exception $e)
282
        {
283
            $this->output->writeln(sprintf('Your website failed to build with the following error: %s',
284
                $e->getMessage()
285
            ));
286
        }
287
    }
288
289
    /**
290
     * @return Configuration
291
     */
292
    public function getConfiguration()
293
    {
294
        return $this->configuration;
295
    }
296
297
    /**
298
     * @param string $configFile
299
     *
300
     * @throws \LogicException
301
     */
302
    public function setConfiguration($configFile)
303
    {
304
        if (!$this->fs->exists($configFile) && !$this->isConfLess())
305
        {
306
            $this->output->error('You are trying to build a website in a directory without a configuration file. Is this what you meant to do?');
307
            $this->output->error("To build a website without a configuration, use the '--no-conf' option");
308
309
            throw new \LogicException('Cannot build a website without a configuration when not in Configuration-less mode');
310
        }
311
312
        if ($this->isConfLess())
313
        {
314
            $configFile = '';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $configFile. This often makes code more readable.
Loading history...
315
        }
316
317
        $this->configuration = new Configuration();
318
        $this->configuration->setLogger($this->output);
319
        $this->configuration->parse($configFile);
320
    }
321
322
    /**
323
     * Get whether or not the website is being built in Configuration-less mode.
324
     *
325
     * @return bool True when being built with no configuration file
326
     */
327
    public function isConfLess()
328
    {
329
        return $this->confLess;
330
    }
331
332
    /**
333
     * Set whether or not the website should be built with a configuration.
334
     *
335
     * @param bool $status True when a website should be built without a configuration
336
     */
337
    public function setConfLess($status)
338
    {
339
        $this->confLess = $status;
340
    }
341
342
    /**
343
     * @param string $filePath
344
     */
345
    private function creationWatcher($filePath)
346
    {
347
        $this->output->writeln(sprintf('File creation detected: %s', $filePath));
348
349
        if ($this->pm->isHandled($filePath))
350
        {
351
            $this->pm->createNewItem($filePath);
352
            $this->pm->refreshItem($filePath);
353
        }
354
        elseif ($this->cm->isHandled($filePath))
355
        {
356
            $contentItem = $this->cm->createNewItem($filePath);
357
            TwigManager::getInstance()->addGlobal('collections', $this->cm->getCollections());
358
359
            $this->pm->trackNewContentItem($contentItem);
360
            $this->compiler->compileContentItem($contentItem);
0 ignored issues
show
Deprecated Code introduced by
The method allejo\stakx\Compiler::compileContentItem() has been deprecated.

This method has been deprecated.

Loading history...
361
            $this->compiler->compileSome(array(
362
                'namespace'  => 'collections',
363
                'dependency' => $contentItem->getNamespace(),
364
            ));
365
        }
366 View Code Duplication
        elseif ($this->dm->isHandled($filePath))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
367
        {
368
            $change = $this->dm->createNewItem($filePath);
369
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
370
371
            $this->compiler->compileSome(array(
372
                'namespace'  => 'data',
373
                'dependency' => $change,
374
            ));
375
        }
376
        elseif (!is_null($this->tm) && $this->tm->isHandled($filePath))
377
        {
378
            $this->tm->createNewItem($filePath);
379
        }
380
        elseif ($this->am->isHandled($filePath))
381
        {
382
            $this->am->createNewItem($filePath);
383
        }
384
    }
385
386
    /**
387
     * @param string $filePath
388
     */
389
    private function modificationWatcher($filePath)
390
    {
391
        $this->output->writeln(sprintf('File change detected: %s', $filePath));
392
393
        if ($this->compiler->isParentTemplate($filePath))
394
        {
395
            TwigManager::getInstance()->clearTemplateCache();
0 ignored issues
show
Deprecated Code introduced by
The method Twig_Environment::clearTemplateCache() has been deprecated with message: since 1.18.3 (to be removed in 2.0)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
396
            $this->compiler->refreshParent($filePath);
397
        }
398
        elseif ($this->pm->isTracked($filePath))
399
        {
400
            $change = $this->pm->refreshItem($filePath);
401
402
            TwigManager::getInstance()->clearTemplateCache();
0 ignored issues
show
Deprecated Code introduced by
The method Twig_Environment::clearTemplateCache() has been deprecated with message: since 1.18.3 (to be removed in 2.0)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
403
            $this->compiler->compilePageView($change);
404
        }
405
        elseif ($this->cm->isTracked($filePath))
406
        {
407
            $contentItem = &$this->cm->getContentItem($filePath);
408
            $contentItem->refreshFileContent();
409
410
            $this->compiler->compileContentItem($contentItem);
0 ignored issues
show
Deprecated Code introduced by
The method allejo\stakx\Compiler::compileContentItem() has been deprecated.

This method has been deprecated.

Loading history...
411
            $this->compiler->compileSome(array(
412
                'namespace'  => 'collections',
413
                'dependency' => $contentItem->getNamespace(),
414
            ));
415
        }
416 View Code Duplication
        elseif ($this->dm->isTracked($filePath))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
417
        {
418
            $change = $this->dm->refreshItem($filePath);
419
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
420
421
            $this->compiler->compileSome(array(
422
                'namespace'  => 'data',
423
                'dependency' => $change,
424
            ));
425
        }
426
        elseif (!is_null($this->tm) && $this->tm->isTracked($filePath))
427
        {
428
            $this->tm->refreshItem($filePath);
429
        }
430
        elseif ($this->am->isTracked($filePath))
431
        {
432
            $this->am->refreshItem($filePath);
433
        }
434
    }
435
436
    /**
437
     * @param string $filePath
438
     */
439
    private function deletionWatcher($filePath)
0 ignored issues
show
Unused Code introduced by
The parameter $filePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
440
    {
441
    }
442
443
    /**
444
     * Prepare the Stakx environment by creating necessary cache folders.
445
     */
446
    private function createFolderStructure()
447
    {
448
        $targetDir = $this->fs->absolutePath($this->configuration->getTargetFolder());
449
450
        if (!Service::getParameter(BuildableCommand::NO_CLEAN))
451
        {
452
            $this->fs->remove($targetDir);
453
        }
454
455
        if (!Service::getParameter(BuildableCommand::USE_CACHE))
456
        {
457
            $this->fs->remove($this->fs->absolutePath(Configuration::CACHE_FOLDER));
458
            $this->fs->mkdir($this->fs->absolutePath($this->fs->appendPath(Configuration::CACHE_FOLDER, 'twig')));
459
        }
460
461
        $this->fs->mkdir($targetDir);
462
    }
463
}
464