/home/ivoiecob/email.hirewise-va.com/modules/ImportExportMailPlugin/Module.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\Modules\ImportExportMailPlugin;

use Aurora\Modules\Mail\Module as MailModule;

/**
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2023, Afterlogic Corp.
 *
 * @property Settings $oModuleSettings
 *
 * @package Modules
 */
class Module extends \Aurora\System\Module\AbstractModule
{
    /*
     * @var $oFilecacheManager \Aurora\System\Managers\Filecache
     */
    public $oFilecacheManager = null;
    public $sLogPrefix = 'plugin-import-export-mail-';

    /**
     * @return Module
     */
    public static function getInstance()
    {
        return parent::getInstance();
    }

    /**
     * @return Module
     */
    public static function Decorator()
    {
        return parent::Decorator();
    }

    /**
     * @return Settings
     */
    public function getModuleSettings()
    {
        return $this->oModuleSettings;
    }

    public function init()
    {
        $this->aErrors = [
            Enums\ErrorCodes::UnknownError	=> $this->i18N('UNKNOWN_ERROR'),
            Enums\ErrorCodes::ErrorSizeLimit	=> $this->i18N('ERROR_SIZE_LIMIT', ['SIZE' => $this->oModuleSettings->UploadSizeLimitMb])
        ];

        $this->AddEntry('transfer-mail', 'EntryTransferMail');
    }

    /**
     * Obtains list of module settings
     *
     * @return array
     */
    public function GetSettings()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return [
            'AllowZip' => class_exists('ZipArchive'),
            'UploadSizeLimitMb' => $this->oModuleSettings->UploadSizeLimitMb
        ];
    }

    /**
     * Creating empty files before data export
     *
     * @return bool|array
     */
    public function ExportMailPrepare()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $mResult = false;
        $oUser = \Aurora\System\Api::getAuthenticatedUser();

        if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
            $sZipName = \md5(\time() . \rand(1000, 9999));
            $mResult = [
                'Zip' => $sZipName,
            ];
            $this->getFilecacheManager()->put($oUser->PublicId, $sZipName, '', '.zip');
            $this->getFilecacheManager()->put($oUser->PublicId, $sZipName, 'prepare', '.info');
        }

        return $mResult;
    }

    /**
     * Exporting mails into zip file
     *
     * @param int $AccountId Mail account ID
     * @param string $Folder Folder name
     * @param string $Zip ZIP file name
     * @return boolean
     * @throws \Exception
     */
    public function ExportMailGenerate($AccountId, $Folder, $Zip)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $bResult = false;
        $oUser = \Aurora\System\Api::getAuthenticatedUser();

        if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
            $sUserPublicId = $oUser->PublicId;
            try {
                $aTempFiles = [];

                $this->getFilecacheManager()->put($oUser->PublicId, $Zip, 'generate', '.info');
                $oAccount = MailModule::Decorator()->GetAccount($AccountId);

                if ($Folder !== null && $Zip !== null
                    && $oAccount
                    && $oAccount->IdUser === $oUser->Id) {
                    $oApiMail = $this->getMailManager();
                    $iOffset = 0;
                    $iLimit = 20;

                    $sZipFilePath = $this->getFilecacheManager()->generateFullFilePath($sUserPublicId, $Zip, '.zip');
                    $rZipResource = fopen($sZipFilePath, 'w+b');

                    $options = new \ZipStream\Option\Archive();
                    // $options->setSendHttpHeaders(true);
                    // $options->setFlushOutput(true);
                    // $options->setContentDisposition('attachment');
                    $options->setOutputStream($rZipResource);

                    $oZip = new \ZipStream\ZipStream($Zip, $options);
                    $this->Log('Start fetching mail');

                    $self = $this;

                    $aData = $oApiMail->getFolderInformation($oAccount, $Folder);
                    $iCount = (is_array($aData) && 4 === count($aData)) ? $aData[0] : 0;

                    while ($iOffset <= $iCount) {
                        $oMessageListCollection =  $oApiMail->getMessageList($oAccount, $Folder, $iOffset, $iLimit);
                        $oMessageListCollection->ForeachList(function ($oMessage) use ($oApiMail, $sUserPublicId, $oAccount, $self, $Folder, &$oZip, &$aTempFiles) {
                            $iUid = $oMessage->getUid();
                            $oApiMail->directMessageToStream(
                                $oAccount,
                                function ($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($sUserPublicId, $self, $iUid, &$oZip, &$aTempFiles) {
                                    $sTempName = \md5(\time() . \rand(1000, 9999) . $sFileName);
                                    $aTempFiles[] = $sTempName;

                                    if (is_resource($rResource) && $self->getFilecacheManager()->putFile($sUserPublicId, $sTempName, $rResource)) {
                                        $sFilePath = $self->getFilecacheManager()->generateFullFilePath($sUserPublicId, $sTempName);
                                        $rSubResource = fopen($sFilePath, 'rb');
                                        if (is_resource($rSubResource)) {
                                            $sFileName = 'uid-' . $iUid . '.eml';
                                            $self->Log('Append file \'' . $sFileName . '\' to ZIP');
                                            $oZip->addFileFromStream($sFileName, $rSubResource);
                                            $MemoryUsage = memory_get_usage(true) / (1024 * 1024);
                                            $self->Log('Memory usage: ' . $MemoryUsage);
                                            @fclose($rSubResource);
                                        }
                                    }
                                },
                                $Folder,
                                $iUid
                            );
                        });
                        $iOffset = $iOffset + $iLimit;
                    }
                    $this->Log('End fetching mail');
                    $oZip->finish();
                    $this->Log('Create ZIP file');
                    foreach ($aTempFiles as $sTempName) {
                        $this->Log('Remove temp file: ' . $sTempName);
                        $self->getFilecacheManager()->clear($sUserPublicId, $sTempName);
                    }
                }
                $this->Log('Generating ZIP Result: ');
                $this->getFilecacheManager()->put($sUserPublicId, $Zip, 'ready', '.info');
                $bResult = true;
            } catch (\Exception $oException) {
                $this->getFilecacheManager()->put($sUserPublicId, $Zip, 'error', '.info');
                $this->Log($oException, true);
                throw $oException;
            }
        }

        return $bResult;
    }

    /**
     * Checking status of export process
     *
     * @param string $Zip ZIP file name
     * @return bool|array
     */
    public function ExportMailStatus($Zip)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $mResult = false;
        $oUser = \Aurora\System\Api::getAuthenticatedUser();

        if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
            if ($this->getFilecacheManager()->isFileExists($oUser->PublicId, $Zip, '.info')) {
                $mResult = [
                    'Status' => $this->getFilecacheManager()->get($oUser->PublicId, $Zip, '.info')
                ];
            }
        }

        return $mResult;
    }

    /**
     * Entry point for mail export
     */
    public function EntryTransferMail()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $sAction = (string) \Aurora\System\Router::getItemByIndex(1, '');
        $sFileName = (string) \Aurora\System\Router::getItemByIndex(2, '');
        if ($sAction === 'export') {
            $this->ExportMail($sFileName);
        }
    }

    /**
     * Downloading of exported emails
     *
     * @param string $sFileName
     */
    public function ExportMail($sFileName)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        @ob_start();
        ini_set('display_errors', 0);
        if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
            $this->getFilecacheManager()->put($oUser->PublicId, $sFileName, 'download', '.info');
            $this->Log('Start downloading ZIP file.. ');

            $sZipFilePath = $this->getFilecacheManager()->generateFullFilePath($oUser->PublicId, $sFileName, '.zip');
            $iFileSize = filesize($sZipFilePath);
            $this->Log('ZIP file size: ' . $iFileSize);

            header("Content-type: application/zip");
            header("Content-Disposition: attachment; filename=export-mail.zip");
            header('Accept-Ranges: none', true);
            header('Content-Transfer-Encoding: binary');
            header("Content-Length: " . $iFileSize);
            header("Pragma: no-cache");
            header("Expires: 0");

            $rZipResource = \fopen($sZipFilePath, 'rb');
            if ($rZipResource !== false) {
                $this->Log('Start write data to buffer');
                rewind($rZipResource);
                while (!\feof($rZipResource)) {
                    $MemoryUsage = memory_get_usage(true) / (1024 * 1024);
                    $this->Log('Write data to buffer - memory usage:' . $MemoryUsage);
                    $sBuffer = @\fread($rZipResource, 8192);
                    if (false !== $sBuffer) {
                        echo $sBuffer;
                        ob_flush();
                        flush();
                        \MailSo\Base\Utils::ResetTimeLimit();
                        continue;
                    }
                    break;
                }
                @\fclose($rZipResource);
                $this->Log('End write data to buffer');
            } else {
                $this->Log("Error. File {$sZipFilePath} not found.");
            }
            $this->Log('Finish ZIP file downloading');
        }
        @ob_get_clean();
        exit;
    }

    /**
     * Importing emails into specified folder
     *
     * @param int $AccountId Mail account ID
     * @param string $Folder Folder name
     * @param array $UploadData Information about uploaded file
     * @return boolean
     * @throws \Aurora\System\Exceptions\BaseException
     * @throws \Aurora\System\Exceptions\ApiException
     */
    public function ImportMail($AccountId, $Folder, $UploadData)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $bResult = false;
        $oAccount = MailModule::Decorator()->GetAccount($AccountId);

        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && is_array($UploadData)) {
            $oUser = \Aurora\System\Api::getAuthenticatedUser();
            if (is_array($UploadData) && $oUser instanceof \Aurora\Modules\Core\Models\User) {
                $iSize = (int) $UploadData['size'];
                $iUploadSizeLimitMb = $this->oModuleSettings->UploadSizeLimitMb;
                if ($iUploadSizeLimitMb > 0 && $iSize / (1024 * 1024) > $iUploadSizeLimitMb) {
                    throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::ErrorSizeLimit);
                }
                $sUploadName = $UploadData['name'];
                $bIsZipExtension  = strtolower(pathinfo($sUploadName, PATHINFO_EXTENSION)) === 'zip';
                if ($bIsZipExtension) {
                    $sSavedName = 'upload-post-' . md5($UploadData['name'] . $UploadData['tmp_name']);
                    if (is_resource($UploadData['tmp_name'])) {
                        $this->getFilecacheManager()->putFile($oUser->UUID, $sSavedName, $UploadData['tmp_name']);
                    } else {
                        $this->getFilecacheManager()->moveUploadedFile($oUser->UUID, $sSavedName, $UploadData['tmp_name']);
                    }
                    if ($this->getFilecacheManager()->isFileExists($oUser->UUID, $sSavedName)) {
                        $sSavedFullName = $this->getFilecacheManager()->generateFullFilePath($oUser->UUID, $sSavedName);
                        if (class_exists('ZipArchive')) {
                            $oZip = new \ZipArchive();
                            if ($oZip->open($sSavedFullName)) {
                                for ($i = 0; $i < $oZip->numFiles; $i++) {
                                    $sFileName = $oZip->getNameIndex($i);
                                    if (strtolower(pathinfo($sFileName, PATHINFO_EXTENSION)) === 'eml') {
                                        $aFileParams = $oZip->statIndex($i);
                                        $iStreamSize = $aFileParams['size'];
                                        $rMessage = $oZip->getStream($sFileName);
                                        if (is_resource($rMessage)) {
                                            $this->getMailManager()->appendMessageFromStream($oAccount, $rMessage, $Folder, $iStreamSize);
                                            @fclose($rMessage);
                                        }
                                    }
                                }
                                $oZip->close();
                                $bResult = true;
                            }
                        } else {
                            throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::ZipArchiveClassNotFound);
                        }
                    } else {
                        throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::UnknownError);
                    }
                }
            }
        } else {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        return $bResult;
    }

    /***private***/
    private function getMailManager()
    {
        static $oMailManager = null;
        if ($oMailManager === null) {
            $oMailManager = new \Aurora\Modules\Mail\Managers\Main\Manager($this);
        }

        return $oMailManager;
    }

    private function getFilecacheManager()
    {
        if ($this->oFilecacheManager === null) {
            $this->oFilecacheManager = new \Aurora\System\Managers\Filecache();
        }

        return $this->oFilecacheManager;
    }

    private function Log($mLog, $bIsException = false, $bIsObject = false)
    {
        if ($bIsException) {
            \Aurora\System\Api::LogException($mLog, \Aurora\System\Enums\LogLevel::Full, $this->sLogPrefix);
        } elseif ($bIsObject) {
            \Aurora\System\Api::LogObject($mLog, \Aurora\System\Enums\LogLevel::Full, $this->sLogPrefix);
        } else {
            \Aurora\System\Api::Log($mLog, \Aurora\System\Enums\LogLevel::Full, $this->sLogPrefix);
        }
    }
}