Completed
Push — master ( e6222a...4bc239 )
by Vladimir
02:26
created

Website::getTwigInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 4
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace allejo\stakx\Object;
4
5
use allejo\stakx\Core\ConsoleInterface;
6
use allejo\stakx\Engines\TwigMarkdownEngine;
7
use allejo\stakx\Manager\PageManager;
8
use allejo\stakx\System\Filesystem;
9
use allejo\stakx\Manager\CollectionManager;
10
use allejo\stakx\Manager\DataManager;
11
use allejo\stakx\System\Folder;
12
use allejo\stakx\Twig\FilesystemExtension;
13
use allejo\stakx\Twig\TwigExtension;
14
use Aptoma\Twig\Extension\MarkdownExtension;
15
use JasonLewis\ResourceWatcher\Tracker;
16
use JasonLewis\ResourceWatcher\Watcher;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Finder\SplFileInfo;
19
use Twig_Environment;
20
use Twig_Loader_Filesystem;
21
22
class Website
23
{
24
    /**
25
     * The Twig environment that will be used to render pages. This includes all of the loaded extensions and global
26
     * variables.
27
     *
28
     * @var Twig_Environment
29
     */
30
    protected static $twig;
31
32
    /**
33
     * The location of where the compiled website will be written to
34
     *
35
     * @var Folder
36
     */
37
    private $outputDirectory;
38
39
    /**
40
     * The main configuration to be used to build the specified website
41
     *
42
     * @var Configuration
43
     */
44
    private $configuration;
45
46
    /**
47
     * An array of the assorted ContentItems belonging to specific collection
48
     *
49
     * @var ContentItem[]
50
     */
51
    private $collections;
52
53
    /**
54
     * @var array
55
     */
56
    private $dataItems;
57
58
    /**
59
     * When set to true, the Stakx website will be built without a configuration file
60
     *
61
     * @var bool
62
     */
63
    private $confLess;
64
65
    /**
66
     * When set to true, Twig templates will not have access to filters or functions which provide access to the
67
     * filesystem
68
     *
69
     * @var bool
70
     */
71
    private $safeMode;
72
73
    /**
74
     * @var array
75
     */
76
    private $siteMenu;
77
78
    /**
79
     * @var ConsoleInterface
80
     */
81
    private $output;
82
83
    /**
84
     * @var CollectionManager
85
     */
86
    private $cm;
87
88
    /**
89
     * @var DataManager
90
     */
91
    private $dm;
92
93
    /**
94
     * @var Filesystem
95
     */
96
    private $fs;
97
98
    /**
99
     * @var PageManager
100
     */
101
    private $pm;
102
103
    /**
104
     * Website constructor.
105
     *
106
     * @param OutputInterface $output
107
     */
108
    public function __construct (OutputInterface $output)
109
    {
110
        $this->output = new ConsoleInterface($output);
111
        $this->cm = new CollectionManager();
112
        $this->dm = new DataManager();
113
        $this->pm = new PageManager();
114
        $this->fs = new Filesystem();
115
    }
116
117
    /**
118
     * Compile the website.
119
     *
120
     * @param bool $cleanDirectory Clean the target directing before rebuilding
121
     */
122
    public function build ($cleanDirectory)
123
    {
124
        // Parse DataItems
125
        $this->dm->setConsoleOutput($this->output);
126
        $this->dm->parseDataItems($this->getConfiguration()->getDataFolders());
0 ignored issues
show
Documentation introduced by
$this->getConfiguration()->getDataFolders() is of type integer|string|null, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
        $this->dm->parseDataSets($this->getConfiguration()->getDataSets());
0 ignored issues
show
Documentation introduced by
$this->getConfiguration()->getDataSets() is of type integer|string|null, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
128
        $this->dataItems = $this->dm->getDataItems();
129
130
        // Prepare Collections
131
        $this->cm->setConsoleOutput($this->output);
132
        $this->cm->parseCollections($this->getConfiguration()->getCollectionsFolders());
133
        $this->collections = $this->cm->getCollections();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->cm->getCollections() of type array<integer,array<inte...x\Object\ContentItem>>> is incompatible with the declared type array<integer,object<all...kx\Object\ContentItem>> of property $collections.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
134
135
        // Handle PageViews
136
        $this->pm->setConsoleOutput($this->output);
137
        $this->pm->parsePageViews($this->getConfiguration()->getPageViewFolders());
138
        $this->pm->prepareDynamicPageViews($this->collections);
0 ignored issues
show
Documentation introduced by
$this->collections is of type array<integer,array<inte...x\Object\ContentItem>>>, but the function expects a array<integer,object<all...kx\Object\ContentItem>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
139
140
        // Handle the site menu
141
        $this->siteMenu = $this->pm->getSiteMenu();
142
143
        // Configure the environment
144
        $this->createFolderStructure($cleanDirectory);
145
        $this->configureTwig();
146
147
        // Our output directory
148
        $this->outputDirectory = new Folder($this->getConfiguration()->getTargetFolder());
149
        $this->outputDirectory->setTargetDirectory($this->getConfiguration()->getBaseUrl());
150
151
        // Compile everything
152
        $this->copyThemeAssets();
153
        $this->copyStaticFiles();
154
155
        $this->pm->compile(
156
            self::$twig,
157
            $this->collections,
158
            $this->outputDirectory
159
        );
160
    }
161
162
    public function watch ()
163
    {
164
        $this->build(true);
165
166
        $tracker    = new Tracker();
167
        $watcher    = new Watcher($tracker, $this->fs);
168
        $listener   = $watcher->watch(getcwd());
169
        $targetPath = $this->getConfiguration()->getTargetFolder();
170
171
        $listener->onModify(function ($resource, $path) use ($targetPath) {
172
            $filePath = $this->fs->getRelativePath($path);
173
174
            if ((substr($filePath, 0, strlen($targetPath)) === $targetPath) ||
175
                (substr($filePath, 0, 1) === '.'))
176
            {
177
                return;
178
            }
179
180
            $this->output->writeln(sprintf("File change detected: %s", $filePath));
181
182
            try
183
            {
184
                $this->build(false);
185
            }
186
            catch (\Exception $e)
187
            {
188
                $this->output->error(sprintf("Your website failed to build with the following error: %s",
189
                    $e->getMessage()
190
                ));
191
            }
192
        });
193
194
        $watcher->start();
195
    }
196
197
    /**
198
     * @return Configuration
199
     */
200
    public function getConfiguration ()
201
    {
202
        return $this->configuration;
203
    }
204
205
    /**
206
     * @param string $configFile
207
     *
208
     * @throws \LogicException
209
     */
210
    public function setConfiguration ($configFile)
211
    {
212
        if (!$this->fs->exists($configFile) && !$this->isConfLess())
213
        {
214
            $this->output->error("You are trying to build a website in a directory without a configuration file. Is this what you meant to do?");
215
            $this->output->error("To build a website without a configuration, use the '--no-conf' option");
216
217
            throw new \LogicException("Cannot build a website without a configuration when not in Configuration-less mode");
218
        }
219
220
        if ($this->isConfLess())
221
        {
222
            $configFile = "";
223
        }
224
225
        $this->configuration = new Configuration($configFile, $this->output);
226
    }
227
228
    public function handleSingleFile ($filePath)
229
    {
230
        $filePath = ltrim($filePath, DIRECTORY_SEPARATOR);
231
        $pageViewDirs = $this->configuration->getPageViewFolders();
232
        $collectionDirs = array_map(function ($a) { return $a["folder"]; }, $this->configuration->getCollectionsFolders());
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 2.

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...
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
233
234
        $_paths = explode(DIRECTORY_SEPARATOR, $filePath);
235
236
        if (count($_paths) > 1 && (in_array($_paths[0], $collectionDirs) || in_array($_paths[0], $pageViewDirs)))
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
237
        {
238
239
        }
240
        else
241
        {
242
            $this->copyToCompiledSite($filePath);
0 ignored issues
show
Documentation introduced by
$filePath is of type string, but the function expects a object<Symfony\Component\Finder\SplFileInfo>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
243
        }
244
    }
245
246
    /**
247
     * Get whether or not the website is being built in Configuration-less mode
248
     *
249
     * @return bool True when being built with no configuration file
250
     */
251
    public function isConfLess ()
252
    {
253
        return $this->confLess;
254
    }
255
256
    /**
257
     * Set whether or not the website should be built with a configuration
258
     *
259
     * @param bool $status True when a website should be built without a configuration
260
     */
261
    public function setConfLess ($status)
262
    {
263
        $this->confLess = $status;
264
    }
265
266
    /**
267
     * Get whether or not the website is being built in safe mode.
268
     *
269
     * Safe mode is defined as disabling file system access from Twig and disabling user Twig extensions
270
     *
271
     * @return bool True when the website is being built in safe mode
272
     */
273
    public function isSafeMode ()
274
    {
275
        return $this->safeMode;
276
    }
277
278
    /**
279
     * Set whether a website should be built in safe mode
280
     *
281
     * @param bool $bool True if a website should be built in safe mode
282
     */
283
    public function setSafeMode ($bool)
284
    {
285
        $this->safeMode = $bool;
286
    }
287
288 3
    public static function getTwigInstance ()
289
    {
290 3
        return self::$twig;
291
    }
292
293
    /**
294
     * Prepare the Stakx environment by creating necessary cache folders
295
     *
296
     * @param bool $cleanDirectory Clean the target directory
297
     */
298
    private function createFolderStructure ($cleanDirectory)
299
    {
300
        $tarDir = $this->fs->absolutePath($this->configuration->getTargetFolder());
301
302
        if ($cleanDirectory)
303
        {
304
            $this->fs->remove($tarDir);
305
        }
306
307
        $this->fs->remove($this->fs->absolutePath('.stakx-cache'));
308
        $this->fs->mkdir('.stakx-cache/twig');
309
        $this->fs->mkdir($tarDir);
310
    }
311
312
    /**
313
     * Configure the Twig environment used by Stakx. This includes loading themes, global variables, extensions, and
314
     * debug settings.
315
     *
316
     * @todo Load custom Twig extensions from _config.yml
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
317
     */
318
    private function configureTwig ()
319
    {
320
        $loader   = new Twig_Loader_Filesystem(array(
321
            getcwd()
322
        ));
323
        $theme    = $this->configuration->getTheme();
324
        $mdEngine = new TwigMarkdownEngine();
325
326
        // Only load a theme if one is specified and actually exists
327
        if (!is_null($theme))
328
        {
329
            try
330
            {
331
                $loader->addPath($this->fs->absolutePath('_themes', $this->configuration->getTheme()), 'theme');
332
            }
333
            catch (\Twig_Error_Loader $e)
334
            {
335
                $this->output->error("The following theme could not be loaded: {theme}", array(
336
                    "theme" => $theme
337
                ));
338
                $this->output->error($e->getMessage());
339
            }
340
        }
341
342
        self::$twig = new Twig_Environment($loader, array(
343
            'autoescape' => $this->getConfiguration()->getTwigAutoescape(),
344
            //'cache'      => '.stakx-cache/twig'
345
        ));
346
347
        self::$twig->addGlobal('site', $this->configuration->getConfiguration());
348
        self::$twig->addGlobal('collections', $this->collections);
349
        self::$twig->addGlobal('menu', $this->siteMenu);
350
        self::$twig->addGlobal('data', $this->dataItems);
351
        self::$twig->addExtension(new TwigExtension());
352
        self::$twig->addExtension(new \Twig_Extensions_Extension_Text());
353
        self::$twig->addExtension(new MarkdownExtension($mdEngine));
354
355
        if (!$this->safeMode)
356
        {
357
            self::$twig->addExtension(new FilesystemExtension());
358
        }
359
360
        if ($this->configuration->isDebug())
361
        {
362
            self::$twig->addExtension(new \Twig_Extension_Debug());
363
            self::$twig->enableDebug();
364
        }
365
    }
366
367
    /**
368
     * Copy static files from a theme to the compiled website
369
     */
370
    private function copyThemeAssets ()
371
    {
372
        $theme = $this->configuration->getTheme();
373
374
        if (is_null($theme))
375
        {
376
            return;
377
        }
378
379
        $themeFolder  = $this->fs->appendPath("_themes", $theme);
380
        $ignoreFile   = $this->fs->absolutePath($themeFolder, ".stakx-ignore");
381
        $ignoredFiles = array();
382
383
        if ($this->fs->exists($ignoreFile))
384
        {
385
            $ignoreList = preg_replace("/[\r\n]+/", "\n", trim(file_get_contents($ignoreFile)));
386
            $ignoredFiles = explode(PHP_EOL, $ignoreList);
387
        }
388
389
        $finder = $this->fs->getFinder(
390
            $this->getConfiguration()->getIncludes(),
0 ignored issues
show
Documentation introduced by
$this->getConfiguration()->getIncludes() is of type integer|string|null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
391
            array_merge(
392
                $this->getConfiguration()->getExcludes(),
393
                $ignoredFiles,
394
                array('.twig')
395
            ),
396
            $this->fs->absolutePath($themeFolder)
397
        );
398
399
        /** @var SplFileInfo $file */
400
        foreach ($finder as $file)
401
        {
402
            $this->copyToCompiledSite($file, $themeFolder);
403
        }
404
    }
405
406
    /**
407
     * Copy the static files from the current directory into the compiled website directory.
408
     *
409
     * Static files are defined as follows:
410
     *   - Does not start with an underscore or is inside of a directory starting with an underscore
411
     *   - Does not start with a period or is inside of a directory starting with a period
412
     */
413
    private function copyStaticFiles ()
414
    {
415
        $finder = $this->fs->getFinder(
416
            $this->getConfiguration()->getIncludes(),
0 ignored issues
show
Documentation introduced by
$this->getConfiguration()->getIncludes() is of type integer|string|null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
417
            $this->getConfiguration()->getExcludes()
0 ignored issues
show
Documentation introduced by
$this->getConfiguration()->getExcludes() is of type integer|string|null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
418
        );
419
420
        /** @var $file SplFileInfo */
421
        foreach ($finder as $file)
422
        {
423
            $this->copyToCompiledSite($file);
424
        }
425
    }
426
427
    /**
428
     * Copy a file from a the source directory to the compiled website directory. The exact relative path to the file
429
     * will be recreated in the compiled website directory.
430
     *
431
     * @param SplFileInfo $file   The relative path of the file to be copied
432
     * @param string      $prefix
433
     */
434
    private function copyToCompiledSite ($file, $prefix = "")
435
    {
436
        if (!$this->fs->exists($file)) { return; }
437
438
        $filePath = $file->getRealPath();
439
        $pathToStrip = $this->fs->appendPath(getcwd(), $prefix);
440
        $siteTargetPath = ltrim(str_replace($pathToStrip, "", $filePath), DIRECTORY_SEPARATOR);
441
442
        try
443
        {
444
            $this->outputDirectory->copyFile($filePath, $siteTargetPath);
445
        }
446
        catch (\Exception $e)
447
        {
448
            $this->output->error($e->getMessage());
449
        }
450
    }
451
}