/home/ivoiecob/email.hirewise-va.com/modules/DropboxFilestorage/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\DropboxFilestorage;

/**
 * Adds ability to work with Dropbox file storage inside Aurora Files module.
 *
 * @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
{
    protected static $sStorageType = 'dropbox';
    protected static $iStorageOrder = 100;
    protected $oClient = null;
    protected $aRequireModules = array(
        'OAuthIntegratorWebclient',
        'DropboxAuthWebclient'
    );

    /**
     * @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;
    }

    protected function issetScope($sScope)
    {
        return in_array($sScope, explode(' ', $this->oModuleSettings->Scopes));
    }

    /***** private functions *****/
    /**
     * Initializes DropBox Module.
     *
     * @ignore
     */
    public function init()
    {
        $this->subscribeEvent('Files::GetStorages::after', array($this, 'onAfterGetStorages'));
        $this->subscribeEvent('Files::GetFile', array($this, 'onGetFile'));
        $this->subscribeEvent('Files::GetItems', array($this, 'onGetItems'));
        $this->subscribeEvent('Files::CreateFolder::after', array($this, 'onAfterCreateFolder'));
        $this->subscribeEvent('Files::CreateFile', array($this, 'onCreateFile'));
        $this->subscribeEvent('Files::Delete::after', array($this, 'onAfterDelete'));
        $this->subscribeEvent('Files::Rename::after', array($this, 'onAfterRename'));
        $this->subscribeEvent('Files::Move::after', array($this, 'onAfterMove'));
        $this->subscribeEvent('Files::Copy::after', array($this, 'onAfterCopy'));
        $this->subscribeEvent('Files::GetFileInfo::after', array($this, 'onAfterGetFileInfo'));
        $this->subscribeEvent('Files::PopulateFileItem::after', array($this, 'onAfterPopulateFileItem'));

        $this->subscribeEvent('Dropbox::GetSettings', array($this, 'onGetSettings'));
        $this->subscribeEvent('Dropbox::UpdateSettings::after', array($this, 'onAfterUpdateSettings'));

        $this->subscribeEvent('Files::GetItems::before', array($this, 'onCheckUrlFile'));
        $this->subscribeEvent('Files::UploadFile::before', array($this, 'onCheckUrlFile'));
        $this->subscribeEvent('Files::CreateFolder::before', array($this, 'onCheckUrlFile'));
        $this->subscribeEvent('Files::CheckQuota::after', array($this, 'onAfterCheckQuota'));
    }

    /**
     * Obtains DropBox client if passed $sType is DropBox account type.
     *
     * @return \Kunnu\Dropbox\Dropbox
     */
    protected function getClient()
    {
        $oDropboxModule = \Aurora\Modules\Dropbox\Module::getInstance();
        if ($oDropboxModule) {
            if (!$oDropboxModule->oModuleSettings->EnableModule || !$this->issetScope('storage')) {
                return false;
            }
        } else {
            return false;
        }

        if ($this->oClient === null) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

            $oOAuthIntegratorWebclientModule = \Aurora\Modules\OAuthIntegratorWebclient\Module::Decorator();
            $oOAuthAccount = $oOAuthIntegratorWebclientModule->GetAccount(self::$sStorageType);

            $oTokenData = \json_decode($oOAuthAccount->AccessToken);
            if ($oTokenData && $oOAuthAccount) {
                $accessToken = $oTokenData->access_token;
                $iCreated = (int) $oTokenData->created;
                $iExpiresIn = (int) $oTokenData->expires_in;
                $now = new \DateTime();
                $now->setTimezone(new \DateTimeZone('GMT'));

                if ($now->getTimestamp() > ($iCreated + $iExpiresIn) && isset($oOAuthAccount->RefreshToken)) {
                    $oConnector = new \Aurora\Modules\DropboxAuthWebclient\Classes\Connector($this);
                    $aResult = $oConnector->RefreshAccessToken(
                        $oDropboxModule->oModuleSettings->Id,
                        $oDropboxModule->oModuleSettings->Secret,
                        $oOAuthAccount->RefreshToken
                    );
                    if (isset($aResult['access_token'])) {
                        $oTokenData->access_token = $aResult['access_token'];
                        $oTokenData->created = $now->getTimestamp();
                        $oTokenData->expires_in = $aResult['expires_in'];

                        $oOAuthAccount->AccessToken = \json_encode($oTokenData);
                        $oOAuthAccount->save();

                        $accessToken = $oTokenData->access_token;
                    }
                }

                $oDropboxApp = new \Kunnu\Dropbox\DropboxApp(
                    $oDropboxModule->oModuleSettings->Id,
                    $oDropboxModule->oModuleSettings->Secret,
                    $accessToken
                );
                $this->oClient = new \Kunnu\Dropbox\Dropbox($oDropboxApp);
            }
        }

        return $this->oClient;
    }

    /**
     * Write to the $mResult variable information about DropBox storage.
     *
     * @ignore
     * @param array $aArgs
     * @param array $mResult
     */
    public function onAfterGetStorages($aArgs, &$mResult)
    {
        if ($this->CheckDropboxAccess()) {
            $mResult[] = [
                'Type' => self::$sStorageType,
                'IsExternal' => true,
                'DisplayName' => 'Dropbox',
                'Order' => self::$iStorageOrder,
                'IsDroppable' => true
            ];
        }
    }

    /**
     * Returns directory name for the specified path.
     *
     * @param string $sPath Path to the file.
     * @return string
     */
    protected function getDirName($sPath)
    {
        $sPath = dirname($sPath);
        return str_replace(DIRECTORY_SEPARATOR, '/', $sPath);
    }

    /**
     * Returns base name for the specified path.
     *
     * @param string $sPath Path to the file.
     * @return string
     */
    protected function getBaseName($sPath)
    {
        $aPath = explode('/', $sPath);
        return end($aPath);
    }

    /**
     *
     * @param \Aurora\Modules\Files\Classes\FileItem $oItem
     * @return string
     */
    protected function getItemHash($oItem)
    {
        return \Aurora\System\Api::EncodeKeyValues(array(
            'UserId' => \Aurora\System\Api::getAuthenticatedUserId(),
            'Type' => $oItem->TypeStr,
            'Path' => '',
            'Name' => $oItem->FullPath,
            'FileName' => $oItem->Name
        ));
    }

    protected function hasThumb($sName)
    {
        return in_array(
            pathinfo($sName, PATHINFO_EXTENSION),
            ['jpg', 'jpeg', 'png', 'tiff', 'tif', 'gif', 'bmp']
        );
    }

    /**
     * Populates file info.
     *
     * @param \Kunnu\Dropbox\Models\FileMetadata|\Kunnu\Dropbox\Models\FolderMetadata $aData contains information about file.
     * @return \Aurora\Modules\Files\Classes\FileItem|false
     */
    protected function populateFileInfo($aData)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        $mResult = false;
        if ($aData) {
            $sPath = ltrim($this->getDirName($aData->getPathDisplay()), '/');

            $mResult /*@var $mResult \Aurora\Modules\Files\Classes\FileItem */ = new  \Aurora\Modules\Files\Classes\FileItem();
            $mResult->IsExternal = true;
            $mResult->TypeStr = self::$sStorageType;
            $mResult->IsFolder = ($aData instanceof \Kunnu\Dropbox\Models\FolderMetadata);
            $mResult->Id = $aData->getName();
            $mResult->Name = $mResult->Id;
            $mResult->Path = !empty($sPath) ? '/' . $sPath : $sPath;
            $mResult->Size = !$mResult->IsFolder ? $aData->getSize() : 0;
            //			$bResult->Owner = $oSocial->Name;
            if (!$mResult->IsFolder) {
                $mResult->LastModified =  date("U", strtotime($aData->getServerModified()));
            }
            //			$mResult->Shared = isset($aData['shared']) ? $aData['shared'] : false;
            $mResult->FullPath = $mResult->Name !== '' ? $mResult->Path . '/' . $mResult->Name : $mResult->Path ;
            $mResult->ContentType = \Aurora\System\Utils::MimeContentType($mResult->Name);

            $mResult->Thumb = $this->hasThumb($mResult->Name);

            if ($mResult->IsFolder) {
                $mResult->AddAction([
                    'list' => []
                ]);
            } else {
                $mResult->AddAction([
                    'view' => [
                        'url' => '?download-file/' . $this->getItemHash($mResult) . '/view'
                    ]
                ]);
                $mResult->AddAction([
                    'download' => [
                        'url' => '?download-file/' . $this->getItemHash($mResult)
                    ]
                ]);
            }
        }
        return $mResult;
    }

    /**
     * Writes to the $mResult variable open file source if $sType is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult

     * @return bool|void
     */
    public function onGetFile($aArgs, &$mResult)
    {
        if ($aArgs['Type'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $sFullPath = $aArgs['Path'] . '/' . ltrim($aArgs['Name'], '/');

                if (isset($aArgs['IsThumb']) && (bool)$aArgs['IsThumb'] === true) {
                    $oThumbnail = $oClient->getThumbnail($sFullPath, 'medium', 'png');
                    if ($oThumbnail) {
                        $mResult = \fopen('php://memory', 'r+');
                        \fwrite($mResult, $oThumbnail->getContents());
                        \rewind($mResult);
                    }
                } else {
                    $mDownloadResult = $oClient->download($sFullPath);
                    if ($mDownloadResult) {
                        $mResult = \fopen('php://memory', 'r+');
                        \fwrite($mResult, $mDownloadResult->getContents());
                        \rewind($mResult);
                    }
                }
            }

            return true;
        }
    }

    /**
     * Writes to $aArgs variable list of DropBox files if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs Is passed by reference.
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onGetItems($aArgs, &$mResult)
    {
        if ($aArgs['Type'] === self::$sStorageType) {
            $mResult = array();
            $oClient = $this->getClient();
            if ($oClient) {
                $aItems = array();
                $Path = '/' . ltrim($aArgs['Path'], '/');
                if (empty($aArgs['Pattern'])) {
                    $oListFolderContents = $oClient->listFolder($Path);
                    $oItems = $oListFolderContents->getItems();
                    $aItems = $oItems->all();
                } else {
                    $oListFolderContents = $oClient->search($Path, $aArgs['Pattern']);
                    $oItems = $oListFolderContents->getItems();
                    $aItems = $oItems->all();
                }

                foreach ($aItems as $oChild) {
                    if ($oChild instanceof \Kunnu\Dropbox\Models\SearchResult) {
                        $oChild = $oChild->getMetadata();
                    }
                    $oItem /*@var $oItem \Aurora\Modules\Files\Classes\FileItem */ = $this->populateFileInfo($oChild);
                    if ($oItem) {
                        $mResult[] = $oItem;
                    }
                }
            }

            return true;
        }
    }

    /**
     * Creates folder if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onAfterCreateFolder($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($aArgs['Type'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $mResult = false;
                $sPath = $aArgs['Path'];

                if ($oClient->createFolder($sPath . '/' . $aArgs['FolderName']) !== null) {
                    $mResult = true;
                }
            }
            return true;
        }
    }

    /**
     * Creates file if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onCreateFile($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($aArgs['Type'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $mResult = false;

                $Path = $aArgs['Path'] . '/' . $aArgs['Name'];
                $rData = $aArgs['Data'];
                if (!is_resource($aArgs['Data'])) {
                    $rData = fopen('php://memory', 'r+');
                    fwrite($rData, $aArgs['Data']);
                    rewind($rData);
                }
                $oDropboxFile = \Kunnu\Dropbox\DropboxFile::createByStream($aArgs['Name'], $rData);
                if ($oClient->upload($oDropboxFile, $Path)) {
                    $mResult = true;
                }

                return true;
            }
        }
    }

    /**
     * Deletes file if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onAfterDelete($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($aArgs['Type'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $mResult = false;

                foreach ($aArgs['Items'] as $aItem) {
                    $oClient->delete($aItem['Path'] . '/' . $aItem['Name']);
                    $mResult = true;
                }
            }
            return true;
        }
    }

    /**
     * Renames file if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onAfterRename($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($aArgs['Type'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $mResult = false;

                $sPath = $aArgs['Path'];
                if ($oClient->move($sPath . '/' . $aArgs['Name'], $sPath . '/' . $aArgs['NewName'])) {
                    $mResult = true;
                }
            }
        }
    }

    /**
     * Moves file if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onAfterMove($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($aArgs['FromType'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $mResult = false;

                if ($aArgs['ToType'] === $aArgs['FromType']) {
                    foreach ($aArgs['Files'] as $aFile) {
                        $oClient->move($aArgs['FromPath'] . '/' . $aFile['Name'], $aArgs['ToPath'] . '/' . $aFile['Name']);
                    }
                    $mResult = true;
                }
            }
            return true;
        }
    }

    /**
     * Copies file if $aArgs['Type'] is DropBox account type.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onAfterCopy($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($aArgs['FromType'] === self::$sStorageType) {
            $oClient = $this->getClient();
            if ($oClient) {
                $mResult = false;

                if ($aArgs['ToType'] === $aArgs['FromType']) {
                    foreach ($aArgs['Files'] as $aFile) {
                        $oClient->copy($aArgs['FromPath'] . '/' . $aFile['Name'], $aArgs['ToPath'] . '/' . $aFile['Name']);
                    }
                    $mResult = true;
                }
            }
            return true;
        }
    }

    protected function _getFileInfo($sPath, $sName)
    {
        $mResult = false;
        $oClient = $this->GetClient();
        if ($oClient) {
            $mResult = $oClient->getMetadata($sPath . '/' . $sName);
        }

        return $mResult;
    }


    /**
     * @ignore
     * @todo not used
     * @param array $aArgs
     * @param mixed $mResult
     *
     * @return bool|void
     */
    public function onAfterGetFileInfo($aArgs, &$mResult)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        if (self::$sStorageType === $aArgs['Type']) {
            $mFileInfo = $this->_getFileInfo($aArgs['Path'], $aArgs['Id']);
            if ($mFileInfo) {
                $mResult = $this->PopulateFileInfo($mFileInfo);
            }
            return true;
        }
    }

    /**
     * @ignore
     * @todo not used
     * @param array $aArgs
     * @param mixed $oItem
     *
     * @return bool|void
     */
    public function onAfterPopulateFileItem($aArgs, &$oItem)
    {
        if ($oItem->IsLink) {
            if (false !== strpos($oItem->LinkUrl, 'dl.dropboxusercontent.com') ||
                    false !== strpos($oItem->LinkUrl, 'dropbox.com')) {
                $aMetadata = $this->getMetadataLink($oItem->LinkUrl);
                if (isset($aMetadata['path_lower']) && $aMetadata['.tag'] == 'folder') {
                    $oItem->UnshiftAction(array(
                        'list' => array()
                    ));

                    $oItem->Thumb = true;
                    $oItem->ThumbnailUrl = \MailSo\Base\Http::SingletonInstance()->GetFullUrl() . 'modules/' . self::GetName() . '/images/dropbox.png';
                }
                $oItem->LinkType = 'dropbox';
                return true;
            }
        }

        return false;
    }

    protected function getMetadataLink($sLink)
    {
        if (!$this->CheckDropboxAccess()) {
            return null;
        }
        $oClient = $this->getClient();
        $response = $oClient->postToAPI(
            '/sharing/get_shared_link_metadata',
            array(
                'url' => $sLink
            )
        );

        if ($response->getHttpStatusCode() === 404) {
            return null;
        }
        if ($response->getHttpStatusCode() !== 200) {
            return null;
        }

        $metadata = $response->getDecodedBody();
        if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) {
            return null;
        }
        return $metadata;
    }

    public function onCheckUrlFile(&$aArgs, &$mResult)
    {
        if ($this->CheckDropboxAccess() && (\pathinfo($aArgs['Path'], PATHINFO_EXTENSION) === 'url' || strpos($aArgs['Path'], '.url/'))) {
            list($sUrl, $sPath) = explode('.url', $aArgs['Path']);
            $sUrl .= '.url';
            $aArgs['Path'] = $sUrl;
            $this->prepareArgs($aArgs);
            if ($sPath && $aArgs['Type'] === self::$sStorageType) {
                $aArgs['Path'] .= $sPath;
            } elseif ($aArgs['Type'] !== self::$sStorageType) {
                $aArgs['Path'] = $sUrl . $sPath;
            }
        }
    }

    protected function prepareArgs(&$aData)
    {
        $aPathInfo = pathinfo($aData['Path']);
        $sExtension = isset($aPathInfo['extension']) ? $aPathInfo['extension'] : '';
        if ($sExtension === 'url') {
            $aArgs = array(
                'UserId' => $aData['UserId'],
                'Type' => $aData['Type'],
                'Path' => $aPathInfo['dirname'],
                'Name' => $aPathInfo['basename'],
                'Id' => $aPathInfo['basename'],
                'IsThumb' => false
            );
            $mResult = false;
            \Aurora\System\Api::GetModuleManager()->broadcastEvent(
                'Files',
                'GetFile',
                $aArgs,
                $mResult
            );
            if (is_resource($mResult)) {
                $aUrlFileInfo = \Aurora\System\Utils::parseIniString(stream_get_contents($mResult));
                if ($aUrlFileInfo && isset($aUrlFileInfo['URL'])) {
                    if (false !== strpos($aUrlFileInfo['URL'], 'dl.dropboxusercontent.com') ||
                        false !== strpos($aUrlFileInfo['URL'], 'dropbox.com')) {
                        $aData['Type'] = 'dropbox';
                        $aMetadata = $this->getMetadataLink($aUrlFileInfo['URL']);
                        if (isset($aMetadata['path_lower'])) {
                            $aData['Path'] = $aMetadata['path_lower'];
                        }
                    }
                }
            }
        }
    }
    /***** private functions *****/

    /**
     * Passes data to connect to service.
     *
     * @ignore
     * @param string $aArgs Service type to verify if data should be passed.
     * @param boolean|array $mResult variable passed by reference to take the result.
     */
    public function onGetSettings($aArgs, &$mResult)
    {
        $oUser = \Aurora\System\Api::getAuthenticatedUser();

        if ($oUser) {
            $aScope = array(
                'Name' => 'storage',
                'Description' => $this->i18N('SCOPE_FILESTORAGE'),
                'Value' => false
            );
            if ($oUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
                $aScope['Value'] = $this->issetScope('storage');
                $mResult['Scopes'][] = $aScope;
            }
            if ($oUser->isNormalOrTenant()) {
                if ($aArgs['OAuthAccount'] instanceof \Aurora\Modules\OAuthIntegratorWebclient\Models\OauthAccount) {
                    $aScope['Value'] = $aArgs['OAuthAccount']->issetScope('storage');
                }
                if ($this->issetScope('storage')) {
                    $mResult['Scopes'][] = $aScope;
                }
            }
        }
    }

    public function onAfterUpdateSettings($aArgs, &$mResult)
    {
        $sScope = '';
        if (isset($aArgs['Scopes']) && is_array($aArgs['Scopes'])) {
            foreach ($aArgs['Scopes'] as $aScope) {
                if ($aScope['Name'] === 'storage') {
                    if ($aScope['Value']) {
                        $sScope = 'storage';
                        break;
                    }
                }
            }
        }
        $this->setConfig('Scopes', $sScope);
        $this->saveModuleConfig();
    }

    /**
     * Checks if storage type is personal.
     *
     * @param string $Type Storage type.
     * @return bool
     */
    protected function checkStorageType($Type)
    {
        return $Type === static::$sStorageType;
    }

    /**
     * @ignore
     * @param array $aArgs Arguments of event.
     * @param mixed $mResult Is passed by reference.
     */
    public function onAfterCheckQuota($aArgs, &$mResult)
    {
        $Type = $aArgs['Type'];
        if ($this->checkStorageType($Type)) {
            $mResult = true;
            return true;
        }
    }

    protected function CheckDropboxAccess()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
        $bEnableDropboxModule = false;

        if (class_exists('Aurora\Modules\Dropbox\Module')) {
            $oDropboxModule = \Aurora\Modules\Dropbox\Module::getInstance();
            $bEnableDropboxModule = $oDropboxModule->oModuleSettings->EnableModule;
        }

        $oOAuthIntegratorWebclientModule = \Aurora\Modules\OAuthIntegratorWebclient\Module::Decorator();
        $oOAuthAccount = $oOAuthIntegratorWebclientModule->GetAccount(self::$sStorageType);

        return ($oOAuthAccount instanceof \Aurora\Modules\OAuthIntegratorWebclient\Models\OauthAccount
            && $oOAuthAccount->Type === self::$sStorageType
            && $bEnableDropboxModule
            && $this->issetScope('storage')
            && $oOAuthAccount->issetScope('storage'));
    }
}