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 ( 10f681...0eafc8 )
by Roelof Jan
04:09
created

Model::__get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
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
     * @var string|null $full_file_name
39
     */
40
    protected $full_file_name = null;
41
42
    protected $extension = 'md';
43
44
    protected $matter = [];
45
46
    protected $body = '';
47
48
    protected $required_fields = [];
49
50
    /**
51
     * Return all instances of the model
52
     *
53
     * @return Collection|ModelInterface[]
54
     */
55 7
    public static function all(): Collection
56
    {
57 7
        return Collection::make((new static())->getModelFiles())
58 7
            ->map(fn (string $filename) => self::find(pathinfo($filename, PATHINFO_FILENAME)));
59
    }
60
61
    /**
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
71
    /**
72
     * Get the folder path for this model
73
     *
74
     * @return string
75
     */
76 66
    public function getFolderPath(): string
77
    {
78 66
        $folder_path = Config::get('aloiacms.collections_path') . "/{$this->folder}";
79
80 66
        FolderCreator::forPath($folder_path);
81
82 66
        return $folder_path;
83
    }
84
85
    /**
86
     * Find a single model
87
     *
88
     * @param string $file_name
89
     * @return ModelInterface
90
     */
91 63
    public static function find(string $file_name): ModelInterface
92
    {
93 63
        $instance = new static();
94
95 63
        $instance->setFileName($file_name);
96
97 63
        return $instance;
98
    }
99
100
    /**
101
     * Set the file name for this instance
102
     *
103
     * @param string $file_name
104
     * @return ModelInterface
105
     */
106 63
    protected function setFileName(string $file_name): ModelInterface
107
    {
108 63
        $this->file_name = $file_name;
109
110 63
        $this->parseFile();
111
112 63
        return $this;
113
    }
114
115
    /**
116
     * Parse the file for this model into model variables
117
     */
118 63
    private function parseFile(): void
119
    {
120 63
        $parsed_file = YamlFrontMatter::parse($this->rawContent());
121
122 63
        $this->matter = $parsed_file->matter();
123 63
        $this->body = $parsed_file->body();
124 63
    }
125
126
    /**
127
     * Get the raw content of the file + front matter
128
     *
129
     * @return string
130
     */
131 63
    public function rawContent(): string
132
    {
133 63
        $file_path = $this->getFilePath();
134
135 63
        if ($this->exists()) {
136 47
            return file_get_contents($file_path);
137
        }
138
139 62
        return "";
140
    }
141
142
    /**
143
     * Get the file path for this instance
144
     *
145
     * @return string
146
     */
147 63
    private function getFilePath(): string
148
    {
149 63
        $folder_path = $this->getFolderPath();
150
151 63
        if (!is_null($matching_filename = $this->getFullFileName())) {
152 47
            $this->setExtension(pathinfo($matching_filename, PATHINFO_EXTENSION));
153
        }
154
155 63
        return "{$folder_path}/{$this->file_name}.{$this->extension}";
156
    }
157
158
    /**
159
     * Get full file name (including extension) for this model.
160
     *
161
     * @return string|null
162
     */
163 63
    private function getFullFileName(): ?string
164
    {
165 63
        if (!$this->full_file_name) {
166 63
            $this->full_file_name = $this->getFileMatchFromDisk();
167
        }
168
169 63
        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 63
    private function getFileMatchFromDisk(): ?string
179
    {
180 63
        $haystack = $this->getModelFiles();
181
182 63
        $min = 0;
183 63
        $max = count($haystack);
184
185
        // No saved files, lookup is pointless
186 63
        if ($max === 0) {
187 62
            return null;
188
        }
189
190 47
        while ($max >= $min) {
191 47
            $mid = floor(($min + $max) / 2);
192
193
            // Current key doesn't exist, so let's try a lower number
194 47
            if (!isset($haystack[$mid])) {
195 2
                $max = $mid - 1;
196 2
                continue;
197
            }
198
199 47
            if (strpos($haystack[$mid], "{$this->file_name}.") !== false) {
200 47
                return $haystack[$mid];
201 6
            } elseif ($haystack[$mid] < $this->file_name) {
202
                // The new chunk will be the second half
203 2
                $min = $mid + 1;
204
            } else {
205
                // The new chunk will be the first half
206 5
                $max = $mid - 1;
207
            }
208
        }
209
210 6
        return null;
211
    }
212
213
    /**
214
     * Get all models for this type
215
     *
216
     * @return array
217
     */
218 63
    private function getModelFiles(): array
219
    {
220 63
        $filenames = array_values(
221
            array_diff(
222 63
                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

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