GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Push — master ( f2d01f...391818 )
by Roelof Jan
05:10
created

Model::parseFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
4
namespace AloiaCms\Models;
5
6
use ContentParser\ContentParser;
7
use AloiaCms\InlineBlockParser;
8
use AloiaCms\Models\Contracts\ModelInterface;
9
use AloiaCms\Models\Contracts\StorableInterface;
10
use AloiaCms\Writer\FolderCreator;
11
use AloiaCms\Writer\FrontMatterCreator;
12
use Exception;
13
use Illuminate\Support\Arr;
14
use Illuminate\Support\Collection;
15
use Illuminate\Support\Facades\Config;
16
use Illuminate\Support\Facades\File;
17
use Spatie\YamlFrontMatter\YamlFrontMatter;
18
19
class Model implements ModelInterface, StorableInterface
20
{
21
    /**
22
     * Represents the folder name where this model saves files
23
     *
24
     * @var string $folder
25
     */
26
    protected $folder = '';
27
28
    /**
29
     * Represents the basename of the base file
30
     *
31
     * @var string|null $file_name
32
     */
33
    protected $file_name = null;
34
35
    /**
36
     * Represents the filename of the base file
37
     *
38 63
     * @var string|null $full_file_name
39
     */
40 63
    protected $full_file_name = null;
41
42 63
    protected $extension = 'md';
43
44 63
    protected $matter = [];
45
46
    protected $body = '';
47
48
    protected $required_fields = [];
49
50
    /**
51
     * Return all instances of the model
52 7
     *
53
     * @return Collection|ModelInterface[]
54 7
     */
55
    public static function all(): Collection
56 7
    {
57
        return Collection::make((new static())->getModelFiles())
58 7
            ->map(fn (string $filename) => self::find(pathinfo($filename, PATHINFO_FILENAME)));
59 7
    }
60 7
61 7
    /**
62
     * Return the amount of models of this type
63
     *
64
     * @return int
65
     */
66
    public static function count(): int
67
    {
68
        return count((new static())->getModelFiles());
69
    }
70 1
71
    /**
72 1
     * Get the folder path for this model
73
     *
74 1
     * @return string
75
     */
76 1
    public function getFolderPath(): string
77
    {
78 1
        $folder_path = Config::get('aloiacms.collections_path') . "/{$this->folder}";
79
80 1
        FolderCreator::forPath($folder_path);
81
82
        return $folder_path;
83
    }
84
85
    /**
86
     * Find a single model
87
     *
88 63
     * @param string $file_name
89
     * @return ModelInterface
90 63
     */
91
    public static function find(string $file_name): ModelInterface
92 63
    {
93 47
        $instance = new static();
94
95
        $instance->setFileName($file_name);
96 62
97
        return $instance;
98
    }
99
100
    /**
101
     * Set the file name for this instance
102 63
     *
103
     * @param string $file_name
104 63
     * @return ModelInterface
105
     */
106 63
    protected function setFileName(string $file_name): ModelInterface
107 63
    {
108 63
        $this->file_name = $file_name;
109
110
        $this->parseFile();
111
112
        return $this;
113
    }
114
115
    /**
116 52
     * Parse the file for this model into model variables
117
     */
118 52
    private function parseFile(): void
119
    {
120 52
        $parsed_file = YamlFrontMatter::parse($this->rawContent());
121
122 51
        $this->matter = $parsed_file->matter();
123
        $this->body = $parsed_file->body();
124 49
    }
125
126 49
    /**
127
     * Get the raw content of the file + front matter
128 49
     *
129
     * @return string
130
     */
131
    public function rawContent(): string
132
    {
133
        $file_path = $this->getFilePath();
134
135
        if ($this->exists()) {
136 63
            return file_get_contents($file_path);
137
        }
138 63
139
        return "";
140 63
    }
141 47
142
    /**
143
     * Get the file path for this instance
144 63
     *
145
     * @return string
146
     */
147
    private function getFilePath(): string
148
    {
149
        $folder_path = $this->getFolderPath();
150
151
        if (!is_null($matching_filename = $this->getFullFileName())) {
152 66
            $this->setExtension(pathinfo($matching_filename, PATHINFO_EXTENSION));
153
        }
154 66
155
        return "{$folder_path}/{$this->file_name}.{$this->extension}";
156 66
    }
157
158 66
    /**
159
     * Get full file name (including extension) for this model.
160
     *
161
     * @return string|null
162
     */
163
    private function getFullFileName(): ?string
164
    {
165
        if (!$this->full_file_name) {
166 2
            $this->full_file_name = $this->getFileMatchFromDisk();
167
        }
168 2
169
        return $this->full_file_name;
170
    }
171
172
    /**
173
     * Get the filename from disk
174
     * This uses the least amount of loops possible.
175
     *
176
     * @return string|null
177
     */
178
    private function getFileMatchFromDisk(): ?string
179
    {
180
        $haystack = $this->getModelFiles();
181
182 1
        $min = 0;
183
        $max = count($haystack);
184 1
185
        if ($max === 0) {
186
            return null;
187
        }
188
189
        while ($max >= $min) {
190
            $mid = floor(($min + $max) / 2);
191
192
            if (!isset($haystack[$mid])) {
193 46
                $max = $mid - 1;
194
                continue;
195 46
            }
196 46
197
            if (strpos($haystack[$mid], "{$this->file_name}.") !== false) {
198
                return $haystack[$mid];
199 46
            } elseif ($haystack[$mid] < $this->file_name) {
200
                // The new chunk will be the second half
201
                $min = $mid + 1;
202
            } else {
203
                // The new chunk will be the first half
204
                $max = $mid - 1;
205
            }
206
        }
207
208 2
        return null;
209
    }
210 2
211
    /**
212
     * Get all models for this type
213
     *
214
     * @return array
215
     */
216
    private function getModelFiles(): array
217
    {
218
        $filenames = array_values(
219 15
            array_diff(
220
                scandir($this->getFolderPath()),
0 ignored issues
show
Bug introduced by
It seems like scandir($this->getFolderPath()) can also be of type false; however, parameter $array1 of array_diff() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
                /** @scrutinizer ignore-type */ scandir($this->getFolderPath()),
Loading history...
221 15
                ['..', '.']
222
            )
223
        );
224
225
        sort($filenames);
226
227
        return $filenames;
228
    }
229
230
    /**
231 3
     * Set the file extension
232
     *
233 3
     * @param string $extension
234
     * @return ModelInterface
235 3
     */
236
    public function setExtension(string $extension): ModelInterface
237
    {
238
        $this->extension = $extension;
239
240
        return $this;
241
    }
242
243
    /**
244 2
     * Determine whether the current model exists
245
     *
246 2
     * @return bool
247 2
     */
248
    public function exists(): bool
249
    {
250 2
        return !is_null($this->getFullFileName());
251
    }
252
253
    /**
254
     * Rename this file to the given name
255
     *
256
     * @param string $new_name
257
     * @return Model
258 21
     */
259
    public function rename(string $new_name): ModelInterface
260 21
    {
261
        $old_file_path = $this->getFilePath();
262
263
        $this->file_name = $new_name;
264
265
        $new_file_path = $this->getFilePath();
266
267
        File::move($old_file_path, $new_file_path);
268 19
269
        return self::find($new_name);
270 19
    }
271
272 19
    /**
273
     * Save this instance to file
274
     *
275
     * @return ModelInterface
276
     * @throws Exception
277
     */
278
    public function save(): ModelInterface
279
    {
280
        $file_content = FrontMatterCreator::seed($this->matter, $this->body)->create();
281 38
282
        $this->assertFilenameExists();
283 38
284
        $this->assertRequiredMatterIsPresent();
285 38
286
        $file_path = $this->getFilePath();
287
288
        file_put_contents($file_path, $file_content);
289
290
        return $this;
291
    }
292
293 20
    /**
294
     * Throw exception when the file name is not set for this instance
295 20
     *
296
     * @throws Exception
297
     */
298
    private function assertFilenameExists()
299
    {
300
        if (is_null($this->file_name)) {
301
            throw new Exception("Filename is required");
302
        }
303
    }
304 47
305
    /**
306 47
     * Throw exception if at least one required matter attribute is not present
307
     *
308 47
     * @throws Exception
309
     */
310
    private function assertRequiredMatterIsPresent()
311
    {
312
        foreach ($this->required_fields as $required_field) {
313
            if (!isset($this->matter[$required_field])) {
314
                throw new Exception("Attribute {$required_field} is required");
315
            }
316 1
        }
317
    }
318 1
319
    /**
320
     * Get the front matter
321
     *
322
     * @return array
323
     */
324
    public function matter(): array
325
    {
326
        return $this->matter;
327 63
    }
328
329 63
    /**
330
     * Set a value on the specified key in the configuration
331 63
     *
332
     * Kept around for backward compatibility
333 63
     *
334
     * @param string $key
335
     * @param $value
336
     * @return ModelInterface
337
     *
338
     * @deprecated since 3.2.0
339
     */
340
    public function addMatter(string $key, $value): ModelInterface
341 63
    {
342
        return $this->set($key, $value);
343 63
    }
344
345
    /**
346
     * Set a value on the specified key in the configuration
347
     *
348
     * @param string $key
349
     * @param $value
350
     * @return $this|ModelInterface
351 63
     */
352
    public function set(string $key, $value): ModelInterface
353 63
    {
354 47
        $this->matter[$key] = $value;
355 63
356
        return $this;
357
    }
358
359
    /**
360
     * Set data in the front matter, but only for the keys specified in the input array
361
     *
362
     * @param array $matter
363 63
     * @return ModelInterface
364
     */
365 63
    public function setMatter(array $matter): ModelInterface
366
    {
367
        foreach (array_keys($matter) as $key) {
368
            $this->matter[$key] = $matter[$key];
369
        }
370
371
        return $this;
372
    }
373 2
374
    /**
375 2
     * Remove a key from the configuration
376
     *
377
     * @param string $key
378
     * @return $this|ModelInterface
379
     */
380
    public function remove(string $key): ModelInterface
381
    {
382
        if ($this->has($key)) {
383
            unset($this->matter[$key]);
384 12
        }
385
386 12
        return $this;
387
    }
388
389
    /**
390
     * Determine whether a key is present in the configuration
391
     *
392
     * @param string $key
393
     * @return bool
394 52
     */
395
    public function has(string $key): bool
396 52
    {
397 1
        return isset($this->matter[$key]);
398
    }
399 51
400
    /**
401
     * Get the parse file body
402
     *
403
     * @return string
404
     */
405
    public function body(): string
406 51
    {
407
        $content = new ContentParser($this->rawBody(), $this->extension());
408 51
409 43
        return (new InlineBlockParser)->parseHtmlString($content->parse());
410 43
    }
411
412
    /**
413 49
     * Get the raw file body
414
     *
415
     * @return string
416
     */
417
    public function rawBody(): string
418
    {
419
        return $this->body;
420
    }
421
422
    /**
423
     * Get the file extension
424
     *
425
     * @return string
426
     */
427
    public function extension(): string
428
    {
429
        return $this->extension;
430
    }
431
432
    /**
433
     * Set the file body
434
     *
435
     * @param string $body
436
     * @return ModelInterface
437
     */
438
    public function setBody(string $body): ModelInterface
439
    {
440
        $this->body = $body;
441
442
        return $this;
443
    }
444
445
    /**
446
     * Get the file name for this instance
447
     *
448
     * @return string
449
     */
450
    public function filename(): ?string
451
    {
452
        return $this->file_name;
453
    }
454
455
    /**
456
     * Delete the current model
457
     *
458
     * @return bool
459
     */
460
    public function delete(): bool
461
    {
462
        return File::delete($this->getFilePath());
463
    }
464
465
    /**
466
     * Get front matter information through an accessor
467
     *
468
     * @param $key
469
     * @return mixed|null
470
     */
471
    public function __get($key)
472
    {
473
        return $this->get($key);
474
    }
475
476
    /**
477
     * Get the value of the specified key, return null if it doesn't exist
478
     *
479
     * @param string $key
480
     * @return mixed|null
481
     */
482
    public function get(string $key)
483
    {
484
        return $this->matter[$key] ?? null;
485
    }
486
}
487