Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
TempFile.php
1<?php
2
4
9
10final class TempFile extends EO_TempFile
11{
12 private ?\CCloudStorageBucket $bucket = null;
13
14 public static function create(Chunk $chunk, UploaderController $controller): Result
15 {
16 $result = new Result();
17 $file = $chunk->getFile();
18 if (!$file->isExists())
19 {
20 return $result->addError(new UploaderError(UploaderError::CHUNK_NOT_FOUND));
21 }
22
23 if (mb_strpos($file->getPhysicalPath(), \CTempFile::getAbsoluteRoot()) !== 0)
24 {
25 // A chunk file could be saved in any folder.
26 // Copy it to the temporary directory. We need to normalize the absolute path.
27 $tempFilePath = self::generateLocalTempFile();
28 if (!copy($chunk->getFile()->getPhysicalPath(), $tempFilePath))
29 {
30 return $result->addError(new UploaderError(UploaderError::CHUNK_COPY_FAILED));
31 }
32
33 $newFile = new IO\File($tempFilePath);
34 if (!$newFile->isExists())
35 {
36 return $result->addError(new UploaderError(UploaderError::CHUNK_COPY_FAILED));
37 }
38
39 $chunk->setFile($newFile);
40 }
41
42 $tempFile = null;
43 if ($chunk->isOnlyOne())
44 {
45 // Cloud and local files are processed by CFile::SaveFile.
46 $tempFile = self::createTempFile($chunk, $controller);
47 }
48 else
49 {
50 // Multipart upload
51 $bucket = self::findBucketForFile($chunk, $controller);
52 if ($bucket)
53 {
54 // cloud file
55 $tempFile = self::createTempFile($chunk, $controller, $bucket);
56 $appendResult = $tempFile->appendToCloud($chunk);
57 if (!$appendResult->isSuccess())
58 {
59 $chunk->getFile()->delete();
60 $tempFile->delete();
61
62 return $result->addErrors($appendResult->getErrors());
63 }
64 }
65 else
66 {
67 // local file
68 $localTempDir = self::generateLocalTempDir();
69 if (!$file->rename($localTempDir))
70 {
71 return $result->addError(new UploaderError(UploaderError::FILE_MOVE_FAILED));
72 }
73
74 $tempFile = self::createTempFile($chunk, $controller);
75 }
76 }
77
78 $result->setData(['tempFile' => $tempFile]);
79
80 return $result;
81 }
82
83 protected static function createTempFile(Chunk $chunk, UploaderController $controller, $bucket = null): TempFile
84 {
85 $tempFile = new TempFile();
86 $tempFile->setFilename($chunk->getName());
87 $tempFile->setMimetype($chunk->getType());
88 $tempFile->setSize($chunk->getFileSize());
89 $tempFile->setReceivedSize($chunk->getSize());
90 $tempFile->setWidth($chunk->getWidth());
91 $tempFile->setHeight($chunk->getHeight());
92 $tempFile->setModuleId($controller->getModuleId());
93 $tempFile->setController($controller->getName());
94
95 if ($bucket)
96 {
97 $path = self::generateCloudTempDir($bucket);
98 }
99 else
100 {
101 $path = $chunk->getFile()->getPhysicalPath();
102 $tempRoot = \CTempFile::getAbsoluteRoot();
103 $path = mb_substr($path, mb_strlen($tempRoot));
104 }
105
106 $tempFile->setPath($path);
107
108 if ($bucket)
109 {
110 $tempFile->setCloud(true);
111 $tempFile->setBucketId($bucket->ID);
112 }
113
114 $tempFile->save();
115
116 return $tempFile;
117 }
118
119 public function append(Chunk $chunk): Result
120 {
121 $result = new Result();
122
123 if ($chunk->getEndRange() < $this->getReceivedSize())
124 {
125 // We already have this part of the file
126 return $result;
127 }
128
129 if ($this->getReceivedSize() !== $chunk->getStartRange())
130 {
131 return $result->addError(new UploaderError(UploaderError::INVALID_CHUNK_OFFSET));
132 }
133
134 if ($this->getReceivedSize() + $chunk->getSize() > $this->getSize())
135 {
136 return $result->addError(new UploaderError(UploaderError::CHUNK_TOO_BIG));
137 }
138
139 $result = $this->isCloud() ? $this->appendToCloud($chunk) : $this->appendToFile($chunk);
140 if ($result->isSuccess())
141 {
142 $this->increaseReceivedSize($chunk->getSize());
143 }
144
145 // Remove a temporary chunk file immediately
146 $chunk->getFile()->delete();
147
148 return $result;
149 }
150
151 public function commit(CommitOptions $commitOptions): Result
152 {
153 $fileAbsolutePath = $this->getAbsolutePath();
154
155 $fileId = \CFile::saveFile(
156 [
157 'name' => $this->getFilename(),
158 'tmp_name' => $fileAbsolutePath,
159 'type' => $this->getMimetype(),
160 'MODULE_ID' => $commitOptions->getModuleId(),
161 'width' => $this->getWidth(),
162 'height' => $this->getHeight(),
163 'size' => $this->getSize(),
164 ],
165 $commitOptions->getSavePath(),
166 $commitOptions->isForceRandom(),
167 $commitOptions->isSkipExtension(),
168 $commitOptions->getAddDirectory()
169 );
170
171 $result = new Result();
172 if (!$fileId)
173 {
174 $this->delete();
175
176 return $result->addError(new UploaderError(UploaderError::SAVE_FILE_FAILED));
177 }
178
179 $this->setFileId($fileId);
180 $this->setUploaded(true);
181 $this->save();
182 $this->fillFile();
183
184 $this->removeActualTempFile();
185
186 return $result;
187 }
188
189 public function isCloud(): bool
190 {
191 return $this->getCloud() && $this->getBucketId() > 0;
192 }
193
194 public function makePersistent(): void
195 {
196 $this->customData->set('deleteBFile', false);
197 $this->delete();
198 }
199
200 public function deleteContent($deleteBFile = true): void
201 {
202 $this->removeActualTempFile();
203 if ($deleteBFile)
204 {
205 \CFile::delete($this->getFileId());
206 }
207 }
208
209 private function removeActualTempFile(): bool
210 {
211 if ($this->getDeleted())
212 {
213 return true;
214 }
215
216 $success = false;
217 if ($this->isCloud())
218 {
219 $bucket = $this->getBucket();
220 if ($bucket)
221 {
222 $success = $bucket->deleteFile($this->getPath());
223 }
224 }
225 else
226 {
227 $success = IO\File::deleteFile($this->getAbsolutePath());
228 }
229
230 if ($success)
231 {
232 $this->setDeleted(true);
233 $this->save();
234 }
235
236 return $success;
237 }
238
239 private function getAbsoluteCloudPath(): ?string
240 {
241 $bucket = $this->getBucket();
242 if (!$bucket)
243 {
244 return null;
245 }
246
247 return $bucket->getFileSRC($this->getPath());
248 }
249
250 private function getAbsoluteLocalPath(): string
251 {
252 return \CTempFile::getAbsoluteRoot() . $this->getPath();
253 }
254
255 private function getAbsolutePath(): ?string
256 {
257 if ($this->isCloud())
258 {
259 return $this->getAbsoluteCloudPath();
260 }
261
262 return $this->getAbsoluteLocalPath();
263 }
264
265 private function appendToFile(Chunk $chunk): Result
266 {
267 $result = new Result();
268 $file = new IO\File($this->getAbsoluteLocalPath());
269
270 if ($chunk->isFirst() || !$file->isExists())
271 {
272 return $result->addError(new UploaderError(UploaderError::FILE_APPEND_NOT_FOUND));
273 }
274
275 if ($chunk->getEndRange() < $file->getSize())
276 {
277 // We already have this part of the file
278 return $result;
279 }
280
281 if (!$chunk->getFile()->isExists())
282 {
283 return $result->addError(new UploaderError(UploaderError::CHUNK_APPEND_NOT_FOUND));
284 }
285
286 if ($file->putContents($chunk->getFile()->getContents(), IO\File::APPEND) === false)
287 {
288 return $result->addError(new UploaderError(UploaderError::CHUNK_APPEND_FAILED));
289 }
290
291 return $result;
292 }
293
294 private function appendToCloud(Chunk $chunk): Result
295 {
296 $result = new Result();
297 $bucket = $this->getBucket();
298 if (!$bucket)
299 {
300 return $result->addError(new UploaderError(UploaderError::CLOUD_EMPTY_BUCKET));
301 }
302
303 $minUploadSize = $bucket->getService()->getMinUploadPartSize();
304 if ($chunk->getSize() < $minUploadSize && !$chunk->isLast())
305 {
306 $postMaxSize = \CUtil::unformat(ini_get('post_max_size'));
307 $uploadMaxFileSize = \CUtil::unformat(ini_get('upload_max_filesize'));
308
309 return $result->addError(
310 new UploaderError(
312 [
313 'chunkSize' => $chunk->getSize(),
314 'minUploadSize' => $minUploadSize,
315 'postMaxSize' => $postMaxSize,
316 'uploadMaxFileSize' => $uploadMaxFileSize,
317 ]
318 )
319 );
320 }
321
322 $cloudUpload = new \CCloudStorageUpload($this->getPath());
323 if (!$cloudUpload->isStarted() && !$cloudUpload->start($bucket->ID, $chunk->getFileSize(), $chunk->getType()))
324 {
325 return $result->addError(new UploaderError(UploaderError::CLOUD_START_UPLOAD_FAILED));
326 }
327
328 if ($cloudUpload->getPos() === doubleval($chunk->getEndRange() + 1))
329 {
330 // We already have this part of the file.
331 if ($chunk->isLast() && !$cloudUpload->finish())
332 {
333 return $result->addError(new UploaderError(UploaderError::CLOUD_FINISH_UPLOAD_FAILED));
334 }
335
336 return $result;
337 }
338
339 $fileContent = $chunk->getFile()->isExists() ? $chunk->getFile()->getContents() : false;
340 if ($fileContent === false)
341 {
342 return $result->addError(new UploaderError(UploaderError::CLOUD_GET_CONTENTS_FAILED));
343 }
344
345 $fails = 0;
346 $success = false;
347 while ($cloudUpload->hasRetries())
348 {
349 if ($cloudUpload->next($fileContent))
350 {
351 $success = true;
352 break;
353 }
354
355 $fails++;
356 }
357
358 if (!$success)
359 {
360 // TODO: CCloudStorageUpload::CleanUp
361 return $result->addError(new UploaderError(UploaderError::CLOUD_UPLOAD_FAILED, ['fails' => $fails]));
362 }
363
364 if ($chunk->isLast() && !$cloudUpload->finish())
365 {
366 // TODO: CCloudStorageUpload::CleanUp
367 return $result->addError(new UploaderError(UploaderError::CLOUD_FINISH_UPLOAD_FAILED));
368 }
369
370 return $result;
371 }
372
373 private function increaseReceivedSize(int $bytes): void
374 {
375 $receivedSize = $this->getReceivedSize();
376 $this->setReceivedSize($receivedSize + $bytes);
377 $this->save();
378 }
379
380 private static function findBucketForFile(Chunk $chunk, UploaderController $controller): ?\CCloudStorageBucket
381 {
382 if (!Loader::includeModule('clouds'))
383 {
384 return null;
385 }
386
387 $bucket = \CCloudStorage::findBucketForFile(
388 [
389 'FILE_SIZE' => $chunk->getFileSize(),
390 'MODULE_ID' => $controller->getCommitOptions()->getModuleId(),
391 ],
392 $chunk->getName()
393 );
394
395 if (!$bucket || !$bucket->init())
396 {
397 return null;
398 }
399
400 return $bucket;
401 }
402
403 public static function generateLocalTempDir(int $hoursToKeepFile = 12): string
404 {
405 $directory = \CTempFile::getDirectoryName(
406 $hoursToKeepFile,
407 [
408 'file-uploader',
409 Security\Random::getString(32),
410 ]
411 );
412
413 if (!IO\Directory::isDirectoryExists($directory))
414 {
415 IO\Directory::createDirectory($directory);
416 }
417
418 $tempName = md5(mt_rand() . mt_rand());
419
420 return $directory . $tempName;
421 }
422
423 public static function generateLocalTempFile(): string
424 {
425 $tmpFilePath = \CTempFile::getFileName('file-uploader' . uniqid(md5(mt_rand() . mt_rand()), true));
426 $directory = IO\Path::getDirectory($tmpFilePath);
427 if (!IO\Directory::isDirectoryExists($directory))
428 {
429 IO\Directory::createDirectory($directory);
430 }
431
432 return $tmpFilePath;
433 }
434
435 public static function generateCloudTempDir(\CCloudStorageBucket $bucket, int $hoursToKeepFile = 12): string
436 {
437 $directory = \CCloudTempFile::getDirectoryName(
438 $bucket,
439 $hoursToKeepFile,
440 [
441 'file-uploader',
442 Security\Random::getString(32),
443 ]
444 );
445
446 $tempName = md5(mt_rand() . mt_rand());
447
448 return $directory . $tempName;
449 }
450
451 private function getBucket(): ?\CCloudStorageBucket
452 {
453 if ($this->bucket !== null)
454 {
455 return $this->bucket;
456 }
457
458 if (!$this->getBucketId() || !Loader::includeModule('clouds'))
459 {
460 return null;
461 }
462
463 $bucket = new \CCloudStorageBucket($this->getBucketId());
464 if ($bucket->init())
465 {
466 $this->bucket = $bucket;
467 }
468
469 return $this->bucket;
470 }
471}
static isDirectoryExists($path)
setFile(IO\File $file)
Definition Chunk.php:182
static createTempFile(Chunk $chunk, UploaderController $controller, $bucket=null)
Definition TempFile.php:83
static generateLocalTempDir(int $hoursToKeepFile=12)
Definition TempFile.php:403
static create(Chunk $chunk, UploaderController $controller)
Definition TempFile.php:14
deleteContent($deleteBFile=true)
Definition TempFile.php:200
static generateCloudTempDir(\CCloudStorageBucket $bucket, int $hoursToKeepFile=12)
Definition TempFile.php:435
commit(CommitOptions $commitOptions)
Definition TempFile.php:151