12
12
13
13
final class ImageStorage
14
14
{
15
+ public const IMAGE_MIME_TYPES = ['image/gif ' , 'image/png ' , 'image/jpeg ' , 'image/webp ' ];
16
+
15
17
private string $ storagePath ;
16
18
17
19
private string $ relativeStoragePath ;
@@ -33,36 +35,65 @@ public function __construct(?string $storagePath = null, string $relativeStorage
33
35
}
34
36
35
37
38
+ /**
39
+ * The method downloads the image from the physical URL and saves it to the internal storage.
40
+ * The download is checked to ensure that it is a valid data type for the image.
41
+ */
36
42
public function save (string $ url ): void
37
43
{
38
44
if (Validators::isUrl ($ url ) === false ) {
39
45
throw new \InvalidArgumentException ('Given input is not valid absolute URL, because " ' . $ url . '" given. ' );
40
46
}
41
47
$ storagePath = $ this ->getInternalPath ($ url );
42
- if (\is_file ($ storagePath ) === false ) {
43
- FileSystem::copy ($ url , $ storagePath );
48
+ if (is_file ($ storagePath ) === false ) { // image does not exist in local storage
49
+ $ content = FileSystem::read ($ url ); // download image
50
+ $ contentType = strtolower (finfo_file (finfo_open (FILEINFO_MIME_TYPE ), $ content ));
51
+ if (in_array ($ contentType , self ::IMAGE_MIME_TYPES , true ) === false ) {
52
+ throw new \RuntimeException (
53
+ 'Security issue: Downloaded file " ' . $ url . '" is not valid image, '
54
+ . 'because content type " ' . $ contentType . '" has been detected. ' ,
55
+ );
56
+ }
57
+ FileSystem::write ($ storagePath , $ content );
44
58
}
45
59
}
46
60
47
61
62
+ /**
63
+ * Returns the absolute path for the internal data store.
64
+ * Specifies the absolute physical disk path where the image will be written to (or read from) based on the URL.
65
+ * The disk path is calculated by a deterministic algorithm for future content readability.
66
+ */
48
67
public function getInternalPath (string $ url ): string
49
68
{
50
69
return $ this ->storagePath . '/ ' . $ this ->getRelativeInternalUrl ($ url );
51
70
}
52
71
53
72
73
+ /**
74
+ * Returns the physical URL to download the image directly to local disk storage.
75
+ */
54
76
public function getAbsoluteInternalUrl (string $ url ): string
55
77
{
56
78
try {
57
79
$ baseUrl = Url::get ()->getBaseUrl ();
58
80
} catch (\Throwable ) {
81
+ if (PHP_SAPI === 'cli ' ) {
82
+ throw new \LogicException (
83
+ __METHOD__ . ': Absolute URL is not available in CLI. '
84
+ . 'Did you set context URL to " ' . Url::class . '" service? ' ,
85
+ );
86
+ }
59
87
$ baseUrl = '' ;
60
88
}
61
89
62
90
return $ baseUrl . '/ ' . $ this ->relativeStoragePath . '/ ' . $ this ->getRelativeInternalUrl ($ url );
63
91
}
64
92
65
93
94
+ /**
95
+ * Returns the relative URL to retrieve the image.
96
+ */
66
97
public function getRelativeInternalUrl (string $ url ): string
67
98
{
68
99
$ originalFileName = (string ) preg_replace_callback (
@@ -76,10 +107,14 @@ public function getRelativeInternalUrl(string $url): string
76
107
}
77
108
78
109
110
+ /**
111
+ * Generate an automatic unique hash based on the image URL
112
+ * so that file names from many different sources cannot collide.
113
+ */
79
114
private function resolvePrefixDir (string $ url ): string
80
115
{
81
116
if ($ url === '' ) {
82
- throw new \InvalidArgumentException ('URL can not be empty string. ' );
117
+ throw new \LogicException ('URL can not be empty string. ' );
83
118
}
84
119
if (preg_match ('/wp-content.+(\d{4})\/(\d{2})/ ' , $ url , $ urlParser )) {
85
120
return $ urlParser [1 ] . '- ' . $ urlParser [2 ];
0 commit comments