|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
/** |
|
6
|
|
|
* balloon |
|
7
|
|
|
* |
|
8
|
|
|
* @copyright Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com) |
|
9
|
|
|
* @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Balloon\Filesystem\Node; |
|
13
|
|
|
|
|
14
|
|
|
use Balloon\Filesystem; |
|
15
|
|
|
use Balloon\Filesystem\Acl; |
|
16
|
|
|
use Balloon\Filesystem\Acl\Exception as AclException; |
|
17
|
|
|
use Balloon\Filesystem\Exception; |
|
18
|
|
|
use Balloon\Filesystem\Storage; |
|
19
|
|
|
use Balloon\Filesystem\Storage\Exception as StorageException; |
|
20
|
|
|
use Balloon\Hook; |
|
21
|
|
|
use MimeType\MimeType; |
|
22
|
|
|
use MongoDB\BSON\ObjectId; |
|
23
|
|
|
use MongoDB\BSON\UTCDateTime; |
|
24
|
|
|
use MongoDB\GridFS\Exception as GridFSException; |
|
25
|
|
|
use Psr\Log\LoggerInterface; |
|
26
|
|
|
use Sabre\DAV\IFile; |
|
27
|
|
|
|
|
28
|
|
|
class File extends AbstractNode implements IFile |
|
29
|
|
|
{ |
|
30
|
|
|
/** |
|
31
|
|
|
* History types. |
|
32
|
|
|
*/ |
|
33
|
|
|
const HISTORY_CREATE = 0; |
|
34
|
|
|
const HISTORY_EDIT = 1; |
|
35
|
|
|
const HISTORY_RESTORE = 2; |
|
36
|
|
|
const HISTORY_DELETE = 3; |
|
37
|
|
|
const HISTORY_UNDELETE = 4; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Empty content hash (NULL). |
|
41
|
|
|
*/ |
|
42
|
|
|
const EMPTY_CONTENT = 'd41d8cd98f00b204e9800998ecf8427e'; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Temporary file patterns. |
|
46
|
|
|
* |
|
47
|
|
|
* @param array |
|
48
|
|
|
**/ |
|
49
|
|
|
protected $temp_files = [ |
|
50
|
|
|
'/^\._(.*)$/', // OS/X resource forks |
|
51
|
|
|
'/^.DS_Store$/', // OS/X custom folder settings |
|
52
|
|
|
'/^desktop.ini$/', // Windows custom folder settings |
|
53
|
|
|
'/^Thumbs.db$/', // Windows thumbnail cache |
|
54
|
|
|
'/^.(.*).swpx$/', // ViM temporary files |
|
55
|
|
|
'/^.(.*).swx$/', // ViM temporary files |
|
56
|
|
|
'/^.(.*).swp$/', // ViM temporary files |
|
57
|
|
|
'/^\.dat(.*)$/', // Smultron seems to create these |
|
58
|
|
|
'/^~lock.(.*)#$/', // Windows 7 lockfiles |
|
59
|
|
|
]; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* MD5 Hash of the content. |
|
63
|
|
|
* |
|
64
|
|
|
* @var string |
|
65
|
|
|
*/ |
|
66
|
|
|
protected $hash; |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* File version. |
|
70
|
|
|
* |
|
71
|
|
|
* @var int |
|
72
|
|
|
*/ |
|
73
|
|
|
protected $version = 0; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* File size. |
|
77
|
|
|
* |
|
78
|
|
|
* @var int |
|
79
|
|
|
*/ |
|
80
|
|
|
protected $size = 0; |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* History. |
|
84
|
|
|
* |
|
85
|
|
|
* @var array |
|
86
|
|
|
*/ |
|
87
|
|
|
protected $history = []; |
|
88
|
|
|
|
|
89
|
|
|
/** |
|
90
|
|
|
* Storage attributes. |
|
91
|
|
|
* |
|
92
|
|
|
* @var mixed |
|
93
|
|
|
*/ |
|
94
|
|
|
protected $storage; |
|
95
|
|
|
|
|
96
|
|
|
/** |
|
97
|
|
|
* Initialize file node. |
|
98
|
|
|
*/ |
|
99
|
10 |
|
public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl, Storage $storage) |
|
100
|
|
|
{ |
|
101
|
10 |
|
$this->_fs = $fs; |
|
102
|
10 |
|
$this->_server = $fs->getServer(); |
|
103
|
10 |
|
$this->_db = $fs->getDatabase(); |
|
104
|
10 |
|
$this->_user = $fs->getUser(); |
|
105
|
10 |
|
$this->_logger = $logger; |
|
106
|
10 |
|
$this->_hook = $hook; |
|
107
|
10 |
|
$this->_storage = $storage; |
|
108
|
10 |
|
$this->_acl = $acl; |
|
109
|
|
|
|
|
110
|
10 |
|
foreach ($attributes as $attr => $value) { |
|
111
|
10 |
|
$this->{$attr} = $value; |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
10 |
|
$this->raw_attributes = $attributes; |
|
115
|
10 |
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* Read content and return ressource. |
|
119
|
|
|
* |
|
120
|
|
|
* @return resource |
|
121
|
|
|
*/ |
|
122
|
|
|
public function get() |
|
123
|
|
|
{ |
|
124
|
|
|
if (null === $this->storage) { |
|
125
|
|
|
return null; |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
try { |
|
129
|
|
|
return $this->_storage->getFile($this, $this->storage); |
|
130
|
|
|
} catch (GridFSException\FileNotFoundException $e) { |
|
|
|
|
|
|
131
|
|
|
throw new Exception\NotFound( |
|
132
|
|
|
'storage blob is gone', |
|
133
|
|
|
Exception\NotFound::CONTENTS_NOT_FOUND, |
|
134
|
|
|
$e |
|
135
|
|
|
); |
|
136
|
|
|
} |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* Copy node. |
|
141
|
|
|
* |
|
142
|
|
|
* @param string $recursion |
|
143
|
|
|
*/ |
|
144
|
|
|
public function copyTo(Collection $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true): NodeInterface |
|
145
|
|
|
{ |
|
146
|
|
|
$this->_hook->run( |
|
147
|
|
|
'preCopyFile', |
|
148
|
|
|
[$this, $parent, &$conflict, &$recursion, &$recursion_first] |
|
149
|
|
|
); |
|
150
|
|
|
|
|
151
|
|
|
if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) { |
|
152
|
|
|
$name = $this->getDuplicateName(); |
|
153
|
|
|
} else { |
|
154
|
|
|
$name = $this->name; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) { |
|
158
|
|
|
$result = $parent->getChild($this->name); |
|
159
|
|
|
$result->put($this->get()); |
|
160
|
|
|
} else { |
|
161
|
|
|
$session = $this->temporarySession($this->get()); |
|
162
|
|
|
$result = $parent->addFile($name, $session, [ |
|
163
|
|
|
'created' => $this->created, |
|
164
|
|
|
'changed' => $this->changed, |
|
165
|
|
|
'deleted' => $this->deleted, |
|
166
|
|
|
'meta' => $this->meta, |
|
167
|
|
|
], NodeInterface::CONFLICT_NOACTION, true); |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
$this->_hook->run( |
|
171
|
|
|
'postCopyFile', |
|
172
|
|
|
[$this, $parent, $result, $conflict, $recursion, $recursion_first] |
|
173
|
|
|
); |
|
174
|
|
|
|
|
175
|
|
|
return $result; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
/** |
|
179
|
|
|
* Get history. |
|
180
|
|
|
*/ |
|
181
|
|
|
public function getHistory(): array |
|
182
|
|
|
{ |
|
183
|
|
|
return $this->history; |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
/** |
|
187
|
|
|
* Restore content to some older version. |
|
188
|
|
|
*/ |
|
189
|
|
|
public function restore(int $version): bool |
|
190
|
|
|
{ |
|
191
|
|
|
if (!$this->_acl->isAllowed($this, 'w')) { |
|
192
|
|
|
throw new AclException\Forbidden( |
|
193
|
|
|
'not allowed to restore node '.$this->name, |
|
194
|
|
|
AclException\Forbidden::NOT_ALLOWED_TO_RESTORE |
|
195
|
|
|
); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
$this->_hook->run('preRestoreFile', [$this, &$version]); |
|
199
|
|
|
|
|
200
|
|
|
if ($this->readonly) { |
|
201
|
|
|
throw new Exception\Conflict( |
|
202
|
|
|
'node is marked as readonly, it is not possible to change any content', |
|
203
|
|
|
Exception\Conflict::READONLY |
|
204
|
|
|
); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
if ($this->version === $version) { |
|
208
|
|
|
throw new Exception('file is already version '.$version); |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
$current = $this->version; |
|
|
|
|
|
|
212
|
|
|
$new = $this->increaseVersion(); |
|
213
|
|
|
|
|
214
|
|
|
$v = array_search($version, array_column($this->history, 'version'), true); |
|
215
|
|
|
if (false === $v) { |
|
216
|
|
|
throw new Exception('failed restore file to version '.$version.', version was not found'); |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
$file = $this->history[$v]['storage']; |
|
220
|
|
|
|
|
221
|
|
|
$this->history[] = [ |
|
222
|
|
|
'version' => $new, |
|
223
|
|
|
'changed' => $this->changed, |
|
224
|
|
|
'user' => $this->owner, |
|
225
|
|
|
'type' => self::HISTORY_RESTORE, |
|
226
|
|
|
'hash' => $this->history[$v]['hash'], |
|
227
|
|
|
'origin' => $this->history[$v]['version'], |
|
228
|
|
|
'storage' => $this->history[$v]['storage'], |
|
229
|
|
|
'storage_adapter' => $this->history[$v]['storage_adapter'], |
|
230
|
|
|
'size' => $this->history[$v]['size'], |
|
231
|
|
|
'mime' => isset($this->history[$v]['mime']) ? $this->history[$v]['mime'] : null, |
|
232
|
|
|
]; |
|
233
|
|
|
|
|
234
|
|
|
try { |
|
235
|
|
|
$this->deleted = false; |
|
236
|
|
|
$this->version = $new; |
|
237
|
|
|
$this->storage = $this->history[$v]['storage']; |
|
238
|
|
|
$this->storage_adapter = $this->history[$v]['storage_adapter']; |
|
239
|
|
|
|
|
240
|
|
|
$this->hash = null === $file ? self::EMPTY_CONTENT : $this->history[$v]['hash']; |
|
241
|
|
|
$this->mime = isset($this->history[$v]['mime']) ? $this->history[$v]['mime'] : null; |
|
242
|
|
|
$this->size = $this->history[$v]['size']; |
|
243
|
|
|
$this->changed = $this->history[$v]['changed']; |
|
244
|
|
|
|
|
245
|
|
|
$this->save([ |
|
246
|
|
|
'deleted', |
|
247
|
|
|
'version', |
|
248
|
|
|
'storage', |
|
249
|
|
|
'storage_adapter', |
|
250
|
|
|
'hash', |
|
251
|
|
|
'mime', |
|
252
|
|
|
'size', |
|
253
|
|
|
'history', |
|
254
|
|
|
'changed', |
|
255
|
|
|
]); |
|
256
|
|
|
|
|
257
|
|
|
$this->_hook->run('postRestoreFile', [$this, &$version]); |
|
258
|
|
|
|
|
259
|
|
|
$this->_logger->info('restored file ['.$this->_id.'] to version ['.$version.']', [ |
|
260
|
|
|
'category' => get_class($this), |
|
261
|
|
|
]); |
|
262
|
|
|
} catch (\Exception $e) { |
|
263
|
|
|
$this->_logger->error('failed restore file ['.$this->_id.'] to version ['.$version.']', [ |
|
264
|
|
|
'category' => get_class($this), |
|
265
|
|
|
'exception' => $e, |
|
266
|
|
|
]); |
|
267
|
|
|
|
|
268
|
|
|
throw $e; |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
return true; |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
/** |
|
275
|
|
|
* Delete node. |
|
276
|
|
|
* |
|
277
|
|
|
* Actually the node will not be deleted (Just set a delete flag), set $force=true to |
|
278
|
|
|
* delete finally |
|
279
|
|
|
* |
|
280
|
|
|
* @param string $recursion |
|
281
|
|
|
*/ |
|
282
|
|
|
public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool |
|
283
|
|
|
{ |
|
284
|
|
|
if (!$this->_acl->isAllowed($this, 'w')) { |
|
285
|
|
|
throw new AclException\Forbidden( |
|
286
|
|
|
'not allowed to delete node '.$this->name, |
|
287
|
|
|
AclException\Forbidden::NOT_ALLOWED_TO_DELETE |
|
288
|
|
|
); |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
$this->_hook->run('preDeleteFile', [$this, &$force, &$recursion, &$recursion_first]); |
|
292
|
|
|
|
|
293
|
|
|
if (true === $force || $this->isTemporaryFile()) { |
|
294
|
|
|
$result = $this->_forceDelete(); |
|
295
|
|
|
$this->_hook->run('postDeleteFile', [$this, $force, $recursion, $recursion_first]); |
|
296
|
|
|
|
|
297
|
|
|
return $result; |
|
298
|
|
|
} |
|
299
|
|
|
|
|
300
|
|
|
$ts = new UTCDateTime(); |
|
301
|
|
|
$this->deleted = $ts; |
|
302
|
|
|
$this->increaseVersion(); |
|
303
|
|
|
|
|
304
|
|
|
$this->history[] = [ |
|
305
|
|
|
'version' => $this->version, |
|
306
|
|
|
'changed' => $ts, |
|
307
|
|
|
'user' => ($this->_user === null) ? null : $this->_user->getId(), |
|
308
|
|
|
'type' => self::HISTORY_DELETE, |
|
309
|
|
|
'storage' => $this->storage, |
|
310
|
|
|
'storage_adapter' => $this->storage_adapter, |
|
311
|
|
|
'size' => $this->size, |
|
312
|
|
|
'hash' => $this->hash, |
|
313
|
|
|
]; |
|
314
|
|
|
|
|
315
|
|
|
$result = $this->save([ |
|
316
|
|
|
'version', |
|
317
|
|
|
'deleted', |
|
318
|
|
|
'history', |
|
319
|
|
|
], [], $recursion, $recursion_first); |
|
320
|
|
|
|
|
321
|
|
|
$this->_hook->run('postDeleteFile', [$this, $force, $recursion, $recursion_first]); |
|
322
|
|
|
|
|
323
|
|
|
return $result; |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
/** |
|
327
|
|
|
* Check if file is temporary. |
|
328
|
|
|
* |
|
329
|
|
|
**/ |
|
330
|
2 |
|
public function isTemporaryFile(): bool |
|
331
|
|
|
{ |
|
332
|
2 |
|
foreach ($this->temp_files as $pattern) { |
|
333
|
2 |
|
if (preg_match($pattern, $this->name)) { |
|
334
|
2 |
|
return true; |
|
335
|
|
|
} |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
1 |
|
return false; |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
/** |
|
342
|
|
|
* Delete version. |
|
343
|
|
|
*/ |
|
344
|
|
|
public function deleteVersion(int $version): bool |
|
345
|
|
|
{ |
|
346
|
|
|
$key = array_search($version, array_column($this->history, 'version'), true); |
|
347
|
|
|
|
|
348
|
|
|
if (false === $key) { |
|
349
|
|
|
throw new Exception('version '.$version.' does not exists'); |
|
350
|
|
|
} |
|
351
|
|
|
|
|
352
|
|
|
$blobs = array_column($this->history, 'storage'); |
|
353
|
|
|
|
|
354
|
|
|
try { |
|
355
|
|
|
//do not remove blob if there are other versions linked against it |
|
356
|
|
|
if ($this->history[$key]['storage'] !== null && count(array_keys($blobs, $this->history[$key]['storage'])) === 1) { |
|
357
|
|
|
$this->_storage->deleteFile($this, $this->history[$key]['storage']); |
|
358
|
|
|
} |
|
359
|
|
|
|
|
360
|
|
|
array_splice($this->history, $key, 1); |
|
361
|
|
|
|
|
362
|
|
|
$this->_logger->debug('removed version ['.$version.'] from file ['.$this->_id.']', [ |
|
363
|
|
|
'category' => get_class($this), |
|
364
|
|
|
]); |
|
365
|
|
|
|
|
366
|
|
|
return $this->save('history'); |
|
367
|
|
|
} catch (StorageException\BlobNotFound $e) { |
|
368
|
|
|
$this->_logger->error('failed remove version ['.$version.'] from file ['.$this->_id.']', [ |
|
369
|
|
|
'category' => get_class($this), |
|
370
|
|
|
'exception' => $e, |
|
371
|
|
|
]); |
|
372
|
|
|
|
|
373
|
|
|
return false; |
|
374
|
|
|
} |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* Cleanup history. |
|
379
|
|
|
*/ |
|
380
|
|
|
public function cleanHistory(): bool |
|
381
|
|
|
{ |
|
382
|
|
|
foreach ($this->history as $node) { |
|
383
|
|
|
$this->deleteVersion($node['version']); |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
return true; |
|
387
|
|
|
} |
|
388
|
|
|
|
|
389
|
|
|
/** |
|
390
|
|
|
* Get Attributes. |
|
391
|
|
|
*/ |
|
392
|
|
|
public function getAttributes(): array |
|
393
|
|
|
{ |
|
394
|
|
|
return [ |
|
395
|
|
|
'_id' => $this->_id, |
|
396
|
|
|
'name' => $this->name, |
|
397
|
|
|
'hash' => $this->hash, |
|
398
|
|
|
'directory' => false, |
|
399
|
|
|
'size' => $this->size, |
|
400
|
|
|
'version' => $this->version, |
|
401
|
|
|
'parent' => $this->parent, |
|
402
|
|
|
'acl' => $this->acl, |
|
403
|
|
|
'app' => $this->app, |
|
404
|
|
|
'meta' => $this->meta, |
|
405
|
|
|
'mime' => $this->mime, |
|
406
|
|
|
'owner' => $this->owner, |
|
407
|
|
|
'history' => $this->history, |
|
408
|
|
|
'shared' => $this->shared, |
|
409
|
|
|
'deleted' => $this->deleted, |
|
410
|
|
|
'changed' => $this->changed, |
|
411
|
|
|
'created' => $this->created, |
|
412
|
|
|
'destroy' => $this->destroy, |
|
413
|
|
|
'readonly' => $this->readonly, |
|
414
|
|
|
'storage_adapter' => $this->storage_adapter, |
|
415
|
|
|
'storage' => $this->storage, |
|
416
|
|
|
]; |
|
417
|
|
|
} |
|
418
|
|
|
|
|
419
|
|
|
/** |
|
420
|
|
|
* Get filename extension. |
|
421
|
|
|
*/ |
|
422
|
2 |
|
public function getExtension(): string |
|
423
|
|
|
{ |
|
424
|
2 |
|
$ext = strrchr($this->name, '.'); |
|
425
|
2 |
|
if (false === $ext) { |
|
426
|
1 |
|
throw new Exception('file does not have an extension'); |
|
427
|
|
|
} |
|
428
|
|
|
|
|
429
|
1 |
|
return substr($ext, 1); |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
/** |
|
433
|
|
|
* Get file size. |
|
434
|
|
|
*/ |
|
435
|
1 |
|
public function getSize(): int |
|
436
|
|
|
{ |
|
437
|
1 |
|
return $this->size; |
|
438
|
|
|
} |
|
439
|
|
|
|
|
440
|
|
|
/** |
|
441
|
|
|
* Get md5 sum of the file content, |
|
442
|
|
|
* actually the hash value comes from the database. |
|
443
|
|
|
*/ |
|
444
|
1 |
|
public function getETag(): string |
|
445
|
|
|
{ |
|
446
|
1 |
|
return "'".$this->hash."'"; |
|
447
|
|
|
} |
|
448
|
|
|
|
|
449
|
|
|
/** |
|
450
|
|
|
* Get hash. |
|
451
|
|
|
* |
|
452
|
|
|
* @return string |
|
453
|
|
|
*/ |
|
454
|
1 |
|
public function getHash(): ?string |
|
455
|
|
|
{ |
|
456
|
1 |
|
return $this->hash; |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
/** |
|
460
|
|
|
* Get version. |
|
461
|
|
|
*/ |
|
462
|
1 |
|
public function getVersion(): int |
|
463
|
|
|
{ |
|
464
|
1 |
|
return $this->version; |
|
465
|
|
|
} |
|
466
|
|
|
|
|
467
|
|
|
/** |
|
468
|
|
|
* Change content (Sabe dav compatible method). |
|
469
|
|
|
*/ |
|
470
|
|
|
public function put($content): int |
|
471
|
|
|
{ |
|
472
|
|
|
$this->_logger->debug('write new file content into temporary storage for file ['.$this->_id.']', [ |
|
473
|
|
|
'category' => get_class($this), |
|
474
|
|
|
]); |
|
475
|
|
|
|
|
476
|
|
|
$session = $this->_storage->storeTemporaryFile($content, $this->_user); |
|
477
|
|
|
|
|
478
|
|
|
return $this->setContent($session); |
|
479
|
|
|
} |
|
480
|
|
|
|
|
481
|
|
|
/** |
|
482
|
|
|
* Set content (temporary file). |
|
483
|
|
|
*/ |
|
484
|
|
|
public function setContent(ObjectId $session, array $attributes = []): int |
|
485
|
|
|
{ |
|
486
|
|
|
$this->_logger->debug('set temporary file ['.$session.'] as file content for ['.$this->_id.']', [ |
|
487
|
|
|
'category' => get_class($this), |
|
488
|
|
|
]); |
|
489
|
|
|
|
|
490
|
|
|
$this->prePutFile($session); |
|
491
|
|
|
$result = $this->_storage->storeFile($this, $session, $this->storage_adapter); |
|
492
|
|
|
$this->storage = $result['reference']; |
|
493
|
|
|
|
|
494
|
|
|
if ($this->hash === $result['hash']) { |
|
495
|
|
|
$this->_logger->debug('do not update file version, hash identical to existing version', [ |
|
496
|
|
|
'category' => get_class($this), |
|
497
|
|
|
]); |
|
498
|
|
|
|
|
499
|
|
|
return $this->version; |
|
500
|
|
|
} |
|
501
|
|
|
|
|
502
|
|
|
$this->hash = $result['hash']; |
|
503
|
|
|
$this->size = $result['size']; |
|
504
|
|
|
|
|
505
|
|
|
if ($this->size === 0) { |
|
506
|
|
|
$this->storage = null; |
|
507
|
|
|
} else { |
|
508
|
|
|
$this->storage = $result['reference']; |
|
509
|
|
|
} |
|
510
|
|
|
|
|
511
|
|
|
$this->mime = MimeType::getType($this->name); |
|
512
|
|
|
$this->increaseVersion(); |
|
513
|
|
|
|
|
514
|
|
|
$this->addVersion($attributes) |
|
515
|
|
|
->postPutFile(); |
|
516
|
|
|
|
|
517
|
|
|
return $this->version; |
|
518
|
|
|
} |
|
519
|
|
|
|
|
520
|
|
|
/** |
|
521
|
|
|
* Completly remove file. |
|
522
|
|
|
*/ |
|
523
|
|
|
protected function _forceDelete(): bool |
|
524
|
|
|
{ |
|
525
|
|
|
try { |
|
526
|
|
|
$this->cleanHistory(); |
|
527
|
|
|
$this->_db->storage->deleteOne([ |
|
528
|
|
|
'_id' => $this->_id, |
|
529
|
|
|
]); |
|
530
|
|
|
|
|
531
|
|
|
$this->_logger->info('removed file node ['.$this->_id.']', [ |
|
532
|
|
|
'category' => get_class($this), |
|
533
|
|
|
]); |
|
534
|
|
|
} catch (\Exception $e) { |
|
535
|
|
|
$this->_logger->error('failed delete file node ['.$this->_id.']', [ |
|
536
|
|
|
'category' => get_class($this), |
|
537
|
|
|
'exception' => $e, |
|
538
|
|
|
]); |
|
539
|
|
|
|
|
540
|
|
|
throw $e; |
|
541
|
|
|
} |
|
542
|
|
|
|
|
543
|
|
|
return true; |
|
544
|
|
|
} |
|
545
|
|
|
|
|
546
|
|
|
/** |
|
547
|
|
|
* Increase version. |
|
548
|
|
|
*/ |
|
549
|
|
|
protected function increaseVersion(): int |
|
550
|
|
|
{ |
|
551
|
|
|
$max = $this->_fs->getServer()->getMaxFileVersion(); |
|
552
|
|
|
if (count($this->history) >= $max) { |
|
553
|
|
|
$del = key($this->history); |
|
554
|
|
|
$this->_logger->debug('history limit ['.$max.'] reached, remove oldest version ['.$del.'] from file ['.$this->_id.']', [ |
|
555
|
|
|
'category' => get_class($this), |
|
556
|
|
|
]); |
|
557
|
|
|
|
|
558
|
|
|
$this->deleteVersion($this->history[$del]['version']); |
|
559
|
|
|
} |
|
560
|
|
|
|
|
561
|
|
|
++$this->version; |
|
562
|
|
|
|
|
563
|
|
|
return $this->version; |
|
564
|
|
|
} |
|
565
|
|
|
|
|
566
|
|
|
/** |
|
567
|
|
|
* Pre content change checks. |
|
568
|
|
|
*/ |
|
569
|
|
|
protected function prePutFile(ObjectId $session): bool |
|
570
|
|
|
{ |
|
571
|
|
|
if (!$this->_acl->isAllowed($this, 'w')) { |
|
572
|
|
|
throw new AclException\Forbidden( |
|
573
|
|
|
'not allowed to modify node', |
|
574
|
|
|
AclException\Forbidden::NOT_ALLOWED_TO_MODIFY |
|
575
|
|
|
); |
|
576
|
|
|
} |
|
577
|
|
|
|
|
578
|
|
|
$this->_hook->run('prePutFile', [$this, &$session]); |
|
579
|
|
|
|
|
580
|
|
|
if ($this->readonly) { |
|
581
|
|
|
throw new Exception\Conflict( |
|
582
|
|
|
'node is marked as readonly, it is not possible to change any content', |
|
583
|
|
|
Exception\Conflict::READONLY |
|
584
|
|
|
); |
|
585
|
|
|
} |
|
586
|
|
|
|
|
587
|
|
|
return true; |
|
588
|
|
|
} |
|
589
|
|
|
|
|
590
|
|
|
/** |
|
591
|
|
|
* Add new version. |
|
592
|
|
|
*/ |
|
593
|
|
|
protected function addVersion(array $attributes = []): self |
|
594
|
|
|
{ |
|
595
|
|
|
if (1 !== $this->version) { |
|
596
|
|
|
if (isset($attributes['changed'])) { |
|
597
|
|
|
if (!($attributes['changed'] instanceof UTCDateTime)) { |
|
|
|
|
|
|
598
|
|
|
throw new Exception\InvalidArgument('attribute changed must be an instance of UTCDateTime'); |
|
599
|
|
|
} |
|
600
|
|
|
|
|
601
|
|
|
$this->changed = $attributes['changed']; |
|
602
|
|
|
} else { |
|
603
|
|
|
$this->changed = new UTCDateTime(); |
|
604
|
|
|
} |
|
605
|
|
|
|
|
606
|
|
|
$this->_logger->debug('added new history version ['.$this->version.'] for file ['.$this->_id.']', [ |
|
607
|
|
|
'category' => get_class($this), |
|
608
|
|
|
]); |
|
609
|
|
|
|
|
610
|
|
|
$this->history[] = [ |
|
611
|
|
|
'version' => $this->version, |
|
612
|
|
|
'changed' => $this->changed, |
|
613
|
|
|
'user' => $this->_user->getId(), |
|
614
|
|
|
'type' => self::HISTORY_EDIT, |
|
615
|
|
|
'storage' => $this->storage, |
|
616
|
|
|
'storage_adapter' => $this->storage_adapter, |
|
617
|
|
|
'size' => $this->size, |
|
618
|
|
|
'mime' => $this->mime, |
|
619
|
|
|
'hash' => $this->hash, |
|
620
|
|
|
]; |
|
621
|
|
|
|
|
622
|
|
|
return $this; |
|
623
|
|
|
} |
|
624
|
|
|
|
|
625
|
|
|
$this->_logger->debug('added first file version [1] for file ['.$this->_id.']', [ |
|
626
|
|
|
'category' => get_class($this), |
|
627
|
|
|
]); |
|
628
|
|
|
|
|
629
|
|
|
$this->history[0] = [ |
|
630
|
|
|
'version' => 1, |
|
631
|
|
|
'changed' => isset($attributes['changed']) ? $attributes['changed'] : new UTCDateTime(), |
|
632
|
|
|
'user' => $this->owner, |
|
633
|
|
|
'type' => self::HISTORY_CREATE, |
|
634
|
|
|
'storage' => $this->storage, |
|
635
|
|
|
'storage_adapter' => $this->storage_adapter, |
|
636
|
|
|
'size' => $this->size, |
|
637
|
|
|
'mime' => $this->mime, |
|
638
|
|
|
'hash' => $this->hash, |
|
639
|
|
|
]; |
|
640
|
|
|
|
|
641
|
|
|
return $this; |
|
642
|
|
|
} |
|
643
|
|
|
|
|
644
|
|
|
/** |
|
645
|
|
|
* Finalize put request. |
|
646
|
|
|
*/ |
|
647
|
|
|
protected function postPutFile(): self |
|
648
|
|
|
{ |
|
649
|
|
|
try { |
|
650
|
|
|
$this->save([ |
|
651
|
|
|
'size', |
|
652
|
|
|
'changed', |
|
653
|
|
|
'mime', |
|
654
|
|
|
'hash', |
|
655
|
|
|
'version', |
|
656
|
|
|
'history', |
|
657
|
|
|
'storage', |
|
658
|
|
|
'storage_adapter', |
|
659
|
|
|
]); |
|
660
|
|
|
|
|
661
|
|
|
$this->_logger->debug('modifed file metadata ['.$this->_id.']', [ |
|
662
|
|
|
'category' => get_class($this), |
|
663
|
|
|
]); |
|
664
|
|
|
|
|
665
|
|
|
$this->_hook->run('postPutFile', [$this]); |
|
666
|
|
|
|
|
667
|
|
|
return $this; |
|
668
|
|
|
} catch (\Exception $e) { |
|
669
|
|
|
$this->_logger->error('failed modify file metadata ['.$this->_id.']', [ |
|
670
|
|
|
'category' => get_class($this), |
|
671
|
|
|
'exception' => $e, |
|
672
|
|
|
]); |
|
673
|
|
|
|
|
674
|
|
|
throw $e; |
|
675
|
|
|
} |
|
676
|
|
|
} |
|
677
|
|
|
} |
|
678
|
|
|
|
Scrutinizer analyzes your
composer.json/composer.lockfile if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.