YetiForceCompany/YetiForceCRM

View on GitHub
modules/Documents/models/Record.php

Summary

Maintainability
D
2 days
Test Coverage
F
51%
<?php

 /* +***********************************************************************************
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
 * ("License"); You may not use this file except in compliance with the License
 * The Original Code is:  vtiger CRM Open Source
 * The Initial Developer of the Original Code is vtiger.
 * Portions created by vtiger are Copyright (C) vtiger.
 * All Rights Reserved.
 * Contributor(s): YetiForce S.A.
 * *********************************************************************************** */

/**
 * Class Documents_Record_Model.
 */
class Documents_Record_Model extends Vtiger_Record_Model
{
    /** @var string[] Types included in the preview of the file. */
    public $filePreview = [
        'application/pdf', 'image/png', 'image/jpeg', 'image/jpeg', 'image/jpeg', 'image/gif', 'image/bmp', 'image/vnd.microsoft.icon', 'image/tiff', 'image/tiff'
    ];

    /**
     * Get download file url.
     *
     * @return string
     */
    public function getDownloadFileURL()
    {
        if ('I' === $this->getValueByField('filelocationtype') && ($fileDetails = $this->getFileDetails())) {
            return 'file.php?module=' . $this->getModuleName() . '&action=DownloadFile&record=' . $this->getId() . '&fileid=' . $fileDetails['attachmentsid'];
        }
        return $this->get('filename');
    }

    /** {@inheritdoc} */
    public function getRecordListViewLinksLeftSide()
    {
        $links = [];
        if (!$this->isReadOnly() && \in_array($this->getValueByField('filetype'), $this->filePreview)) {
            $links['LBL_PREVIEW_FILE'] = Vtiger_Link_Model::getInstanceFromValues([
                'linklabel' => 'LBL_PREVIEW_FILE',
                'linkhref' => true,
                'linkurl' => $this->getDownloadFileURL() . '&show=1',
                'linkicon' => 'fas fa-binoculars',
                'linkclass' => 'btn-sm btn-light',
                'linktarget' => '_blank',
            ]);
        }
        return array_merge($links, parent::getRecordListViewLinksLeftSide());
    }

    /** {@inheritdoc} */
    public function getRecordRelatedListViewLinksLeftSide(Vtiger_RelationListView_Model $viewModel)
    {
        $links = [];
        if (!$this->isReadOnly() && \in_array($this->getValueByField('filetype'), $this->filePreview)) {
            $links['LBL_PREVIEW_FILE'] = Vtiger_Link_Model::getInstanceFromValues([
                'linklabel' => 'LBL_PREVIEW_FILE',
                'linkhref' => true,
                'linkurl' => $this->getDownloadFileURL() . '&show=1',
                'linkicon' => 'fas fa-binoculars',
                'linkclass' => 'btn-sm btn-light',
                'linktarget' => '_blank'
            ]);
        }
        return array_merge($links, parent::getRecordRelatedListViewLinksLeftSide($viewModel));
    }

    /**
     * Check file integrity url.
     *
     * @return string
     */
    public function checkFileIntegrityURL()
    {
        return "javascript:Documents_Detail_Js.checkFileIntegrity('index.php?module=" . $this->getModuleName() . '&action=CheckFileIntegrity&record=' . $this->getId() . "')";
    }

    /**
     * Check file integrity.
     *
     * @return bool
     */
    public function checkFileIntegrity()
    {
        $returnValue = false;
        if ('I' === $this->get('filelocationtype') && ($fileDetails = $this->getFileDetails())) {
            $fileName = html_entity_decode($fileDetails['name'], ENT_QUOTES, \App\Config::main('default_charset'));
            $savedFile = $fileDetails['path'] . $fileDetails['attachmentsid'];
            $returnValue = (file_exists($savedFile) && fopen($savedFile, 'r')) || (file_exists("{$savedFile}_{$fileName}") && fopen("{$savedFile}_{$fileName}", 'r'));
        }
        return $returnValue;
    }

    /**
     * Get file details.
     *
     * @return array
     */
    public function getFileDetails()
    {
        if (!isset($this->fileDetails)) {
            $this->fileDetails = (new \App\Db\Query())->from('vtiger_attachments')
                ->innerJoin('vtiger_seattachmentsrel', 'vtiger_seattachmentsrel.attachmentsid = vtiger_attachments.attachmentsid')
                ->where(['crmid' => $this->get('id')])
                ->one();
        }
        return $this->fileDetails;
    }

    /**
     * Download file.
     */
    public function downloadFile()
    {
        $fileContent = false;
        if ($path = $this->getFilePath()) {
            $fileDetails = $this->getFileDetails();
            $filePath = ROOT_DIRECTORY . DIRECTORY_SEPARATOR . $path;
            if ($this->get('return')) {
                return \App\Fields\File::loadFromInfo([
                    'path' => $filePath,
                    'name' => $fileDetails['name'],
                    'mimeType' => $fileDetails['type'],
                ]);
            }
            $fileSize = filesize($filePath);
            $fileSize = $fileSize + ($fileSize % 1024);
            if (fopen($filePath, 'r')) {
                $fileContent = fread(fopen($filePath, 'r'), $fileSize);
                $fileName = $this->get('filename');
                header('content-type: ' . $fileDetails['type']);
                header('pragma: public');
                header('cache-control: private');
                if ($this->get('show')) {
                    header('content-disposition: inline');
                } else {
                    header("content-disposition: attachment; filename=\"$fileName\"");
                }
            }
        }
        echo $fileContent;
    }

    /**
     * Get file path.
     *
     * @return string
     */
    public function getFilePath(): string
    {
        $path = '';
        if ($fileDetails = $this->getFileDetails()) {
            $fileName = $fileDetails['name'];
            if ('I' === $this->get('filelocationtype')) {
                $fileName = html_entity_decode($fileName, ENT_QUOTES, \App\Config::main('default_charset'));
                if (file_exists(ROOT_DIRECTORY . DIRECTORY_SEPARATOR . $fileDetails['path'] . $fileDetails['attachmentsid'])) {
                    $savedFile = $fileDetails['attachmentsid'];
                } else {
                    $savedFile = $fileDetails['attachmentsid'] . '_' . $fileName;
                }
                if (file_exists(ROOT_DIRECTORY . DIRECTORY_SEPARATOR . $fileDetails['path'] . $savedFile)) {
                    $path = $fileDetails['path'] . $savedFile;
                }
            }
        }
        return $path;
    }

    /**
     * Download files.
     *
     * @param int[] $recordsIds
     */
    public static function downloadFiles($recordsIds)
    {
        $zip = new ZipArchive();
        $postfix = time() . '_' . random_int(0, 1000);
        $zipPath = ROOT_DIRECTORY . '/cache/';
        $fileName = $zipPath . "documentsZipFile_{$postfix}.zip";
        if (true !== $zip->open($fileName, ZIPARCHIVE::CREATE)) {
            \App\Log::error("cannot open <$fileName>\n");
            throw new \App\Exceptions\NoPermitted("cannot open <$fileName>");
        }

        foreach ($recordsIds as $recordId) {
            $documentModel = self::getInstanceById($recordId);
            if ($fileDetails = $documentModel->getFileDetails()) {
                $filePath = $fileDetails['path'];
                if ('I' === $documentModel->get('filelocationtype')) {
                    if (file_exists($filePath . $fileDetails['attachmentsid'])) {
                        $savedFile = $fileDetails['attachmentsid'];
                    } else {
                        $savedFile = $fileDetails['attachmentsid'] . '_' . html_entity_decode($fileDetails['name'], ENT_QUOTES, \App\Config::main('default_charset'));
                    }
                    if (file_exists($filePath . $savedFile)) {
                        $zip->addFile($filePath . $savedFile, basename($documentModel->get('filename')));
                        $documentModel->updateDownloadCount();
                    }
                }
            }
        }
        $zip->close();
        header('content-type: ' . \App\Fields\File::getMimeContentType($fileName));
        header('content-disposition: attachment; filename="' . basename($fileName) . '";');
        header('accept-ranges: bytes');
        header('content-length: ' . filesize($fileName));

        readfile($fileName);
        unlink($fileName);
    }

    /**
     * Update file status.
     *
     * @param int $status
     */
    public function updateFileStatus($status)
    {
        App\Db::getInstance()->createCommand()->update('vtiger_notes', ['filestatus' => $status], ['notesid' => $this->get('id')])->execute();
    }

    /**
     * Update download count.
     */
    public function updateDownloadCount()
    {
        $notesId = $this->get('id');
        $downloadCount = (new \App\Db\Query())->select(['filedownloadcount'])->from('vtiger_notes')->where(['notesid' => $notesId])->scalar();
        \App\Db::getInstance()->createCommand()->update('vtiger_notes', ['filedownloadcount' => ++$downloadCount], ['notesid' => $notesId])->execute();
    }

    /**
     * Get download count update url.
     *
     * @return string
     */
    public function getDownloadCountUpdateUrl()
    {
        return 'index.php?module=Documents&action=UpdateDownloadCount&record=' . $this->getId();
    }

    /**
     * Get reference module by doc id.
     *
     * @param int $record
     *
     * @return array
     */
    public static function getReferenceModuleByDocId($record)
    {
        return (new App\Db\Query())->select(['vtiger_crmentity.setype'])->from('vtiger_crmentity')->innerJoin('vtiger_senotesrel', 'vtiger_senotesrel.crmid = vtiger_crmentity.crmid')->where(['vtiger_crmentity.deleted' => 0, 'vtiger_senotesrel.notesid' => $record])->distinct()->column();
    }

    public static function getFileIconByFileType($fileType)
    {
        return \App\Layout\Icon::getIconByFileType($fileType);
    }

    /** {@inheritdoc} */
    public function isMandatorySave()
    {
        return parent::isMandatorySave() || $_FILES;
    }

    /**
     * Sets field value for save.
     *
     * @param string $fieldName
     * @param mixed  $value
     *
     * @return $this
     */
    public function setFieldValue(string $fieldName, $value)
    {
        $fieldModel = $this->getField($fieldName);
        if ($fieldModel) {
            $this->set($fieldModel->getName(), $value);
            $this->setDataForSave([$fieldModel->getTableName() => [$fieldModel->getColumnName() => $value]]);
        }
        return $this;
    }

    /**
     * Function to save record.
     */
    public function saveToDb()
    {
        $db = \App\Db::getInstance();
        $fileNameByField = 'filename';
        if ('I' === $this->get('filelocationtype')) {
            if (isset($this->file)) {
                $file = $this->file;
            } else {
                $file = $_FILES[$fileNameByField] ?? [];
            }
            if (!empty($file['name']) && isset($file['error']) && $file['size'] > 0) {
                if (UPLOAD_ERR_OK === $file['error'] && ($fileInstance = \App\Fields\File::loadFromRequest($file)) && $fileInstance->validateAndSecure()) {
                    $this->setFieldValue('filename', \App\Purifier::decodeHtml(App\Purifier::purify($file['name'])))
                        ->setFieldValue('filetype', $fileInstance->getMimeType())
                        ->setFieldValue('filesize', $fileInstance->getSize())
                        ->setFieldValue('filedownloadcount', 0);
                } else {
                    \App\Log::error("Error while saving a file, saving failed. | ID: {$this->getId()} | File: {$file['name']} | Error: " . \App\Fields\File::getErrorMessage($file['error']));
                    $file = [];
                }
            } else {
                $file = [];
            }
        } elseif ('E' === $this->get('filelocationtype')) {
            $fileName = $this->get($fileNameByField);
            // If filename does not has the protocol prefix, default it to http://
            // Protocol prefix could be like (https://, smb://, file://, \\, smb:\\,...)
            if (!empty($fileName) && !preg_match('/^\w{1,5}:\/\/|^\w{0,3}:?\\\\\\\\/', trim($fileName), $match)) {
                $fileName = "http://$fileName";
            }
            $this->setFieldValue('filename', $fileName)
                ->setFieldValue('filesize', 0)
                ->setFieldValue('filetype', '')
                ->setFieldValue('filedownloadcount', null);
        }
        parent::saveToDb();
        //Inserting into attachments table
        if ('I' === $this->get('filelocationtype')) {
            if ($file) {
                $file['original_name'] = \App\Request::_get('0_hidden');
                if ($this->uploadAndSaveFile($file) && isset($this->file)) {
                    $this->file = [];
                }
            }
        } else {
            $db->createCommand()->delete('vtiger_seattachmentsrel', ['crmid' => $this->getId()])->execute();
        }
    }

    /**
     * This function is used to upload the attachment in the server and save that attachment information in db.
     *
     * @param array $fileDetails - array which contains the file information(name, type, size, tmp_name and error)
     *
     * @return bool
     */
    public function uploadAndSaveFile($fileDetails)
    {
        $id = $this->getId();
        $moduleName = $this->getModuleName();
        $result = false;
        \App\Log::trace("Entering into uploadAndSaveFile($id,$moduleName) method.");
        $fileInstance = \App\Fields\File::loadFromRequest($fileDetails);
        $this->ext['attachmentsName'] = $fileName = empty($fileDetails['original_name']) ? $fileDetails['name'] : $fileDetails['original_name'];
        $db = \App\Db::getInstance();
        $uploadFilePath = \App\Fields\File::initStorageFileDirectory($moduleName);
        $db->createCommand()->insert('vtiger_attachments', [
            'name' => ltrim(App\Purifier::purify($fileName)),
            'type' => $fileDetails['type'],
            'path' => $uploadFilePath
        ])->execute();
        $currentId = $db->getLastInsertID('vtiger_attachments_attachmentsid_seq');
        if (\App\Config::module($this->getModuleName(), 'storagePath')) {
            $destinyFilePath = $uploadFilePath . $currentId;
        } else {
            $destinyFilePath = ROOT_DIRECTORY . DIRECTORY_SEPARATOR . $uploadFilePath . $currentId;
        }
        if ($fileInstance->moveFile($destinyFilePath)) {
            $db->createCommand()->delete('vtiger_seattachmentsrel', ['crmid' => $id])->execute();
            $db->createCommand()->insert('vtiger_seattachmentsrel', ['crmid' => $id, 'attachmentsid' => $currentId])->execute();
            $this->ext['attachmentsId'] = $currentId;
            $result = true;
        } else {
            $db->createCommand()->delete('vtiger_attachments', ['attachmentsid' => $currentId])->execute();
        }
        \App\Log::trace('Exiting uploadAndSaveFile');
        return $result;
    }

    /** {@inheritdoc} */
    public function delete()
    {
        parent::delete();
        $dbCommand = \App\Db::getInstance()->createCommand();
        $attachmentsIds = (new \App\Db\Query())->select(['attachmentsid'])->from('vtiger_seattachmentsrel')->where(['crmid' => $this->getId()])->column();
        if (!empty($attachmentsIds)) {
            $dataReader = (new \App\Db\Query())->select(['path', 'attachmentsid'])->from('vtiger_attachments')->where(['attachmentsid' => $attachmentsIds])->createCommand()->query();
            while ($row = $dataReader->read()) {
                $fileName = $row['path'] . $row['attachmentsid'];
                if (file_exists($fileName)) {
                    unlink($fileName);
                }
            }
            $dataReader->close();
            $dbCommand->delete('vtiger_attachments', ['attachmentsid' => $attachmentsIds])->execute();
        }
    }
}