PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.20
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.20
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.20, at app/Services/FileSystem/Drivers/S3/S3Driver.php

283 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 = Arr::get($getSettings, 'bucket', '');
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 Arr::get($params, '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' => Arr::get($params, 'bucket'),
66 'name' => $response['path'],
67 ],
68
69 ];
70 }
71
72
73 public function listFiles(array $params = [])
74 {
75 return S3FileList::get(
76 $this->secretKey,
77 $this->accessKey,
78 Arr::get($params, 'activeBucket'),
79 $this->region,
80 Arr::get($params, 'search', ''),
81 );
82 }
83
84 protected function generatePresignedDownloadUrlOld(string $filePath, $expirationMinutes = 15, $bucket = null, $fileName = null): string
85 {
86 $this->bucket = $bucket ?? $this->bucket;
87 $this->region = S3::getBucketRegion($this->bucket);
88
89
90 if (empty($fileName)) {
91 $fileName = basename($filePath);
92 }
93
94 $fileName = explode('_____fluent-cart_____', $fileName)[0];
95 $fileName = explode('__fluent-cart__', $fileName)[0];
96
97 $expirationMinutes = is_numeric($expirationMinutes) ? (int)$expirationMinutes : 15;
98 $expires = time() + ($expirationMinutes * 60);
99
100 $stringToSign = "GET\n\n\n{$expires}\n/{$this->bucket}/{$filePath}";
101
102 $queryParams = [
103 'AWSAccessKeyId' => $this->accessKey,
104 'Expires' => $expires,
105 ];
106
107 if (!empty($fileName)) {
108 $contentDisposition = "attachment; filename=\"{$fileName}\"";
109 $queryParams['response-content-disposition'] = $contentDisposition;
110
111 // For Signature V1, the parameter value is NOT URL-encoded in string-to-sign
112 $stringToSign .= '?response-content-disposition=' . $contentDisposition;
113 }
114
115 $signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->secretKey, true));
116 $queryParams['Signature'] = $signature;
117
118 $url = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$filePath}";
119 $queryString = http_build_query($queryParams);
120
121 return $url . '?' . $queryString;
122 }
123
124 protected function generatePresignedDownloadUrl(string $filePath, $expirationMinutes = 15, $bucket = null, $fileName = null): string
125 {
126 $this->bucket = $bucket ?? $this->bucket;
127 $this->region = S3::getBucketRegion($this->bucket);
128
129 if (empty($fileName)) {
130 $fileName = basename($filePath);
131 }
132
133 $fileName = explode('_____fluent-cart_____', $fileName)[0];
134 $fileName = explode('__fluent-cart__', $fileName)[0];
135
136 $expirationMinutes = is_numeric($expirationMinutes) ? (int)$expirationMinutes : 15;
137 $expiresInSeconds = $expirationMinutes * 60;
138
139 // AWS Signature V4 requires ISO8601 format
140 $dateTime = gmdate('Ymd\THis\Z');
141 $dateStamp = gmdate('Ymd');
142
143 // Credential scope
144 $credentialScope = "{$dateStamp}/{$this->region}/s3/aws4_request";
145
146 $hasDot = strpos($this->bucket, '.') !== false;
147
148 // Canonical request components
149 $encodedFilePath = '/' . implode('/', array_map(
150 'rawurlencode',
151 explode('/', ltrim($filePath, '/'))
152 ));
153
154 // For dotted buckets, use path-style: include bucket in canonical URI
155 if ($hasDot) {
156 $canonicalUri = '/' . $this->bucket . $encodedFilePath;
157 $host = "s3.{$this->region}.amazonaws.com";
158 } else {
159 $canonicalUri = $encodedFilePath;
160 $host = "{$this->bucket}.s3.{$this->region}.amazonaws.com";
161 }
162
163 $canonicalQueryString = [
164 'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256',
165 'X-Amz-Credential' => $this->accessKey . '/' . $credentialScope,
166 'X-Amz-Date' => $dateTime,
167 'X-Amz-Expires' => $expiresInSeconds,
168 'X-Amz-SignedHeaders' => 'host',
169 ];
170
171 if (!empty($fileName)) {
172 $canonicalQueryString['response-content-disposition'] = 'attachment; filename="' . $fileName . '"';
173 }
174
175 // Sort query parameters
176 ksort($canonicalQueryString);
177
178 // Build canonical query string
179 $canonicalQueryStringEncoded = [];
180 foreach ($canonicalQueryString as $key => $value) {
181 $canonicalQueryStringEncoded[] = rawurlencode($key) . '=' . rawurlencode($value);
182 }
183 $canonicalQueryStringStr = implode('&', $canonicalQueryStringEncoded);
184
185 // Canonical headers
186 $canonicalHeaders = "host:{$host}\n";
187
188 // Create canonical request
189 $canonicalRequest = "GET\n{$canonicalUri}\n{$canonicalQueryStringStr}\n{$canonicalHeaders}\nhost\nUNSIGNED-PAYLOAD";
190
191 // String to sign
192 $stringToSign = "AWS4-HMAC-SHA256\n{$dateTime}\n{$credentialScope}\n" . hash('sha256', $canonicalRequest);
193
194 // Calculate signature
195 $kDate = hash_hmac('sha256', $dateStamp, 'AWS4' . $this->secretKey, true);
196 $kRegion = hash_hmac('sha256', $this->region, $kDate, true);
197 $kService = hash_hmac('sha256', 's3', $kRegion, true);
198 $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
199 $signature = hash_hmac('sha256', $stringToSign, $kSigning);
200
201 // Build final URL
202 $url = "https://{$host}{$canonicalUri}?{$canonicalQueryStringStr}&X-Amz-Signature={$signature}";
203
204 return $url;
205 }
206 protected function retrieveFileForDownload(string $downloadableFilePath, $bucket = null)
207 {
208 $this->bucket = $bucket;
209 $this->region = S3::getBucketRegion($this->bucket);
210 $hasDot = strpos($this->bucket, '.') !== false;
211
212 if ($hasDot) {
213 $url = "https://s3.{$this->region}.amazonaws.com/{$this->bucket}/{$downloadableFilePath}";
214 } else {
215 $url = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$downloadableFilePath}";
216 }
217
218 $date = gmdate('D, d M Y H:i:s T');
219 $signature = base64_encode(hash_hmac('sha1', "GET\n\n\n{$date}\n/{$this->bucket}/{$downloadableFilePath}", $this->secretKey, true));
220 $response = wp_remote_get($url, [
221 'headers' => [
222 "Date" => $date,
223 "Authorization" => "AWS {$this->accessKey}:{$signature}",
224 ],
225 ]);
226
227 if (is_wp_error($response)) {
228 return $response->get_error_message();
229 }
230
231 return $response['body'];
232 }
233
234 public function getSignedDownloadUrl(string $filePath, $bucket = null, $productDownload = null): string
235 {
236 $expirationMinutes = 7 * 24 * 60;
237 apply_filters('fluent_cart/download_expiration_minutes', $expirationMinutes, [
238 'file_path' => $filePath,
239 'bucket' => $bucket,
240 'driver' => 's3',
241 ]);
242 $fileName = null;
243 if($productDownload instanceof ProductDownload){
244 $fileName = $productDownload->file_name;
245 }
246 return $this->generatePresignedDownloadUrl($filePath, $expirationMinutes, $bucket, $fileName);
247 }
248
249 public function downloadFile(string $filePath, $fileName = null, $bucket = null)
250 {
251 $expirationMinutes = 7 * 24 * 60;
252 apply_filters('fluent_cart/download_expiration_minutes', $expirationMinutes, [
253 'file_path' => $filePath,
254 'bucket' => $bucket,
255 'driver' => 's3',
256 ]);
257 wp_redirect($this->generatePresignedDownloadUrl($filePath, $expirationMinutes, $bucket, $fileName));
258 exit;
259 }
260
261 public function getFilePath(string $filePath, $fileName = null, $bucket = null)
262 {
263 return $this->retrieveFileForDownload($filePath, $bucket);
264 }
265
266 protected function getDefaultDirPath()
267 {
268 return $this->bucket;
269 }
270
271 public function deleteFile($filePath, $bucket = null)
272 {
273 $bucket = $bucket ?: $this->bucket;
274 return S3FileDeleter::delete(
275 $this->secretKey,
276 $this->accessKey,
277 $bucket,
278 $this->region,
279 $filePath
280 );
281 }
282 }
283