portal.mkgtu.ru/common/modules/abiturient/models/File.php

347 lines
10 KiB
PHP
Raw Permalink Normal View History

2024-03-28 09:51:45 +03:00
<?php
namespace common\modules\abiturient\models;
use backend\models\Consent;
use backend\models\DocumentTemplate;
use common\components\filesystem\FilterFilename;
use common\models\Attachment;
use common\models\AttachmentArchive;
use common\models\errors\RecordNotValid;
use common\models\SendingFile;
use common\models\traits\HtmlPropsEncoder;
use common\modules\abiturient\models\bachelor\AdmissionAgreement;
use common\modules\abiturient\models\bachelor\AgreementDecline;
use common\modules\abiturient\models\interfaces\IReceivedFile;
use geoffry304\enveditor\exceptions\UnableWriteToFileException;
use Yii;
use yii\base\UserException;
use yii\behaviors\TimestampBehavior;
use yii\helpers\FileHelper;
use yii\web\UploadedFile;
class File extends \yii\db\ActiveRecord
{
use HtmlPropsEncoder;
public const BASE_PATH = '@storage/web/scans/';
public static function tableName()
{
return '{{%files}}';
}
public function behaviors()
{
return [TimestampBehavior::class];
}
public function rules()
{
return [
[[
'content_hash',
'upload_name',
'real_file_name',
'base_path',
'uid',
], 'string'],
[[
'upload_name',
'real_file_name',
'base_path',
], 'required'],
];
}
public function getFilePath()
{
return FileHelper::normalizePath(Yii::getAlias($this->base_path) . DIRECTORY_SEPARATOR . $this->real_file_name);
}
public function getFileContent(): ?string
{
return file_get_contents($this->getFilePath()) ?: null;
}
public function getSize()
{
$file = $this->getFilePath();
if (file_exists($file) && !is_dir($file)) {
return filesize($file);
}
return null;
}
public function __set($name, $value)
{
if ($name == 'upload_name') {
$value = FilterFilename::sanitize($value);
}
parent::__set($name, $value);
}
public function getExtension(): ?string
{
$file = $this->getFilePath();
return pathinfo($file)['extension'];
}
public function calcFileHash()
{
$this->content_hash = FilesManager::CalculateFileHash($this);
}
public function beforeValidate()
{
if (!$this->content_hash) {
$this->calcFileHash();
}
return parent::beforeValidate();
}
public function getLinkedAttachments()
{
return $this->hasMany(Attachment::class, ['id' => Attachment::getFileRelationColumn()])
->viaTable(Attachment::getFileRelationTable(), ['file_id' => 'id']);
}
public function getLinkedConsents()
{
return $this->hasMany(Consent::class, ['id' => Consent::getFileRelationColumn()])
->viaTable(Consent::getFileRelationTable(), ['file_id' => 'id']);
}
public function getLinkedDocumentTemplates()
{
return $this->hasMany(DocumentTemplate::class, ['id' => DocumentTemplate::getFileRelationColumn()])
->viaTable(DocumentTemplate::getFileRelationTable(), ['file_id' => 'id']);
}
public function getLinkedArchivedAttachments()
{
return $this->hasMany(AttachmentArchive::class, ['id' => AttachmentArchive::getFileRelationColumn()])
->viaTable(AttachmentArchive::getFileRelationTable(), ['file_id' => 'id']);
}
public function getLinkedAdmissionAgreements()
{
return $this->hasMany(AdmissionAgreement::class, ['id' => AdmissionAgreement::getFileRelationColumn()])
->viaTable(AdmissionAgreement::getFileRelationTable(), ['file_id' => 'id']);
}
public function getLinkedAgreementDeclines()
{
return $this->hasMany(AgreementDecline::class, ['id' => AgreementDecline::getFileRelationColumn()])
->viaTable(AgreementDecline::getFileRelationTable(), ['file_id' => 'id']);
}
public function hasLinks(): bool
{
return $this->getLinkedAttachments()->exists()
|| $this->getLinkedAdmissionAgreements()->exists()
|| $this->getLinkedAgreementDeclines()->exists()
|| $this->getLinkedConsents()->exists()
|| $this->getLinkedDocumentTemplates()->exists()
|| $this->getLinkedArchivedAttachments()->exists();
}
public function destroyIfNotUsed(): void
{
if (!$this->hasLinks()) {
$this->delete();
}
}
protected function isStoredFileUsedByOthers(): bool
{
return File::find()
->where(['base_path' => $this->base_path])
->andWhere(['real_file_name' => $this->real_file_name])
->andWhere(['not', ['id' => $this->id]])
->exists();
}
public function fileExists(): bool
{
return file_exists($this->getFilePath());
}
public function afterDelete()
{
parent::afterDelete();
if (!$this->isStoredFileUsedByOthers()) {
$abs_path = $this->getFilePath();
if (file_exists($abs_path) && !is_dir($abs_path)) {
FileHelper::unlink($abs_path);
}
}
}
protected static function makeSalt()
{
return substr(md5(microtime()), rand(0, 26), 5);
}
public static function GetOrCreateByTempFile(string $base_path, $file): File
{
[$upload_name, $file_extension, $file_hash, $file_uid, $save_callback] = File::getTempFileInfos($file);
$stored_file = FilesManager::FindFile(
$upload_name,
$file_hash,
$file_uid,
$base_path,
);
if (!$stored_file) {
$stored_file = new File();
}
if ($stored_file->getIsNewRecord() || !$stored_file->fileExists()) {
$salt = File::makeSalt();
$filename = md5($upload_name . $salt) . time() . '.' . $file_extension;
$full_path = FileHelper::normalizePath(Yii::getAlias($base_path) . '/' . $filename);
$new_dir = pathinfo($full_path)['dirname'];
FilesManager::EnsureDirectoryExists($new_dir);
if (!$save_callback($full_path)) {
throw new UserException('Не удалось сохранить файл');
}
$stored_file->base_path = $base_path;
$stored_file->upload_name = $upload_name;
$stored_file->uid = $file_uid;
$stored_file->real_file_name = $filename;
if (!$stored_file->save()) {
throw new RecordNotValid($stored_file);
}
}
return $stored_file;
}
public function getPartsCount(): int
{
return intval(ceil($this->size / SendingFile::getChunkSize()));
}
protected static function getTempFileInfos($file): array
{
if ($file instanceof UploadedFile) {
$upload_name = $file->name;
$extension = $file->extension;
$hash = FilesManager::GetFileHash($file->tempName);
$save_callback = function (string $path) use ($file): bool {
return $file->saveAs($path);
};
return [$upload_name, $extension, $hash, null, $save_callback];
} elseif ($file instanceof IReceivedFile) {
$upload_name = $file->uploadName;
$extension = $file->extension;
$hash = $file->hash;
$uid = $file->fileUID;
$save_callback = function (string $path) use ($file): bool {
return file_put_contents($path, $file->getFileContent()) !== false;
};
return [$upload_name, $extension, $hash, $uid, $save_callback];
} elseif (is_array($file)) {
return $file;
}
throw new UserException('Неизвестный тип объекта');
}
public function sameAs(?File $other): bool
{
if (!$other) {
return false;
}
if ($this->id == $other->id) {
return true;
}
return $this->upload_name == $other->upload_name
&& $this->content_hash == $other->content_hash
&& $this->uid == $other->uid
&& $this->base_path == $other->base_path;
}
public function getCopy(string $new_base_path, ?string $new_uid): File
{
if ($this->base_path == $new_base_path && $new_uid == $this->uid) {
return $this;
}
$stored_file = FilesManager::FindFile(
$this->upload_name,
$this->content_hash,
$new_uid,
$new_base_path
);
$file_found = boolval($stored_file);
$file_exists_in_found = $file_found && $stored_file->fileExists();
if (!($file_found && $file_exists_in_found)) {
$transaction = Yii::$app->db->beginTransaction();
try {
if (!$file_found) {
$stored_file = new File();
$stored_file->base_path = $new_base_path;
$stored_file->upload_name = $this->upload_name;
$stored_file->uid = $new_uid;
$stored_file->real_file_name = $this->real_file_name;
$stored_file->content_hash = $this->content_hash;
$stored_file->save(false);
$file_exists_in_found = $stored_file->fileExists();
}
if (!$file_exists_in_found) {
$new_dir = pathinfo($stored_file->getFilePath())['dirname'];
FilesManager::EnsureDirectoryExists($new_dir);
$result = copy(
$this->getFilePath(),
$stored_file->getFilePath()
);
if (!$result) {
Yii::error("Не удалось скопировать файл {$this->getFilePath()} в {$stored_file->getFilePath()}");
throw new UnableWriteToFileException('Не удалось скопировать файл');
}
}
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
}
return $stored_file;
}
}