Completed
Push — master ( a6c8f9...b97be3 )
by Vladimir
03:52 queued 19s
created

FrontMatterDocument::getFrontMatter()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.1755

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 7
cts 9
cp 0.7778
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 3
nop 1
crap 4.1755
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\Document;
9
10
use allejo\stakx\Exception\FileAwareException;
11
use allejo\stakx\Exception\InvalidSyntaxException;
12
use allejo\stakx\FrontMatter\Exception\YamlVariableUndefinedException;
13
use allejo\stakx\FrontMatter\FrontMatterParser;
14
use Symfony\Component\Yaml\Exception\ParseException;
15
use Symfony\Component\Yaml\Yaml;
16
17
abstract class FrontMatterDocument extends ReadableDocument implements \IteratorAggregate, \ArrayAccess
18
{
19
    const TEMPLATE = "---\n%s\n---\n\n%s";
20
21
    /** @var array Functions that are white listed and can be called from templates. */
22
    public static $whiteListedFunctions = [
23
        'getPermalink', 'getRedirects', 'getTargetFile', 'getContent',
24
        'getFilename', 'getBasename', 'getExtension', 'isDraft',
25
    ];
26
27
    /** @var array FrontMatter keys that will be defined internally and cannot be overridden by users. */
28
    protected $specialFrontMatter = [
29
        'filePath' => null,
30
    ];
31
32
    protected $frontMatterEvaluated = false;
33
    protected $bodyContentEvaluated = false;
34
35
    /** @var FrontMatterParser */
36
    protected $frontMatterParser;
37
38
    /** @var array FrontMatter that is read from user documents. */
39
    protected $frontMatter = [];
40
41
    /** @var int The number of lines that Twig template errors should offset. */
42
    protected $lineOffset = 0;
43
44
    ///
45
    // Getter functions
46
    ///
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 19
    public function getIterator()
52
    {
53 19
        return (new \ArrayIterator($this->frontMatter));
54
    }
55
56
    /**
57
     * Get the number of lines that are taken up by FrontMatter and whitespace.
58
     *
59
     * @return int
60
     */
61 1
    public function getLineOffset()
62
    {
63 1
        return $this->lineOffset;
64
    }
65
66
    /**
67
     * Get whether or not this document is a draft.
68
     *
69
     * @return bool
70
     */
71 4
    public function isDraft()
72
    {
73 4
        return (isset($this->frontMatter['draft']) && $this->frontMatter['draft'] === true);
74
    }
75
76
    ///
77
    // FrontMatter functionality
78
    ///
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 119
    public function readContent()
84
    {
85
        // $fileStructure[1] is the YAML
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
86
        // $fileStructure[2] is the amount of new lines after the closing `---` and the beginning of content
87
        // $fileStructure[3] is the body of the document
88 119
        $fileStructure = array();
89
90 119
        $rawFileContents = $this->file->getContents();
91 119
        preg_match('/---\R(.*?\R)?---(\s+)(.*)/s', $rawFileContents, $fileStructure);
92
93 119
        if (count($fileStructure) != 4)
94 119
        {
95 9
            throw new InvalidSyntaxException('Invalid FrontMatter file', 0, null, $this->getRelativeFilePath());
96
        }
97
98 110
        if (empty(trim($fileStructure[3])))
99 110
        {
100 1
            throw new InvalidSyntaxException('FrontMatter files must have a body to render', 0, null, $this->getRelativeFilePath());
101
        }
102
103
        // The hard coded 1 is the offset used to count the new line used after the first `---` that is not caught in the regex
104 109
        $this->lineOffset = substr_count($fileStructure[1], "\n") + substr_count($fileStructure[2], "\n") + 1;
105 109
        $this->bodyContent = $fileStructure[3];
106
107 109
        if (!empty(trim($fileStructure[1])))
108 109
        {
109 92
            $this->frontMatter = Yaml::parse($fileStructure[1], Yaml::PARSE_DATETIME);
110
111 92
            if (!empty($this->frontMatter) && !is_array($this->frontMatter))
112 92
            {
113 1
                throw new ParseException('The evaluated FrontMatter should be an array');
114
            }
115 91
        }
116
        else
117
        {
118 20
            $this->frontMatter = array();
119
        }
120
121 108
        $this->frontMatterEvaluated = false;
122 108
        $this->bodyContentEvaluated = false;
123 108
    }
124
125
    /**
126
     * Get the FrontMatter for this document.
127
     *
128
     * @param bool $evaluateYaml Whether or not to evaluate any variables.
129
     *
130
     * @return array
131
     */
132 30
    final public function getFrontMatter($evaluateYaml = true)
133
    {
134 30
        if ($this->frontMatter === null)
135 30
        {
136
            $this->frontMatter = [];
137
        }
138 30
        elseif (!$this->frontMatterEvaluated && $evaluateYaml)
139
        {
140 24
            $this->evaluateYaml($this->frontMatter);
141 23
        }
142
143 29
        return $this->frontMatter;
144
    }
145
146
    /**
147
     * Evaluate the FrontMatter in this object by merging a custom array of data.
148
     *
149
     * @param array|null $variables An array of YAML variables to use in evaluating the `$permalink` value
150
     */
151 9
    final public function evaluateFrontMatter(array $variables = null)
152
    {
153 9
        if ($variables !== null)
154 9
        {
155 9
            $this->frontMatter = array_merge($this->frontMatter, $variables);
156 9
            $this->evaluateYaml($this->frontMatter);
157 9
        }
158 9
    }
159
160
    /**
161
     * Returns true when the evaluated Front Matter has expanded values embeded.
162
     *
163
     * @return bool
164
     */
165 2
    final public function hasExpandedFrontMatter()
166
    {
167 2
        return ($this->frontMatterParser !== null && $this->frontMatterParser->hasExpansion());
168
    }
169
170
    /**
171
     * Evaluate an array of data for FrontMatter variables. This function will modify the array in place.
172
     *
173
     * @param array $yaml An array of data containing FrontMatter variables
174
     *
175
     * @see $specialFrontMatter
176
     *
177
     * @throws YamlVariableUndefinedException A FrontMatter variable used does not exist
178
     */
179 31
    private function evaluateYaml(&$yaml)
180
    {
181
        try
182
        {
183
            // The second parameter for this parser must match the $specialFrontMatter structure
184 31
            $this->frontMatterParser = new FrontMatterParser($yaml, [
185 31
                'filePath' => $this->getRelativeFilePath(),
186 31
            ]);
187 31
            $this->frontMatterParser->parse();
188 30
            $this->frontMatterEvaluated = true;
189
        }
190 31
        catch (\Exception $e)
191
        {
192 1
            throw FileAwareException::castException($e, $this->getRelativeFilePath());
193
        }
194 30
    }
195
196
    ///
197
    // ArrayAccess Implementation
198
    ///
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function offsetSet($offset, $value)
204
    {
205
        throw new \LogicException('FrontMatter is read-only.');
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 34
    public function offsetExists($offset)
212
    {
213 34
        if (isset($this->frontMatter[$offset]) || isset($this->specialFrontMatter[$offset]))
214 34
        {
215 33
            return true;
216
        }
217
218 14
        $fxnCall = 'get' . ucfirst($offset);
219
220 14
        return method_exists($this, $fxnCall) && in_array($fxnCall, static::$whiteListedFunctions);
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226
    public function offsetUnset($offset)
227
    {
228
        throw new \LogicException('FrontMatter is read-only.');
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234 47
    public function offsetGet($offset)
235
    {
236 47
        if (isset($this->specialFrontMatter[$offset]))
237 47
        {
238
            return $this->specialFrontMatter[$offset];
239
        }
240
241 47
        $fxnCall = 'get' . ucfirst($offset);
242
243 47
        if (in_array($fxnCall, self::$whiteListedFunctions) && method_exists($this, $fxnCall))
244 47
        {
245 6
            return call_user_func_array([$this, $fxnCall], []);
246
        }
247
248 41
        if (isset($this->frontMatter[$offset]))
249 41
        {
250 40
            return $this->frontMatter[$offset];
251
        }
252
253 2
        return null;
254
    }
255
}
256