PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.21
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.21
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / app / Services / FileSystem / Drivers / S3 / S3Driver.php

S3Driver.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.21, at app/Services/FileSystem/Drivers/S3/S3Driver.php

282 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\FileSystem\Drivers\S3;
4
5 use FluentCart\App\Models\ProductDownload;
6 use FluentCart\App\Modules\StorageDrivers\S3\S3;
7 use FluentCart\App\Modules\StorageDrivers\S3\S3 as S3StorageDriver;
8 use FluentCart\App\Modules\StorageDrivers\S3\S3Settings;
9 use FluentCart\App\Services\FileSystem\Drivers\BaseDriver;
10 use FluentCart\Framework\Support\Arr;
11
12 class S3Driver extends BaseDriver
13 {
14 private string $accessKey;
15 private string $secretKey;
16 private string $bucket;
17 private string $region;
18
19
20 public function __construct(?string $dirPath = null, ?string $dirName = null)
21 {
22 parent::__construct($dirPath, $dirName);
23
24 $getSettings = (new S3Settings())->get();
25
26 $this->secretKey = Arr::get($getSettings, 'secret_key', '');
27 $this->accessKey = Arr::get($getSettings, 'access_key', '');
28 $this->bucket = S3Settings::resolveEffectiveBucket($getSettings);
29 $this->region = Arr::get($getSettings, 'region', '');
30 $this->storageDriver = new S3StorageDriver();
31 }
32
33 public function buckets()
34 {
35 return S3BucketList::get(
36 $this->secretKey,
37 $this->accessKey,
38 $this->region
39 );
40 }
41
42 public function uploadFile($localFilePath, $uploadToFilePath, $file, $params = [])
43 {
44 $fileSize = $file->toArray()['size_in_bytes'];
45
46 $response = S3FileUploader::upload(
47 $this->secretKey,
48 $this->accessKey,
49 $this->bucket,
50 $this->region,
51 $localFilePath,
52 $uploadToFilePath
53 );
54
55 if (is_wp_error($response)) {
56 return $response;
57 }
58
59 return [
60 'message' => __('File Uploaded Successfully', 'fluent-cart'),
61 'path' => $response['path'],
62 'file' => [
63 'driver' => 's3',
64 'size' => $fileSize,
65 'bucket' => $this->bucket,
66 'name' => $response['path'],
67 ],
68 ];
69 }
70
71
72 public function listFiles(array $params = [])
73 {
74 return S3FileList::get(
75 $this->secretKey,
76 $this->accessKey,
77 $this->bucket,
78 $this->region,
79 Arr::get($params, 'search', ''),
80 );
81 }
82
83 protected function generatePresignedDownloadUrlOld(string $filePath, $expirationMinutes = 15, $bucket = null, $fileName = null): string
84 {
85 $this->bucket = $bucket ?? $this->bucket;
86 $this->region = S3::getBucketRegion($this->bucket);
87
88
89 if (empty($fileName)) {
90 $fileName = basename($filePath);
91 }
92
93 $fileName = explode('_____fluent-cart_____', $fileName)[0];
94 $fileName = explode('__fluent-cart__', $fileName)[0];
95
96 $expirationMinutes = is_numeric($expirationMinutes) ? (int)$expirationMinutes : 15;
97 $expires = time() + ($expirationMinutes * 60);
98
99 $stringToSign = "GET\n\n\n{$expires}\n/{$this->bucket}/{$filePath}";
100
101 $queryParams = [
102 'AWSAccessKeyId' => $this->accessKey,
103 'Expires' => $expires,
104 ];
105
106 if (!empty($fileName)) {
107 $contentDisposition = "attachment; filename=\"{$fileName}\"";
108 $queryParams['response-content-disposition'] = $contentDisposition;
109
110 // For Signature V1, the parameter value is NOT URL-encoded in string-to-sign
111 $stringToSign .= '?response-content-disposition=' . $contentDisposition;
112 }
113
114 $signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->secretKey, true));
115 $queryParams['Signature'] = $signature;
116
117 $url = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$filePath}";
118 $queryString = http_build_query($queryParams);
119
120 return $url . '?' . $queryString;
121 }
122
123 protected function generatePresignedDownloadUrl(string $filePath, $expirationMinutes = 15, $bucket = null, $fileName = null): string
124 {
125 $this->bucket = $bucket ?? $this->bucket;
126 $this->region = S3::getBucketRegion($this->bucket);
127
128 if (empty($fileName)) {
129 $fileName = basename($filePath);
130 }
131
132 $fileName = explode('_____fluent-cart_____', $fileName)[0];
133 $fileName = explode('__fluent-cart__', $fileName)[0];
134
135 $expirationMinutes = is_numeric($expirationMinutes) ? (int)$expirationMinutes : 15;
136 $expiresInSeconds = $expirationMinutes * 60;
137
138 // AWS Signature V4 requires ISO8601 format
139 $dateTime = gmdate('Ymd\THis\Z');
140 $dateStamp = gmdate('Ymd');
141
142 // Credential scope
143 $credentialScope = "{$dateStamp}/{$this->region}/s3/aws4_request";
144
145 $hasDot = strpos($this->bucket, '.') !== false;
146
147 // Canonical request components
148 $encodedFilePath = '/' . implode('/', array_map(
149 'rawurlencode',
150 explode('/', ltrim($filePath, '/'))
151 ));
152
153 // For dotted buckets, use path-style: include bucket in canonical URI
154 if ($hasDot) {
155 $canonicalUri = '/' . $this->bucket . $encodedFilePath;
156 $host = "s3.{$this->region}.amazonaws.com";
157 } else {
158 $canonicalUri = $encodedFilePath;
159 $host = "{$this->bucket}.s3.{$this->region}.amazonaws.com";
160 }
161
162 $canonicalQueryString = [
163 'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256',
164 'X-Amz-Credential' => $this->accessKey . '/' . $credentialScope,
165 'X-Amz-Date' => $dateTime,
166 'X-Amz-Expires' => $expiresInSeconds,
167 'X-Amz-SignedHeaders' => 'host',
168 ];
169
170 if (!empty($fileName)) {
171 $canonicalQueryString['response-content-disposition'] = 'attachment; filename="' . $fileName . '"';
172 }
173
174 // Sort query parameters
175 ksort($canonicalQueryString);
176
177 // Build canonical query string
178 $canonicalQueryStringEncoded = [];
179 foreach ($canonicalQueryString as $key => $value) {
180 $canonicalQueryStringEncoded[] = rawurlencode($key) . '=' . rawurlencode($value);
181 }
182 $canonicalQueryStringStr = implode('&', $canonicalQueryStringEncoded);
183
184 // Canonical headers
185 $canonicalHeaders = "host:{$host}\n";
186
187 // Create canonical request
188 $canonicalRequest = "GET\n{$canonicalUri}\n{$canonicalQueryStringStr}\n{$canonicalHeaders}\nhost\nUNSIGNED-PAYLOAD";
189
190 // String to sign
191 $stringToSign = "AWS4-HMAC-SHA256\n{$dateTime}\n{$credentialScope}\n" . hash('sha256', $canonicalRequest);
192
193 // Calculate signature
194 $kDate = hash_hmac('sha256', $dateStamp, 'AWS4' . $this->secretKey, true);
195 $kRegion = hash_hmac('sha256', $this->region, $kDate, true);
196 $kService = hash_hmac('sha256', 's3', $kRegion, true);
197 $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
198 $signature = hash_hmac('sha256', $stringToSign, $kSigning);
199
200 // Build final URL
201 $url = "https://{$host}{$canonicalUri}?{$canonicalQueryStringStr}&X-Amz-Signature={$signature}";
202
203 return $url;
204 }
205 protected function retrieveFileForDownload(string $downloadableFilePath, $bucket = null)
206 {
207 $this->bucket = $bucket;
208 $this->region = S3::getBucketRegion($this->bucket);
209 $hasDot = strpos($this->bucket, '.') !== false;
210
211 if ($hasDot) {
212 $url = "https://s3.{$this->region}.amazonaws.com/{$this->bucket}/{$downloadableFilePath}";
213 } else {
214 $url = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$downloadableFilePath}";
215 }
216
217 $date = gmdate('D, d M Y H:i:s T');
218 $signature = base64_encode(hash_hmac('sha1', "GET\n\n\n{$date}\n/{$this->bucket}/{$downloadableFilePath}", $this->secretKey, true));
219 $response = wp_remote_get($url, [
220 'headers' => [
221 "Date" => $date,
222 "Authorization" => "AWS {$this->accessKey}:{$signature}",
223 ],
224 ]);
225
226 if (is_wp_error($response)) {
227 return $response->get_error_message();
228 }
229
230 return $response['body'];
231 }
232
233 public function getSignedDownloadUrl(string $filePath, $bucket = null, $productDownload = null): string
234 {
235 $expirationMinutes = 7 * 24 * 60;
236 apply_filters('fluent_cart/download_expiration_minutes', $expirationMinutes, [
237 'file_path' => $filePath,
238 'bucket' => $bucket,
239 'driver' => 's3',
240 ]);
241 $fileName = null;
242 if($productDownload instanceof ProductDownload){
243 $fileName = $productDownload->file_name;
244 }
245 return $this->generatePresignedDownloadUrl($filePath, $expirationMinutes, $bucket, $fileName);
246 }
247
248 public function downloadFile(string $filePath, $fileName = null, $bucket = null)
249 {
250 $expirationMinutes = 7 * 24 * 60;
251 apply_filters('fluent_cart/download_expiration_minutes', $expirationMinutes, [
252 'file_path' => $filePath,
253 'bucket' => $bucket,
254 'driver' => 's3',
255 ]);
256 wp_redirect($this->generatePresignedDownloadUrl($filePath, $expirationMinutes, $bucket, $fileName));
257 exit;
258 }
259
260 public function getFilePath(string $filePath, $fileName = null, $bucket = null)
261 {
262 return $this->retrieveFileForDownload($filePath, $bucket);
263 }
264
265 protected function getDefaultDirPath()
266 {
267 return $this->bucket;
268 }
269
270 public function deleteFile($filePath, $bucket = null)
271 {
272 $bucket = $bucket ?: $this->bucket;
273 return S3FileDeleter::delete(
274 $this->secretKey,
275 $this->accessKey,
276 $bucket,
277 $this->region,
278 $filePath
279 );
280 }
281 }
282