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

347 lines
10 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}