FeaturedImage::hasLicenseName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Hyde;
8
use Stringable;
9
use Hyde\Facades\Config;
10
use Illuminate\Support\Str;
11
use Hyde\Support\BuildWarnings;
12
use Illuminate\Support\Facades\Http;
13
use Hyde\Foundation\Kernel\Hyperlinks;
14
use Hyde\Support\Filesystem\MediaFile;
15
use Hyde\Markdown\Contracts\FrontMatter\SubSchemas\FeaturedImageSchema;
16
17
use function array_key_exists;
18
use function array_flip;
19
use function key;
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 $licenseName = null,
60
        protected readonly ?string $licenseUrl = null,
61
        protected readonly ?string $copyrightText = null,
62
        protected readonly ?string $caption = null
63
    ) {
64
        $this->type = Hyperlinks::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 Hyde\Foundation\Kernel\H...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...
65
        $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...
66
    }
67
68
    public function __toString(): string
69
    {
70
        return $this->getSource();
71
    }
72
73
    /**
74
     * Get the source of the image, must be usable within the src attribute of an image tag,
75
     * and is thus not necessarily the path to the source image on disk.
76
     *
77
     * @return string The image's url or path
78
     */
79
    public function getSource(): string
80
    {
81
        if ($this->type === self::TYPE_LOCAL) {
82
            // Return value is always resolvable from a compiled page in the _site directory.
83
            return (string) Hyde::asset($this->source);
0 ignored issues
show
Bug introduced by
The method asset() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

83
            return (string) Hyde::/** @scrutinizer ignore-call */ asset($this->source);
Loading history...
84
        }
85
86
        return $this->source;
87
    }
88
89
    protected function setSource(string $source): string
90
    {
91
        if ($this->type === self::TYPE_LOCAL) {
92
            // Normalize away any leading media path prefixes.
93
            return Str::after($source, Hyde::getMediaDirectory().'/');
0 ignored issues
show
Bug introduced by
The method getMediaDirectory() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

93
            return Str::after($source, Hyde::/** @scrutinizer ignore-call */ getMediaDirectory().'/');
Loading history...
94
        }
95
96
        return $source;
97
    }
98
99
    public function getContentLength(): int
100
    {
101
        if ($this->type === self::TYPE_LOCAL) {
102
            return $this->getContentLengthForLocalImage();
103
        }
104
105
        return $this->getContentLengthForRemoteImage();
106
    }
107
108
    /** @return self::TYPE_* */
109
    public function getType(): string
110
    {
111
        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...
112
    }
113
114
    /**
115
     * Used in resources/views/components/post/image.blade.php to add meta tags with itemprop attributes.
116
     *
117
     * @return array{text?: string|null, name?: string|null, url: string, contentUrl: string}
118
     */
119
    public function getMetadataArray(): array
120
    {
121
        $metadata = [];
122
123
        if ($this->hasAltText()) {
124
            $metadata['text'] = $this->getAltText();
125
        }
126
127
        if ($this->hasTitleText()) {
128
            $metadata['name'] = $this->getTitleText();
129
        }
130
131
        if ($this->hasCaption()) {
132
            $metadata['caption'] = $this->getCaption();
133
        }
134
135
        $metadata['url'] = $this->getSource();
136
        $metadata['contentUrl'] = $this->getSource();
137
138
        return $metadata;
139
    }
140
141
    public function getAltText(): ?string
142
    {
143
        return $this->altText ?? $this->caption;
144
    }
145
146
    public function getTitleText(): ?string
147
    {
148
        return $this->titleText;
149
    }
150
151
    public function getAuthorName(): ?string
152
    {
153
        return $this->authorName;
154
    }
155
156
    public function getAuthorUrl(): ?string
157
    {
158
        return $this->authorUrl;
159
    }
160
161
    public function getCopyrightText(): ?string
162
    {
163
        return $this->copyrightText;
164
    }
165
166
    public function getLicenseName(): ?string
167
    {
168
        return $this->licenseName;
169
    }
170
171
    public function getLicenseUrl(): ?string
172
    {
173
        return $this->licenseUrl;
174
    }
175
176
    public function getCaption(): ?string
177
    {
178
        return $this->caption;
179
    }
180
181
    public function hasAltText(): bool
182
    {
183
        return $this->has('altText');
184
    }
185
186
    public function hasTitleText(): bool
187
    {
188
        return $this->has('titleText');
189
    }
190
191
    public function hasAuthorName(): bool
192
    {
193
        return $this->has('authorName');
194
    }
195
196
    public function hasAuthorUrl(): bool
197
    {
198
        return $this->has('authorUrl');
199
    }
200
201
    public function hasCopyrightText(): bool
202
    {
203
        return $this->has('copyrightText');
204
    }
205
206
    public function hasLicenseName(): bool
207
    {
208
        return $this->has('licenseName');
209
    }
210
211
    public function hasLicenseUrl(): bool
212
    {
213
        return $this->has('licenseUrl');
214
    }
215
216
    public function hasCaption(): bool
217
    {
218
        return $this->has('caption');
219
    }
220
221
    protected function has(string $property): bool
222
    {
223
        return $this->$property !== null;
224
    }
225
226
    protected function getContentLengthForLocalImage(): int
227
    {
228
        return MediaFile::get($this->source)->getLength();
229
    }
230
231
    protected function getContentLengthForRemoteImage(): int
232
    {
233
        // API calls can be skipped in the config, or by setting the --no-api flag when running the build command.
234
        if (Config::getBool('hyde.api_calls', true)) {
235
            /** @var string[][] $headers */
236
            $headers = Http::withHeaders([
237
                'User-Agent' => Config::getString('hyde.http_user_agent', 'RSS Request Client'),
238
            ])->head($this->getSource())->headers();
239
240
            if (array_key_exists('Content-Length', $headers)) {
241
                return (int) key(array_flip($headers['Content-Length']));
242
            }
243
244
            BuildWarnings::report('The image "'.$this->getSource().'" has a content length of zero.');
245
        }
246
247
        return 0;
248
    }
249
}
250