Completed
Push — master ( 3fdc21...2574b5 )
by ReliQ
01:51
created

Product::loadMeta()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.8017
c 0
b 0
f 0
cc 6
nc 32
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ReliqArts\Docweaver\Models;
6
7
use Carbon\Carbon;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Contracts\Support\Jsonable;
10
use Illuminate\Support\Str;
11
use ReliqArts\Docweaver\Contracts\ConfigProvider;
12
use ReliqArts\Docweaver\Contracts\Exception;
13
use ReliqArts\Docweaver\Contracts\Filesystem;
14
use ReliqArts\Docweaver\Exceptions\ParsingFailed;
15
use ReliqArts\Docweaver\Exceptions\Product\AssetPublicationFailed;
16
use ReliqArts\Docweaver\Exceptions\Product\InvalidAssetDirectory;
17
use Symfony\Component\Yaml\Exception\ParseException;
18
use Symfony\Component\Yaml\Yaml;
19
20
/**
21
 * A documented product.
22
 */
23
class Product implements Arrayable, Jsonable
24
{
25
    public const VERSION_MASTER = 'master';
26
    public const VERSION_UNKNOWN = 'unknown';
27
28
    private const ASSET_URL_PLACEHOLDER_1 = '{{docs}}';
29
    private const ASSET_URL_PLACEHOLDER_2 = '{{doc}}';
30
    private const ASSET_URL_PLACEHOLDERS = [
31
        self::ASSET_URL_PLACEHOLDER_1,
32
        self::ASSET_URL_PLACEHOLDER_2,
33
    ];
34
    private const META_FILE = '.docweaver.yml';
35
36
    /**
37
     * Product key.
38
     *
39
     * @var string
40
     */
41
    private $key;
42
43
    /**
44
     * Filesystem.
45
     *
46
     * @var Filesystem
47
     */
48
    private $filesystem;
49
50
    /**
51
     * @var ConfigProvider
52
     */
53
    private $configProvider;
54
55
    /**
56
     * Last time product was modified (timestamp).
57
     *
58
     * @var int
59
     */
60
    private $lastModified;
61
62
    /**
63
     * Product name.
64
     *
65
     * @var string
66
     */
67
    private $name;
68
69
    /**
70
     * Product description.
71
     *
72
     * @var null|string
73
     */
74
    private $description;
75
76
    /**
77
     * Product image url.
78
     *
79
     * @var null|string
80
     */
81
    private $imageUrl;
82
83
    /**
84
     * Product resource directory.
85
     *
86
     * @var string
87
     */
88
    private $directory;
89
90
    /**
91
     * Product meta (from file).
92
     *
93
     * @var array
94
     */
95
    private $meta;
96
97
    /**
98
     * List of available product versions.
99
     *
100
     * @var array
101
     */
102
    private $versions;
103
104
    /**
105
     * Create product instance.
106
     *
107
     * @param Filesystem     $filesystem
108
     * @param ConfigProvider $configProvider
109
     * @param string         $directory
110
     */
111
    public function __construct(Filesystem $filesystem, ConfigProvider $configProvider, string $directory)
112
    {
113
        $this->filesystem = $filesystem;
114
        $this->configProvider = $configProvider;
115
        $this->name = Str::title(basename($directory));
116
        $this->key = strtolower($this->name);
117
        $this->directory = $directory;
118
        $this->meta = [];
119
        $this->versions = [];
120
    }
121
122
    /**
123
     * Populate product.
124
     *
125
     * @throws Exception if meta file could not be parsed
126
     */
127
    public function populate(): void
128
    {
129
        $this->loadVersions();
130
        $this->loadMeta();
131
    }
132
133
    /**
134
     * @return string
135
     */
136
    public function getKey(): string
137
    {
138
        return $this->key;
139
    }
140
141
    /**
142
     * Get default version for product.
143
     *
144
     * @param bool $allowWordedDefault whether a worded version should be accepted as default
145
     *
146
     * @return string
147
     */
148
    public function getDefaultVersion(bool $allowWordedDefault = false): string
149
    {
150
        $versions = empty($this->versions) ? $this->getVersions() : $this->versions;
151
        $allowWordedDefault = $allowWordedDefault || $this->configProvider->isWordedDefaultVersionAllowed();
152
        $defaultVersion = self::VERSION_UNKNOWN;
153
154
        foreach ($versions as $tag => $ver) {
155
            if (!$allowWordedDefault) {
156
                if (is_numeric($tag)) {
157
                    $defaultVersion = $tag;
158
159
                    break;
160
                }
161
            } else {
162
                $defaultVersion = $tag;
163
164
                break;
165
            }
166
        }
167
168
        return $defaultVersion;
169
    }
170
171
    /**
172
     * Get product directory.
173
     *
174
     * @return string
175
     */
176
    public function getDirectory(): string
177
    {
178
        return $this->directory;
179
    }
180
181
    /**
182
     * Get product name.
183
     *
184
     * @return string
185
     */
186
    public function getName(): string
187
    {
188
        return $this->name;
189
    }
190
191
    /**
192
     * Get product description.
193
     *
194
     * @return null|string
195
     */
196
    public function getDescription(): ?string
197
    {
198
        return $this->description;
199
    }
200
201
    /**
202
     * Get product image url.
203
     *
204
     * @return null|string
205
     */
206
    public function getImageUrl(): ?string
207
    {
208
        return $this->imageUrl;
209
    }
210
211
    /**
212
     * Get the publicly available versions of the product.
213
     *
214
     * @return array
215
     */
216
    public function getVersions(): array
217
    {
218
        return $this->versions;
219
    }
220
221
    /**
222
     * Get last modified time.
223
     *
224
     * @return Carbon
225
     */
226
    public function getLastModified(): Carbon
227
    {
228
        return Carbon::createFromTimestamp($this->lastModified);
229
    }
230
231
    /**
232
     * Determine if the given string is a valid version.
233
     *
234
     * @param string $version
235
     *
236
     * @return bool
237
     */
238
    public function hasVersion(string $version): bool
239
    {
240
        return in_array($version, array_keys($this->getVersions()), true);
241
    }
242
243
    /**
244
     * Publish product public assets.
245
     *
246
     * @param string $version
247
     *
248
     * @throws Exception if products asset directory is invalid or assets could not be published
249
     */
250
    public function publishAssets(string $version): void
251
    {
252
        $version = empty($version) ? $this->getDefaultVersion() : $version;
253
        $storagePath = storage_path(
254
            sprintf('app/public/%s/%s/%s', $this->configProvider->getRoutePrefix(), $this->key, $version)
255
        );
256
        $imageDirectory = sprintf('%s/%s/images', $this->directory, $version);
257
258
        if (!$this->filesystem->isDirectory($imageDirectory)) {
259
            throw InvalidAssetDirectory::forDirectory($imageDirectory);
260
        }
261
262
        if (!$this->filesystem->copyDirectory($imageDirectory, sprintf('%s/images', $storagePath))) {
263
            throw AssetPublicationFailed::forProductAssetsOfType($this, 'image');
264
        }
265
    }
266
267
    /**
268
     * Get the instance as an array.
269
     *
270
     * @return array
271
     */
272
    public function toArray(): array
273
    {
274
        return [
275
            'key' => $this->key,
276
            'name' => $this->name,
277
            'description' => $this->description,
278
            'imageUrl' => $this->imageUrl,
279
            'directory' => $this->directory,
280
            'versions' => $this->versions,
281
            'defaultVersion' => $this->getDefaultVersion(),
282
            'lastModified' => $this->getLastModified(),
283
        ];
284
    }
285
286
    /**
287
     * Convert the object to its JSON representation.
288
     *
289
     * @param int $options
290
     *
291
     * @return string
292
     */
293
    public function toJson($options = 0): string
294
    {
295
        return json_encode($this->toArray());
296
    }
297
298
    /**
299
     * @return string
300
     */
301
    public function getMasterDirectory(): string
302
    {
303
        return sprintf('%s/%s', $this->getDirectory(), Product::VERSION_MASTER);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
304
    }
305
306
    /**
307
     * Convert url string to asset url relative to current product.
308
     *
309
     * @param string $url
310
     * @param string $version
311
     *
312
     * @return string
313
     */
314
    private function getAssetUrl(string $url, string $version): string
315
    {
316
        $url = empty($url) ? self::ASSET_URL_PLACEHOLDER_1 : $url;
317
318
        if (stripos($url, 'http') === 0) {
319
            return $url;
320
        }
321
322
        return asset(
323
            str_replace(
324
                self::ASSET_URL_PLACEHOLDERS,
325
                sprintf('storage/%s/%s/%s', $this->configProvider->getRoutePrefix(), $this->key, $version),
326
                $url
327
            )
328
        );
329
    }
330
331
    /**
332
     * Load meta onto product.
333
     *
334
     * @param string $version Version to load configuration from. (optional)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $version not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
335
     *
336
     * @throws Exception if meta file could not be parsed
337
     */
338
    private function loadMeta(string $version = null): void
339
    {
340
        $version = empty($version) ? $this->getDefaultVersion() : $version;
341
        $metaFile = realpath(sprintf('%s/%s/%s', $this->directory, $version, self::META_FILE));
342
343
        if (empty($metaFile)) {
344
            return;
345
        }
346
347
        try {
348
            $meta = Yaml::parse(file_get_contents($metaFile));
349
350
            if (!empty($meta['name'])) {
351
                $this->name = $meta['name'];
352
            }
353
            if (!empty($meta['description'])) {
354
                $this->description = $meta['description'];
355
            }
356
357
            $this->imageUrl = $this->getAssetUrl($meta['image_url'] ?? '', $version);
358
            $this->meta = $meta;
359
        } catch (ParseException $exception) {
360
            $message = sprintf(
361
                'Failed to parse meta file `%s`. %s',
362
                $metaFile,
363
                $exception->getMessage()
364
            );
365
366
            throw ParsingFailed::forFile($metaFile)->withMessage($message);
367
        }
368
    }
369
370
    /**
371
     * Load product versions.
372
     */
373
    private function loadVersions(): void
374
    {
375
        $versions = [];
376
377
        if ($this->key) {
378
            $versionDirs = $this->filesystem->directories($this->directory);
379
380
            // add versions to version array
381
            foreach ($versionDirs as $ver) {
382
                $versionTag = basename($ver);
383
                $versionName = Str::title($versionTag);
384
                $versions[$versionTag] = $versionName;
385
            }
386
387
            // update last modified
388
            $this->lastModified = $this->filesystem->lastModified($this->directory);
389
390
            // sort versions
391
            krsort($versions);
392
        }
393
394
        $this->versions = $versions;
395
    }
396
}
397