Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/ui/lib/FileUploader/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/ui/lib/FileUploader/TempFile.php |
<?php namespace Bitrix\UI\FileUploader; use Bitrix\Main\Loader; use Bitrix\Main\Security; use Bitrix\Main\IO; use Bitrix\Main\Result; final class TempFile extends EO_TempFile { private ?\CCloudStorageBucket $bucket = null; public static function create(Chunk $chunk, UploaderController $controller): Result { $result = new Result(); $file = $chunk->getFile(); if (!$file->isExists()) { return $result->addError(new UploaderError(UploaderError::CHUNK_NOT_FOUND)); } if (mb_strpos($file->getPhysicalPath(), \CTempFile::getAbsoluteRoot()) !== 0) { // A chunk file could be saved in any folder. // Copy it to the temporary directory. We need to normalize the absolute path. $tempFilePath = self::generateLocalTempFile(); if (!copy($chunk->getFile()->getPhysicalPath(), $tempFilePath)) { return $result->addError(new UploaderError(UploaderError::CHUNK_COPY_FAILED)); } $newFile = new IO\File($tempFilePath); if (!$newFile->isExists()) { return $result->addError(new UploaderError(UploaderError::CHUNK_COPY_FAILED)); } $chunk->setFile($newFile); } $tempFile = null; if ($chunk->isOnlyOne()) { // Cloud and local files are processed by CFile::SaveFile. $tempFile = self::createTempFile($chunk, $controller); } else { // Multipart upload $bucket = self::findBucketForFile($chunk, $controller); if ($bucket) { // cloud file $tempFile = self::createTempFile($chunk, $controller, $bucket); $appendResult = $tempFile->appendToCloud($chunk); if (!$appendResult->isSuccess()) { $chunk->getFile()->delete(); $tempFile->delete(); return $result->addErrors($appendResult->getErrors()); } } else { // local file $localTempDir = self::generateLocalTempDir(); if (!$file->rename($localTempDir)) { return $result->addError(new UploaderError(UploaderError::FILE_MOVE_FAILED)); } $tempFile = self::createTempFile($chunk, $controller); } } $result->setData(['tempFile' => $tempFile]); return $result; } protected static function createTempFile(Chunk $chunk, UploaderController $controller, $bucket = null): TempFile { $tempFile = new TempFile(); $tempFile->setFilename($chunk->getName()); $tempFile->setMimetype($chunk->getType()); $tempFile->setSize($chunk->getFileSize()); $tempFile->setReceivedSize($chunk->getSize()); $tempFile->setWidth($chunk->getWidth()); $tempFile->setHeight($chunk->getHeight()); $tempFile->setModuleId($controller->getModuleId()); $tempFile->setController($controller->getName()); if ($bucket) { $path = self::generateCloudTempDir($bucket); } else { $path = $chunk->getFile()->getPhysicalPath(); $tempRoot = \CTempFile::getAbsoluteRoot(); $path = mb_substr($path, mb_strlen($tempRoot)); } $tempFile->setPath($path); if ($bucket) { $tempFile->setCloud(true); $tempFile->setBucketId($bucket->ID); } $tempFile->save(); return $tempFile; } public function append(Chunk $chunk): Result { $result = new Result(); if ($chunk->getEndRange() < $this->getReceivedSize()) { // We already have this part of the file return $result; } if ($this->getReceivedSize() !== $chunk->getStartRange()) { return $result->addError(new UploaderError(UploaderError::INVALID_CHUNK_OFFSET)); } if ($this->getReceivedSize() + $chunk->getSize() > $this->getSize()) { return $result->addError(new UploaderError(UploaderError::CHUNK_TOO_BIG)); } $result = $this->isCloud() ? $this->appendToCloud($chunk) : $this->appendToFile($chunk); if ($result->isSuccess()) { $this->increaseReceivedSize($chunk->getSize()); } // Remove a temporary chunk file immediately $chunk->getFile()->delete(); return $result; } public function commit(CommitOptions $commitOptions): Result { $fileAbsolutePath = $this->getAbsolutePath(); $fileId = \CFile::saveFile( [ 'name' => $this->getFilename(), 'tmp_name' => $fileAbsolutePath, 'type' => $this->getMimetype(), 'MODULE_ID' => $commitOptions->getModuleId(), 'width' => $this->getWidth(), 'height' => $this->getHeight(), 'size' => $this->getSize(), ], $commitOptions->getSavePath(), $commitOptions->isForceRandom(), $commitOptions->isSkipExtension(), $commitOptions->getAddDirectory() ); $result = new Result(); if (!$fileId) { $this->delete(); return $result->addError(new UploaderError(UploaderError::SAVE_FILE_FAILED)); } $this->setFileId($fileId); $this->setUploaded(true); $this->save(); $this->fillFile(); $this->removeActualTempFile(); return $result; } public function isCloud(): bool { return $this->getCloud() && $this->getBucketId() > 0; } public function makePersistent(): void { $this->customData->set('deleteBFile', false); $this->delete(); } public function deleteContent($deleteBFile = true): void { $this->removeActualTempFile(); if ($deleteBFile) { \CFile::delete($this->getFileId()); } } private function removeActualTempFile(): bool { if ($this->getDeleted()) { return true; } $success = false; if ($this->isCloud()) { $bucket = $this->getBucket(); if ($bucket) { $success = $bucket->deleteFile($this->getPath()); } } else { $success = IO\File::deleteFile($this->getAbsolutePath()); } if ($success) { $this->setDeleted(true); $this->save(); } return $success; } private function getAbsoluteCloudPath(): ?string { $bucket = $this->getBucket(); if (!$bucket) { return null; } return $bucket->getFileSRC($this->getPath()); } private function getAbsoluteLocalPath(): string { return \CTempFile::getAbsoluteRoot() . $this->getPath(); } private function getAbsolutePath(): ?string { if ($this->isCloud()) { return $this->getAbsoluteCloudPath(); } return $this->getAbsoluteLocalPath(); } private function appendToFile(Chunk $chunk): Result { $result = new Result(); $file = new IO\File($this->getAbsoluteLocalPath()); if ($chunk->isFirst() || !$file->isExists()) { return $result->addError(new UploaderError(UploaderError::FILE_APPEND_NOT_FOUND)); } if ($chunk->getEndRange() < $file->getSize()) { // We already have this part of the file return $result; } if (!$chunk->getFile()->isExists()) { return $result->addError(new UploaderError(UploaderError::CHUNK_APPEND_NOT_FOUND)); } if ($file->putContents($chunk->getFile()->getContents(), IO\File::APPEND) === false) { return $result->addError(new UploaderError(UploaderError::CHUNK_APPEND_FAILED)); } return $result; } private function appendToCloud(Chunk $chunk): Result { $result = new Result(); $bucket = $this->getBucket(); if (!$bucket) { return $result->addError(new UploaderError(UploaderError::CLOUD_EMPTY_BUCKET)); } $minUploadSize = $bucket->getService()->getMinUploadPartSize(); if ($chunk->getSize() < $minUploadSize && !$chunk->isLast()) { $postMaxSize = \CUtil::unformat(ini_get('post_max_size')); $uploadMaxFileSize = \CUtil::unformat(ini_get('upload_max_filesize')); return $result->addError( new UploaderError( UploaderError::CLOUD_INVALID_CHUNK_SIZE, [ 'chunkSize' => $chunk->getSize(), 'minUploadSize' => $minUploadSize, 'postMaxSize' => $postMaxSize, 'uploadMaxFileSize' => $uploadMaxFileSize, ] ) ); } $cloudUpload = new \CCloudStorageUpload($this->getPath()); if (!$cloudUpload->isStarted() && !$cloudUpload->start($bucket->ID, $chunk->getFileSize(), $chunk->getType())) { return $result->addError(new UploaderError(UploaderError::CLOUD_START_UPLOAD_FAILED)); } if ($cloudUpload->getPos() === doubleval($chunk->getEndRange() + 1)) { // We already have this part of the file. if ($chunk->isLast() && !$cloudUpload->finish()) { return $result->addError(new UploaderError(UploaderError::CLOUD_FINISH_UPLOAD_FAILED)); } return $result; } $fileContent = $chunk->getFile()->isExists() ? $chunk->getFile()->getContents() : false; if ($fileContent === false) { return $result->addError(new UploaderError(UploaderError::CLOUD_GET_CONTENTS_FAILED)); } $fails = 0; $success = false; while ($cloudUpload->hasRetries()) { if ($cloudUpload->next($fileContent)) { $success = true; break; } $fails++; } if (!$success) { // TODO: CCloudStorageUpload::CleanUp return $result->addError(new UploaderError(UploaderError::CLOUD_UPLOAD_FAILED, ['fails' => $fails])); } if ($chunk->isLast() && !$cloudUpload->finish()) { // TODO: CCloudStorageUpload::CleanUp return $result->addError(new UploaderError(UploaderError::CLOUD_FINISH_UPLOAD_FAILED)); } return $result; } private function increaseReceivedSize(int $bytes): void { $receivedSize = $this->getReceivedSize(); $this->setReceivedSize($receivedSize + $bytes); $this->save(); } private static function findBucketForFile(Chunk $chunk, UploaderController $controller): ?\CCloudStorageBucket { if (!Loader::includeModule('clouds')) { return null; } $bucket = \CCloudStorage::findBucketForFile( [ 'FILE_SIZE' => $chunk->getFileSize(), 'MODULE_ID' => $controller->getCommitOptions()->getModuleId(), ], $chunk->getName() ); if (!$bucket || !$bucket->init()) { return null; } return $bucket; } public static function generateLocalTempDir(int $hoursToKeepFile = 12): string { $directory = \CTempFile::getDirectoryName( $hoursToKeepFile, [ 'file-uploader', Security\Random::getString(32), ] ); if (!IO\Directory::isDirectoryExists($directory)) { IO\Directory::createDirectory($directory); } $tempName = md5(mt_rand() . mt_rand()); return $directory . $tempName; } public static function generateLocalTempFile(): string { $tmpFilePath = \CTempFile::getFileName('file-uploader' . uniqid(md5(mt_rand() . mt_rand()), true)); $directory = IO\Path::getDirectory($tmpFilePath); if (!IO\Directory::isDirectoryExists($directory)) { IO\Directory::createDirectory($directory); } return $tmpFilePath; } public static function generateCloudTempDir(\CCloudStorageBucket $bucket, int $hoursToKeepFile = 12): string { $directory = \CCloudTempFile::getDirectoryName( $bucket, $hoursToKeepFile, [ 'file-uploader', Security\Random::getString(32), ] ); $tempName = md5(mt_rand() . mt_rand()); return $directory . $tempName; } private function getBucket(): ?\CCloudStorageBucket { if ($this->bucket !== null) { return $this->bucket; } if (!$this->getBucketId() || !Loader::includeModule('clouds')) { return null; } $bucket = new \CCloudStorageBucket($this->getBucketId()); if ($bucket->init()) { $this->bucket = $bucket; } return $this->bucket; } }