1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace CedricZiel\FalFlysystem\Fal; |
4
|
|
|
|
5
|
|
|
/*************************************************************** |
6
|
|
|
* Copyright notice |
7
|
|
|
* |
8
|
|
|
* (c) 2016 Cedric Ziel <[email protected]> |
9
|
|
|
* |
10
|
|
|
* All rights reserved |
11
|
|
|
* |
12
|
|
|
* This script is part of the TYPO3 project. The TYPO3 project is |
13
|
|
|
* free software; you can redistribute it and/or modify |
14
|
|
|
* it under the terms of the GNU General Public License as published by |
15
|
|
|
* the Free Software Foundation; either version 3 of the License, or |
16
|
|
|
* (at your option) any later version. |
17
|
|
|
* |
18
|
|
|
* The GNU General Public License can be found at |
19
|
|
|
* http://www.gnu.org/copyleft/gpl.html. |
20
|
|
|
* |
21
|
|
|
* This script is distributed in the hope that it will be useful, |
22
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
23
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24
|
|
|
* GNU General Public License for more details. |
25
|
|
|
* |
26
|
|
|
* This copyright notice MUST APPEAR in all copies of the script! |
27
|
|
|
***************************************************************/ |
28
|
|
|
|
29
|
|
|
use League\Flysystem\Adapter\Local; |
30
|
|
|
use League\Flysystem\AdapterInterface; |
31
|
|
|
use League\Flysystem\Config; |
32
|
|
|
use League\Flysystem\FileExistsException; |
33
|
|
|
use League\Flysystem\FileNotFoundException; |
34
|
|
|
use League\Flysystem\FilesystemInterface; |
35
|
|
|
use TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver; |
36
|
|
|
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException; |
37
|
|
|
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; |
38
|
|
|
use TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException; |
39
|
|
|
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException; |
40
|
|
|
use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException; |
41
|
|
|
use TYPO3\CMS\Core\Resource\ResourceStorage; |
42
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
43
|
|
|
use TYPO3\CMS\Core\Utility\PathUtility; |
44
|
|
|
use TYPO3\CMS\Extbase\Utility\DebuggerUtility; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Class FlysystemDriver |
48
|
|
|
* @package CedricZiel\FalFlysystem\Fal |
49
|
|
|
*/ |
50
|
|
|
abstract class FlysystemDriver extends AbstractHierarchicalFilesystemDriver |
51
|
|
|
{ |
52
|
|
|
/** |
53
|
|
|
* @var FilesystemInterface |
54
|
|
|
*/ |
55
|
|
|
protected $filesystem; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var AdapterInterface |
59
|
|
|
*/ |
60
|
|
|
protected $adapter; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var string |
64
|
|
|
*/ |
65
|
|
|
protected $entryPath; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* FlysystemDriver constructor. |
69
|
|
|
* @param array $configuration |
70
|
|
|
*/ |
71
|
75 |
|
public function __construct(array $configuration = []) |
72
|
|
|
{ |
73
|
75 |
|
parent::__construct($configuration); |
74
|
|
|
// The capabilities default of this driver. See CAPABILITY_* constants for possible values |
75
|
75 |
|
$this->capabilities = |
76
|
25 |
|
ResourceStorage::CAPABILITY_BROWSABLE |
77
|
75 |
|
| ResourceStorage::CAPABILITY_PUBLIC |
78
|
75 |
|
| ResourceStorage::CAPABILITY_WRITABLE; |
79
|
75 |
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Processes the configuration for this driver. |
83
|
|
|
* @return void |
84
|
|
|
*/ |
85
|
|
|
public function processConfiguration() |
86
|
|
|
{ |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Merges the capabilities merged by the user at the storage |
91
|
|
|
* configuration into the actual capabilities of the driver |
92
|
|
|
* and returns the result. |
93
|
|
|
* |
94
|
|
|
* @param int $capabilities |
95
|
|
|
* @return int |
96
|
|
|
*/ |
97
|
|
|
public function mergeConfigurationCapabilities($capabilities) |
98
|
|
|
{ |
99
|
|
|
$this->capabilities &= $capabilities; |
100
|
|
|
return $this->capabilities; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns the identifier of the root level folder of the storage. |
105
|
|
|
* |
106
|
|
|
* @return string |
107
|
|
|
*/ |
108
|
|
|
public function getRootLevelFolder() |
109
|
|
|
{ |
110
|
|
|
return '/'; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Returns the identifier of the default folder new files should be put into. |
115
|
|
|
* |
116
|
|
|
* @return string |
117
|
|
|
*/ |
118
|
3 |
|
public function getDefaultFolder() |
119
|
|
|
{ |
120
|
3 |
|
$identifier = '/user_upload/'; |
121
|
3 |
|
$createFolder = !$this->folderExists($identifier); |
122
|
3 |
|
if (true === $createFolder) { |
123
|
3 |
|
$identifier = $this->createFolder('user_upload'); |
124
|
2 |
|
} |
125
|
3 |
|
return $identifier; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Checks if a folder exists. |
130
|
|
|
* |
131
|
|
|
* @param string $folderIdentifier |
132
|
|
|
* @return bool |
133
|
|
|
*/ |
134
|
12 |
|
public function folderExists($folderIdentifier) |
135
|
|
|
{ |
136
|
12 |
|
$normalizedIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier); |
137
|
12 |
|
$normalizedIdentifier = ltrim(rtrim($normalizedIdentifier, '/'), '/'); |
138
|
|
|
|
139
|
12 |
|
if ('/' === $folderIdentifier) { |
140
|
|
|
return true; |
141
|
|
|
} else { |
142
|
|
|
return ( |
143
|
12 |
|
$this->filesystem->has($normalizedIdentifier) |
144
|
12 |
|
&& $this->filesystem->get($normalizedIdentifier)->isDir() |
145
|
8 |
|
); |
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Creates a folder, within a parent folder. |
151
|
|
|
* If no parent folder is given, a root level folder will be created |
152
|
|
|
* |
153
|
|
|
* @param string $newFolderName |
154
|
|
|
* @param string $parentFolderIdentifier |
155
|
|
|
* @param bool $recursive |
156
|
|
|
* @return string the Identifier of the new folder |
157
|
|
|
*/ |
158
|
9 |
|
public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false) |
159
|
|
|
{ |
160
|
9 |
|
$parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier); |
161
|
9 |
|
$newFolderName = trim($newFolderName, '/'); |
162
|
|
|
|
163
|
9 |
|
$newFolderName = $this->sanitizeFileName($newFolderName); |
164
|
9 |
|
$newIdentifier = $parentFolderIdentifier . $newFolderName . '/'; |
165
|
9 |
|
$this->filesystem->createDir($newIdentifier); |
166
|
|
|
|
167
|
9 |
|
return $newIdentifier; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Returns the public URL to a file. |
172
|
|
|
* Either fully qualified URL or relative to PATH_site (rawurlencoded). |
173
|
|
|
* |
174
|
|
|
* @param string $identifier |
175
|
|
|
* @return string |
176
|
|
|
*/ |
177
|
|
|
public function getPublicUrl($identifier) |
178
|
|
|
{ |
179
|
|
|
return '/'; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Renames a folder in this storage. |
184
|
|
|
* |
185
|
|
|
* @param string $folderIdentifier |
186
|
|
|
* @param string $newName |
187
|
|
|
* @return array A map of old to new file identifiers of all affected resources |
188
|
|
|
*/ |
189
|
3 |
|
public function renameFolder($folderIdentifier, $newName) |
190
|
|
|
{ |
191
|
3 |
|
$renameResult = $this->filesystem->rename($folderIdentifier, $newName); |
192
|
|
|
|
193
|
3 |
|
if (true === $renameResult) { |
194
|
3 |
|
return [$folderIdentifier => $newName]; |
195
|
|
|
} else { |
196
|
|
|
return [$folderIdentifier => $folderIdentifier]; |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Removes a folder in filesystem. |
202
|
|
|
* |
203
|
|
|
* @param string $folderIdentifier |
204
|
|
|
* @param bool $deleteRecursively |
205
|
|
|
* @return bool |
206
|
|
|
* @throws FileOperationErrorException |
207
|
|
|
*/ |
208
|
|
|
public function deleteFolder($folderIdentifier, $deleteRecursively = false) |
209
|
|
|
{ |
210
|
|
|
$folderIdentifier = ltrim($folderIdentifier, '/'); |
211
|
|
|
$result = $this->filesystem->deleteDir(rtrim($folderIdentifier, '/')); |
212
|
|
|
if (false === $result) { |
213
|
|
|
throw new FileOperationErrorException( |
214
|
|
|
'Deleting folder "' . $folderIdentifier . '" failed.', |
215
|
|
|
1330119451 |
216
|
|
|
); |
217
|
|
|
} |
218
|
|
|
return $result; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Checks if a file exists. |
223
|
|
|
* |
224
|
|
|
* @param string $fileIdentifier |
225
|
|
|
* @return bool |
226
|
|
|
*/ |
227
|
18 |
|
public function fileExists($fileIdentifier) |
228
|
|
|
{ |
229
|
18 |
|
if ($this->filesystem->has($fileIdentifier) && !$this->filesystem->get($fileIdentifier)->isDir()) { |
230
|
18 |
|
return true; |
231
|
|
|
} |
232
|
12 |
|
return false; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Checks if a folder contains files and (if supported) other folders. |
237
|
|
|
* |
238
|
|
|
* @param string $folderIdentifier |
239
|
|
|
* @return bool TRUE if there are no files and folders within $folder |
240
|
|
|
*/ |
241
|
3 |
|
public function isFolderEmpty($folderIdentifier) |
242
|
|
|
{ |
243
|
3 |
|
return 0 === count($this->filesystem->listContents($folderIdentifier)); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Adds a file from the local server hard disk to a given path in TYPO3s |
248
|
|
|
* virtual file system. This assumes that the local file exists, so no |
249
|
|
|
* further check is done here! After a successful the original file must |
250
|
|
|
* not exist anymore. |
251
|
|
|
* |
252
|
|
|
* @param string $localFilePath (within PATH_site) |
253
|
|
|
* @param string $targetFolderIdentifier |
254
|
|
|
* @param string $newFileName optional, if not given original name is used |
255
|
|
|
* @param bool $removeOriginal if set the original file will be removed |
256
|
|
|
* after successful operation |
257
|
|
|
* @return string the identifier of the new file |
258
|
|
|
*/ |
259
|
3 |
|
public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true) |
260
|
|
|
{ |
261
|
3 |
|
$localFilePath = $this->canonicalizeAndCheckFilePath($localFilePath); |
262
|
3 |
|
$newFileName = $this->sanitizeFileName($newFileName !== '' ? $newFileName : PathUtility::basename($localFilePath)); |
263
|
3 |
|
$newFileIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier) . $newFileName; |
264
|
|
|
|
265
|
3 |
|
$targetPath = ltrim($newFileIdentifier, '/'); |
266
|
|
|
|
267
|
3 |
|
$content = file_get_contents($localFilePath); |
268
|
|
|
|
269
|
3 |
|
if ($removeOriginal) { |
270
|
2 |
|
$result = $this->filesystem->put($targetPath, $content); |
271
|
|
|
unlink($localFilePath); |
272
|
|
|
} else { |
273
|
3 |
|
$result = $this->filesystem->put($targetPath, $content); |
274
|
|
|
} |
275
|
3 |
|
if ($result === false || !$this->filesystem->has($targetPath)) { |
276
|
|
|
throw new \RuntimeException('Adding file ' . $localFilePath . ' at ' . $newFileIdentifier . ' failed.'); |
277
|
|
|
} |
278
|
3 |
|
clearstatcache(); |
279
|
3 |
|
return $newFileIdentifier; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Creates a new (empty) file and returns the identifier. |
284
|
|
|
* |
285
|
|
|
* @param string $fileName |
286
|
|
|
* @param string $parentFolderIdentifier |
287
|
|
|
* @return string |
288
|
|
|
* @throws InvalidFileNameException |
289
|
|
|
*/ |
290
|
3 |
|
public function createFile($fileName, $parentFolderIdentifier) |
291
|
|
|
{ |
292
|
3 |
|
if (!$this->isValidFilename($fileName)) { |
293
|
|
|
throw new InvalidFileNameException( |
294
|
|
|
'Invalid characters in fileName "' . $fileName . '"', |
295
|
|
|
1320572272 |
296
|
|
|
); |
297
|
|
|
} |
298
|
|
|
|
299
|
3 |
|
$parentFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($parentFolderIdentifier); |
300
|
3 |
|
$fileIdentifier = $this->canonicalizeAndCheckFileIdentifier( |
301
|
3 |
|
$parentFolderIdentifier . $this->sanitizeFileName(ltrim($fileName, '/')) |
302
|
2 |
|
); |
303
|
|
|
|
304
|
3 |
|
$path = ltrim($parentFolderIdentifier . $fileName, '/'); |
305
|
3 |
|
$result = $this->filesystem->put($path, ''); |
306
|
|
|
|
307
|
3 |
|
if ($result !== true) { |
308
|
|
|
throw new \RuntimeException('Creating file ' . $fileIdentifier . ' failed.', 1320569854); |
309
|
|
|
} |
310
|
|
|
|
311
|
3 |
|
return $fileIdentifier; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Copies a file *within* the current storage. |
316
|
|
|
* Note that this is only about an inner storage copy action, |
317
|
|
|
* where a file is just copied to another folder in the same storage. |
318
|
|
|
* |
319
|
|
|
* @param string $fileIdentifier |
320
|
|
|
* @param string $targetFolderIdentifier |
321
|
|
|
* @param string $fileName |
322
|
|
|
* @return string the Identifier of the new file |
323
|
|
|
*/ |
324
|
|
|
public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName) |
325
|
|
|
{ |
326
|
|
|
$newFileIdentifier = $this->canonicalizeAndCheckFileIdentifier($targetFolderIdentifier . '/' . $fileName); |
327
|
|
|
|
328
|
|
|
$trimmedSourceFile = ltrim($fileIdentifier, '/'); |
329
|
|
|
$trimmedTargetFile = ltrim($newFileIdentifier, '/'); |
330
|
|
|
|
331
|
|
|
return $this->filesystem->copy($trimmedSourceFile, $trimmedTargetFile); |
|
|
|
|
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Renames a file in this storage. |
336
|
|
|
* |
337
|
|
|
* @param string $fileIdentifier |
338
|
|
|
* @param string $newName The target path (including the file name!) |
339
|
|
|
* @return string The identifier of the file after renaming |
340
|
|
|
* @throws ExistingTargetFileNameException |
341
|
|
|
*/ |
342
|
6 |
|
public function renameFile($fileIdentifier, $newName) |
343
|
|
|
{ |
344
|
|
|
// Makes sure the Path given as parameter is valid |
345
|
6 |
|
$newName = $this->sanitizeFileName($newName); |
346
|
|
|
|
347
|
6 |
|
$newIdentifier = $this->canonicalizeAndCheckFileIdentifier($newName); |
348
|
|
|
// The target should not exist already |
349
|
6 |
|
if ($this->fileExists($newIdentifier)) { |
350
|
3 |
|
throw new ExistingTargetFileNameException( |
351
|
3 |
|
'The target file "' . $newIdentifier . '" already exists.', |
352
|
1 |
|
1320291063 |
353
|
2 |
|
); |
354
|
|
|
} |
355
|
|
|
|
356
|
3 |
|
$sourcePath = ltrim($fileIdentifier, '/'); |
357
|
3 |
|
$targetPath = ltrim($newIdentifier, '/'); |
358
|
3 |
|
$result = $this->filesystem->rename($sourcePath, $targetPath); |
359
|
3 |
|
if ($result === false) { |
360
|
|
|
throw new \RuntimeException('Renaming file ' . $sourcePath . ' to ' . $targetPath . ' failed.', 1320375115); |
361
|
|
|
} |
362
|
5 |
|
return $newIdentifier; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Replaces a file with file in local file system. |
367
|
|
|
* |
368
|
|
|
* @param string $fileIdentifier |
369
|
|
|
* @param string $localFilePath |
370
|
|
|
* @return bool TRUE if the operation succeeded |
371
|
|
|
*/ |
372
|
|
|
public function replaceFile($fileIdentifier, $localFilePath) |
373
|
|
|
{ |
374
|
|
|
// TODO: Implement replaceFile() method. |
375
|
|
|
DebuggerUtility::var_dump([ |
376
|
|
|
'$fileIdentifier' => $fileIdentifier, |
377
|
|
|
'$localFilePath' => $localFilePath |
378
|
|
|
], 'replaceFile'); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Removes a file from the filesystem. This does not check if the file is |
383
|
|
|
* still used or if it is a bad idea to delete it for some other reason |
384
|
|
|
* this has to be taken care of in the upper layers (e.g. the Storage)! |
385
|
|
|
* |
386
|
|
|
* @param string $fileIdentifier |
387
|
|
|
* @return bool TRUE if deleting the file succeeded |
388
|
|
|
*/ |
389
|
3 |
|
public function deleteFile($fileIdentifier) |
390
|
|
|
{ |
391
|
3 |
|
return $this->filesystem->delete($fileIdentifier); |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Creates a hash for a file. |
396
|
|
|
* |
397
|
|
|
* @param string $fileIdentifier |
398
|
|
|
* @param string $hashAlgorithm The hash algorithm to use |
399
|
|
|
* @return string |
400
|
|
|
*/ |
401
|
9 |
|
public function hash($fileIdentifier, $hashAlgorithm) |
402
|
|
|
{ |
403
|
9 |
|
if (!in_array($hashAlgorithm, ['sha1', 'md5'])) { |
404
|
3 |
|
throw new \InvalidArgumentException( |
405
|
3 |
|
'Hash algorithm "' . $hashAlgorithm . '" is not supported.', |
406
|
1 |
|
1304964032 |
407
|
2 |
|
); |
408
|
|
|
} |
409
|
6 |
|
$propertiesToHash = ['name', 'size', 'mtime', 'identifier']; |
410
|
|
|
switch ($hashAlgorithm) { |
411
|
6 |
|
case 'sha1': |
412
|
3 |
|
$hash = sha1(implode('-', $this->getFileInfoByIdentifier($fileIdentifier, $propertiesToHash))); |
413
|
3 |
|
break; |
414
|
2 |
|
case 'md5': |
415
|
3 |
|
$hash = md5(implode('-', $this->getFileInfoByIdentifier($fileIdentifier, $propertiesToHash))); |
416
|
3 |
|
break; |
417
|
|
|
default: |
418
|
|
|
throw new \RuntimeException('Hash algorithm ' . $hashAlgorithm . ' is not implemented.', 1329644451); |
419
|
|
|
} |
420
|
6 |
|
return $hash; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Moves a file *within* the current storage. |
425
|
|
|
* Note that this is only about an inner-storage move action, |
426
|
|
|
* where a file is just moved to another folder in the same storage. |
427
|
|
|
* |
428
|
|
|
* @param string $fileIdentifier |
429
|
|
|
* @param string $targetFolderIdentifier |
430
|
|
|
* @param string $newFileName |
431
|
|
|
* @return string |
432
|
|
|
*/ |
433
|
|
|
public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName) |
434
|
|
|
{ |
435
|
|
|
// TODO: Implement moveFileWithinStorage() method. |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Folder equivalent to moveFileWithinStorage(). |
440
|
|
|
* |
441
|
|
|
* @param string $sourceFolderIdentifier |
442
|
|
|
* @param string $targetFolderIdentifier |
443
|
|
|
* @param string $newFolderName |
444
|
|
|
* @return array All files which are affected, map of old => new file identifiers |
445
|
|
|
*/ |
446
|
|
|
public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) |
447
|
|
|
{ |
448
|
|
|
// TODO: Implement moveFolderWithinStorage() method. |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Folder equivalent to copyFileWithinStorage(). |
453
|
|
|
* |
454
|
|
|
* @param string $sourceFolderIdentifier |
455
|
|
|
* @param string $targetFolderIdentifier |
456
|
|
|
* @param string $newFolderName |
457
|
|
|
* @return bool |
458
|
|
|
*/ |
459
|
|
|
public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) |
460
|
|
|
{ |
461
|
|
|
// This target folder path already includes the topmost level, i.e. the folder this method knows as $folderToCopy. |
462
|
|
|
// We can thus rely on this folder being present and just create the subfolder we want to copy to. |
463
|
|
|
$newFolderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($targetFolderIdentifier . '/' . $newFolderName); |
464
|
|
|
|
465
|
|
|
$trimmedSourcePath = ltrim($sourceFolderIdentifier, '/'); |
466
|
|
|
$trimmedTargetPath = ltrim($newFolderIdentifier, '/'); |
467
|
|
|
|
468
|
|
|
return $this->copyFolderRecursively($trimmedSourcePath, $trimmedTargetPath); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Returns the contents of a file. Beware that this requires to load the |
473
|
|
|
* complete file into memory and also may require fetching the file from an |
474
|
|
|
* external location. So this might be an expensive operation (both in terms |
475
|
|
|
* of processing resources and money) for large files. |
476
|
|
|
* |
477
|
|
|
* @param string $fileIdentifier |
478
|
|
|
* @return string The file contents |
479
|
|
|
*/ |
480
|
3 |
|
public function getFileContents($fileIdentifier) |
481
|
|
|
{ |
482
|
3 |
|
return $this->filesystem->read($fileIdentifier); |
|
|
|
|
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* Sets the contents of a file to the specified value. |
487
|
|
|
* |
488
|
|
|
* @param string $fileIdentifier |
489
|
|
|
* @param string $contents |
490
|
|
|
* @return int The number of bytes written to the file |
491
|
|
|
*/ |
492
|
3 |
|
public function setFileContents($fileIdentifier, $contents) |
493
|
|
|
{ |
494
|
3 |
|
$this->filesystem->put($fileIdentifier, $contents); |
495
|
|
|
|
496
|
3 |
|
return $this->filesystem->getSize($fileIdentifier); |
|
|
|
|
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* Checks if a file inside a folder exists |
501
|
|
|
* |
502
|
|
|
* @param string $fileName |
503
|
|
|
* @param string $folderIdentifier |
504
|
|
|
* @return bool |
505
|
|
|
*/ |
506
|
3 |
|
public function fileExistsInFolder($fileName, $folderIdentifier) |
507
|
|
|
{ |
508
|
3 |
|
$identifier = $folderIdentifier . '/' . $fileName; |
509
|
3 |
|
$identifier = $this->canonicalizeAndCheckFileIdentifier($identifier); |
510
|
3 |
|
return $this->fileExists($identifier); |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* Checks if a folder inside a folder exists. |
515
|
|
|
* |
516
|
|
|
* @param string $folderName |
517
|
|
|
* @param string $folderIdentifier |
518
|
|
|
* @return bool |
519
|
|
|
*/ |
520
|
3 |
|
public function folderExistsInFolder($folderName, $folderIdentifier) |
521
|
|
|
{ |
522
|
3 |
|
$identifier = $folderIdentifier . '/' . $folderName; |
523
|
3 |
|
$identifier = $this->canonicalizeAndCheckFolderIdentifier($identifier); |
524
|
3 |
|
return $this->folderExists($identifier); |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* Returns a path to a local copy of a file for processing it. When changing the |
529
|
|
|
* file, you have to take care of replacing the current version yourself! |
530
|
|
|
* |
531
|
|
|
* @param string $fileIdentifier |
532
|
|
|
* @param bool $writable Set this to FALSE if you only need the file for read |
533
|
|
|
* operations. This might speed up things, e.g. by using |
534
|
|
|
* a cached local version. Never modify the file if you |
535
|
|
|
* have set this flag! |
536
|
|
|
* @return string The path to the file on the local disk |
537
|
|
|
*/ |
538
|
|
|
public function getFileForLocalProcessing($fileIdentifier, $writable = true) |
539
|
|
|
{ |
540
|
|
|
return $this->copyFileToTemporaryPath($fileIdentifier); |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
/** |
544
|
|
|
* Returns the permissions of a file/folder as an array |
545
|
|
|
* (keys r, w) of boolean flags |
546
|
|
|
* |
547
|
|
|
* @param string $identifier |
548
|
|
|
* @return array |
549
|
|
|
*/ |
550
|
|
|
public function getPermissions($identifier) |
551
|
|
|
{ |
552
|
|
|
return array( |
553
|
|
|
'r' => true, |
554
|
|
|
'w' => true, |
555
|
|
|
); |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
/** |
559
|
|
|
* Directly output the contents of the file to the output |
560
|
|
|
* buffer. Should not take care of header files or flushing |
561
|
|
|
* buffer before. Will be taken care of by the Storage. |
562
|
|
|
* |
563
|
|
|
* @param string $identifier |
564
|
|
|
* @return void |
565
|
|
|
*/ |
566
|
|
|
public function dumpFileContents($identifier) |
567
|
|
|
{ |
568
|
|
|
// TODO: Implement dumpFileContents() method. |
569
|
|
|
DebuggerUtility::var_dump([ |
570
|
|
|
'$identifier' => $identifier, |
571
|
|
|
], 'dumpFileContents'); |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* Checks if a given identifier is within a container, e.g. if |
576
|
|
|
* a file or folder is within another folder. |
577
|
|
|
* This can e.g. be used to check for web-mounts. |
578
|
|
|
* |
579
|
|
|
* Hint: this also needs to return TRUE if the given identifier |
580
|
|
|
* matches the container identifier to allow access to the root |
581
|
|
|
* folder of a filemount. |
582
|
|
|
* |
583
|
|
|
* @param string $folderIdentifier |
584
|
|
|
* @param string $identifier identifier to be checked against $folderIdentifier |
585
|
|
|
* @return bool TRUE if $content is within or matches $folderIdentifier |
586
|
|
|
*/ |
587
|
|
|
public function isWithin($folderIdentifier, $identifier) |
588
|
|
|
{ |
589
|
|
|
$folderIdentifier = $this->canonicalizeAndCheckFileIdentifier($folderIdentifier); |
590
|
|
|
$entryIdentifier = $this->canonicalizeAndCheckFileIdentifier($identifier); |
591
|
|
|
if ($folderIdentifier === $entryIdentifier) { |
592
|
|
|
return true; |
593
|
|
|
} |
594
|
|
|
// File identifier canonicalization will not modify a single slash so |
595
|
|
|
// we must not append another slash in that case. |
596
|
|
|
if ($folderIdentifier !== '/') { |
597
|
|
|
$folderIdentifier .= '/'; |
598
|
|
|
} |
599
|
|
|
return GeneralUtility::isFirstPartOfStr($entryIdentifier, $folderIdentifier); |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
/** |
603
|
|
|
* Returns information about a file. |
604
|
|
|
* |
605
|
|
|
* @param string $fileIdentifier |
606
|
|
|
* @param array $propertiesToExtract Array of properties which are be extracted |
607
|
|
|
* If empty all will be extracted |
608
|
|
|
* @return array |
609
|
|
|
* @throws FileDoesNotExistException |
610
|
|
|
*/ |
611
|
12 |
|
public function getFileInfoByIdentifier($fileIdentifier, array $propertiesToExtract = []) |
612
|
|
|
{ |
613
|
12 |
|
$relativeDriverPath = ltrim($fileIdentifier, '/'); |
614
|
12 |
|
if (!$this->filesystem->has($relativeDriverPath) || !$this->filesystem->get($relativeDriverPath)->isFile()) { |
615
|
3 |
|
throw new FileDoesNotExistException('File ' . $fileIdentifier . ' does not exist.', 1314516809); |
616
|
|
|
} |
617
|
9 |
|
$dirPath = PathUtility::dirname($fileIdentifier); |
618
|
9 |
|
$dirPath = $this->canonicalizeAndCheckFolderIdentifier($dirPath); |
619
|
9 |
|
return $this->extractFileInformation($relativeDriverPath, $dirPath, $propertiesToExtract); |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* Returns information about a file. |
624
|
|
|
* |
625
|
|
|
* @param string $folderIdentifier |
626
|
|
|
* @return array |
627
|
|
|
* @throws FolderDoesNotExistException |
628
|
|
|
*/ |
629
|
6 |
|
public function getFolderInfoByIdentifier($folderIdentifier) |
630
|
|
|
{ |
631
|
6 |
|
$folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier); |
632
|
|
|
|
633
|
6 |
|
if (!$this->folderExists($folderIdentifier)) { |
634
|
3 |
|
throw new FolderDoesNotExistException( |
635
|
3 |
|
'Folder "' . $folderIdentifier . '" does not exist.', |
636
|
1 |
|
1314516810 |
637
|
2 |
|
); |
638
|
|
|
} |
639
|
|
|
return [ |
640
|
3 |
|
'identifier' => $folderIdentifier, |
641
|
3 |
|
'name' => PathUtility::basename($folderIdentifier), |
642
|
3 |
|
'storage' => $this->storageUid |
643
|
2 |
|
]; |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
/** |
647
|
|
|
* Returns the identifier of a file inside the folder |
648
|
|
|
* |
649
|
|
|
* @param string $fileName |
650
|
|
|
* @param string $folderIdentifier |
651
|
|
|
* @return string file identifier |
652
|
|
|
*/ |
653
|
3 |
|
public function getFileInFolder($fileName, $folderIdentifier) |
654
|
|
|
{ |
655
|
3 |
|
return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName); |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
/** |
659
|
|
|
* Returns a list of files inside the specified path |
660
|
|
|
* |
661
|
|
|
* @param string $folderIdentifier |
662
|
|
|
* @param int $start |
663
|
|
|
* @param int $numberOfItems |
664
|
|
|
* @param bool $recursive |
665
|
|
|
* @param array $filenameFilterCallbacks callbacks for filtering the items |
666
|
|
|
* @param string $sort Property name used to sort the items. |
667
|
|
|
* Among them may be: '' (empty, no sorting), name, |
668
|
|
|
* fileext, size, tstamp and rw. |
669
|
|
|
* If a driver does not support the given property, it |
670
|
|
|
* should fall back to "name". |
671
|
|
|
* @param bool $sortRev TRUE to indicate reverse sorting (last to first) |
672
|
|
|
* @return array of FileIdentifiers |
673
|
|
|
*/ |
674
|
3 |
|
public function getFilesInFolder( |
675
|
|
|
$folderIdentifier, |
676
|
|
|
$start = 0, |
677
|
|
|
$numberOfItems = 0, |
678
|
|
|
$recursive = false, |
679
|
|
|
array $filenameFilterCallbacks = [], |
680
|
|
|
$sort = '', |
681
|
|
|
$sortRev = false |
682
|
|
|
) { |
683
|
3 |
|
$calculatedFolderIdentifier = ltrim($this->canonicalizeAndCheckFolderIdentifier($folderIdentifier), '/'); |
684
|
3 |
|
$contents = $this->filesystem->listContents($calculatedFolderIdentifier); |
685
|
3 |
|
$files = []; |
686
|
|
|
|
687
|
|
|
/* |
688
|
|
|
* Filter directories |
689
|
|
|
*/ |
690
|
3 |
|
foreach ($contents as $directoryItem) { |
691
|
3 |
|
if ('file' === $directoryItem['type']) { |
692
|
3 |
|
$files['/' . $directoryItem['path']] = '/' . $directoryItem['path']; |
693
|
2 |
|
} |
694
|
2 |
|
} |
695
|
|
|
|
696
|
3 |
|
return $files; |
697
|
|
|
} |
698
|
|
|
|
699
|
|
|
/** |
700
|
|
|
* Returns the identifier of a folder inside the folder |
701
|
|
|
* |
702
|
|
|
* @param string $folderName The name of the target folder |
703
|
|
|
* @param string $folderIdentifier |
704
|
|
|
* @return string folder identifier |
705
|
|
|
*/ |
706
|
3 |
|
public function getFolderInFolder($folderName, $folderIdentifier) |
707
|
|
|
{ |
708
|
3 |
|
$folderIdentifier = $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName); |
709
|
3 |
|
return $folderIdentifier; |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
/** |
713
|
|
|
* Returns a list of folders inside the specified path |
714
|
|
|
* |
715
|
|
|
* @param string $folderIdentifier |
716
|
|
|
* @param int $start |
717
|
|
|
* @param int $numberOfItems |
718
|
|
|
* @param bool $recursive |
719
|
|
|
* @param array $folderNameFilterCallbacks callbacks for filtering the items |
720
|
|
|
* @param string $sort Property name used to sort the items. |
721
|
|
|
* Among them may be: '' (empty, no sorting), name, |
722
|
|
|
* fileext, size, tstamp and rw. |
723
|
|
|
* If a driver does not support the given property, it |
724
|
|
|
* should fall back to "name". |
725
|
|
|
* @param bool $sortRev TRUE to indicate reverse sorting (last to first) |
726
|
|
|
* @return array of Folder Identifier |
727
|
|
|
* @TODO: Implement pagination with $start and $numberOfItems |
728
|
|
|
* @TODO: Implement directory filter callbacks |
729
|
|
|
* @TODO: Implement sorting |
730
|
|
|
*/ |
731
|
3 |
|
public function getFoldersInFolder( |
732
|
|
|
$folderIdentifier, |
733
|
|
|
$start = 0, |
734
|
|
|
$numberOfItems = 0, |
735
|
|
|
$recursive = false, |
736
|
|
|
array $folderNameFilterCallbacks = [], |
737
|
|
|
$sort = '', |
738
|
|
|
$sortRev = false |
739
|
|
|
) { |
740
|
3 |
|
$calculatedFolderIdentifier = ltrim($this->canonicalizeAndCheckFolderIdentifier($folderIdentifier), '/'); |
741
|
3 |
|
$contents = $this->filesystem->listContents($calculatedFolderIdentifier); |
742
|
3 |
|
$directories = []; |
743
|
|
|
|
744
|
|
|
/* |
745
|
|
|
* Filter directories |
746
|
|
|
*/ |
747
|
3 |
|
foreach ($contents as $directoryItem) { |
748
|
3 |
|
if ('dir' === $directoryItem['type']) { |
749
|
3 |
|
$directories['/' . $directoryItem['path']] |
750
|
3 |
|
= '/' . $directoryItem['path']; |
751
|
2 |
|
} |
752
|
2 |
|
} |
753
|
|
|
|
754
|
3 |
|
return $directories; |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
/** |
758
|
|
|
* Returns the number of files inside the specified path |
759
|
|
|
* |
760
|
|
|
* @param string $folderIdentifier |
761
|
|
|
* @param bool $recursive |
762
|
|
|
* @param array $filenameFilterCallbacks callbacks for filtering the items |
763
|
|
|
* @return int Number of files in folder |
764
|
|
|
* @TODO: Implement recursive count |
765
|
|
|
* @TODO: Implement filename filtering |
766
|
|
|
*/ |
767
|
|
|
public function countFilesInFolder($folderIdentifier, $recursive = false, array $filenameFilterCallbacks = []) |
768
|
|
|
{ |
769
|
|
|
|
770
|
|
|
return count($this->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filenameFilterCallbacks)); |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
/** |
774
|
|
|
* Returns the number of folders inside the specified path |
775
|
|
|
* |
776
|
|
|
* @param string $folderIdentifier |
777
|
|
|
* @param bool $recursive |
778
|
|
|
* @param array $folderNameFilterCallbacks callbacks for filtering the items |
779
|
|
|
* @return int Number of folders in folder |
780
|
|
|
*/ |
781
|
3 |
|
public function countFoldersInFolder($folderIdentifier, $recursive = false, array $folderNameFilterCallbacks = []) |
782
|
|
|
{ |
783
|
3 |
|
$count = 0; |
784
|
3 |
|
$filesystemRelativeIdentifier = ltrim($folderIdentifier, '/'); |
785
|
3 |
|
$directoryListing = $this->filesystem->listContents($filesystemRelativeIdentifier); |
786
|
3 |
|
foreach ($directoryListing as $entry) { |
787
|
3 |
|
if ('dir' === $entry['type']) { |
788
|
3 |
|
$count++; |
789
|
2 |
|
} |
790
|
2 |
|
} |
791
|
|
|
|
792
|
3 |
|
return $count; |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
/** |
796
|
|
|
* Extracts information about a file from the filesystem. |
797
|
|
|
* |
798
|
|
|
* @param string $filePath The absolute path to the file |
799
|
|
|
* @param string $containerPath The relative path to the file's container |
800
|
|
|
* @param array $propertiesToExtract array of properties which should be returned, if empty all will be extracted |
801
|
|
|
* @return array |
802
|
|
|
*/ |
803
|
9 |
|
protected function extractFileInformation($filePath, $containerPath, array $propertiesToExtract = array()) |
804
|
|
|
{ |
805
|
9 |
|
if (empty($propertiesToExtract)) { |
806
|
|
|
$propertiesToExtract = array( |
807
|
3 |
|
'size', |
808
|
2 |
|
'atime', |
809
|
2 |
|
'atime', |
810
|
2 |
|
'mtime', |
811
|
2 |
|
'ctime', |
812
|
2 |
|
'mimetype', |
813
|
2 |
|
'name', |
814
|
2 |
|
'identifier', |
815
|
2 |
|
'identifier_hash', |
816
|
2 |
|
'storage', |
817
|
|
|
'folder_hash' |
818
|
2 |
|
); |
819
|
2 |
|
} |
820
|
9 |
|
$fileInformation = array(); |
821
|
9 |
|
foreach ($propertiesToExtract as $property) { |
822
|
9 |
|
$fileInformation[$property] = $this->getSpecificFileInformation($filePath, $containerPath, $property); |
823
|
6 |
|
} |
824
|
9 |
|
return $fileInformation; |
825
|
|
|
} |
826
|
|
|
|
827
|
|
|
/** |
828
|
|
|
* Extracts a specific FileInformation from the FileSystems. |
829
|
|
|
* |
830
|
|
|
* @param string $fileIdentifier |
831
|
|
|
* @param string $containerPath |
832
|
|
|
* @param string $property |
833
|
|
|
* |
834
|
|
|
* @return bool|int|string |
835
|
|
|
* @throws \InvalidArgumentException |
836
|
|
|
*/ |
837
|
9 |
|
public function getSpecificFileInformation($fileIdentifier, $containerPath, $property) |
838
|
|
|
{ |
839
|
9 |
|
$baseName = basename($fileIdentifier); |
840
|
9 |
|
$parts = explode('/', $fileIdentifier); |
841
|
9 |
|
$identifier = $this->canonicalizeAndCheckFileIdentifier($containerPath . PathUtility::basename($fileIdentifier)); |
842
|
|
|
|
843
|
9 |
|
$file = $this->filesystem->getMetadata($fileIdentifier); |
844
|
|
|
|
845
|
|
|
switch ($property) { |
846
|
9 |
|
case 'size': |
847
|
9 |
|
return $file['size']; |
848
|
6 |
|
case 'atime': |
849
|
3 |
|
return $file['timestamp']; |
850
|
6 |
|
case 'mtime': |
851
|
9 |
|
return $file['timestamp']; |
852
|
6 |
|
case 'ctime': |
853
|
3 |
|
return $file['timestamp']; |
854
|
6 |
|
case 'name': |
855
|
9 |
|
return $baseName; |
856
|
6 |
|
case 'mimetype': |
857
|
3 |
|
return 'application/octet-stream'; |
858
|
6 |
|
case 'identifier': |
859
|
9 |
|
return $identifier; |
860
|
2 |
|
case 'storage': |
861
|
3 |
|
return $this->storageUid; |
862
|
2 |
|
case 'identifier_hash': |
863
|
3 |
|
return $this->hashIdentifier($identifier); |
864
|
2 |
|
case 'folder_hash': |
865
|
3 |
|
if (1 < count($parts)) { |
866
|
3 |
|
return $this->hashIdentifier($this->getParentFolderIdentifierOfIdentifier($identifier)); |
867
|
3 |
|
} elseif (1 === count($parts)) { |
868
|
3 |
|
return sha1('/'); |
869
|
|
|
} else { |
870
|
|
|
return ''; |
871
|
|
|
} |
872
|
|
|
default: |
873
|
|
|
throw new \InvalidArgumentException(sprintf('The information "%s" is not available.', $property)); |
874
|
|
|
} |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
/** |
878
|
|
|
* Copies a file to a temporary path and returns that path. |
879
|
|
|
* |
880
|
|
|
* @param string $fileIdentifier |
881
|
|
|
* @return string The temporary path |
882
|
|
|
* @throws \RuntimeException |
883
|
|
|
*/ |
884
|
|
|
protected function copyFileToTemporaryPath($fileIdentifier) |
885
|
|
|
{ |
886
|
|
|
$temporaryPath = $this->getTemporaryPathForFile($fileIdentifier); |
887
|
|
|
$contents = $this->filesystem->read(ltrim($fileIdentifier, '/')); |
888
|
|
|
|
889
|
|
|
$res = fopen($temporaryPath, 'w'); |
890
|
|
|
$result = fwrite($res, $contents); |
891
|
|
|
fclose($res); |
892
|
|
|
|
893
|
|
|
if (false === $result) { |
894
|
|
|
throw new \RuntimeException( |
895
|
|
|
'Copying file "' . $fileIdentifier . '" to temporary path "' . $temporaryPath . '" failed.', |
896
|
|
|
1320577649 |
897
|
|
|
); |
898
|
|
|
} |
899
|
|
|
return $temporaryPath; |
900
|
|
|
} |
901
|
|
|
|
902
|
|
|
/** |
903
|
|
|
* @param string $trimmedSourcePath |
904
|
|
|
* @param string $trimmedTargetPath |
905
|
|
|
* @return bool |
906
|
|
|
*/ |
907
|
|
|
protected function copyFolderRecursively($trimmedSourcePath, $trimmedTargetPath) |
908
|
|
|
{ |
909
|
|
|
try { |
910
|
|
|
$contents = $this->filesystem->listContents($trimmedSourcePath, true); |
911
|
|
|
foreach ($contents as $item) { |
912
|
|
|
if ('file' === $item['type']) { |
913
|
|
|
try { |
914
|
|
|
$relPath = substr_replace($trimmedSourcePath, '', 0, strlen($item['path'])); |
915
|
|
|
$targetPath = $trimmedTargetPath . $relPath . '/' . $item['basename']; |
916
|
|
|
$this->filesystem->copy($item['path'], $targetPath); |
917
|
|
|
} catch (FileExistsException $fee) { |
918
|
|
|
continue; |
919
|
|
|
} catch (FileNotFoundException $fnfe) { |
920
|
|
|
return false; |
921
|
|
|
} |
922
|
|
|
} |
923
|
|
|
} |
924
|
|
|
} catch (\Exception $e) { |
925
|
|
|
return false; |
926
|
|
|
} |
927
|
|
|
return true; |
928
|
|
|
} |
929
|
|
|
} |
930
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.