• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

68publishers / image-storage / 22677854876

04 Mar 2026 04:07PM UTC coverage: 87.038% (-4.3%) from 91.356%
22677854876

push

github

tg666
Added port to Referer header if exists

0 of 3 new or added lines in 1 file covered. (0.0%)

113 existing lines in 8 files now uncovered.

1484 of 1705 relevant lines covered (87.04%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.32
/src/Resource/ResourceFactory.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace SixtyEightPublishers\ImageStorage\Resource;
6

7
use Intervention\Image\Exception\ImageException;
8
use Intervention\Image\Image;
9
use Intervention\Image\ImageManager;
10
use League\Flysystem\FilesystemException as LeagueFilesystemException;
11
use League\Flysystem\FilesystemReader;
12
use League\Flysystem\UnableToReadFile;
13
use Psr\Http\Message\StreamInterface;
14
use RuntimeException;
15
use SixtyEightPublishers\FileStorage\Exception\FileNotFoundException;
16
use SixtyEightPublishers\FileStorage\Exception\FilesystemException;
17
use SixtyEightPublishers\FileStorage\PathInfoInterface;
18
use SixtyEightPublishers\FileStorage\Resource\ResourceFactoryInterface;
19
use SixtyEightPublishers\FileStorage\Resource\ResourceInterface;
20
use SixtyEightPublishers\ImageStorage\Modifier\Facade\ModifierFacadeInterface;
21
use SixtyEightPublishers\ImageStorage\PathInfoInterface as ImagePathInfoInterface;
22
use SixtyEightPublishers\ImageStorage\Persistence\ImagePersisterInterface;
23
use function error_clear_last;
24
use function error_get_last;
25
use function fclose;
26
use function file_put_contents;
27
use function filter_var;
28
use function fopen;
29
use function fwrite;
30
use function get_debug_type;
31
use function implode;
32
use function is_file;
33
use function is_string;
34
use function parse_url;
35
use function sprintf;
36
use function stream_context_create;
37
use function sys_get_temp_dir;
38
use function tempnam;
39

40
final class ResourceFactory implements ResourceFactoryInterface
41
{
42
    public function __construct(
1✔
43
        private readonly FilesystemReader $filesystemReader,
44
        private readonly ImageManager $imageManager,
45
        private readonly ModifierFacadeInterface $modifierFacade,
46
    ) {}
1✔
47

48
    /**
49
     * @throws FileNotFoundException
50
     * @throws LeagueFilesystemException
51
     * @throws FilesystemException
52
     */
53
    public function createResource(PathInfoInterface $pathInfo): ResourceInterface
54
    {
55
        if ($pathInfo instanceof ImagePathInfoInterface && null !== $pathInfo->getModifiers()) {
1✔
56
            $sourcePathInfo = $pathInfo->withModifiers(null);
1✔
57
        }
58

59
        $path = isset($sourcePathInfo) ? $sourcePathInfo->getPath() : $pathInfo->getPath();
1✔
60
        $filesystemPath = ImagePersisterInterface::FILESYSTEM_PREFIX_SOURCE . $path;
1✔
61

62
        if (false === $this->filesystemReader->fileExists($filesystemPath)) {
1✔
63
            throw new FileNotFoundException($path);
1✔
64
        }
65

66
        try {
67
            $source = $this->filesystemReader->readStream($filesystemPath);
1✔
68
        } catch (UnableToReadFile $e) {
1✔
69
            throw new FilesystemException(sprintf(
1✔
70
                'Unable to read file "%s".',
1✔
71
                $path,
72
            ), 0, $e);
1✔
73
        } catch (LeagueFilesystemException $e) {
1✔
74
            throw new FilesystemException($e->getMessage(), 0, $e);
1✔
75
        }
76

77
        return $this->createTmpFileResource(
1✔
78
            pathInfo: $pathInfo,
1✔
79
            location: $path,
80
            source: $source,
81
        );
82
    }
83

84
    public function createResourceFromFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
85
    {
86
        if (!filter_var($filename, FILTER_VALIDATE_URL)) {
1✔
87
            return new ImageResource(
1✔
88
                pathInfo: $pathInfo,
1✔
89
                image: $this->makeImage(
1✔
90
                    source: $filename,
1✔
91
                    location: $filename,
92
                ),
93
                localFilename: $filename,
94
                modifierFacade: $this->modifierFacade,
1✔
95
            );
96
        }
97

98
        error_clear_last();
×
99

UNCOV
100
        $headers = [
×
101
            "Accept-language: en\r\n",
102
            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n",
103
        ];
104

UNCOV
105
        $parsedUrl = parse_url($filename) ?: [];
×
106

UNCOV
107
        if (isset($parsedUrl['scheme'], $parsedUrl['host'])) {
×
NEW
108
            $host = $parsedUrl['host'];
×
109

NEW
110
            if (isset($parsedUrl['port'])) {
×
NEW
111
                $host .= ':' . $parsedUrl['port'];
×
112
            }
113

114
            $headers[] = sprintf(
×
115
                "Referer: %s://%s\r\n",
×
116
                $parsedUrl['scheme'],
×
117
                $host,
118
            );
119
        }
120

121
        $context = stream_context_create(
×
122
            options: [
123
                'http' => [
124
                    'method' => 'GET',
×
UNCOV
125
                    'protocol_version' => 1.1,
×
126
                    'follow_location' => true,
UNCOV
127
                    'header' => implode('', $headers),
×
128
                ],
129
            ],
130
        );
131

132
        $source = @fopen(
×
UNCOV
133
            filename: $filename,
×
UNCOV
134
            mode: 'rb',
×
135
            context: $context,
136
        );
137

UNCOV
138
        if (false === $source) {
×
UNCOV
139
            throw new FilesystemException(
×
UNCOV
140
                message: sprintf(
×
UNCOV
141
                    'Can not read stream from url "%s". %s',
×
142
                    $filename,
143
                    error_get_last()['message'] ?? '',
×
144
                ),
145
            );
146
        }
147

UNCOV
148
        return $this->createTmpFileResource(
×
UNCOV
149
            pathInfo: $pathInfo,
×
150
            location: $filename,
151
            source: $source,
152
        );
153
    }
154

155
    public function createResourceFromPsrStream(PathInfoInterface $pathInfo, StreamInterface $stream): ResourceInterface
156
    {
157
        if ($stream->isSeekable()) {
1✔
158
            try {
159
                $stream->rewind();
1✔
UNCOV
160
            } catch (RuntimeException $e) {
×
161
                # ignore
162
            }
163
        }
164

165
        $uri = $stream->getMetadata('uri');
1✔
166

167
        if (is_string($uri) && @is_file($uri)) {
1✔
168
            return new ImageResource(
1✔
169
                pathInfo: $pathInfo,
1✔
170
                image: $this->makeImage(
1✔
171
                    source: $stream,
1✔
172
                    location: $uri,
173
                ),
174
                localFilename: $uri,
175
                modifierFacade: $this->modifierFacade,
1✔
176
            );
177
        }
178

179
        return $this->createTmpFileResource(
1✔
180
            pathInfo: $pathInfo,
1✔
181
            location: get_debug_type($stream),
1✔
182
            source: $stream,
183
        );
184
    }
185

186
    /**
187
     * @throws FilesystemException
188
     */
189
    private function createTmpFileResource(PathInfoInterface $pathInfo, string $location, mixed $source): TmpFileImageResource
190
    {
191
        $tmpFilename = (string) tempnam(sys_get_temp_dir(), '68Publishers_ImageStorage');
1✔
192

193
        if ($source instanceof StreamInterface) {
1✔
194
            $tmpStream = @fopen($tmpFilename, 'wb');
1✔
195

196
            if (false === $tmpStream) {
1✔
UNCOV
197
                throw new FilesystemException(
×
198
                    message: sprintf(
×
199
                        'Unable to open tmp file "%s" for writing.',
×
200
                        $tmpFilename,
201
                    ),
202
                );
203
            }
204

205
            try {
206
                while (!$source->eof()) {
1✔
207
                    $chunk = $source->read(8192);
1✔
208

209
                    if ('' === $chunk) {
1✔
UNCOV
210
                        break;
×
211
                    }
212

213
                    fwrite($tmpStream, $chunk);
1✔
214
                }
UNCOV
215
            } catch (RuntimeException $e) {
×
UNCOV
216
                throw new FilesystemException(
×
UNCOV
217
                    message: sprintf(
×
UNCOV
218
                        'Unable to write tmp file for "%s". %s',
×
219
                        $location,
UNCOV
220
                        $e->getMessage(),
×
221
                    ),
222
                    previous: $e,
223
                );
224
            } finally {
1✔
225
                fclose($tmpStream);
1✔
226
            }
227
        } elseif (false === file_put_contents($tmpFilename, $source)) {
1✔
UNCOV
228
            throw new FilesystemException(
×
UNCOV
229
                message: sprintf(
×
UNCOV
230
                    'Unable to write tmp file for "%s".',
×
231
                    $location,
232
                ),
233
            );
234
        }
235

236
        return new TmpFileImageResource(
1✔
237
            pathInfo: $pathInfo,
238
            image: $this->makeImage(
1✔
239
                source: $tmpFilename,
1✔
240
                location: $location,
241
            ),
242
            modifierFacade: $this->modifierFacade,
1✔
243
            tmpFile: new TmpFile($tmpFilename),
1✔
244
        );
245
    }
246

247
    /**
248
     * @throws FilesystemException
249
     */
250
    private function makeImage(mixed $source, string $location): Image
251
    {
252
        try {
253
            return $this->imageManager->make($source);
1✔
UNCOV
254
        } catch (ImageException $e) {
×
UNCOV
255
            throw new FilesystemException(
×
UNCOV
256
                message: sprintf(
×
UNCOV
257
                    'Unable to create image "%s". %s',
×
258
                    $location,
UNCOV
259
                    $e->getMessage(),
×
260
                ),
261
                previous: $e,
262
            );
263
        }
264
    }
265
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc