@artilingo/artiframe-cli 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/core-stubs/bin/SystemMethod.php +600 -521
  2. package/core-stubs/bin/ViewMethod.php +591 -393
  3. package/core-stubs/docs/de.html +853 -1172
  4. package/core-stubs/docs/en.html +585 -904
  5. package/core-stubs/docs/es.html +934 -1253
  6. package/core-stubs/docs/fr.html +913 -1232
  7. package/core-stubs/docs/tr.html +4212 -5021
  8. package/package.json +1 -1
  9. package/src/App.php +38 -0
  10. package/src/Commands/AddCommand.php +1 -1
  11. package/src/Commands/AuthCommand.php +219 -0
  12. package/src/Commands/IssuesCommand.php +174 -0
  13. package/src/Commands/ListCommand.php +2 -1
  14. package/src/Commands/MakeApiCommand.php +1 -1
  15. package/src/Commands/MakeClassCommand.php +1 -1
  16. package/src/Commands/MakeViewCommand.php +1 -1
  17. package/src/Commands/NewProjectCommand.php +37 -25
  18. package/src/Commands/RemoveCommand.php +21 -10
  19. package/src/Commands/ServeCommand.php +29 -9
  20. package/src/Commands/SuggestCommand.php +143 -0
  21. package/src/Commands/TableCommand.php +2 -1
  22. package/src/Commands/UpgradeCommand.php +209 -0
  23. package/src/Commands/VersionCommand.php +2 -1
  24. package/src/Lang/de.php +9 -1
  25. package/src/Lang/en.php +8 -0
  26. package/src/Lang/es.php +9 -1
  27. package/src/Lang/fr.php +8 -0
  28. package/src/Lang/tr.php +10 -2
  29. package/src/Services/Safeguard.php +25 -0
  30. package/stubs/service/image.stub +334 -334
  31. package/stubs/service/iyzico.stub +637 -637
  32. package/stubs/service/jwt.stub +312 -312
  33. package/stubs/service/phpmailer.stub +143 -143
  34. package/stubs/service/pusher.stub +232 -232
  35. package/stubs/service/qrcode.stub +169 -169
  36. package/stubs/service/redis.stub +361 -361
  37. package/stubs/service/s3.stub +377 -377
  38. package/stubs/service/sentry.stub +76 -106
  39. package/stubs/service/stripe.stub +526 -526
  40. package/stubs/service/twilio.stub +361 -361
  41. package/core-stubs/app/R2Manager.php +0 -130
@@ -1,377 +1,377 @@
1
- <?php
2
-
3
- namespace Service;
4
-
5
- use Aws\S3\S3Client;
6
- use Aws\S3\Exception\S3Exception;
7
-
8
- /**
9
- * S3Storage — Wrapper for AWS S3 SDK.
10
- *
11
- * Provides simple methods for uploading, downloading, deleting, listing,
12
- * and managing files on Amazon S3, MinIO, DigitalOcean Spaces, or any
13
- * S3-compatible object storage.
14
- *
15
- * Required ENV variables:
16
- * AWS_ACCESS_KEY_ID — IAM access key
17
- * AWS_SECRET_ACCESS_KEY — IAM secret key
18
- * AWS_DEFAULT_REGION — e.g. "eu-central-1"
19
- * AWS_BUCKET — Default bucket name
20
- *
21
- * Optional ENV variables:
22
- * AWS_ENDPOINT — Custom endpoint URL (MinIO, Spaces, etc.)
23
- *
24
- * @package Service
25
- */
26
- class S3Storage
27
- {
28
- /** @var S3Client AWS S3 client instance */
29
- private S3Client $client;
30
-
31
- /** @var string Default bucket name */
32
- private string $bucket;
33
-
34
- /**
35
- * Create the S3 client from environment variables.
36
- *
37
- * When AWS_ENDPOINT is set the client connects to a custom
38
- * S3-compatible service (MinIO, DigitalOcean Spaces, Cloudflare R2, etc.).
39
- */
40
- public function __construct()
41
- {
42
- $region = $_ENV['AWS_DEFAULT_REGION'] ?? 'us-east-1';
43
- $endpoint = $_ENV['AWS_ENDPOINT'] ?? null;
44
-
45
- $config = [
46
- 'version' => 'latest',
47
- 'region' => $region,
48
- 'credentials' => [
49
- 'key' => $_ENV['AWS_ACCESS_KEY_ID'],
50
- 'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'],
51
- ],
52
- ];
53
-
54
- // Custom endpoint for S3-compatible services
55
- if ($endpoint) {
56
- $config['endpoint'] = $endpoint;
57
- $config['use_path_style_endpoint'] = true;
58
- }
59
-
60
- $this->client = new S3Client($config);
61
- $this->bucket = $_ENV['AWS_BUCKET'];
62
- }
63
-
64
- // ---------------------------------------------------------------
65
- // Upload
66
- // ---------------------------------------------------------------
67
-
68
- /**
69
- * Upload a local file to S3.
70
- *
71
- * @param string $localPath Absolute path to the local file.
72
- * @param string $remotePath Object key (path) in the bucket.
73
- * @param string $acl Canned ACL: private, public-read, etc.
74
- * @return array{status: bool, url: string|null, error: string|null}
75
- */
76
- public function upload(string $localPath, string $remotePath, string $acl = 'private'): array
77
- {
78
- try {
79
- $result = $this->client->putObject([
80
- 'Bucket' => $this->bucket,
81
- 'Key' => $remotePath,
82
- 'SourceFile' => $localPath,
83
- 'ACL' => $acl,
84
- 'ContentType' => mime_content_type($localPath) ?: 'application/octet-stream',
85
- ]);
86
-
87
- return [
88
- 'status' => true,
89
- 'url' => $result['ObjectURL'] ?? $this->url($remotePath),
90
- 'error' => null,
91
- ];
92
- } catch (S3Exception $e) {
93
- error_log('[S3Storage::upload] ' . $e->getMessage());
94
- return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
95
- }
96
- }
97
-
98
- // ---------------------------------------------------------------
99
- // Upload from String
100
- // ---------------------------------------------------------------
101
-
102
- /**
103
- * Upload content from a string directly to S3.
104
- *
105
- * Useful for storing generated data (JSON, CSV, HTML) without
106
- * writing a temporary file to disk first.
107
- *
108
- * @param string $content The raw string content to store.
109
- * @param string $remotePath Object key (path) in the bucket.
110
- * @param string $contentType MIME type, e.g. "application/json".
111
- * @param string $acl Canned ACL: private, public-read, etc.
112
- * @return array{status: bool, url: string|null, error: string|null}
113
- */
114
- public function uploadFromString(string $content, string $remotePath, string $contentType, string $acl = 'private'): array
115
- {
116
- try {
117
- $result = $this->client->putObject([
118
- 'Bucket' => $this->bucket,
119
- 'Key' => $remotePath,
120
- 'Body' => $content,
121
- 'ACL' => $acl,
122
- 'ContentType' => $contentType,
123
- ]);
124
-
125
- return [
126
- 'status' => true,
127
- 'url' => $result['ObjectURL'] ?? $this->url($remotePath),
128
- 'error' => null,
129
- ];
130
- } catch (S3Exception $e) {
131
- error_log('[S3Storage::uploadFromString] ' . $e->getMessage());
132
- return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
133
- }
134
- }
135
-
136
- // ---------------------------------------------------------------
137
- // Download
138
- // ---------------------------------------------------------------
139
-
140
- /**
141
- * Download a file from S3 to a local path.
142
- *
143
- * @param string $remotePath Object key in the bucket.
144
- * @param string $localPath Absolute local destination path.
145
- * @return bool True on success.
146
- */
147
- public function download(string $remotePath, string $localPath): bool
148
- {
149
- try {
150
- $this->client->getObject([
151
- 'Bucket' => $this->bucket,
152
- 'Key' => $remotePath,
153
- 'SaveAs' => $localPath,
154
- ]);
155
-
156
- return true;
157
- } catch (S3Exception $e) {
158
- error_log('[S3Storage::download] ' . $e->getMessage());
159
- return false;
160
- }
161
- }
162
-
163
- // ---------------------------------------------------------------
164
- // Delete
165
- // ---------------------------------------------------------------
166
-
167
- /**
168
- * Delete a single object from S3.
169
- *
170
- * @param string $remotePath Object key to delete.
171
- * @return bool True on success.
172
- */
173
- public function delete(string $remotePath): bool
174
- {
175
- try {
176
- $this->client->deleteObject([
177
- 'Bucket' => $this->bucket,
178
- 'Key' => $remotePath,
179
- ]);
180
-
181
- return true;
182
- } catch (S3Exception $e) {
183
- error_log('[S3Storage::delete] ' . $e->getMessage());
184
- return false;
185
- }
186
- }
187
-
188
- /**
189
- * Delete multiple objects from S3 in a single request.
190
- *
191
- * @param array<string> $remotePaths List of object keys to delete.
192
- * @return bool True when all objects are deleted successfully.
193
- */
194
- public function deleteMany(array $remotePaths): bool
195
- {
196
- try {
197
- $objects = array_map(fn(string $key) => ['Key' => $key], $remotePaths);
198
-
199
- $this->client->deleteObjects([
200
- 'Bucket' => $this->bucket,
201
- 'Delete' => [
202
- 'Objects' => $objects,
203
- 'Quiet' => true,
204
- ],
205
- ]);
206
-
207
- return true;
208
- } catch (S3Exception $e) {
209
- error_log('[S3Storage::deleteMany] ' . $e->getMessage());
210
- return false;
211
- }
212
- }
213
-
214
- // ---------------------------------------------------------------
215
- // URL helpers
216
- // ---------------------------------------------------------------
217
-
218
- /**
219
- * Get the public URL for an object.
220
- *
221
- * @param string $remotePath Object key in the bucket.
222
- * @return string The public URL.
223
- */
224
- public function url(string $remotePath): string
225
- {
226
- return $this->client->getObjectUrl($this->bucket, $remotePath);
227
- }
228
-
229
- /**
230
- * Generate a pre-signed (temporary) URL for private objects.
231
- *
232
- * @param string $remotePath Object key in the bucket.
233
- * @param int $minutes Link validity in minutes (default 60).
234
- * @return string Pre-signed URL.
235
- */
236
- public function signedUrl(string $remotePath, int $minutes = 60): string
237
- {
238
- $command = $this->client->getCommand('GetObject', [
239
- 'Bucket' => $this->bucket,
240
- 'Key' => $remotePath,
241
- ]);
242
-
243
- $request = $this->client->createPresignedRequest($command, "+{$minutes} minutes");
244
-
245
- return (string) $request->getUri();
246
- }
247
-
248
- // ---------------------------------------------------------------
249
- // Existence check
250
- // ---------------------------------------------------------------
251
-
252
- /**
253
- * Check whether an object exists in the bucket.
254
- *
255
- * @param string $remotePath Object key to check.
256
- * @return bool True if the object exists.
257
- */
258
- public function exists(string $remotePath): bool
259
- {
260
- try {
261
- return $this->client->doesObjectExistV2($this->bucket, $remotePath);
262
- } catch (S3Exception $e) {
263
- error_log('[S3Storage::exists] ' . $e->getMessage());
264
- return false;
265
- }
266
- }
267
-
268
- // ---------------------------------------------------------------
269
- // List objects
270
- // ---------------------------------------------------------------
271
-
272
- /**
273
- * List objects under a given prefix (folder).
274
- *
275
- * Returns an array of associative arrays with key, size, and
276
- * last_modified for each object.
277
- *
278
- * @param string $prefix Key prefix to filter by (e.g. "uploads/").
279
- * @return array<int, array{key: string, size: int, last_modified: string}>
280
- */
281
- public function list(string $prefix = ''): array
282
- {
283
- try {
284
- $results = [];
285
- $params = [
286
- 'Bucket' => $this->bucket,
287
- 'Prefix' => $prefix,
288
- ];
289
-
290
- $paginator = $this->client->getPaginator('ListObjectsV2', $params);
291
-
292
- foreach ($paginator as $page) {
293
- $contents = $page['Contents'] ?? [];
294
- foreach ($contents as $object) {
295
- $results[] = [
296
- 'key' => $object['Key'],
297
- 'size' => $object['Size'],
298
- 'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'),
299
- ];
300
- }
301
- }
302
-
303
- return $results;
304
- } catch (S3Exception $e) {
305
- error_log('[S3Storage::list] ' . $e->getMessage());
306
- return [];
307
- }
308
- }
309
-
310
- // ---------------------------------------------------------------
311
- // Copy & Move
312
- // ---------------------------------------------------------------
313
-
314
- /**
315
- * Copy an object to a new key within the same bucket.
316
- *
317
- * @param string $from Source object key.
318
- * @param string $to Destination object key.
319
- * @return bool True on success.
320
- */
321
- public function copy(string $from, string $to): bool
322
- {
323
- try {
324
- $this->client->copyObject([
325
- 'Bucket' => $this->bucket,
326
- 'CopySource' => "{$this->bucket}/{$from}",
327
- 'Key' => $to,
328
- ]);
329
-
330
- return true;
331
- } catch (S3Exception $e) {
332
- error_log('[S3Storage::copy] ' . $e->getMessage());
333
- return false;
334
- }
335
- }
336
-
337
- /**
338
- * Move an object to a new key (copy + delete).
339
- *
340
- * @param string $from Source object key.
341
- * @param string $to Destination object key.
342
- * @return bool True when copy and delete both succeed.
343
- */
344
- public function move(string $from, string $to): bool
345
- {
346
- if ($this->copy($from, $to)) {
347
- return $this->delete($from);
348
- }
349
-
350
- return false;
351
- }
352
-
353
- // ---------------------------------------------------------------
354
- // File size
355
- // ---------------------------------------------------------------
356
-
357
- /**
358
- * Get the size of an object in bytes.
359
- *
360
- * @param string $remotePath Object key in the bucket.
361
- * @return int File size in bytes, or -1 on failure.
362
- */
363
- public function size(string $remotePath): int
364
- {
365
- try {
366
- $result = $this->client->headObject([
367
- 'Bucket' => $this->bucket,
368
- 'Key' => $remotePath,
369
- ]);
370
-
371
- return (int) ($result['ContentLength'] ?? 0);
372
- } catch (S3Exception $e) {
373
- error_log('[S3Storage::size] ' . $e->getMessage());
374
- return -1;
375
- }
376
- }
377
- }
1
+ <?php
2
+
3
+ namespace Src\Service;
4
+
5
+ use Aws\S3\S3Client;
6
+ use Aws\S3\Exception\S3Exception;
7
+
8
+ /**
9
+ * S3Storage — Wrapper for AWS S3 SDK.
10
+ *
11
+ * Provides simple methods for uploading, downloading, deleting, listing,
12
+ * and managing files on Amazon S3, MinIO, DigitalOcean Spaces, or any
13
+ * S3-compatible object storage.
14
+ *
15
+ * Required ENV variables:
16
+ * AWS_ACCESS_KEY_ID — IAM access key
17
+ * AWS_SECRET_ACCESS_KEY — IAM secret key
18
+ * AWS_DEFAULT_REGION — e.g. "eu-central-1"
19
+ * AWS_BUCKET — Default bucket name
20
+ *
21
+ * Optional ENV variables:
22
+ * AWS_ENDPOINT — Custom endpoint URL (MinIO, Spaces, etc.)
23
+ *
24
+ * @package Service
25
+ */
26
+ class S3Storage
27
+ {
28
+ /** @var S3Client AWS S3 client instance */
29
+ private S3Client $client;
30
+
31
+ /** @var string Default bucket name */
32
+ private string $bucket;
33
+
34
+ /**
35
+ * Create the S3 client from environment variables.
36
+ *
37
+ * When AWS_ENDPOINT is set the client connects to a custom
38
+ * S3-compatible service (MinIO, DigitalOcean Spaces, Cloudflare R2, etc.).
39
+ */
40
+ public function __construct()
41
+ {
42
+ $region = $_ENV['AWS_DEFAULT_REGION'] ?? 'us-east-1';
43
+ $endpoint = $_ENV['AWS_ENDPOINT'] ?? null;
44
+
45
+ $config = [
46
+ 'version' => 'latest',
47
+ 'region' => $region,
48
+ 'credentials' => [
49
+ 'key' => $_ENV['AWS_ACCESS_KEY_ID'],
50
+ 'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'],
51
+ ],
52
+ ];
53
+
54
+ // Custom endpoint for S3-compatible services
55
+ if ($endpoint) {
56
+ $config['endpoint'] = $endpoint;
57
+ $config['use_path_style_endpoint'] = true;
58
+ }
59
+
60
+ $this->client = new S3Client($config);
61
+ $this->bucket = $_ENV['AWS_BUCKET'];
62
+ }
63
+
64
+ // ---------------------------------------------------------------
65
+ // Upload
66
+ // ---------------------------------------------------------------
67
+
68
+ /**
69
+ * Upload a local file to S3.
70
+ *
71
+ * @param string $localPath Absolute path to the local file.
72
+ * @param string $remotePath Object key (path) in the bucket.
73
+ * @param string $acl Canned ACL: private, public-read, etc.
74
+ * @return array{status: bool, url: string|null, error: string|null}
75
+ */
76
+ public function upload(string $localPath, string $remotePath, string $acl = 'private'): array
77
+ {
78
+ try {
79
+ $result = $this->client->putObject([
80
+ 'Bucket' => $this->bucket,
81
+ 'Key' => $remotePath,
82
+ 'SourceFile' => $localPath,
83
+ 'ACL' => $acl,
84
+ 'ContentType' => mime_content_type($localPath) ?: 'application/octet-stream',
85
+ ]);
86
+
87
+ return [
88
+ 'status' => true,
89
+ 'url' => $result['ObjectURL'] ?? $this->url($remotePath),
90
+ 'error' => null,
91
+ ];
92
+ } catch (S3Exception $e) {
93
+ error_log('[S3Storage::upload] ' . $e->getMessage());
94
+ return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
95
+ }
96
+ }
97
+
98
+ // ---------------------------------------------------------------
99
+ // Upload from String
100
+ // ---------------------------------------------------------------
101
+
102
+ /**
103
+ * Upload content from a string directly to S3.
104
+ *
105
+ * Useful for storing generated data (JSON, CSV, HTML) without
106
+ * writing a temporary file to disk first.
107
+ *
108
+ * @param string $content The raw string content to store.
109
+ * @param string $remotePath Object key (path) in the bucket.
110
+ * @param string $contentType MIME type, e.g. "application/json".
111
+ * @param string $acl Canned ACL: private, public-read, etc.
112
+ * @return array{status: bool, url: string|null, error: string|null}
113
+ */
114
+ public function uploadFromString(string $content, string $remotePath, string $contentType, string $acl = 'private'): array
115
+ {
116
+ try {
117
+ $result = $this->client->putObject([
118
+ 'Bucket' => $this->bucket,
119
+ 'Key' => $remotePath,
120
+ 'Body' => $content,
121
+ 'ACL' => $acl,
122
+ 'ContentType' => $contentType,
123
+ ]);
124
+
125
+ return [
126
+ 'status' => true,
127
+ 'url' => $result['ObjectURL'] ?? $this->url($remotePath),
128
+ 'error' => null,
129
+ ];
130
+ } catch (S3Exception $e) {
131
+ error_log('[S3Storage::uploadFromString] ' . $e->getMessage());
132
+ return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
133
+ }
134
+ }
135
+
136
+ // ---------------------------------------------------------------
137
+ // Download
138
+ // ---------------------------------------------------------------
139
+
140
+ /**
141
+ * Download a file from S3 to a local path.
142
+ *
143
+ * @param string $remotePath Object key in the bucket.
144
+ * @param string $localPath Absolute local destination path.
145
+ * @return bool True on success.
146
+ */
147
+ public function download(string $remotePath, string $localPath): bool
148
+ {
149
+ try {
150
+ $this->client->getObject([
151
+ 'Bucket' => $this->bucket,
152
+ 'Key' => $remotePath,
153
+ 'SaveAs' => $localPath,
154
+ ]);
155
+
156
+ return true;
157
+ } catch (S3Exception $e) {
158
+ error_log('[S3Storage::download] ' . $e->getMessage());
159
+ return false;
160
+ }
161
+ }
162
+
163
+ // ---------------------------------------------------------------
164
+ // Delete
165
+ // ---------------------------------------------------------------
166
+
167
+ /**
168
+ * Delete a single object from S3.
169
+ *
170
+ * @param string $remotePath Object key to delete.
171
+ * @return bool True on success.
172
+ */
173
+ public function delete(string $remotePath): bool
174
+ {
175
+ try {
176
+ $this->client->deleteObject([
177
+ 'Bucket' => $this->bucket,
178
+ 'Key' => $remotePath,
179
+ ]);
180
+
181
+ return true;
182
+ } catch (S3Exception $e) {
183
+ error_log('[S3Storage::delete] ' . $e->getMessage());
184
+ return false;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Delete multiple objects from S3 in a single request.
190
+ *
191
+ * @param array<string> $remotePaths List of object keys to delete.
192
+ * @return bool True when all objects are deleted successfully.
193
+ */
194
+ public function deleteMany(array $remotePaths): bool
195
+ {
196
+ try {
197
+ $objects = array_map(fn(string $key) => ['Key' => $key], $remotePaths);
198
+
199
+ $this->client->deleteObjects([
200
+ 'Bucket' => $this->bucket,
201
+ 'Delete' => [
202
+ 'Objects' => $objects,
203
+ 'Quiet' => true,
204
+ ],
205
+ ]);
206
+
207
+ return true;
208
+ } catch (S3Exception $e) {
209
+ error_log('[S3Storage::deleteMany] ' . $e->getMessage());
210
+ return false;
211
+ }
212
+ }
213
+
214
+ // ---------------------------------------------------------------
215
+ // URL helpers
216
+ // ---------------------------------------------------------------
217
+
218
+ /**
219
+ * Get the public URL for an object.
220
+ *
221
+ * @param string $remotePath Object key in the bucket.
222
+ * @return string The public URL.
223
+ */
224
+ public function url(string $remotePath): string
225
+ {
226
+ return $this->client->getObjectUrl($this->bucket, $remotePath);
227
+ }
228
+
229
+ /**
230
+ * Generate a pre-signed (temporary) URL for private objects.
231
+ *
232
+ * @param string $remotePath Object key in the bucket.
233
+ * @param int $minutes Link validity in minutes (default 60).
234
+ * @return string Pre-signed URL.
235
+ */
236
+ public function signedUrl(string $remotePath, int $minutes = 60): string
237
+ {
238
+ $command = $this->client->getCommand('GetObject', [
239
+ 'Bucket' => $this->bucket,
240
+ 'Key' => $remotePath,
241
+ ]);
242
+
243
+ $request = $this->client->createPresignedRequest($command, "+{$minutes} minutes");
244
+
245
+ return (string) $request->getUri();
246
+ }
247
+
248
+ // ---------------------------------------------------------------
249
+ // Existence check
250
+ // ---------------------------------------------------------------
251
+
252
+ /**
253
+ * Check whether an object exists in the bucket.
254
+ *
255
+ * @param string $remotePath Object key to check.
256
+ * @return bool True if the object exists.
257
+ */
258
+ public function exists(string $remotePath): bool
259
+ {
260
+ try {
261
+ return $this->client->doesObjectExistV2($this->bucket, $remotePath);
262
+ } catch (S3Exception $e) {
263
+ error_log('[S3Storage::exists] ' . $e->getMessage());
264
+ return false;
265
+ }
266
+ }
267
+
268
+ // ---------------------------------------------------------------
269
+ // List objects
270
+ // ---------------------------------------------------------------
271
+
272
+ /**
273
+ * List objects under a given prefix (folder).
274
+ *
275
+ * Returns an array of associative arrays with key, size, and
276
+ * last_modified for each object.
277
+ *
278
+ * @param string $prefix Key prefix to filter by (e.g. "uploads/").
279
+ * @return array<int, array{key: string, size: int, last_modified: string}>
280
+ */
281
+ public function list(string $prefix = ''): array
282
+ {
283
+ try {
284
+ $results = [];
285
+ $params = [
286
+ 'Bucket' => $this->bucket,
287
+ 'Prefix' => $prefix,
288
+ ];
289
+
290
+ $paginator = $this->client->getPaginator('ListObjectsV2', $params);
291
+
292
+ foreach ($paginator as $page) {
293
+ $contents = $page['Contents'] ?? [];
294
+ foreach ($contents as $object) {
295
+ $results[] = [
296
+ 'key' => $object['Key'],
297
+ 'size' => $object['Size'],
298
+ 'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'),
299
+ ];
300
+ }
301
+ }
302
+
303
+ return $results;
304
+ } catch (S3Exception $e) {
305
+ error_log('[S3Storage::list] ' . $e->getMessage());
306
+ return [];
307
+ }
308
+ }
309
+
310
+ // ---------------------------------------------------------------
311
+ // Copy & Move
312
+ // ---------------------------------------------------------------
313
+
314
+ /**
315
+ * Copy an object to a new key within the same bucket.
316
+ *
317
+ * @param string $from Source object key.
318
+ * @param string $to Destination object key.
319
+ * @return bool True on success.
320
+ */
321
+ public function copy(string $from, string $to): bool
322
+ {
323
+ try {
324
+ $this->client->copyObject([
325
+ 'Bucket' => $this->bucket,
326
+ 'CopySource' => "{$this->bucket}/{$from}",
327
+ 'Key' => $to,
328
+ ]);
329
+
330
+ return true;
331
+ } catch (S3Exception $e) {
332
+ error_log('[S3Storage::copy] ' . $e->getMessage());
333
+ return false;
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Move an object to a new key (copy + delete).
339
+ *
340
+ * @param string $from Source object key.
341
+ * @param string $to Destination object key.
342
+ * @return bool True when copy and delete both succeed.
343
+ */
344
+ public function move(string $from, string $to): bool
345
+ {
346
+ if ($this->copy($from, $to)) {
347
+ return $this->delete($from);
348
+ }
349
+
350
+ return false;
351
+ }
352
+
353
+ // ---------------------------------------------------------------
354
+ // File size
355
+ // ---------------------------------------------------------------
356
+
357
+ /**
358
+ * Get the size of an object in bytes.
359
+ *
360
+ * @param string $remotePath Object key in the bucket.
361
+ * @return int File size in bytes, or -1 on failure.
362
+ */
363
+ public function size(string $remotePath): int
364
+ {
365
+ try {
366
+ $result = $this->client->headObject([
367
+ 'Bucket' => $this->bucket,
368
+ 'Key' => $remotePath,
369
+ ]);
370
+
371
+ return (int) ($result['ContentLength'] ?? 0);
372
+ } catch (S3Exception $e) {
373
+ error_log('[S3Storage::size] ' . $e->getMessage());
374
+ return -1;
375
+ }
376
+ }
377
+ }