Passed
Push — master ( f2ac3c...abba28 )
by Caen
03:46 queued 12s
created

FeaturedImage::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Framework\Features\Blogging\Models;
6
7
use Hyde\Facades\Config;
8
use Hyde\Framework\Exceptions\FileNotFoundException;
9
use Hyde\Hyde;
10
use Hyde\Markdown\Contracts\FrontMatter\SubSchemas\FeaturedImageSchema;
11
use Illuminate\Support\Facades\Http;
12
use Illuminate\Support\Str;
13
use Stringable;
14
use function array_flip;
15
use function array_key_exists;
16
use function file_exists;
17
use function filesize;
18
use function key;
19
use function sprintf;
20
21
/**
22
 * Object representation of a blog post's featured image.
23
 *
24
 * While the object can of course be used for any other page type,
25
 * it is named "FeaturedImage" as it's only usage within Hyde
26
 * is for the featured image of a Markdown blog post.
27
 *
28
 * @see \Hyde\Framework\Factories\FeaturedImageFactory
29
 */
30
class FeaturedImage implements Stringable, FeaturedImageSchema
31
{
32
    /**
33
     * A featured image object, for a file stored locally.
34
     *
35
     * The internal data structure forces the image source to reference a file in the _media directory,
36
     * and thus that is what is required for the input. However, when outputting data, the data will
37
     * be used for the _site/media directory, so it will provide data relative to the site root.
38
     *
39
     * The source information is stored in $this->source, which is a file in the _media directory.
40
     */
41
    protected final const TYPE_LOCAL = 'local';
42
43
    /**
44
     * A featured image object, for a file stored remotely.
45
     */
46
    protected final const TYPE_REMOTE = 'remote';
47
48
    /** @var self::TYPE_* */
49
    protected readonly string $type;
50
51
    protected readonly string $source;
52
53
    public function __construct(
54
        string $source,
55
        protected readonly ?string $altText = null,
56
        protected readonly ?string $titleText = null,
57
        protected readonly ?string $authorName = null,
58
        protected readonly ?string $authorUrl = null,
59
        protected readonly ?string $copyrightText = null,
60
        protected readonly ?string $licenseName = null,
61
        protected readonly ?string $licenseUrl = null
62
    ) {
63
        $this->type = $this->isRemote($source) ? self::TYPE_REMOTE : self::TYPE_LOCAL;
0 ignored issues
show
Bug introduced by
The property type is declared read-only in Hyde\Framework\Features\...ng\Models\FeaturedImage.
Loading history...
Documentation Bug introduced by
It seems like $this->isRemote($source)...MOTE : self::TYPE_LOCAL of type string is incompatible with the declared type Hyde\Framework\Features\...ng\Models\FeaturedImage of property $type.

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...
64
        $this->source = $this->setSource($source);
0 ignored issues
show
Bug introduced by
The property source is declared read-only in Hyde\Framework\Features\...ng\Models\FeaturedImage.
Loading history...
65
    }
66
67
    public function __toString(): string
68
    {
69
        return $this->getSource();
70
    }
71
72
    /**
73
     * Get the source of the image, must be usable within the src attribute of an image tag,
74
     * and is thus not necessarily the path to the source image on disk.
75
     *
76
     * @return string The image's url or path
77
     */
78
    public function getSource(): string
79
    {
80
        if ($this->type === self::TYPE_LOCAL) {
81
            // Return value is always resolvable from a compiled page in the _site directory.
82
            return Hyde::mediaLink($this->source);
83
        }
84
85
        return $this->source;
86
    }
87
88
    protected function setSource(string $source): string
89
    {
90
        if ($this->type === self::TYPE_LOCAL) {
91
            // Normalize away any leading media path prefixes.
92
            return Str::after($source, Hyde::getMediaDirectory().'/');
93
        }
94
95
        return $source;
96
    }
97
98
    public function getContentLength(): int
99
    {
100
        if ($this->type === self::TYPE_LOCAL) {
101
            return $this->getContentLengthForLocalImage();
102
        }
103
104
        return $this->getContentLengthForRemoteImage();
105
    }
106
107
    /** @return self::TYPE_* */
108
    public function getType(): string
109
    {
110
        return $this->type;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->type returns the type string which is incompatible with the documented return type Hyde\Framework\Features\...ng\Models\FeaturedImage.
Loading history...
111
    }
112
113
    /**
114
     * Used in resources/views/components/post/image.blade.php to add meta tags with itemprop attributes.
115
     *
116
     * @return array{text?: string|null, name?: string|null, url: string, contentUrl: string}
117
     */
118
    public function getMetadataArray(): array
119
    {
120
        $metadata = [];
121
122
        if ($this->hasAltText()) {
123
            $metadata['text'] = $this->getAltText();
124
        }
125
126
        if ($this->hasTitleText()) {
127
            $metadata['name'] = $this->getTitleText();
128
        }
129
130
        $metadata['url'] = $this->getSource();
131
        $metadata['contentUrl'] = $this->getSource();
132
133
        return $metadata;
134
    }
135
136
    public function getAltText(): ?string
137
    {
138
        return $this->altText;
139
    }
140
141
    public function getTitleText(): ?string
142
    {
143
        return $this->titleText;
144
    }
145
146
    public function getAuthorName(): ?string
147
    {
148
        return $this->authorName;
149
    }
150
151
    public function getAuthorUrl(): ?string
152
    {
153
        return $this->authorUrl;
154
    }
155
156
    public function getCopyrightText(): ?string
157
    {
158
        return $this->copyrightText;
159
    }
160
161
    public function getLicenseName(): ?string
162
    {
163
        return $this->licenseName;
164
    }
165
166
    public function getLicenseUrl(): ?string
167
    {
168
        return $this->licenseUrl;
169
    }
170
171
    public function hasAltText(): bool
172
    {
173
        return $this->has('altText');
174
    }
175
176
    public function hasTitleText(): bool
177
    {
178
        return $this->has('titleText');
179
    }
180
181
    public function hasAuthorName(): bool
182
    {
183
        return $this->has('authorName');
184
    }
185
186
    public function hasAuthorUrl(): bool
187
    {
188
        return $this->has('authorUrl');
189
    }
190
191
    public function hasCopyrightText(): bool
192
    {
193
        return $this->has('copyrightText');
194
    }
195
196
    public function hasLicenseName(): bool
197
    {
198
        return $this->has('licenseName');
199
    }
200
201
    public function hasLicenseUrl(): bool
202
    {
203
        return $this->has('licenseUrl');
204
    }
205
206
    protected function has(string $property): bool
207
    {
208
        return $this->$property !== null;
209
    }
210
211
    protected function getContentLengthForLocalImage(): int
212
    {
213
        $storagePath = Hyde::mediaPath($this->source);
214
215
        if (! file_exists($storagePath)) {
216
            throw new FileNotFoundException(sprintf('Image at %s does not exist', Hyde::pathToRelative($storagePath)));
217
        }
218
219
        return filesize($storagePath);
220
    }
221
222
    protected function getContentLengthForRemoteImage(): int
223
    {
224
        $headers = Http::withHeaders([
225
            'User-Agent' => Config::getString('hyde.http_user_agent', 'RSS Request Client'),
226
        ])->head($this->getSource())->headers();
227
228
        if (array_key_exists('Content-Length', $headers)) {
229
            return (int) key(array_flip($headers['Content-Length']));
230
        }
231
232
        // Here we could throw an exception if we want to be strict about this, or add a warning.
233
234
        return 0;
235
    }
236
237
    public static function isRemote(string $source): bool
238
    {
239
        return str_starts_with($source, 'http') || str_starts_with($source, '//');
240
    }
241
}
242