aCurrentActionParams = array(); $this->oHttp = null; $this->oLogger = null; $this->oPlugins = null; $this->oMailClient = null; $this->oSocial = null; $this->oConfig = null; $this->aCachers = array(); $this->oStorageProvider = null; $this->oLocalStorageProvider = null; $this->oSettingsProvider = null; $this->oLocalSettingsProvider = null; $this->oFilesProvider = null; $this->oFiltersProvider = null; $this->oDomainProvider = null; $this->oAddressBookProvider = null; $this->oSuggestionsProvider = null; $this->oChangePasswordProvider = null; $this->oTwoFactorAuthProvider = null; $this->sSpecAuthToken = ''; $oConfig = $this->Config(); $this->Plugins()->RunHook('filter.application-config', array(&$oConfig)); $this->Logger()->Ping(); } /** * @return \RainLoop\Actions */ public static function NewInstance() { return new self(); } /** * @param string $sSpecAuthToken * * @return \RainLoop\Application */ public function SetSpecAuthToken($sSpecAuthToken) { $this->sSpecAuthToken = $sSpecAuthToken; return $this; } /** * @return string */ public function GetSpecAuthToken() { return $this->sSpecAuthToken; } /** * @return string */ public function GetShortLifeSpecAuthToken($iLife = 60) { $sToken = $this->getAuthToken(); $aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken); if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && is_array($aAccountHash)) { $aAccountHash[10] = \time() + $iLife; return \RainLoop\Utils::EncodeKeyValues($aAccountHash); } return ''; } /** * @return \RainLoop\Application */ public function Config() { if (null === $this->oConfig) { $this->oConfig = new \RainLoop\Config\Application(); $bSave = defined('APP_INSTALLED_START'); if (!$this->oConfig->Load()) { $bSave = true; } else if (!$bSave) { $bSave = APP_VERSION !== $this->oConfig->Get('version', 'current'); } if ($bSave) { $this->oConfig->Save(); } } return $this->oConfig; } /** * @param string $sName * @param \RainLoop\Model\Account $oAccount = null * * @return mixed */ private function fabrica($sName, $oAccount = null) { $oResult = null; $this->Plugins() ->RunHook('main.fabrica', array($sName, &$oResult), false) ->RunHook('main.fabrica[2]', array($sName, &$oResult, $oAccount), false) ; if (null === $oResult) { switch ($sName) { case 'files': // RainLoop\Providers\Files\IFiles $oResult = new \RainLoop\Providers\Files\FileStorage(APP_PRIVATE_DATA.'storage/files'); break; case 'storage': case 'storage-local': // RainLoop\Providers\Storage\IStorage $oResult = new \RainLoop\Providers\Storage\FileStorage( APP_PRIVATE_DATA.'storage', 'storage-local' === $sName); break; case 'settings': case 'settings-local': // RainLoop\Providers\Settings\ISettings $oResult = new \RainLoop\Providers\Settings\DefaultSettings( $this->StorageProvider('settings-local' === $sName)); break; case 'login': // \RainLoop\Providers\Login\LoginInterface $oResult = new \RainLoop\Providers\Login\DefaultLogin(); break; case 'domain': // \RainLoop\Providers\Domain\DomainAdminInterface $oResult = new \RainLoop\Providers\Domain\DefaultDomain(APP_PRIVATE_DATA.'domains', $this->Cacher()); break; case 'filters': // \RainLoop\Providers\Filters\FiltersInterface $oResult = new \RainLoop\Providers\Filters\SieveStorage( $this->Plugins(), $this->Config() ); break; case 'address-book': // \RainLoop\Providers\AddressBook\AddressBookInterface if (!\RainLoop\Utils::IsOwnCloud()) // disabled for ownCloud { $sDsn = \trim($this->Config()->Get('contacts', 'pdo_dsn', '')); $sUser = \trim($this->Config()->Get('contacts', 'pdo_user', '')); $sPassword = (string) $this->Config()->Get('contacts', 'pdo_password', ''); $sDsnType = $this->ValidateContactPdoType(\trim($this->Config()->Get('contacts', 'type', 'sqlite'))); if ('sqlite' === $sDsnType) { $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook( 'sqlite:'.APP_PRIVATE_DATA.'AddressBook.sqlite', '', '', 'sqlite'); } else { $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook($sDsn, $sUser, $sPassword, $sDsnType); } } break; case 'suggestions': // \RainLoop\Providers\Suggestions\ISuggestions // $oResult = new \RainLoop\Providers\Suggestions\TestSuggestions(); if (\RainLoop\Utils::IsOwnCloud()) { $oResult = new \RainLoop\Providers\Suggestions\OwnCloudSuggestions(); } break; case 'change-password': // \RainLoop\Providers\ChangePassword\ChangePasswordInterface break; case 'two-factor-auth': // \RainLoop\Providers\TwoFactorAuth\TwoFactorAuthInterface $oResult = new \RainLoop\Providers\TwoFactorAuth\GoogleTwoFactorAuth(); break; } } if ($oResult && \method_exists($oResult, 'SetLogger')) { $oResult->SetLogger($this->Logger()); } $this->Plugins()->RunHook('filter.fabrica', array($sName, &$oResult, $oAccount), false); return $oResult; } /** * @return void */ public function BootStart() { if (defined('APP_INSTALLED_START') && defined('APP_INSTALLED_VERSION') && APP_INSTALLED_START && !APP_INSTALLED_VERSION) { try { $this->KeenIO('Install'); } catch (\Exception $oException) { unset($oException); } } } /** * @return void */ public function BootEnd() { try { if ($this->MailClient()->IsLoggined()) { $this->MailClient()->LogoutAndDisconnect(); } } catch (\Exception $oException) { unset($oException); } } /** * @return string */ public function ParseQueryAuthString() { $sQuery = \trim($this->Http()->GetQueryString()); $iPos = \strpos($sQuery, '&'); if (0 < $iPos) { $sQuery = \substr($sQuery, 0, $iPos); } $sQuery = \trim(\trim($sQuery), ' /'); $sSubQuerty = \trim(\trim($this->Http()->GetQuery('s', '')), ' /'); $sSubSubQuerty = \trim(\trim($this->Http()->GetQuery('ss', '')), ' /'); $sQuery .= 0 < \strlen($sSubQuerty) ? '/'.$sSubQuerty : ''; $sQuery .= 0 < \strlen($sSubSubQuerty) ? '/'.$sSubSubQuerty : ''; if ('' === $this->GetSpecAuthToken()) { $aPaths = \explode('/', $sQuery); if (!empty($aPaths[0]) && !empty($aPaths[1]) && '_' === substr($aPaths[1], 0, 1)) { $this->SetSpecAuthToken($aPaths[1]); } } return $sQuery; } /** * @param string $sLine * @param \RainLoop\Model\Account $oAccount = null * * @return string */ private function compileLogParams($sLine, $oAccount = null) { if (false !== \strpos($sLine, '{date:')) { $sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) { return \gmdate($aMatch[1]); }, $sLine); $sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine); } if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) { if (!$oAccount) { $this->ParseQueryAuthString(); $oAccount = $this->getAccountFromToken(false); } if ($oAccount) { $sLine = \str_replace('{imap:login}', $oAccount->IncLogin(), $sLine); $sLine = \str_replace('{imap:host}', $oAccount->DomainIncHost(), $sLine); $sLine = \str_replace('{imap:port}', $oAccount->DomainIncPort(), $sLine); $sLine = \str_replace('{smtp:login}', $oAccount->OutLogin(), $sLine); $sLine = \str_replace('{smtp:host}', $oAccount->DomainOutHost(), $sLine); $sLine = \str_replace('{smtp:port}', $oAccount->DomainOutPort(), $sLine); } $sLine = \preg_replace('/\{imap:([^}]*)\}/i', 'imap', $sLine); $sLine = \preg_replace('/\{smtp:([^}]*)\}/i', 'imap', $sLine); } if (false !== \strpos($sLine, '{request:')) { if (false !== \strpos($sLine, '{request:ip}')) { $sLine = \str_replace('{request:ip}', $this->Http()->GetClientIp( $this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine); } $sLine = \preg_replace('/\{request:([^}]*)\}/i', 'request', $sLine); } if (false !== \strpos($sLine, '{user:')) { if (false !== \strpos($sLine, '{user:uid}')) { $sLine = \str_replace('{user:uid}', \base_convert(\sprintf('%u', \crc32(\md5(\RainLoop\Utils::GetConnectionToken()))), 10, 32), $sLine ); } if (false !== \strpos($sLine, '{user:ip}')) { $sLine = \str_replace('{user:ip}', $this->Http()->GetClientIp( $this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine); } if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) { if (!$oAccount) { $this->ParseQueryAuthString(); $oAccount = $this->getAccountFromToken(false); } if ($oAccount) { $sEmail = $oAccount->Email(); $sLine = \str_replace('{user:email}', $sEmail, $sLine); $sLine = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine); $sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine); } } $sLine = \preg_replace('/\{user:([^}]*)\}/i', 'unknown', $sLine); } if (false !== \strpos($sLine, '{labs:')) { $sLine = \preg_replace_callback('/\{labs:rand:([1-9])\}/', function ($aMatch) { return \rand(\pow(10, $aMatch[1] - 1), \pow(10, $aMatch[1]) - 1); }, $sLine); $sLine = \preg_replace('/\{labs:([^}]*)\}/', 'labs', $sLine); } return $sLine; } /** * @param string $sFileName * * @return string */ private function compileLogFileName($sFileName) { $sFileName = \trim($sFileName); if (0 !== \strlen($sFileName)) { $sFileName = $this->compileLogParams($sFileName); $sFileName = \preg_replace('/[\/]+/', '/', \preg_replace('/[.]+/', '.', $sFileName)); $sFileName = \preg_replace('/[^a-zA-Z0-9@_+=\-\.\/!()\[\]]/', '', $sFileName); } if (0 === \strlen($sFileName)) { $sFileName = 'rainloop-log.txt'; } return $sFileName; } /** * @return void */ public function SetAuthLogoutToken() { @\header('X-RainLoop-Action: Logout'); \RainLoop\Utils::SetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, \md5(APP_START_TIME), 0, '/', null, null, true); } /** * @param \RainLoop\Model\Account $oAccount * * @return void */ public function SetAuthToken($oAccount) { if ($oAccount) { $sSpecAuthToken = '_'.$oAccount->GetAuthToken(); $this->SetSpecAuthToken($sSpecAuthToken); \RainLoop\Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken, 0, '/', null, null, true); if ($oAccount->SignMe() && 0 < \strlen($oAccount->SignMeToken())) { \RainLoop\Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, \RainLoop\Utils::EncodeKeyValues(array( 'e' => $oAccount->Email(), 't' => $oAccount->SignMeToken() )), \time() + 60 * 60 * 24 * 30, '/', null, null, true); $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'sign_me', \RainLoop\Utils::EncodeKeyValues(array( 'Time' => \time(), 'AuthToken' => $oAccount->GetAuthToken(), 'SignMetToken' => $oAccount->SignMeToken() )) ); } } } /** * @return string */ public function GetSpecAuthTokenWithDeletion() { $sResult = \RainLoop\Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, ''); if (0 < strlen($sResult)) { \RainLoop\Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY); } return $sResult; } /** * @return string */ public function GetSpecAuthLogoutTokenWithDeletion() { $sResult = \RainLoop\Utils::GetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, ''); if (0 < strlen($sResult)) { \RainLoop\Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY); } return $sResult; } /** * @return void */ private function setAdminAuthToken($sToken) { \RainLoop\Utils::SetCookie(self::AUTH_ADMIN_TOKEN_KEY, $sToken, 0, '/', null, null, true); } /** * @return string */ private function getAuthToken() { $sToken = $this->GetSpecAuthToken(); return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : ''; } /** * @return string */ private function getAdminAuthToken() { return \RainLoop\Utils::GetCookie(self::AUTH_ADMIN_TOKEN_KEY, ''); } /** * @return void */ public function ClearAdminAuthToken() { $aAdminHash = \RainLoop\Utils::DecodeKeyValues($this->getAdminAuthToken()); if ( !empty($aAdminHash[0]) && !empty($aAdminHash[1]) && !empty($aAdminHash[2]) && 'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1] ) { $this->Cacher(null, true)->Delete(\RainLoop\KeyPathHelper::SessionAdminKey($aAdminHash[2])); } \RainLoop\Utils::ClearCookie(self::AUTH_ADMIN_TOKEN_KEY); } /** * @param bool $bThrowExceptionOnFalse = false * * @return \RainLoop\Model\Account|bool * @throws \RainLoop\Exceptions\ClientException */ public function GetAccount($bThrowExceptionOnFalse = false) { return $this->getAccountFromToken($bThrowExceptionOnFalse); } /** * @return \MailSo\Base\Http */ public function Http() { if (null === $this->oHttp) { $this->oHttp = \MailSo\Base\Http::SingletonInstance(); } return $this->oHttp; } /** * @return \RainLoop\Social */ public function Social() { if (null === $this->oSocial) { $this->oSocial = new \RainLoop\Social($this->Http(), $this); } return $this->oSocial; } /** * @return \MailSo\Mail\MailClient */ public function MailClient() { if (null === $this->oMailClient) { $this->oMailClient = \MailSo\Mail\MailClient::NewInstance(); $this->oMailClient->SetLogger($this->Logger()); } return $this->oMailClient; } /** * @return \RainLoop\Providers\Filters */ public function FiltersProvider() { if (null === $this->oFiltersProvider) { $this->oFiltersProvider = new \RainLoop\Providers\Filters( $this->fabrica('filters')); } return $this->oFiltersProvider; } /** * @return \RainLoop\Providers\ChangePassword */ public function ChangePasswordProvider() { if (null === $this->oChangePasswordProvider) { $this->oChangePasswordProvider = new \RainLoop\Providers\ChangePassword( $this, $this->fabrica('change-password'), !!$this->Config()->Get('labs', 'check_new_password_strength', true) ); } return $this->oChangePasswordProvider; } /** * @return \RainLoop\Providers\TwoFactorAuth */ public function TwoFactorAuthProvider() { if (null === $this->oTwoFactorAuthProvider) { $this->oTwoFactorAuthProvider = new \RainLoop\Providers\TwoFactorAuth( $this->Config()->Get('security', 'allow_two_factor_auth', false) ? $this->fabrica('two-factor-auth') : null ); } return $this->oTwoFactorAuthProvider; } /** * @param bool $bLocal = false * * @return \RainLoop\Providers\Storage */ public function StorageProvider($bLocal = false) { if ($bLocal) { if (null === $this->oLocalStorageProvider) { $this->oLocalStorageProvider = new \RainLoop\Providers\Storage( $this->fabrica('storage-local')); } return $this->oLocalStorageProvider; } else { if (null === $this->oStorageProvider) { $this->oStorageProvider = new \RainLoop\Providers\Storage( $this->fabrica('storage')); } return $this->oStorageProvider; } return null; } /** * @return \RainLoop\Providers\Settings */ public function SettingsProvider($bLocal = false) { if ($bLocal) { if (null === $this->oLocalSettingsProvider) { $this->oLocalSettingsProvider = new \RainLoop\Providers\Settings( $this->fabrica('settings-local')); } return $this->oLocalSettingsProvider; } else { if (null === $this->oSettingsProvider) { $this->oSettingsProvider = new \RainLoop\Providers\Settings( $this->fabrica('settings')); } return $this->oSettingsProvider; } return null; } /** * @return \RainLoop\Providers\Files */ public function FilesProvider() { if (null === $this->oFilesProvider) { $this->oFilesProvider = new \RainLoop\Providers\Files( $this->fabrica('files')); } return $this->oFilesProvider; } /** * @return \RainLoop\Providers\Domain */ public function DomainProvider() { if (null === $this->oDomainProvider) { $this->oDomainProvider = new \RainLoop\Providers\Domain( $this->fabrica('domain'), $this->Plugins()); } return $this->oDomainProvider; } /** * @return \RainLoop\Providers\Suggestions */ public function SuggestionsProvider() { if (null === $this->oSuggestionsProvider) { $this->oSuggestionsProvider = new \RainLoop\Providers\Suggestions($this->fabrica('suggestions')); } return $this->oSuggestionsProvider; } /** * @param \RainLoop\Model\Account $oAccount = null * @param bool $bForceEnable = false * * @return \RainLoop\Providers\AddressBook */ public function AddressBookProvider($oAccount = null, $bForceEnable = false) { if (null === $this->oAddressBookProvider) { $this->oAddressBookProvider = new \RainLoop\Providers\AddressBook( $this->Config()->Get('contacts', 'enable', false) || $bForceEnable ? $this->fabrica('address-book', $oAccount) : null); $this->oAddressBookProvider->SetLogger($this->Logger()); } return $this->oAddressBookProvider; } /** * @param \RainLoop\Model\Account $oAccount = null * @param bool $bForceFile = false * * @return \MailSo\Cache\CacheClient */ public function Cacher($oAccount = null, $bForceFile = false) { $sKey = ''; if ($oAccount) { $sKey = $oAccount->ParentEmailHelper(); } $sIndexKey = empty($sKey) ? '_default_' : $sKey; if ($bForceFile) { $sIndexKey .= '/_files_'; } if (!isset($this->aCachers[$sIndexKey])) { $this->aCachers[$sIndexKey] = \MailSo\Cache\CacheClient::NewInstance(); $oDriver = null; $sDriver = \strtoupper(\trim($this->Config()->Get('cache', 'fast_cache_driver', 'files'))); switch (true) { default: case $bForceFile: $oDriver = \MailSo\Cache\Drivers\File::NewInstance(APP_PRIVATE_DATA.'cache', $sKey); break; case ('APC' === $sDriver || 'APCU' === $sDriver) && \MailSo\Base\Utils::FunctionExistsAndEnabled(array( 'apc_store', 'apc_fetch', 'apc_delete', 'apc_clear_cache')): $oDriver = \MailSo\Cache\Drivers\APC::NewInstance($sKey); break; case ('MEMCACHE' === $sDriver || 'MEMCACHED' === $sDriver) && \MailSo\Base\Utils::FunctionExistsAndEnabled('memcache_connect'): $oDriver = \MailSo\Cache\Drivers\Memcache::NewInstance( $this->Config()->Get('labs', 'fast_cache_memcache_host', '127.0.0.1'), (int) $this->Config()->Get('labs', 'fast_cache_memcache_port', 11211), 43200, $sKey ); break; } if ($oDriver) { $this->aCachers[$sIndexKey]->SetDriver($oDriver); } $this->aCachers[$sIndexKey]->SetCacheIndex($this->Config()->Get('cache', 'fast_cache_index', '')); } return $this->aCachers[$sIndexKey]; } /** * @return \RainLoop\Plugins\Manager */ public function Plugins() { if (null === $this->oPlugins) { $this->oPlugins = new \RainLoop\Plugins\Manager($this); $this->oPlugins->SetLogger($this->Logger()); } return $this->oPlugins; } /** * @return \MailSo\Log\Logger */ public function Logger() { if (null === $this->oLogger) { $this->oLogger = \MailSo\Log\Logger::SingletonInstance(); if (!!$this->Config()->Get('logs', 'enable', false)) { $this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true)); $sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName( $this->Config()->Get('logs', 'filename', '')); $sLogFileDir = \dirname($sLogFileFullPath); if (!@is_dir($sLogFileDir)) { @mkdir($sLogFileDir, 0755, true); } $this->oLogger->Add( \MailSo\Log\Drivers\File::NewInstance($sLogFileFullPath) ->WriteOnErrorOnly($this->Config()->Get('logs', 'write_on_error_only', false)) ->WriteOnPhpErrorOnly($this->Config()->Get('logs', 'write_on_php_error_only', false)) ->WriteOnTimeoutOnly($this->Config()->Get('logs', 'write_on_timeout_only', 0)) ); if (!$this->Config()->Get('debug', 'enable', false)) { $this->oLogger->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME); } $this->oLogger->WriteEmptyLine(); $oHttp = $this->Http(); $this->oLogger->Write('[DATE:'.\gmdate('d.m.y').'][RL:'.APP_VERSION.'][PHP:'.PHP_VERSION.'][IP:'. $oHttp->GetClientIp($this->Config()->Get('labs', 'http_client_ip_check_proxy', false)).'][PID:'. (\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['. $oHttp->GetServer('SERVER_SOFTWARE', '~').']['. (\MailSo\Base\Utils::FunctionExistsAndEnabled('php_sapi_name') ? \php_sapi_name() : '~' ).']' ); $sPdo = (\class_exists('PDO') ? \implode(',', \PDO::getAvailableDrivers()) : 'off'); $sPdo = empty($sPdo) ? '~' : $sPdo; $this->oLogger->Write('['. 'Suhosin:'.(\extension_loaded('suhosin') || @\ini_get('suhosin.get.max_value_length') ? 'on' : 'off'). '][APC:'.(\MailSo\Base\Utils::FunctionExistsAndEnabled('apc_fetch') ? 'on' : 'off'). '][MB:'.(\MailSo\Base\Utils::FunctionExistsAndEnabled('mb_convert_encoding') ? 'on' : 'off'). '][PDO:'.$sPdo. (\RainLoop\Utils::IsOwnCloud() ? '][ownCloud:true' : ''). '][Streams:'.\implode(',', \stream_get_transports()). ']'); $this->oLogger->Write( '['.$oHttp->GetMethod().'] '.$oHttp->GetScheme().'://'.$oHttp->GetHost(false, false).$oHttp->GetServer('REQUEST_URI', ''), \MailSo\Log\Enumerations\Type::NOTE, 'REQUEST'); } } return $this->oLogger; } /** * @return \MailSo\Log\Logger */ public function LoggerAuth() { if (null === $this->oLoggerAuth) { $this->oLoggerAuth = \MailSo\Log\Logger::NewInstance(false); if (!!$this->Config()->Get('logs', 'auth_logging', false)) { $sAuthLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName( $this->Config()->Get('logs', 'auth_logging_filename', '')); $sLogFileDir = \dirname($sAuthLogFileFullPath); if (!@is_dir($sLogFileDir)) { @mkdir($sLogFileDir, 0755, true); } $this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::MEMORY); $this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME); $this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME_DELTA); $this->oLoggerAuth->Add( \MailSo\Log\Drivers\File::NewInstance($sAuthLogFileFullPath) ); } } return $this->oLoggerAuth; } /** * @return string */ private function getAdminToken() { $sRand = \MailSo\Base\Utils::Md5Rand(); if (!$this->Cacher(null, true)->Set(\RainLoop\KeyPathHelper::SessionAdminKey($sRand), \time())) { $this->oLogger->Write('Cannot store an admin token', \MailSo\Log\Enumerations\Type::WARNING); $sRand = ''; } return '' === $sRand ? '' : \RainLoop\Utils::EncodeKeyValues(array('token', \md5(APP_SALT), $sRand)); } /** * @param bool $bThrowExceptionOnFalse = true * * @return bool */ public function IsAdminLoggined($bThrowExceptionOnFalse = true) { $bResult = false; if ($this->Config()->Get('security', 'allow_admin_panel', true)) { $aAdminHash = \RainLoop\Utils::DecodeKeyValues($this->getAdminAuthToken()); if (!empty($aAdminHash[0]) && !empty($aAdminHash[1]) && !empty($aAdminHash[2]) && 'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1] && '' !== $this->Cacher(null, true)->Get(\RainLoop\KeyPathHelper::SessionAdminKey($aAdminHash[2]), '') ) { $bResult = true; } } if (!$bResult && $bThrowExceptionOnFalse) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } return $bResult; } /** * @param string $sTo */ public function SetMailtoRequest($sTo) { if (!empty($sTo)) { \RainLoop\Utils::SetCookie(self::AUTH_MAILTO_TOKEN_KEY, \RainLoop\Utils::EncodeKeyValues(array( 'Time' => \microtime(true), 'MailTo' => 'MailTo', 'To' => $sTo )), 0, '/', null, null, true); } } /** * @param string $sEmail * @param string $sLogin * @param string $sPassword * @param string $sSignMeToken = '' * @param bool $bThrowProvideException = false * * @return \RainLoop\Model\Account|null */ public function LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken = '', $bThrowProvideException = false) { $oAccount = null; if (0 < \strlen($sEmail) && 0 < \strlen($sLogin) && 0 < \strlen($sPassword)) { $oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true); if ($oDomain instanceof \RainLoop\Model\Domain) { if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) { $oAccount = \RainLoop\Model\Account::NewInstance($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken); $this->Plugins()->RunHook('filter.acount', array(&$oAccount)); if ($bThrowProvideException && !($oAccount instanceof \RainLoop\Model\Account)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } } else if ($bThrowProvideException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountNotAllowed); } } else if ($bThrowProvideException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::DomainNotAllowed); } } return $oAccount; } /** * @param string $sToken * @param bool $bThrowExceptionOnFalse = true * @param bool $bValidateShortToken = true * * @return \RainLoop\Model\Account|bool * @throws \RainLoop\Exceptions\ClientException */ public function GetAccountFromCustomToken($sToken, $bThrowExceptionOnFalse = true, $bValidateShortToken = true) { $oResult = false; if (!empty($sToken)) { $aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken); if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && // simple token validation 8 <= \count($aAccountHash) && // length checking !empty($aAccountHash[7]) && // does short token exist (!$bValidateShortToken || \RainLoop\Utils::GetShortToken() === $aAccountHash[7] || // check short token if needed (isset($aAccountHash[10]) && 0 < $aAccountHash[10] && \time() < $aAccountHash[10])) ) { $oAccount = $this->LoginProvide($aAccountHash[1], $aAccountHash[2], $aAccountHash[3], empty($aAccountHash[5]) ? '' : $aAccountHash[5], $bThrowExceptionOnFalse); if ($oAccount instanceof \RainLoop\Model\Account) { if (!empty($aAccountHash[8]) && !empty($aAccountHash[9])) // init proxy user/password { $oAccount->SetProxyAuthUser($aAccountHash[8]); $oAccount->SetProxyAuthPassword($aAccountHash[9]); } $this->Logger()->AddSecret($oAccount->Password()); $this->Logger()->AddSecret($oAccount->ProxyAuthPassword()); $oAccount->SetParentEmail($aAccountHash[6]); $oResult = $oAccount; } } else if ($bThrowExceptionOnFalse) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } } if ($bThrowExceptionOnFalse && !($oResult instanceof \RainLoop\Model\Account)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } return $oResult; } /** * @return \RainLoop\Model\Account|bool */ public function GetAccountFromSignMeToken() { $oAccount = false; $sSignMeToken = \RainLoop\Utils::GetCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY, ''); if (!empty($sSignMeToken)) { $aTokenData = \RainLoop\Utils::DecodeKeyValues($sSignMeToken); if (\is_array($aTokenData) && !empty($aTokenData['e']) && !empty($aTokenData['t'])) { $sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'], \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'sign_me' ); if (!empty($sTokenSettings)) { $aSignMeData = \RainLoop\Utils::DecodeKeyValues($sTokenSettings); if (\is_array($aSignMeData) && !empty($aSignMeData['AuthToken']) && !empty($aSignMeData['SignMetToken']) && $aSignMeData['SignMetToken'] === $aTokenData['t']) { $oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false); } } } } else { \RainLoop\Utils::ClearCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY); } return $oAccount; } /** * @param bool $bThrowExceptionOnFalse = true * * @return \RainLoop\Model\Account|bool * @throws \RainLoop\Exceptions\ClientException */ private function getAccountFromToken($bThrowExceptionOnFalse = true) { return $this->GetAccountFromCustomToken($this->getAuthToken(), $bThrowExceptionOnFalse); } /** * @return bool */ private function PremType() { static $bResult = null; if (null === $bResult) { $bResult = $this->licenseParser($this->licenseHelper(false, true)); } return $bResult; } /** * @param bool $bAdmin * @param string $sAuthAccountHash = '' * * @return array */ public function AppData($bAdmin, $sAuthAccountHash = '') { if (0 < \strlen($sAuthAccountHash) && \preg_match('/[^_\-\.a-zA-Z0-9]/', $sAuthAccountHash)) { $sAuthAccountHash = ''; } $oAccount = null; $oConfig = $this->Config(); $aResult = array( 'Version' => APP_VERSION, 'Auth' => false, 'AccountHash' => '', 'WebPath' => \RainLoop\Utils::WebPath(), 'WebVersionPath' => \RainLoop\Utils::WebVersionPath(), 'AccountSignMe' => false, 'AuthAccountHash' => '', 'MailToEmail' => '', 'Email' => '', 'DevEmail' => '', 'DevPassword' => '', 'Title' => 'RainLoop Webmail', 'LoadingDescription' => 'RainLoop', 'LoadingDescriptionEsc' => 'RainLoop', 'LoginDescription' => '', 'LoginPowered' => true, 'LoginLogo' => '', 'LoginBackground' => '', 'LoginCss' => '', 'UserLogo' => '', 'UserCss' => '', 'IncludeCss' => '', 'IncludeBackground' => '', 'Token' => $oConfig->Get('security', 'csrf_protection', false) ? \RainLoop\Utils::GetCsrfToken() : '', 'InIframe' => (bool) $oConfig->Get('labs', 'in_iframe', false), 'AllowAdminPanel' => (bool) $oConfig->Get('security', 'allow_admin_panel', true), 'AllowHtmlEditorSourceButton' => (bool) $oConfig->Get('labs', 'allow_html_editor_source_button', false), 'AllowHtmlEditorBitiButtons' => (bool) $oConfig->Get('labs', 'allow_html_editor_biti_buttons', false), 'AllowCtrlEnterOnCompose' => (bool) $oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false), 'UseRsaEncryption' => (bool) $oConfig->Get('security', 'use_rsa_encryption', false), 'RsaPublicKey' => '', 'HideDangerousActions' => $oConfig->Get('labs', 'hide_dangerous_actions', false), 'CustomLoginLink' => $oConfig->Get('labs', 'custom_login_link', ''), 'CustomLogoutLink' => $oConfig->Get('labs', 'custom_logout_link', ''), 'LoginDefaultDomain' => $oConfig->Get('login', 'default_domain', ''), 'DetermineUserLanguage' => (bool) $oConfig->Get('login', 'determine_user_language', true), 'DetermineUserDomain' => (bool) $oConfig->Get('login', 'determine_user_domain', false), 'ForgotPasswordLinkUrl' => \trim($oConfig->Get('login', 'forgot_password_link_url', '')), 'RegistrationLinkUrl' => \trim($oConfig->Get('login', 'registration_link_url', '')), 'ContactsIsAllowed' => false, 'ChangePasswordIsAllowed' => false, 'JsHash' => \md5(\RainLoop\Utils::GetConnectionToken()), 'UseImapThread' => (bool) $oConfig->Get('labs', 'use_imap_thread', false), 'UseImapSubscribe' => (bool) $oConfig->Get('labs', 'use_imap_list_subscribe', true), 'AllowAppendMessage' => (bool) $oConfig->Get('labs', 'allow_message_append', false), 'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true), 'PremType' => $this->PremType(), 'Admin' => array(), 'Capa' => array(), 'Plugins' => array() ); if ($aResult['UseRsaEncryption'] && \file_exists(APP_PRIVATE_DATA.'rsa/public') && \file_exists(APP_PRIVATE_DATA.'rsa/private')) { $aResult['RsaPublicKey'] = \file_get_contents(APP_PRIVATE_DATA.'rsa/public'); $aResult['RsaPublicKey'] = $aResult['RsaPublicKey'] ? $aResult['RsaPublicKey'] : ''; if (false === \strpos($aResult['RsaPublicKey'], 'PUBLIC KEY')) { $aResult['RsaPublicKey'] = ''; } } if (0 === strlen($aResult['RsaPublicKey'])) { $aResult['UseRsaEncryption'] = false; } if (0 < \strlen($sAuthAccountHash)) { $aResult['AuthAccountHash'] = $sAuthAccountHash; } $aResult['Title'] = $oConfig->Get('webmail', 'title', ''); $aResult['LoadingDescription'] = $oConfig->Get('webmail', 'loading_description', ''); if ($this->PremType()) { $aResult['LoginLogo'] = $oConfig->Get('branding', 'login_logo', ''); $aResult['LoginBackground'] = $oConfig->Get('branding', 'login_background', ''); $aResult['LoginCss'] = $oConfig->Get('branding', 'login_css', ''); $aResult['LoginDescription'] = $oConfig->Get('branding', 'login_desc', ''); $aResult['LoginPowered'] = !!$oConfig->Get('branding', 'login_powered', true); $aResult['UserLogo'] = $oConfig->Get('branding', 'user_logo', ''); $aResult['UserCss'] = $oConfig->Get('branding', 'user_css', ''); } $aResult['LoadingDescriptionEsc'] = \htmlspecialchars($aResult['LoadingDescription'], ENT_QUOTES|ENT_IGNORE, 'UTF-8'); $oSettings = null; $oSettingsLocal = null; if (!$bAdmin) { $oAccount = $this->getAccountFromToken(false); if ($oAccount instanceof \RainLoop\Model\Account) { $aResult['IncludeCss'] = $aResult['UserCss']; $oAddressBookProvider = $this->AddressBookProvider($oAccount); $aResult['Auth'] = true; $aResult['Email'] = $oAccount->Email(); $aResult['IncLogin'] = $oAccount->IncLogin(); $aResult['OutLogin'] = $oAccount->OutLogin(); $aResult['AccountHash'] = $oAccount->Hash(); $aResult['AccountSignMe'] = $oAccount->SignMe(); $aResult['ChangePasswordIsAllowed'] = $this->ChangePasswordProvider()->PasswordChangePossibility($oAccount); $aResult['ContactsIsAllowed'] = $oAddressBookProvider->IsActive(); $aResult['ContactsSharingIsAllowed'] = $oAddressBookProvider->IsSharingAllowed(); $aResult['ContactsSyncIsAllowed'] = (bool) $oConfig->Get('contacts', 'allow_sync', false); $aResult['ContactsSyncInterval'] = (int) $oConfig->Get('contacts', 'sync_interval', 20); $aResult['EnableContactsSync'] = false; $aResult['ContactsSyncUrl'] = ''; $aResult['ContactsSyncUser'] = ''; $aResult['ContactsSyncPassword'] = ''; if ($aResult['ContactsIsAllowed'] && $aResult['ContactsSyncIsAllowed']) { $mData = $this->getContactsSyncData($oAccount); if (\is_array($mData)) { $aResult['EnableContactsSync'] = isset($mData['Enable']) ? !!$mData['Enable'] : false; $aResult['ContactsSyncUrl'] = isset($mData['Url']) ? \trim($mData['Url']) : ''; $aResult['ContactsSyncUser'] = isset($mData['User']) ? \trim($mData['User']) : ''; $aResult['ContactsSyncPassword'] = APP_DUMMY; } } if ($aResult['AccountSignMe']) { $sToken = \RainLoop\Utils::GetCookie(self::AUTH_MAILTO_TOKEN_KEY, null); if (null !== $sToken) { \RainLoop\Utils::ClearCookie(self::AUTH_MAILTO_TOKEN_KEY); $mMailToData = \RainLoop\Utils::DecodeKeyValues($sToken); if (\is_array($mMailToData) && !empty($mMailToData['MailTo']) && 'MailTo' === $mMailToData['MailTo'] && !empty($mMailToData['To'])) { $aResult['MailToEmail'] = $mMailToData['To']; } } } $oSettings = $this->SettingsProvider()->Load($oAccount); $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); } else { $oAccount = null; $aResult['IncludeBackground'] = $aResult['LoginBackground']; $aResult['IncludeCss'] = $aResult['LoginCss']; $aResult['DevEmail'] = $oConfig->Get('labs', 'dev_email', ''); $aResult['DevPassword'] = $oConfig->Get('labs', 'dev_password', ''); } $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); $aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); $aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true); $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); $aResult['GoogleClientID'] = \trim($oConfig->Get('social', 'google_client_id', '')); $aResult['GoogleApiKey'] = \trim($oConfig->Get('social', 'google_api_key', '')); if (!$aResult['AllowGoogleSocial'] || ($aResult['AllowGoogleSocial'] && ( '' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', ''))))) { $aResult['AllowGoogleSocialAuth'] = false; $aResult['AllowGoogleSocialDrive'] = false; $aResult['GoogleClientID'] = ''; $aResult['GoogleApiKey'] = ''; } if (!$aResult['AllowGoogleSocial']) { $aResult['AllowGoogleSocialPreview'] = false; } if ($aResult['AllowGoogleSocial'] && !$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview']) { $aResult['AllowGoogleSocial'] = false; } $aResult['AllowFacebookSocial'] = (bool) $oConfig->Get('social', 'fb_enable', false); if ($aResult['AllowFacebookSocial'] && ( '' === \trim($oConfig->Get('social', 'fb_app_id', '')) || '' === \trim($oConfig->Get('social', 'fb_app_secret', '')))) { $aResult['AllowFacebookSocial'] = false; } $aResult['AllowTwitterSocial'] = (bool) $oConfig->Get('social', 'twitter_enable', false); if ($aResult['AllowTwitterSocial'] && ( '' === \trim($oConfig->Get('social', 'twitter_consumer_key', '')) || '' === \trim($oConfig->Get('social', 'twitter_consumer_secret', '')))) { $aResult['AllowTwitterSocial'] = false; } $aResult['AllowDropboxSocial'] = (bool) $oConfig->Get('social', 'dropbox_enable', false); $aResult['DropboxApiKey'] = \trim($oConfig->Get('social', 'dropbox_api_key', '')); if (!$aResult['AllowDropboxSocial']) { $aResult['DropboxApiKey'] = ''; } else if (0 === strlen($aResult['DropboxApiKey'])) { $aResult['AllowDropboxSocial'] = false; } $aResult['Capa'] = $this->Capa(false, $oAccount); } else { $aResult['Auth'] = $this->IsAdminLoggined(false); if ($aResult['Auth']) { $aResult['AdminLogin'] = $oConfig->Get('security', 'admin_login', ''); $aResult['AdminDomain'] = APP_SITE; $aResult['UseTokenProtection'] = (bool) $oConfig->Get('security', 'csrf_protection', true); $aResult['EnabledPlugins'] = (bool) $oConfig->Get('plugins', 'enable', false); $aResult['VerifySslCertificate'] = !!$oConfig->Get('ssl', 'verify_certificate', false); $aResult['AllowSelfSigned'] = !!$oConfig->Get('ssl', 'allow_self_signed', true); $aDrivers = \class_exists('PDO') ? \PDO::getAvailableDrivers() : array(); $aResult['MySqlIsSupported'] = \is_array($aDrivers) ? \in_array('mysql', $aDrivers) : false; $aResult['SQLiteIsSupported'] = \is_array($aDrivers) ? \in_array('sqlite', $aDrivers) : false; $aResult['PostgreSqlIsSupported'] = \is_array($aDrivers) ? \in_array('pgsql', $aDrivers) : false; $aResult['ContactsEnable'] = (bool) $oConfig->Get('contacts', 'enable', false); $aResult['ContactsSharing'] = (bool) $oConfig->Get('contacts', 'allow_sharing', false); $aResult['ContactsSync'] = (bool) $oConfig->Get('contacts', 'allow_sync', false); $aResult['ContactsPdoType'] = $this->ValidateContactPdoType(\trim($this->Config()->Get('contacts', 'type', 'sqlite'))); $aResult['ContactsPdoDsn'] = (string) $oConfig->Get('contacts', 'pdo_dsn', ''); $aResult['ContactsPdoType'] = (string) $oConfig->Get('contacts', 'type', ''); $aResult['ContactsPdoUser'] = (string) $oConfig->Get('contacts', 'pdo_user', ''); $aResult['ContactsPdoPassword'] = APP_DUMMY; $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); $aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); $aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true); $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); $aResult['GoogleClientID'] = (string) $oConfig->Get('social', 'google_client_id', ''); $aResult['GoogleClientSecret'] = (string) $oConfig->Get('social', 'google_client_secret', ''); $aResult['GoogleApiKey'] = (string) $oConfig->Get('social', 'google_api_key', ''); $aResult['AllowFacebookSocial'] = (bool) $oConfig->Get('social', 'fb_enable', false); $aResult['FacebookAppID'] = (string) $oConfig->Get('social', 'fb_app_id', ''); $aResult['FacebookAppSecret'] = (string) $oConfig->Get('social', 'fb_app_secret', ''); $aResult['AllowTwitterSocial'] = (bool) $oConfig->Get('social', 'twitter_enable', false); $aResult['TwitterConsumerKey'] = (string) $oConfig->Get('social', 'twitter_consumer_key', ''); $aResult['TwitterConsumerSecret'] = (string) $oConfig->Get('social', 'twitter_consumer_secret', ''); $aResult['AllowDropboxSocial'] = (bool) $oConfig->Get('social', 'dropbox_enable', false); $aResult['DropboxApiKey'] = (string) $oConfig->Get('social', 'dropbox_api_key', ''); $aResult['SubscriptionEnabled'] = \MailSo\Base\Utils::ValidateDomain($aResult['AdminDomain']); $aResult['WeakPassword'] = $oConfig->ValidatePassword('12345'); $aResult['CoreAccess'] = $this->rainLoopCoreAccess(); $aResult['PhpUploadSizes'] = array( 'upload_max_filesize' => \ini_get('upload_max_filesize'), 'post_max_size' => \ini_get('post_max_size') ); } $aResult['Capa'] = $this->Capa(true); } $aResult['SupportedFacebookSocial'] = (bool) \version_compare(PHP_VERSION, '5.4.0', '>='); if (!$aResult['SupportedFacebookSocial']) { $aResult['AllowFacebookSocial'] = false; $aResult['FacebookAppID'] = ''; $aResult['FacebookAppSecret'] = ''; } $aResult['ProjectHash'] = \md5($aResult['AccountHash'].APP_VERSION.$this->Plugins()->Hash()); $sLanguage = $oConfig->Get('webmail', 'language', 'en'); $sTheme = $oConfig->Get('webmail', 'theme', 'Default'); $aResult['Themes'] = $this->GetThemes(); $aResult['Languages'] = $this->GetLanguages(); $aResult['AllowLanguagesOnSettings'] = (bool) $oConfig->Get('webmail', 'allow_languages_on_settings', true); $aResult['AllowLanguagesOnLogin'] = (bool) $oConfig->Get('login', 'allow_languages_on_login', true); $aResult['AttachmentLimit'] = ((int) $oConfig->Get('webmail', 'attachment_size_limit', 10)) * 1024 * 1024; $aResult['SignMe'] = (string) $oConfig->Get('login', 'sign_me_auto', \RainLoop\Enumerations\SignMeType::DEFAILT_OFF); $aResult['UseLocalProxyForExternalImages'] = (bool) $oConfig->Get('labs', 'use_local_proxy_for_external_images', false); // user $aResult['ShowImages'] = (bool) $oConfig->Get('defaults', 'show_images', false); $aResult['MPP'] = (int) $oConfig->Get('webmail', 'messages_per_page', 25); $aResult['SoundNotification'] = false; $aResult['DesktopNotifications'] = false; $aResult['Layout'] = (int) $oConfig->Get('defaults', 'view_layout', \RainLoop\Enumerations\Layout::SIDE_PREVIEW); $aResult['EditorDefaultType'] = (string) $oConfig->Get('defaults', 'view_editor_type', ''); $aResult['UseCheckboxesInList'] = (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true); $aResult['AutoLogout'] = (int) $oConfig->Get('defaults', 'autologout', 30); $aResult['UseThreads'] = (bool) $oConfig->Get('defaults', 'mail_use_threads', false); $aResult['ReplySameFolder'] = (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false); $aResult['ContactsAutosave'] = (bool) $oConfig->Get('defaults', 'contacts_autosave', true); $aResult['EnableTwoFactor'] = false; $aResult['ParentEmail'] = ''; $aResult['InterfaceAnimation'] = true; $aResult['UserBackgroundName'] = ''; $aResult['UserBackgroundHash'] = ''; if (!$bAdmin && $oSettings instanceof \RainLoop\Settings && $oSettingsLocal instanceof \RainLoop\Settings) { if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) { $sLanguage = $oSettings->GetConf('Language', $sLanguage); } if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::THEMES, $oAccount)) { $sTheme = $oSettingsLocal->GetConf('Theme', $sTheme); } $aResult['SentFolder'] = $oSettingsLocal->GetConf('SentFolder', ''); $aResult['DraftFolder'] = $oSettingsLocal->GetConf('DraftFolder', ''); $aResult['SpamFolder'] = $oSettingsLocal->GetConf('SpamFolder', ''); $aResult['TrashFolder'] = $oSettingsLocal->GetConf('TrashFolder', ''); $aResult['ArchiveFolder'] = $oSettingsLocal->GetConf('ArchiveFolder', ''); $aResult['NullFolder'] = $oSettingsLocal->GetConf('NullFolder', ''); $aResult['EditorDefaultType'] = $oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']); $aResult['ShowImages'] = (bool) $oSettings->GetConf('ShowImages', $aResult['ShowImages']); $aResult['ContactsAutosave'] = (bool) $oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']); $aResult['MPP'] = (int) $oSettings->GetConf('MPP', $aResult['MPP']); $aResult['SoundNotification'] = (bool) $oSettings->GetConf('SoundNotification', $aResult['SoundNotification']); $aResult['DesktopNotifications'] = (bool) $oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']); $aResult['UseCheckboxesInList'] = (bool) $oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']); $aResult['AutoLogout'] = (int) $oSettings->GetConf('AutoLogout', $aResult['AutoLogout']); $aResult['Layout'] = (int) $oSettings->GetConf('Layout', $aResult['Layout']); $aResult['UseThreads'] = (bool) $oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']); $aResult['ReplySameFolder'] = (bool) $oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::AUTOLOGOUT, $oAccount)) { $aResult['AutoLogout'] = 0; } if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) { $aResult['UserBackgroundName'] = (string) $oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']); $aResult['UserBackgroundHash'] = (string) $oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']); // if (!empty($aResult['UserBackgroundName']) && !empty($aResult['UserBackgroundHash'])) // { // $aResult['IncludeBackground'] = './?/Raw/&s=/{{USER}}/UserBackground/&ss=/'. // $aResult['UserBackgroundHash'].'/'; // } } $aResult['EnableTwoFactor'] = !!$oSettings->GetConf('EnableTwoFactor', $aResult['EnableTwoFactor']); $aResult['ParentEmail'] = $oAccount->ParentEmail(); } if (0 < \strlen($aResult['ParentEmail'])) { $aResult['AllowGoogleSocial'] = false; $aResult['AllowGoogleSocialAuth'] = false; $aResult['AllowGoogleSocialDrive'] = false; $aResult['AllowFacebookSocial'] = false; $aResult['AllowTwitterSocial'] = false; } $sStaticCache = \md5(APP_VERSION.$this->Plugins()->Hash()); $sTheme = $this->ValidateTheme($sTheme); $sNewThemeLink = './?/Css/0/'.($bAdmin ? 'Admin' : 'User').'/-/'.$sTheme.'/-/'.$sStaticCache.'/Hash/-/'; $bUserLanguage = false; if (!$bAdmin && !$aResult['Auth'] && !empty($_COOKIE['rllang']) && $oConfig->Get('login', 'allow_languages_on_login', true)) { $sLanguage = $_COOKIE['rllang']; } else if (!$bAdmin && !$aResult['Auth']) { $sUserLanguage = ''; if (!$bAdmin && !$aResult['Auth'] && $oConfig->Get('login', 'allow_languages_on_login', true) && $oConfig->Get('login', 'determine_user_language', true)) { $sUserLanguage = $this->detectUserLanguage(); } $sLanguage = $this->ValidateLanguage($sLanguage); if (0 < \strlen($sUserLanguage) && $sLanguage !== $sUserLanguage) { $sLanguage = $sUserLanguage; $bUserLanguage = true; } } $sPluginsLink = ''; if (0 < $this->Plugins()->Count() && $this->Plugins()->HaveJs($bAdmin)) { $sPluginsLink = './?/Plugins/0/'.($bAdmin ? 'Admin' : 'User').'/'.$sStaticCache.'/'; } $aResult['Theme'] = $sTheme; $aResult['NewThemeLink'] = $sNewThemeLink; $aResult['Language'] = $this->ValidateLanguage($sLanguage); $aResult['UserLanguage'] = $bUserLanguage; $aResult['LangLink'] = './?/Lang/0/'.($bAdmin ? 'en' : $aResult['Language']).'/'.$sStaticCache.'/'; $aResult['TemplatesLink'] = './?/Templates/0/'.($bAdmin ? 'Admin' : 'App').'/'.$sStaticCache.'/'; $aResult['PluginsLink'] = $sPluginsLink; $aResult['EditorDefaultType'] = \in_array($aResult['EditorDefaultType'], array('Plain', 'Html', 'HtmlForced', 'PlainForced')) ? $aResult['EditorDefaultType'] : 'Plain'; // IDN $aResult['Email'] = \MailSo\Base\Utils::IdnToUtf8($aResult['Email']); $aResult['ParentEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['ParentEmail']); $aResult['MailToEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['MailToEmail']); $aResult['DevEmail'] = \MailSo\Base\Utils::IdnToUtf8($aResult['DevEmail']); $this->Plugins()->InitAppData($bAdmin, $aResult, $oAccount); return $aResult; } private function detectUserLanguage() { $sLang = ''; $sAcceptLang = $this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'); if (false !== \strpos($sAcceptLang, ',')) { $aParts = \explode(',', $sAcceptLang, 2); $sLang = !empty($aParts[0]) ? \strtolower($aParts[0]) : ''; } if (!empty($sLang)) { $sLang = \preg_replace('/[^a-zA-Z0-9]+/', '-', $sLang); if ($sLang !== $this->ValidateLanguage($sLang)) { if (2 < strlen($sLang)) { $sLang = \substr($sLang, 0, 2); } } } return $this->ValidateLanguage($sLang); } private function loginErrorDelay() { $iDelay = (int) $this->Config()->Get('labs', 'login_fault_delay', 0); if (0 < $iDelay) { \sleep($iDelay); } } /** * @param \RainLoop\Model\Account $oAccount */ public function AuthToken($oAccount) { if ($oAccount instanceof \RainLoop\Model\Account) { $this->SetAuthToken($oAccount); $aAccounts = $this->GetAccounts($oAccount); if (\is_array($aAccounts) && isset($aAccounts[$oAccount->Email()])) { $aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken(); $this->SetAccounts($oAccount, $aAccounts); } } } /** * @param \RainLoop\Model\Account $oAccount * @param bool $bAuthLog = false * * @throws \RainLoop\Exceptions\ClientException */ public function CheckMailConnection($oAccount, $bAuthLog = false) { try { $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); } catch (\RainLoop\Exceptions\ClientException $oException) { throw $oException; } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); } catch (\MailSo\Imap\Exceptions\LoginBadCredentialsException $oException) { if ($bAuthLog) { $sLine = $this->Config()->Get('logs', 'auth_logging_format', ''); if (!empty($sLine)) { $this->LoggerAuth()->Write($this->compileLogParams($sLine, $oAccount), \MailSo\Log\Enumerations\Type::WARNING, 'IMAP'); } } if ($this->Config()->Get('labs', 'imap_show_login_alert', true)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException, $oException->getAlertFromStatus()); } else { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException); } } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException); } } /** * @param string $sEmail * @param string $sPassword * @param string $sSignMeToken = '' * @param string $sAdditionalCode = '' * @param string $bAdditionalCodeSignMe = false * * @return \RainLoop\Model\Account * @throws \RainLoop\Exceptions\ClientException */ public function LoginProcess(&$sEmail, &$sPassword, $sSignMeToken = '', $sAdditionalCode = '', $bAdditionalCodeSignMe = false) { $this->Plugins()->RunHook('filter.login-credentials.step-1', array(&$sEmail, &$sPassword)); $sEmail = \MailSo\Base\Utils::StrToLowerIfAscii($sEmail); if (false === \strpos($sEmail, '@')) { $this->Logger()->Write('The email address "'.$sEmail.'" is not complete', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); if (false === \strpos($sEmail, '@') && !!$this->Config()->Get('login', 'determine_user_domain', false)) { $sUserHost = \trim($this->Http()->GetHost(false, true, true)); $this->Logger()->Write('Determined user domain: '.$sUserHost, \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); $bAdded = false; $iLimit = 14; $aDomainParts = \explode('.', $sUserHost); $oDomainProvider = $this->DomainProvider(); while (0 < \count($aDomainParts) && 0 < $iLimit) { $sLine = \trim(\implode('.', $aDomainParts), '. '); $oDomain = $oDomainProvider->Load($sLine); if ($oDomain && $oDomain instanceof \RainLoop\Model\Domain) { $bAdded = true; $this->Logger()->Write('Check "'.$sLine.'": OK ('.$sEmail.' > '.$sEmail.'@'.$sLine.')', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); $sEmail = $sEmail.'@'.$sLine; break; } else { $this->Logger()->Write('Check "'.$sLine.'": NO', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); } \array_shift($aDomainParts); $iLimit--; } if (!$bAdded) { $this->Logger()->Write('Domain was not found!', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); } } $sDefDomain = \trim($this->Config()->Get('login', 'default_domain', '')); if (false === \strpos($sEmail, '@') && 0 < \strlen($sDefDomain)) { $this->Logger()->Write('Default domain "'.$sDefDomain.'" was used. ('.$sEmail.' > '.$sEmail.'@'.$sDefDomain.')', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); $sEmail = $sEmail.'@'.$sDefDomain; } } $this->Plugins()->RunHook('filter.login-credentials.step-2', array(&$sEmail, &$sPassword)); if (false === \strpos($sEmail, '@') || 0 === \strlen($sPassword)) { $this->loginErrorDelay(); throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); } $this->Logger()->AddSecret($sPassword); $sLogin = $sEmail; $this->Plugins()->RunHook('filter.login-credentials', array(&$sEmail, &$sLogin, &$sPassword)); $this->Logger()->AddSecret($sPassword); $this->Plugins()->RunHook('event.login-pre-login-provide', array()); try { $oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken, true); if (!($oAccount instanceof \RainLoop\Model\Account)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } $this->Plugins()->RunHook('event.login-post-login-provide', array(&$oAccount)); if (!($oAccount instanceof \RainLoop\Model\Account)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } } catch (\Exception $oException) { $this->loginErrorDelay(); throw $oException; } // Two factor auth if ($this->TwoFactorAuthProvider()->IsActive()) { $aData = $this->getTwoFactorInfo($oAccount); if ($aData && isset($aData['IsSet'], $aData['Enable']) && !empty($aData['Secret']) && $aData['IsSet'] && $aData['Enable']) { $sSecretHash = \md5(APP_SALT.$aData['Secret'].\RainLoop\Utils::Fingerprint()); $sSecretCookieHash = \RainLoop\Utils::GetCookie(self::AUTH_TFA_SIGN_ME_TOKEN_KEY, ''); if (empty($sSecretCookieHash) || $sSecretHash !== $sSecretCookieHash) { $sAdditionalCode = \trim($sAdditionalCode); if (empty($sAdditionalCode)) { $this->Logger()->Write('TFA: Required Code for '.$oAccount->ParentEmailHelper().' account.'); throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountTwoFactorAuthRequired); } else { $this->Logger()->Write('TFA: Verify Code for '.$oAccount->ParentEmailHelper().' account.'); $bGood = false; if (6 < \strlen($sAdditionalCode) && !empty($aData['BackupCodes'])) { $aBackupCodes = \explode(' ', \trim(\preg_replace('/[^\d]+/', ' ', $aData['BackupCodes']))); $bGood = \in_array($sAdditionalCode, $aBackupCodes); if ($bGood) { $this->removeBackupCodeFromTwoFactorInfo($oAccount->ParentEmailHelper(), $sAdditionalCode); } } if ($bAdditionalCodeSignMe) { \RainLoop\Utils::SetCookie(self::AUTH_TFA_SIGN_ME_TOKEN_KEY, $sSecretHash, \time() + 60 * 60 * 24 * 14, '/', null, null, true); } if (!$bGood && !$this->TwoFactorAuthProvider()->VerifyCode($aData['Secret'], $sAdditionalCode)) { $this->loginErrorDelay(); throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountTwoFactorAuthError); } } } } } try { $this->CheckMailConnection($oAccount, true); } catch (\Exception $oException) { $this->loginErrorDelay(); throw $oException; } return $oAccount; } /** * @param string $sEncryptedData * * @return string */ private function clientRsaDecryptHelper($sEncryptedData) { $aMatch = array(); if ('rsa:xxx:' === substr($sEncryptedData, 0, 8) && $this->Config()->Get('security', 'use_rsa_encryption', false)) { $oLogger = $this->Logger(); $oLogger->Write('Trying to decode encrypted data', \MailSo\Log\Enumerations\Type::INFO, 'RSA'); $sPrivateKey = file_exists(APP_PRIVATE_DATA.'rsa/private') ? \file_get_contents(APP_PRIVATE_DATA.'rsa/private') : ''; if (!empty($sPrivateKey)) { $sData = \trim(\substr($sEncryptedData, 8)); if (!\class_exists('Crypt_RSA')) { \set_include_path(\get_include_path().PATH_SEPARATOR.APP_VERSION_ROOT_PATH.'app/libraries/phpseclib'); include_once 'Crypt/RSA.php'; \defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); } $oLogger->HideErrorNotices(true); $oRsa = new \Crypt_RSA(); $oRsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); $oRsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1); $oRsa->setPrivateKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1); $oRsa->loadKey($sPrivateKey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1); $sData = $oRsa->decrypt(\base64_decode($sData)); if (\preg_match('/^[a-z0-9]{32}:(.+):[a-z0-9]{32}$/', $sData, $aMatch) && isset($aMatch[1])) { $sEncryptedData = $aMatch[1]; } else { $oLogger->Write('Invalid decrypted data', \MailSo\Log\Enumerations\Type::WARNING, 'RSA'); } $oLogger->HideErrorNotices(false); } else { $oLogger->Write('Private key is not found', \MailSo\Log\Enumerations\Type::WARNING, 'RSA'); } } return $sEncryptedData; } /** * @param string $sEmail * * @return string */ private function generateSignMeToken($sEmail) { return \MailSo\Base\Utils::Md5Rand(APP_SALT.$sEmail); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoLogin() { $sEmail = \trim($this->GetActionParam('Email', '')); $sPassword = $this->GetActionParam('Password', ''); $sLanguage = $this->GetActionParam('Language', ''); $bSignMe = '1' === (string) $this->GetActionParam('SignMe', '0'); $sAdditionalCode = $this->GetActionParam('AdditionalCode', ''); $bAdditionalCodeSignMe = '1' === (string) $this->GetActionParam('AdditionalCodeSignMe', '0'); $oAccount = null; $sPassword = $this->clientRsaDecryptHelper($sPassword); $this->Logger()->AddSecret($sPassword); if (0 < \strlen($sEmail) && 0 < \strlen($sPassword) && $this->Config()->Get('security', 'allow_universal_login', true) && $this->Config()->Get('security', 'allow_admin_panel', true) && $sEmail === $this->Config()->Get('security', 'admin_login', '') ) { if ($this->Config()->ValidatePassword($sPassword)) { $this->setAdminAuthToken($this->getAdminToken()); return $this->DefaultResponse(__FUNCTION__, true, array( 'Admin' => true )); } else { $this->loginErrorDelay(); throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } } try { $oAccount = $this->LoginProcess($sEmail, $sPassword, $bSignMe ? $this->generateSignMeToken($sEmail) : '', $sAdditionalCode, $bAdditionalCodeSignMe); } catch (\RainLoop\Exceptions\ClientException $oException) { if ($oException && \RainLoop\Notifications::AccountTwoFactorAuthRequired === $oException->getCode()) { return $this->DefaultResponse(__FUNCTION__, true, array( 'TwoFactorAuth' => true )); } else { throw $oException; } } $this->AuthToken($oAccount); if ($oAccount && 0 < \strlen($sLanguage)) { $oSettings = $this->SettingsProvider()->Load($oAccount); if ($oSettings) { $sLanguage = $this->ValidateLanguage($sLanguage); $sCurrentLanguage = $oSettings->GetConf('Language', ''); if ($sCurrentLanguage !== $sLanguage) { $oSettings->SetConf('Language', $sLanguage); $this->SettingsProvider()->Save($oAccount, $oSettings); } } } return $this->TrueResponse(__FUNCTION__); } /** * @param \RainLoop\Model\Account $oAccount * * @return array */ public function GetAccounts($oAccount) { if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { $sAccounts = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts' ); $aAccounts = array(); if ('' !== $sAccounts && '{' === \substr($sAccounts, 0, 1)) { $aAccounts = @\json_decode($sAccounts, true); } if (\is_array($aAccounts) && 0 < \count($aAccounts)) { if (1 === \count($aAccounts)) { $this->SetAccounts($oAccount, array()); } else if (1 < \count($aAccounts)) { $sOrder = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts_identities_order' ); $aOrder = empty($sOrder) ? array() : @\json_decode($sOrder, true); if (isset($aOrder['Accounts']) && \is_array($aOrder['Accounts']) && 1 < \count($aOrder['Accounts'])) { $aAccounts = \array_merge(\array_flip($aOrder['Accounts']), $aAccounts); $aAccounts = \array_filter($aAccounts, function ($sHash) { return 5 < \strlen($sHash); }); } } return $aAccounts; } } $aAccounts = array(); if (!$oAccount->IsAdditionalAccount()) { $aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken(); } return $aAccounts; } /** * @param \RainLoop\Model\Account $oAccount * * @return array */ public function GetTemplates($oAccount) { $aTemplates = array(); if ($oAccount) { $aData = array(); $sData = $this->StorageProvider(true)->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'templates' ); if ('' !== $sData && '[' === \substr($sData, 0, 1)) { $aData = @\json_decode($sData, true); } if (\is_array($aData) && 0 < \count($aData)) { foreach ($aData as $aItem) { $oItem = \RainLoop\Model\Template::NewInstance(); $oItem->FromJSON($aItem); if ($oItem && $oItem->Validate()) { \array_push($aTemplates, $oItem); } } } if (1 < \count($aTemplates)) { $sOrder = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'templates_order' ); $aOrder = empty($sOrder) ? array() : @\json_decode($sOrder, true); if (\is_array($aOrder) && 1 < \count($aOrder)) { \usort($aTemplates, function ($a, $b) use ($aOrder) { return \array_search($a->Id(), $aOrder) < \array_search($b->Id(), $aOrder) ? -1 : 1; }); } } } return $aTemplates; } /** * @param \RainLoop\Model\Account $oAccount * @param string $sID * * @return \RainLoop\Model\Identity */ public function GetTemplateByID($oAccount, $sID) { $aTemplates = $this->GetTemplates($oAccount); if (\is_array($aTemplates)) { foreach ($aTemplates as $oIdentity) { if ($oIdentity && $sID === $oIdentity->Id()) { return $oIdentity; } } } return isset($aTemplates[0]) ? $aTemplates[0] : null; } /** * @param \RainLoop\Model\Account $oAccount * * @return array */ public function GetIdentities($oAccount) { $aIdentities = array(); if ($oAccount) { $aSubIdentities = array(); $sData = $this->StorageProvider(true)->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'identities' ); if ('' !== $sData && '[' === \substr($sData, 0, 1)) { $aSubIdentities = @\json_decode($sData, true); } $bHasAccountIdentity = false; if (\is_array($aSubIdentities) && 0 < \count($aSubIdentities)) { foreach ($aSubIdentities as $aItem) { $oItem = \RainLoop\Model\Identity::NewInstance(); $oItem->FromJSON($aItem); if ($oItem && $oItem->Validate()) { if ('' === $oItem->Id()) { $oItem->SetEmail($oAccount->Email()); $bHasAccountIdentity = true; \array_unshift($aIdentities, $oItem); } else { \array_push($aIdentities, $oItem); } } } } if (!$bHasAccountIdentity) { \array_unshift($aIdentities, \RainLoop\Model\Identity::NewInstanceFromAccount($oAccount)); } if (1 < \count($aIdentities)) { $sOrder = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts_identities_order' ); $aOrder = empty($sOrder) ? array() : @\json_decode($sOrder, true); if (isset($aOrder['Identities']) && \is_array($aOrder['Identities']) && 1 < \count($aOrder['Identities'])) { $aList = $aOrder['Identities']; \usort($aIdentities, function ($a, $b) use ($aList) { return \array_search($a->Id(), $aList) < \array_search($b->Id(), $aList) ? -1 : 1; }); } } } return $aIdentities; } /** * @param \RainLoop\Model\Account $oAccount * @param string $sID * @param bool $bFirstOnEmpty = false * * @return \RainLoop\Model\Identity */ public function GetIdentityByID($oAccount, $sID, $bFirstOnEmpty = false) { $aIdentities = $this->GetIdentities($oAccount); if (\is_array($aIdentities)) { foreach ($aIdentities as $oIdentity) { if ($oIdentity && $sID === $oIdentity->Id()) { return $oIdentity; } } } return $bFirstOnEmpty && \is_array($aIdentities) && isset($aIdentities[0]) ? $aIdentities[0] : null; } /** * @param \RainLoop\Model\Account $oAccount * * @return \RainLoop\Model\Identity */ public function GetAccountIdentity($oAccount) { return $this->GetIdentityByID($oAccount, '', true); } /** * @param \RainLoop\Model\Account $oAccount * @param array $aAccounts = array() * * @return array */ public function SetAccounts($oAccount, $aAccounts = array()) { $sParentEmail = $oAccount->ParentEmailHelper(); if (!\is_array($aAccounts) || 0 >= \count($aAccounts) || (1 === \count($aAccounts) && !empty($aAccounts[$sParentEmail]))) { $this->StorageProvider()->Clear($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts' ); } else { $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts', @\json_encode($aAccounts) ); } } /** * @param \RainLoop\Model\Account $oAccount * @param array $aIdentities = array() * * @return array */ public function SetIdentities($oAccount, $aIdentities = array()) { $aResult = array(); foreach ($aIdentities as $oItem) { $aResult[] = $oItem->ToSimpleJSON(false); } return $this->StorageProvider(true)->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'identities', @\json_encode($aResult) ); } /** * @param \RainLoop\Model\Account $oAccount * @param array $aTemplates = array() * * @return array */ public function SetTemplates($oAccount, $aTemplates = array()) { $aResult = array(); foreach ($aTemplates as $oItem) { $aResult[] = $oItem->ToSimpleJSON(false); } return $this->StorageProvider(true)->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'templates', @\json_encode($aResult) ); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFilters() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::FILTERS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $aFakeFilters = null; $this->Plugins() ->RunHook('filter.filters-fake', array($oAccount, &$aFakeFilters)) ; if ($aFakeFilters) { return $this->DefaultResponse(__FUNCTION__, $aFakeFilters); } return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Load($oAccount, $oAccount->DomainSieveAllowRaw())); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFiltersSave() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::FILTERS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $aIncFilters = $this->GetActionParam('Filters', array()); $sRaw = $this->GetActionParam('Raw', ''); $bRawIsActive = '1' === (string) $this->GetActionParam('RawIsActive', '0'); $aFilters = array(); foreach ($aIncFilters as $aFilter) { if ($aFilter) { $oFilter = new \RainLoop\Providers\Filters\Classes\Filter(); if ($oFilter->FromJSON($aFilter)) { $aFilters[] = $oFilter; } } } $this->Plugins() ->RunHook('filter.filters-save', array($oAccount, &$aFilters, &$sRaw, &$bRawIsActive)) ; return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($oAccount, $aFilters, $sRaw, $bRawIsActive)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoAccountSetup() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sParentEmail = $oAccount->ParentEmailHelper(); $aAccounts = $this->GetAccounts($oAccount); if (!\is_array($aAccounts)) { $aAccounts = array(); } $sEmail = \trim($this->GetActionParam('Email', '')); $sPassword = $this->GetActionParam('Password', ''); $bNew = '1' === (string) $this->GetActionParam('New', '1'); $sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); if ($bNew && ($oAccount->Email() === $sEmail || $sParentEmail === $sEmail || isset($aAccounts[$sEmail]))) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountAlreadyExists); } else if (!$bNew && !isset($aAccounts[$sEmail])) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountDoesNotExist); } $oNewAccount = $this->LoginProcess($sEmail, $sPassword); $oNewAccount->SetParentEmail($sParentEmail); $aAccounts[$oNewAccount->Email()] = $oNewAccount->GetAuthToken(); if (0 === \strlen($oAccount->ParentEmail())) { $aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken(); } $this->SetAccounts($oAccount, $aAccounts); return $this->TrueResponse(__FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoAccountDelete() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sParentEmail = $oAccount->ParentEmailHelper(); $sEmailToDelete = \trim($this->GetActionParam('EmailToDelete', '')); $sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true); $aAccounts = $this->GetAccounts($oAccount); if (0 < \strlen($sEmailToDelete) && $sEmailToDelete !== $sParentEmail && \is_array($aAccounts) && isset($aAccounts[$sEmailToDelete])) { unset($aAccounts[$sEmailToDelete]); $oAccountToChange = null; if ($oAccount->Email() === $sEmailToDelete && !empty($aAccounts[$sParentEmail])) { $oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail], false, false); if ($oAccountToChange) { $this->AuthToken($oAccountToChange); } } $this->SetAccounts($oAccount, $aAccounts); return $this->TrueResponse(__FUNCTION__, array('Reload' => !!$oAccountToChange)); } return $this->FalseResponse(__FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoIdentityUpdate() { $oAccount = $this->getAccountFromToken(); $oIdentity = \RainLoop\Model\Identity::NewInstance(); if (!$oIdentity->FromJSON($this->GetActionParams(), true)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); } $aIdentitiesForSave = array(); $aIdentities = $this->GetIdentities($oAccount); foreach ($aIdentities as $oItem) { if ($oItem && $oItem->Id() !== $oIdentity->Id()) { $aIdentitiesForSave[] = $oItem; } } $aIdentitiesForSave[] = $oIdentity; return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aIdentitiesForSave)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoIdentityDelete() { $oAccount = $this->getAccountFromToken(); $sId = \trim($this->GetActionParam('IdToDelete', '')); if (empty($sId)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); } $aNew = array(); $aIdentities = $this->GetIdentities($oAccount); foreach ($aIdentities as $oItem) { if ($oItem && $sId !== $oItem->Id()) { $aNew[] = $oItem; } } return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aNew)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoTemplateSetup() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $oTemplate = \RainLoop\Model\Template::NewInstance(); if (!$oTemplate->FromJSON($this->GetActionParams(), true)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); } if ('' === $oTemplate->Id()) { $oTemplate->GenerateID(); } $aTemplatesForSave = array(); $aTemplates = $this->GetTemplates($oAccount); foreach ($aTemplates as $oItem) { if ($oItem && $oItem->Id() !== $oTemplate->Id()) { $aTemplatesForSave[] = $oItem; } } $aTemplatesForSave[] = $oTemplate; return $this->DefaultResponse(__FUNCTION__, $this->SetTemplates($oAccount, $aTemplatesForSave)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoTemplateDelete() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sId = \trim($this->GetActionParam('IdToDelete', '')); if (empty($sId)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); } $aNew = array(); $aTemplates = $this->GetTemplates($oAccount); foreach ($aTemplates as $oItem) { if ($oItem && $sId !== $oItem->Id()) { $aNew[] = $oItem; } } return $this->DefaultResponse(__FUNCTION__, $this->SetTemplates($oAccount, $aNew)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoTemplateGetByID() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sId = \trim($this->GetActionParam('ID', '')); if (empty($sId)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); } $oTemplate = false; $aTemplates = $this->GetTemplates($oAccount); foreach ($aTemplates as $oItem) { if ($oItem && $sId === $oItem->Id()) { $oTemplate = $oItem; break; } } $oTemplate->SetPopulateAlways(true); return $this->DefaultResponse(__FUNCTION__, $oTemplate); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoAccountsAndIdentitiesSortOrder() { $oAccount = $this->getAccountFromToken(); $aAccounts = $this->GetActionParam('Accounts', null); $aIdentities = $this->GetActionParam('Identities', null); if (!\is_array($aAccounts) || !\is_array($aIdentities)) { return $this->FalseResponse(__FUNCTION__); } return $this->DefaultResponse(__FUNCTION__, $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts_identities_order', \json_encode(array( 'Accounts' => $aAccounts, 'Identities' => $aIdentities )) )); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoAccountsAndIdentities() { $oAccount = $this->getAccountFromToken(); $mAccounts = false; if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { $mAccounts = $this->GetAccounts($oAccount); $mAccounts = \array_keys($mAccounts); foreach ($mAccounts as $iIndex => $sName) { $mAccounts[$iIndex] = \MailSo\Base\Utils::IdnToUtf8($sName); } } return $this->DefaultResponse(__FUNCTION__, array( 'Accounts' => $mAccounts, 'Identities' => $this->GetIdentities($oAccount) )); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoTemplates() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } return $this->DefaultResponse(__FUNCTION__, array( 'Templates' => $this->GetTemplates($oAccount) )); } /** * @param string $sHash * * @return int * * @throws \MailSo\Base\Exceptions\Exception */ public function getAccountUnredCountFromHash($sHash) { $iResult = 0; $oAccount = $this->GetAccountFromCustomToken($sHash, false); if ($oAccount) { try { $oMailClient = \MailSo\Mail\MailClient::NewInstance(); $oMailClient->SetLogger($this->Logger()); $oAccount->IncConnectAndLoginHelper($this->Plugins(),$oMailClient, $this->Config()); $iResult = $oMailClient->InboxUnreadCount(); $oMailClient->LogoutAndDisconnect(); } catch (\Exception $oException) { $this->Logger()->WriteException($oException); } } return $iResult; } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoAccountsCounts() { $oAccount = $this->getAccountFromToken(); $bComplete = true; $aCounts = array(); if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { $iLimit = 7; $mAccounts = $this->GetAccounts($oAccount); if (\is_array($mAccounts) && 0 < \count($mAccounts)) { if ($iLimit > \count($mAccounts)) { $mAccounts = \array_slice($mAccounts, 0, $iLimit); } else { $bComplete = false; } if (0 < \count($mAccounts)) { foreach ($mAccounts as $sEmail => $sHash) { $aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($sEmail), $oAccount->Email() === $sEmail ? 0 : $this->getAccountUnredCountFromHash($sHash)); } } } } else { $aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($oAccount->Email()), 0); } return $this->DefaultResponse(__FUNCTION__, array( 'Complete' => $bComplete, 'Counts' => $aCounts )); } /** * @param \RainLoop\Model\Account $oAccount */ public function ClearSignMeData($oAccount) { if ($oAccount) { \RainLoop\Utils::ClearCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY); $this->StorageProvider()->Clear($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'sign_me' ); } } /** * @return array */ public function DoLogout() { $oAccount = $this->getAccountFromToken(false); if ($oAccount) { if ($oAccount->SignMe()) { $this->ClearSignMeData($oAccount); } if (!$oAccount->IsAdditionalAccount()) { \RainLoop\Utils::ClearCookie(\RainLoop\Actions::AUTH_SPEC_TOKEN_KEY); } } return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoAppDelayStart() { $this->Plugins()->RunHook('service.app-delay-start-begin'); \RainLoop\Utils::UpdateConnectionToken(); $bMainCache = false; $bFilesCache = false; $bVersionsCache = false; $iOneDay1 = 60 * 60 * 23; $iOneDay2 = 60 * 60 * 25; $iOneDay3 = 60 * 60 * 30; $sTimers = $this->StorageProvider()->Get(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', ''); $aTimers = \explode(',', $sTimers); $iMainCacheTime = !empty($aTimers[0]) && \is_numeric($aTimers[0]) ? (int) $aTimers[0] : 0; $iFilesCacheTime = !empty($aTimers[1]) && \is_numeric($aTimers[1]) ? (int) $aTimers[1] : 0; $iVersionsCacheTime = !empty($aTimers[2]) && \is_numeric($aTimers[2]) ? (int) $aTimers[2] : 0; if (0 === $iMainCacheTime || $iMainCacheTime + $iOneDay1 < \time()) { $bMainCache = true; $iMainCacheTime = \time(); } if (0 === $iFilesCacheTime || $iFilesCacheTime + $iOneDay2 < \time()) { $bFilesCache = true; $iFilesCacheTime = \time(); } if (0 === $iVersionsCacheTime || $iVersionsCacheTime + $iOneDay3 < \time()) { $bVersionsCache = true; $iVersionsCacheTime = \time(); } if ($bMainCache || $bFilesCache || $bVersionsCache) { if (!$this->StorageProvider()->Put(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', \implode(',', array($iMainCacheTime, $iFilesCacheTime, $iVersionsCacheTime)))) { $bMainCache = $bFilesCache = $bVersionsCache = false; } } if ($bMainCache) { $this->Logger()->Write('Cacher GC: Begin'); $this->Cacher()->GC(48); $this->Logger()->Write('Cacher GC: End'); } else if ($bFilesCache) { $this->Logger()->Write('Files GC: Begin'); $this->FilesProvider()->GC(48); $this->Logger()->Write('Files GC: End'); } else if ($bVersionsCache) { $this->removeOldVersion(); } $this->Plugins()->RunHook('service.app-delay-start-end'); return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoSystemFoldersUpdate() { $oAccount = $this->getAccountFromToken(); $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); $oSettingsLocal->SetConf('SentFolder', $this->GetActionParam('SentFolder', '')); $oSettingsLocal->SetConf('DraftFolder', $this->GetActionParam('DraftFolder', '')); $oSettingsLocal->SetConf('SpamFolder', $this->GetActionParam('SpamFolder', '')); $oSettingsLocal->SetConf('TrashFolder', $this->GetActionParam('TrashFolder', '')); $oSettingsLocal->SetConf('ArchiveFolder', $this->GetActionParam('ArchiveFolder', '')); $oSettingsLocal->SetConf('NullFolder', $this->GetActionParam('NullFolder', '')); return $this->DefaultResponse(__FUNCTION__, $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); } /** * @param \RainLoop\Config\Application $oConfig * @param string $sParamName * @param string $sConfigSector * @param string $sConfigName * @param string $sType = 'string' * @param callable|null $mStringCallback = null */ private function setConfigFromParams(&$oConfig, $sParamName, $sConfigSector, $sConfigName, $sType = 'string', $mStringCallback = null) { $sValue = $this->GetActionParam($sParamName, ''); if ($this->HasActionParam($sParamName)) { switch ($sType) { default: case 'string': $sValue = (string) $sValue; if ($mStringCallback && is_callable($mStringCallback)) { $sValue = call_user_func($mStringCallback, $sValue); } $oConfig->Set($sConfigSector, $sConfigName, (string) $sValue); break; case 'dummy': $sValue = (string) $this->GetActionParam('ContactsPdoPassword', APP_DUMMY); if (APP_DUMMY !== $sValue) { $oConfig->Set($sConfigSector, $sConfigName, (string) $sValue); } break; case 'int': $iValue = (int) $sValue; $oConfig->Set($sConfigSector, $sConfigName, $iValue); break; case 'bool': $oConfig->Set($sConfigSector, $sConfigName, '1' === (string) $sValue); break; } } } /** * @param \RainLoop\Config\Application $oConfig * @param string $sParamName * @param string $sCapa */ private function setCapaFromParams(&$oConfig, $sParamName, $sCapa) { switch ($sCapa) { case \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS: $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_accounts', 'bool'); break; case \RainLoop\Enumerations\Capa::TEMPLATES: $this->setConfigFromParams($oConfig, $sParamName, 'capa', 'templates', 'bool'); break; case \RainLoop\Enumerations\Capa::TWO_FACTOR: $this->setConfigFromParams($oConfig, $sParamName, 'security', 'allow_two_factor_auth', 'bool'); break; case \RainLoop\Enumerations\Capa::GRAVATAR: $this->setConfigFromParams($oConfig, $sParamName, 'labs', 'allow_gravatar', 'bool'); break; case \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS: $this->setConfigFromParams($oConfig, $sParamName, 'interface', 'show_attachment_thumbnail', 'bool'); break; case \RainLoop\Enumerations\Capa::THEMES: $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_themes', 'bool'); break; case \RainLoop\Enumerations\Capa::USER_BACKGROUND: $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_user_background', 'bool'); break; case \RainLoop\Enumerations\Capa::OPEN_PGP: $this->setConfigFromParams($oConfig, $sParamName, 'security', 'openpgp', 'bool'); break; } } /** * @param \RainLoop\Settings $oSettings * @param string $sConfigName * @param string $sType = 'string' * @param callable|null $mStringCallback = null */ private function setSettingsFromParams(&$oSettings, $sConfigName, $sType = 'string', $mStringCallback = null) { if ($this->HasActionParam($sConfigName)) { $sValue = $this->GetActionParam($sConfigName, ''); switch ($sType) { default: case 'string': $sValue = (string) $sValue; if ($mStringCallback && is_callable($mStringCallback)) { $sValue = call_user_func($mStringCallback, $sValue); } $oSettings->SetConf($sConfigName, (string) $sValue); break; case 'int': $iValue = (int) $sValue; $oSettings->SetConf($sConfigName, $iValue); break; case 'bool': $oSettings->SetConf($sConfigName, '1' === (string) $sValue); break; } } } /** * @return array */ public function DoAdminSettingsUpdate() { // sleep(3); // return $this->DefaultResponse(__FUNCTION__, false); $this->IsAdminLoggined(); $oConfig = $this->Config(); $self = $this; $this->setConfigFromParams($oConfig, 'Language', 'webmail', 'language', 'string', function ($sLanguage) use ($self) { return $self->ValidateLanguage($sLanguage); }); $this->setConfigFromParams($oConfig, 'Theme', 'webmail', 'theme', 'string', function ($sTheme) use ($self) { return $self->ValidateTheme($sTheme); }); $this->setConfigFromParams($oConfig, 'VerifySslCertificate', 'ssl', 'verify_certificate', 'bool'); $this->setConfigFromParams($oConfig, 'AllowSelfSigned', 'ssl', 'allow_self_signed', 'bool'); $this->setConfigFromParams($oConfig, 'UseLocalProxyForExternalImages', 'labs', 'use_local_proxy_for_external_images', 'bool'); $this->setConfigFromParams($oConfig, 'AllowLanguagesOnSettings', 'webmail', 'allow_languages_on_settings', 'bool'); $this->setConfigFromParams($oConfig, 'AllowLanguagesOnLogin', 'login', 'allow_languages_on_login', 'bool'); $this->setConfigFromParams($oConfig, 'AttachmentLimit', 'webmail', 'attachment_size_limit', 'int'); $this->setConfigFromParams($oConfig, 'LoginDefaultDomain', 'login', 'default_domain', 'string'); $this->setConfigFromParams($oConfig, 'ContactsEnable', 'contacts', 'enable', 'bool'); $this->setConfigFromParams($oConfig, 'ContactsSharing', 'contacts', 'allow_sharing', 'bool'); $this->setConfigFromParams($oConfig, 'ContactsSync', 'contacts', 'allow_sync', 'bool'); $this->setConfigFromParams($oConfig, 'ContactsPdoDsn', 'contacts', 'pdo_dsn', 'string'); $this->setConfigFromParams($oConfig, 'ContactsPdoUser', 'contacts', 'pdo_user', 'string'); $this->setConfigFromParams($oConfig, 'ContactsPdoPassword', 'contacts', 'pdo_password', 'dummy'); $this->setConfigFromParams($oConfig, 'ContactsPdoType', 'contacts', 'type', 'string', function ($sType) use ($self) { return $self->ValidateContactPdoType($sType); }); $this->setCapaFromParams($oConfig, 'CapaAdditionalAccounts', \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS); $this->setCapaFromParams($oConfig, 'CapaTemplates', \RainLoop\Enumerations\Capa::TEMPLATES); $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuth', \RainLoop\Enumerations\Capa::TWO_FACTOR); $this->setCapaFromParams($oConfig, 'CapaOpenPGP', \RainLoop\Enumerations\Capa::OPEN_PGP); $this->setCapaFromParams($oConfig, 'CapaGravatar', \RainLoop\Enumerations\Capa::GRAVATAR); $this->setCapaFromParams($oConfig, 'CapaThemes', \RainLoop\Enumerations\Capa::THEMES); $this->setCapaFromParams($oConfig, 'CapaUserBackground', \RainLoop\Enumerations\Capa::USER_BACKGROUND); $this->setCapaFromParams($oConfig, 'CapaAttachmentThumbnails', \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS); $this->setConfigFromParams($oConfig, 'DetermineUserLanguage', 'login', 'determine_user_language', 'bool'); $this->setConfigFromParams($oConfig, 'DetermineUserDomain', 'login', 'determine_user_domain', 'bool'); $this->setConfigFromParams($oConfig, 'Title', 'webmail', 'title', 'string'); $this->setConfigFromParams($oConfig, 'LoadingDescription', 'webmail', 'loading_description', 'string'); if ($this->HasOneOfActionParams(array('LoginLogo', 'LoginBackground', 'LoginDescription', 'LoginCss', 'LoginPowered', 'UserLogo', 'UserCss')) && $this->PremType()) { $this->setConfigFromParams($oConfig, 'LoginLogo', 'branding', 'login_logo', 'string'); $this->setConfigFromParams($oConfig, 'LoginBackground', 'branding', 'login_background', 'string'); $this->setConfigFromParams($oConfig, 'LoginDescription', 'branding', 'login_desc', 'string'); $this->setConfigFromParams($oConfig, 'LoginCss', 'branding', 'login_css', 'string'); $this->setConfigFromParams($oConfig, 'LoginPowered', 'branding', 'login_powered', 'bool'); $this->setConfigFromParams($oConfig, 'UserLogo', 'branding', 'user_logo', 'string'); $this->setConfigFromParams($oConfig, 'UserCss', 'branding', 'user_css', 'string'); } $this->setConfigFromParams($oConfig, 'TokenProtection', 'security', 'csrf_protection', 'bool'); $this->setConfigFromParams($oConfig, 'EnabledPlugins', 'plugins', 'enable', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnable', 'social', 'google_enable', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnableAuth', 'social', 'google_enable_auth', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnableDrive', 'social', 'google_enable_drive', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string'); $this->setConfigFromParams($oConfig, 'GoogleClientSecret', 'social', 'google_client_secret', 'string'); $this->setConfigFromParams($oConfig, 'GoogleApiKey', 'social', 'google_api_key', 'string'); $this->setConfigFromParams($oConfig, 'FacebookEnable', 'social', 'fb_enable', 'bool'); $this->setConfigFromParams($oConfig, 'FacebookAppID', 'social', 'fb_app_id', 'string'); $this->setConfigFromParams($oConfig, 'FacebookAppSecret', 'social', 'fb_app_secret', 'string'); $this->setConfigFromParams($oConfig, 'TwitterEnable', 'social', 'twitter_enable', 'bool'); $this->setConfigFromParams($oConfig, 'TwitterConsumerKey', 'social', 'twitter_consumer_key', 'string'); $this->setConfigFromParams($oConfig, 'TwitterConsumerSecret', 'social', 'twitter_consumer_secret', 'string'); $this->setConfigFromParams($oConfig, 'DropboxEnable', 'social', 'dropbox_enable', 'bool'); $this->setConfigFromParams($oConfig, 'DropboxApiKey', 'social', 'dropbox_api_key', 'string'); return $this->DefaultResponse(__FUNCTION__, $oConfig->Save()); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoAdminLogin() { $sLogin = trim($this->GetActionParam('Login', '')); $sPassword = $this->GetActionParam('Password', ''); $this->Logger()->AddSecret($sPassword); if (0 === strlen($sLogin) || 0 === strlen($sPassword) || !$this->Config()->Get('security', 'allow_admin_panel', true) || $sLogin !== $this->Config()->Get('security', 'admin_login', '') || !$this->Config()->ValidatePassword($sPassword)) { $this->loginErrorDelay(); throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } $sToken = $this->getAdminToken(); $this->setAdminAuthToken($sToken); return $this->DefaultResponse(__FUNCTION__, $sToken ? true : false); } /** * @return array */ public function DoAdminLogout() { $this->ClearAdminAuthToken(); return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoAdminPing() { $this->IsAdminLoggined(); return $this->DefaultResponse(__FUNCTION__, true); } /** * @return array */ public function DoAdminContactsTest() { $this->IsAdminLoggined(); $oConfig = $this->Config(); $this->setConfigFromParams($oConfig, 'ContactsPdoDsn', 'contacts', 'pdo_dsn', 'string'); $this->setConfigFromParams($oConfig, 'ContactsPdoUser', 'contacts', 'pdo_user', 'string'); $this->setConfigFromParams($oConfig, 'ContactsPdoPassword', 'contacts', 'pdo_password', 'dummy'); $self = $this; $this->setConfigFromParams($oConfig, 'ContactsPdoType', 'contacts', 'type', 'string', function ($sType) use ($self) { return $self->ValidateContactPdoType($sType); }); $sTestMessage = $this->AddressBookProvider(null, true)->Test(); return $this->DefaultResponse(__FUNCTION__, array( 'Result' => '' === $sTestMessage, 'Message' => \MailSo\Base\Utils::Utf8Clear($sTestMessage, '?') )); } /** * @param string $sDomain * * @return string */ // public function domainPathHelper($sDomain) // { // $sDomain = \strtolower(\trim($sDomain)); // // $sTempDomain = \preg_replace('/^(webmail|email|mail)\./', '', $sDomain); // if (false === \strpos($sTempDomain, '.')) // { // $sTempDomain = $sDomain; // } // // $sDomainPrefix = $sTempDomain; // if (false === \strpos($sDomainPrefix, '.')) // { // $sDomainPrefix = $sTempDomain; // } // // $sDomainPrefix = \substr(\preg_replace('/[^a-z0-9]+/', '', $sDomainPrefix), 0, 2); // $sDomainPrefix = \str_pad($sDomainPrefix, 2, '_'); // // return 'd/'.\substr($sDomainPrefix, 0, 1).'/'.$sDomainPrefix.'/'.\urlencode($sTempDomain); // } /** * @return string */ public function licenseHelper($sForce = false, $bLongCache = false, $iFastCacheTimeInMin = 10, $iLongCacheTimeInDays = 3) { $sDomain = \trim(APP_SITE); $oCacher = $this->Cacher(null, true); $oHttp = \MailSo\Base\Http::SingletonInstance(); if (0 === \strlen($sDomain) || $oHttp->CheckLocalhost($sDomain) || !$oCacher || !$oCacher->Verify(true)) { return 'NO'; } $sDomainKeyValue = \RainLoop\KeyPathHelper::LicensingDomainKeyValue($sDomain); $sDomainLongKeyValue = \RainLoop\KeyPathHelper::LicensingDomainKeyOtherValue($sDomain); $sValue = ''; if (!$sForce) { if ($bLongCache) { $bLock = $oCacher->GetLock($sDomainLongKeyValue); $iTime = $bLock ? 0 : $oCacher->GetTimer($sDomainLongKeyValue); if ($bLock || (0 < $iTime && \time() < $iTime + (60 * 60 * 24) * $iLongCacheTimeInDays)) { $sValue = $oCacher->Get($sDomainLongKeyValue); } } else { $iTime = $oCacher->GetTimer($sDomainKeyValue); if (0 < $iTime && \time() < $iTime + 60 * $iFastCacheTimeInMin) { $sValue = $oCacher->Get($sDomainKeyValue); } } } if (0 === \strlen($sValue)) { if ($bLongCache) { if (!$oCacher->SetTimer($sDomainLongKeyValue)) { return 'NO'; } $oCacher->SetLock($sDomainLongKeyValue); } $iCode = 0; $sContentType = ''; // $sValue = $oHttp->GetUrlAsString(APP_STATUS_PATH.$this->domainPathHelper($sDomain), // 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 5, // $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), // array(), false // ); $sValue = $oHttp->GetUrlAsString(APP_API_PATH.'status/'.\urlencode($sDomain), 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 5, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), array(), false ); $this->Logger()->Write($sValue); /*if (404 === $iCode) { $sValue = 'NO'; } else */if (200 !== $iCode) { $sValue = ''; } $oCacher->SetTimer($sDomainKeyValue); $oCacher->Set($sDomainKeyValue, $sValue); $oCacher->Set($sDomainLongKeyValue, $sValue); if ($bLongCache) { $oCacher->RemoveLock($sDomainLongKeyValue); } } return $sValue; } /** * @param string $sInput * @param int $iExpired = 0 * * @return bool */ public function licenseParser($sInput, &$iExpired = 0) { $aMatch = array(); if (\preg_match('/^EXPIRED:([\d]+)$/', $sInput, $aMatch)) { $iExpired = (int) $aMatch[1]; return \time() < $iExpired; } return false; } /** * @return array */ public function DoAdminLicensing() { $iStart = \time(); $this->IsAdminLoggined(); $bForce = '1' === (string) $this->GetActionParam('Force', '0'); $mResult = false; $iErrorCode = -1; if (2 < \strlen(APP_SITE)) { $sValue = $this->licenseHelper($bForce); if ($iStart === \time()) { \sleep(1); } $iExpired = 0; if ($this->licenseParser($sValue, $iExpired)) { $mResult = array( 'Banned' => false, 'Expired' => $iExpired, ); } else if ($sValue === 'NO' || \preg_match('/^EXPIRED:[\d]+$/', $sValue)) { $iErrorCode = -1; } else if ($sValue === 'TOO_MANY_CONNECTIONS') { $iErrorCode = -1; } else { $iErrorCode = \RainLoop\Notifications::LicensingServerIsUnavailable; } } if (0 < $iErrorCode && !$mResult) { throw new \RainLoop\Exceptions\ClientException($iErrorCode); } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * @return array */ public function DoAdminLicensingActivate() { $iStart = \time(); $this->IsAdminLoggined(); $sDomain = (string) $this->GetActionParam('Domain', ''); $sKey = (string) $this->GetActionParam('Key', ''); $mResult = false; $iErrorCode = -1; if (2 < \strlen($sDomain) && 2 < \strlen($sKey) && $sDomain === APP_SITE) { $iCode = 0; $sContentType = ''; $oHttp = \MailSo\Base\Http::SingletonInstance(); \sleep(1); $sValue = $oHttp->GetUrlAsString(APP_API_PATH.'activate/'.\urlencode($sDomain).'/'.\urlencode($sKey), 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 10, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), array(), false ); if (200 !== $iCode) { $sValue = ''; } if ($iStart + 2 > \time()) { \sleep(1); } $aMatch = array(); if ('OK' === $sValue) { $mResult = true; } else if ('TOO_MANY_CONNECTIONS' === $sValue) { $mResult = 'Too many connections. Please try again later.'; } else if (\preg_match('/^ERROR:(.+)$/', $sValue, $aMatch) && !empty($aMatch[1])) { $mResult = trim($aMatch[1]); $this->Logger()->Write('Activation error for: '.$sKey.' ('.$sDomain.')', \MailSo\Log\Enumerations\Type::ERROR); $this->Logger()->Write($mResult, \MailSo\Log\Enumerations\Type::ERROR); } else { $iErrorCode = \RainLoop\Notifications::LicensingServerIsUnavailable; } } if (0 < $iErrorCode && !$mResult) { throw new \RainLoop\Exceptions\ClientException($iErrorCode); } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * @return array */ public function DoAdminPasswordUpdate() { $this->IsAdminLoggined(); $bResult = false; $oConfig = $this->Config(); $sLogin = \trim($this->GetActionParam('Login', '')); $sPassword = $this->GetActionParam('Password', ''); $this->Logger()->AddSecret($sPassword); $sNewPassword = $this->GetActionParam('NewPassword', ''); if (0 < \strlen(\trim($sNewPassword))) { $this->Logger()->AddSecret($sNewPassword); } if ($oConfig->ValidatePassword($sPassword)) { if (0 < \strlen($sLogin)) { $oConfig->Set('security', 'admin_login', $sLogin); } if (0 < \strlen(\trim($sNewPassword))) { $oConfig->SetPassword($sNewPassword); } $bResult = true; } return $this->DefaultResponse(__FUNCTION__, $bResult ? ($oConfig->Save() ? array('Weak' => $oConfig->ValidatePassword('12345')) : false) : false); } /** * @return array */ public function DoAdminDomainLoad() { $this->IsAdminLoggined(); return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->Load($this->GetActionParam('Name', ''), false, false)); } /** * @return array */ public function DoAdminDomainList() { $this->IsAdminLoggined(); $iOffset = (int) $this->GetActionParam('Offset', 0); $iLimit = (int) $this->GetActionParam('Limit', 20); $sSearch = (string) $this->GetActionParam('Search', ''); $iOffset = 0; $iLimit = 99; $sSearch = ''; return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->GetList($iOffset, $iLimit, $sSearch)); } /** * @return array */ public function DoAdminDomainDelete() { $this->IsAdminLoggined(); return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->Delete((string) $this->GetActionParam('Name', ''))); } /** * @return array */ public function DoAdminDomainDisable() { $this->IsAdminLoggined(); return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->Disable( (string) $this->GetActionParam('Name', ''), '1' === (string) $this->GetActionParam('Disabled', '0') )); } /** * @return array */ public function DoAdminDomainSave() { $this->IsAdminLoggined(); $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this); return $this->DefaultResponse(__FUNCTION__, $oDomain instanceof \RainLoop\Model\Domain ? $this->DomainProvider()->Save($oDomain) : false); } /** * @return array */ public function DoAdminDomainTest() { $this->IsAdminLoggined(); $bImapResult = false; $sImapErrorDesc = ''; $bSmtpResult = false; $sSmtpErrorDesc = ''; $bSieveResult = false; $sSieveErrorDesc = ''; $iImapTime = 0; $iSmtpTime = 0; $iSieveTime = 0; $iConnectionTimeout = 5; $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this, 'domain-test-connection.de'); if ($oDomain) { try { $oImapClient = \MailSo\Imap\ImapClient::NewInstance()->SetLogger($this->Logger()); $oImapClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); $oImapClient->Connect($oDomain->IncHost(), $oDomain->IncPort(), $oDomain->IncSecure(), !!$this->Config()->Get('ssl', 'verify_certificate', false), !!$this->Config()->Get('ssl', 'allow_self_signed', true) ); $iImapTime = \microtime(true) - $iTime; $oImapClient->Disconnect(); $bImapResult = true; } catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sImapErrorDesc = $oException->getSocketMessage(); if (empty($sImapErrorDesc)) { $sImapErrorDesc = $oException->getMessage(); } } catch (\Exception $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sImapErrorDesc = $oException->getMessage(); } if ($oDomain->OutUsePhpMail()) { $bSmtpResult = \MailSo\Base\Utils::FunctionExistsAndEnabled('mail'); if (!$bSmtpResult) { $sSmtpErrorDesc = 'PHP: mail() function is undefined'; } } else { try { $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); $oSmtpClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); $oSmtpClient->Connect($oDomain->OutHost(), $oDomain->OutPort(), \MailSo\Smtp\SmtpClient::EhloHelper(), $oDomain->OutSecure(), !!$this->Config()->Get('ssl', 'verify_certificate', false), !!$this->Config()->Get('ssl', 'allow_self_signed', true) ); $iSmtpTime = \microtime(true) - $iTime; $oSmtpClient->Disconnect(); $bSmtpResult = true; } catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sSmtpErrorDesc = $oException->getSocketMessage(); if (empty($sSmtpErrorDesc)) { $sSmtpErrorDesc = $oException->getMessage(); } } catch (\Exception $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sSmtpErrorDesc = $oException->getMessage(); } } if ($oDomain->UseSieve()) { try { $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->Logger()); $oSieveClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); $oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(), !!$this->Config()->Get('ssl', 'verify_certificate', false), !!$this->Config()->Get('ssl', 'allow_self_signed', true) ); $iSieveTime = \microtime(true) - $iTime; $oSieveClient->Disconnect(); $bSieveResult = true; } catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sSieveErrorDesc = $oException->getSocketMessage(); if (empty($sSieveErrorDesc)) { $sSieveErrorDesc = $oException->getMessage(); } } catch (\Exception $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sSieveErrorDesc = $oException->getMessage(); } } else { $bSieveResult = true; } } return $this->DefaultResponse(__FUNCTION__, array( 'Imap' => $bImapResult ? true : $sImapErrorDesc, 'Smtp' => $bSmtpResult ? true : $sSmtpErrorDesc, 'Sieve' => $bSieveResult ? true : $sSieveErrorDesc )); } /** * @return string */ private function rainloopRepo() { $sUrl = APP_REP_PATH; if ('' !== $sUrl) { $sUrl = rtrim($sUrl, '\\/').'/'; } return $sUrl; } private function rainLoopUpdatable() { return @file_exists(APP_INDEX_ROOT_PATH.'index.php') && @is_writable(APP_INDEX_ROOT_PATH.'index.php') && @is_writable(APP_INDEX_ROOT_PATH.'rainloop/') && APP_VERSION !== APP_DEV_VERSION ; } private function rainLoopCoreAccess() { $sCoreAccess = \strtolower(\preg_replace('/[\s,;]+/', ' ', $this->Config()->Get('security', 'core_install_access_domain', ''))); return '' === $sCoreAccess || '*' === $sCoreAccess || APP_SITE === $sCoreAccess; } /** * @param string $sRepo * @param bool $bReal = false * * @return array */ private function getRepositoryDataByUrl($sRepo, &$bReal = false) { $bReal = false; $aRep = null; $sRep = ''; $sRepoFile = 'repository.json'; $iRepTime = 0; $oHttp = \MailSo\Base\Http::SingletonInstance(); $sCacheKey = \RainLoop\KeyPathHelper::RepositoryCacheFile($sRepo, $sRepoFile); $sRep = $this->Cacher()->Get($sCacheKey); if ('' !== $sRep) { $iRepTime = $this->Cacher()->GetTimer($sCacheKey); } if ('' === $sRep || 0 === $iRepTime || time() - 3600 > $iRepTime) { $iCode = 0; $sContentType = ''; $sRepPath = $sRepo.$sRepoFile; $sRep = '' !== $sRepo ? $oHttp->GetUrlAsString($sRepPath, 'RainLoop', $sContentType, $iCode, $this->Logger(), 10, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')) : false; if (false !== $sRep) { $aRep = @\json_decode($sRep); $bReal = \is_array($aRep) && 0 < \count($aRep); if ($bReal) { $this->Cacher()->Set($sCacheKey, $sRep); $this->Cacher()->SetTimer($sCacheKey); } } else { $this->Logger()->Write('Cannot read remote repository file: '.$sRepPath, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } } else if ('' !== $sRep) { $aRep = @\json_decode($sRep, false, 10); $bReal = \is_array($aRep) && 0 < \count($aRep); } $aResult = array(); if (\is_array($aRep)) { foreach ($aRep as $oItem) { if ($oItem && isset($oItem->type, $oItem->id, $oItem->name, $oItem->version, $oItem->release, $oItem->file, $oItem->description)) { if (!empty($oItem->required) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->required, '<')) { continue; } if (!empty($oItem->depricated) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->depricated, '>=')) { continue; } if ('plugin' === $oItem->type) { $aResult[] = array( 'type' => $oItem->type, 'id' => $oItem->id, 'name' => $oItem->name, 'installed' => '', 'version' => $oItem->version, 'file' => $oItem->file, 'release' => $oItem->release, 'release_notes' => isset($oItem->{'release_notes'}) ? $oItem->{'release_notes'} : '', 'desc' => $oItem->description ); } } } } return $aResult; } /** * @return string */ private function getCoreChannel() { $sChannel = \trim(\strtolower($this->Config()->Get('labs', 'update_channel', 'stable'))); if (empty($sChannel) || !\in_array($sChannel, array('stable', 'beta'))) { $sChannel = 'stable'; } return $sChannel; } private function getCoreData(&$bReal) { $bReal = false; $sChannel = $this->getCoreChannel(); $sRepo = \str_replace('{{channel}}', $sChannel, APP_REPO_CORE_FILE); $oHttp = \MailSo\Base\Http::SingletonInstance(); $sCacheKey = \RainLoop\KeyPathHelper::RepositoryCacheCore($sRepo); $sRep = $this->Cacher()->Get($sCacheKey); if ('' !== $sRep) { $iRepTime = $this->Cacher()->GetTimer($sCacheKey); } if ('' === $sRep || 0 === $iRepTime || time() - 3600 > $iRepTime) { $iCode = 0; $sContentType = ''; $sRep = '' !== $sRepo ? $oHttp->GetUrlAsString($sRepo, 'RainLoop', $sContentType, $iCode, $this->Logger(), 10, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')) : false; if (false !== $sRep) { $aRep = @\json_decode($sRep, true, 10); $bReal = \is_array($aRep) && 0 < \count($aRep) && isset($aRep['id']) && 'rainloop' === $aRep['id']; if ($bReal) { $this->Cacher()->Set($sCacheKey, $sRep); $this->Cacher()->SetTimer($sCacheKey); } } else { $this->Logger()->Write('Cannot read remote repository file: '.$sRepo, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } } else if ('' !== $sRep) { $aRep = @\json_decode($sRep, true, 10); $bReal = \is_array($aRep) && 0 < \count($aRep) && isset($aRep['id']) && 'rainloop' === $aRep['id']; } return $bReal ? $aRep : false; } private function getRepositoryData(&$bReal, &$bRainLoopUpdatable) { $bRainLoopUpdatable = $this->rainLoopUpdatable(); $aResult = $this->getRepositoryDataByUrl($this->rainloopRepo(), $bReal); $aSub = array(); if (\is_array($aResult)) { foreach ($aResult as $aItem) { if ('plugin' === $aItem['type']) { $aSub[] = $aItem; } } $aResult = $aSub; unset($aSub); } $aInstalled = $this->Plugins()->InstalledPlugins(); if (\is_array($aInstalled)) { foreach ($aResult as &$aItem) { if ('plugin' === $aItem['type']) { foreach ($aInstalled as &$aSubItem) { if (\is_array($aSubItem) && isset($aSubItem[0]) && $aSubItem[0] === $aItem['id']) { $aSubItem[2] = true; $aItem['installed'] = $aSubItem[1]; } } } } foreach ($aInstalled as $aSubItemSec) { if ($aSubItemSec && !isset($aSubItemSec[2])) { \array_push($aResult, array( 'type' => 'plugin', 'id' => $aSubItemSec[0], 'name' => $aSubItemSec[0], 'installed' => $aSubItemSec[1], 'version' => '', 'file' => '', 'release' => '', 'release_notes' => '', 'desc' => '' )); } } } foreach ($aResult as &$aItem) { $aItem['compare'] = \version_compare($aItem['installed'], $aItem['version'], '<'); $aItem['canBeDeleted'] = '' !== $aItem['installed'] && 'plugin' === $aItem['type']; $aItem['canBeUpdated'] = $aItem['compare']; $aItem['canBeInstalled'] = true; } return $aResult; } /** * @return array */ public function DoAdminPackagesList() { $this->IsAdminLoggined(); $bReal = false; $bRainLoopUpdatable = false; $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); return $this->DefaultResponse(__FUNCTION__, array( 'Real' => $bReal, 'MainUpdatable' => $bRainLoopUpdatable, 'List' => $aList )); } /** * @return array */ public function DoAdminCoreData() { $this->IsAdminLoggined(); $bReal = false; $aData = array(); $bRainLoopUpdatable = $this->rainLoopUpdatable(); $bRainLoopAccess = $this->rainLoopCoreAccess(); if ($bRainLoopAccess) { $aData = $this->getCoreData($bReal); } $sVersion = empty($aData['version']) ? '' : $aData['version']; $sType = empty($aData['channel']) ? 'stable' : $aData['channel']; $sWarnings = empty($aData['warnings']) ? '' : $aData['warnings']; $aWarnings = $sWarnings ? explode('|', $sWarnings) : array(); $sCurrentVersion = APP_VERSION; $bShowWarning = false; if ($sCurrentVersion !== APP_DEV_VERSION) { foreach ($aWarnings as $sWarningVersion) { $sWarningVersion = \trim($sWarningVersion); if (\version_compare($sCurrentVersion, $sWarningVersion, '<') && \version_compare($sVersion, $sWarningVersion, '>=')) { $bShowWarning = true; break; } } } return $this->DefaultResponse(__FUNCTION__, array( 'Real' => $bReal, 'Access' => $bRainLoopAccess, 'Updatable' => $bRainLoopUpdatable, 'Warning' => $bShowWarning, 'Channel' => $this->getCoreChannel(), 'Type' => $sType, 'Version' => $sCurrentVersion, 'RemoteVersion' => $sVersion, 'RemoteRelease' => empty($aData['release']) ? '' : $aData['release'], 'VersionCompare' => \version_compare($sCurrentVersion, $sVersion) )); } /** * @return array */ public function DoAdminUpdateCoreData() { $this->IsAdminLoggined(); $bReal = false; $sNewVersion = ''; $bRainLoopUpdatable = $this->rainLoopUpdatable(); $bRainLoopAccess = $this->rainLoopCoreAccess(); $aData = array(); if ($bRainLoopUpdatable && $bRainLoopAccess) { $aData = $this->getCoreData($bReal); } $bResult = false; if ($bReal && !empty($aData['file'])) { $sTmp = $this->downloadRemotePackageByUrl($aData['file']); if (!empty($sTmp)) { include_once APP_VERSION_ROOT_PATH.'app/libraries/pclzip/pclzip.lib.php'; $oArchive = new \PclZip($sTmp); $sTmpFolder = APP_PRIVATE_DATA.\md5($sTmp); \mkdir($sTmpFolder); if (\is_dir($sTmpFolder)) { $bResult = 0 !== $oArchive->extract(PCLZIP_OPT_PATH, $sTmpFolder); if (!$bResult) { $this->Logger()->Write('Cannot extract package files: '.$oArchive->errorInfo(), \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } if ($bResult && \file_exists($sTmpFolder.'/index.php') && \is_writable(APP_INDEX_ROOT_PATH.'rainloop/') && \is_writable(APP_INDEX_ROOT_PATH.'index.php') && \is_dir($sTmpFolder.'/rainloop/')) { $aMatch = array(); $sIndexFile = \file_get_contents($sTmpFolder.'/index.php'); if (\preg_match('/\'APP_VERSION\', \'([^\']+)\'/', $sIndexFile, $aMatch) && !empty($aMatch[1])) { $sNewVersion = \trim($aMatch[1]); } if (empty($sNewVersion)) { $this->Logger()->Write('Unknown version', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } else if (!\is_dir(APP_INDEX_ROOT_PATH.'rainloop/v/'.$sNewVersion)) { \MailSo\Base\Utils::CopyDir($sTmpFolder.'/rainloop/', APP_INDEX_ROOT_PATH.'rainloop/'); if (\is_dir(APP_INDEX_ROOT_PATH.'rainloop/v/'.$sNewVersion) && \is_file(APP_INDEX_ROOT_PATH.'rainloop/v/'.$sNewVersion.'/index.php')) { $bResult = \copy($sTmpFolder.'/index.php', APP_INDEX_ROOT_PATH.'index.php'); if ($bResult) { if (\MailSo\Base\Utils::FunctionExistsAndEnabled('opcache_invalidate')) { @\opcache_invalidate(APP_INDEX_ROOT_PATH.'index.php', true); } if (\MailSo\Base\Utils::FunctionExistsAndEnabled('apc_delete_file')) { @\apc_delete_file(APP_INDEX_ROOT_PATH.'index.php'); } } } else { $this->Logger()->Write('Cannot copy new package files', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); $this->Logger()->Write($sTmpFolder.'/rainloop/ -> '.APP_INDEX_ROOT_PATH.'rainloop/', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } } else if (!empty($sNewVersion)) { $this->Logger()->Write('"'.$sNewVersion.'" version already installed', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } } else if ($bResult) { $this->Logger()->Write('Cannot validate package files', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } \MailSo\Base\Utils::RecRmDir($sTmpFolder); } else { $this->Logger()->Write('Cannot create tmp folder: '.$sTmpFolder, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } @\unlink($sTmp); } } return $this->DefaultResponse(__FUNCTION__, $bResult); } public function removeOldVersion() { $sVPath = APP_INDEX_ROOT_PATH.'rainloop/v/'; $this->Logger()->Write('Versions GC: Begin'); $aDirs = @\array_map('basename', @\array_filter(@\glob($sVPath.'*'), 'is_dir')); $this->Logger()->Write('Versions GC: Count:'.(\is_array($aDirs) ? \count($aDirs) : 0)); if (\is_array($aDirs) && 5 < \count($aDirs)) { \uasort($aDirs, 'version_compare'); foreach ($aDirs as $sName) { if (APP_DEV_VERSION !== $sName && APP_VERSION !== $sName) { $this->Logger()->Write('Versions GC: Begin to remove "'.$sVPath.$sName.'" version'); if (@\unlink($sVPath.$sName.'/index.php')) { @\MailSo\Base\Utils::RecRmDir($sVPath.$sName); } else { $this->Logger()->Write('Versions GC (Error): index file cant be removed from"'.$sVPath.$sName.'"', \MailSo\Log\Enumerations\Type::ERROR); } $this->Logger()->Write('Versions GC: End to remove "'.$sVPath.$sName.'" version'); break; } } } $this->Logger()->Write('Versions GC: End'); } /** * @return array */ public function DoAdminPackageDelete() { $this->IsAdminLoggined(); $sId = $this->GetActionParam('Id', ''); $bReal = false; $bRainLoopUpdatable = false; $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); $sResultId = ''; foreach ($aList as $oItem) { if ($oItem && 'plugin' === $oItem['type'] && $sId === $oItem['id']) { $sResultId = $sId; break; } } $bResult = false; if ('' !== $sResultId) { $bResult = \MailSo\Base\Utils::RecRmDir(APP_PLUGINS_PATH.$sResultId); if ($bResult) { $this->pluginEnable($sResultId, false); } } return $this->DefaultResponse(__FUNCTION__, $bResult); } /** * @param string $sUrl * * @return string */ private function downloadRemotePackageByUrl($sUrl) { $bResult = false; $sTmp = APP_PRIVATE_DATA.\md5(\microtime(true).$sUrl).'.zip'; $pDest = @\fopen($sTmp, 'w+b'); if ($pDest) { $iCode = 0; $sContentType = ''; @\set_time_limit(90); $oHttp = \MailSo\Base\Http::SingletonInstance(); $bResult = $oHttp->SaveUrlToFile($sUrl, $pDest, $sTmp, $sContentType, $iCode, $this->Logger(), 60, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')); if (!$bResult) { $this->Logger()->Write('Cannot save url to temp file: ', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); $this->Logger()->Write($sUrl.' -> '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } @\fclose($pDest); } else { $this->Logger()->Write('Cannot create temp file: '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } return $bResult ? $sTmp : ''; } /** * @return array */ public function DoAdminPackageInstall() { $this->IsAdminLoggined(); $sType = $this->GetActionParam('Type', ''); $sId = $this->GetActionParam('Id', ''); $sFile = $this->GetActionParam('File', ''); $this->Logger()->Write('Start package install: '.$sFile.' ('.$sType.')', \MailSo\Log\Enumerations\Type::INFO, 'INSTALLER'); $sRealFile = ''; $bReal = false; $bRainLoopUpdatable = false; $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); if ('plugin' === $sType) { foreach ($aList as $oItem) { if ($oItem && $sFile === $oItem['file'] && $sId === $oItem['id']) { $sRealFile = $sFile; break; } } } $sTmp = ''; $bResult = false; if ('' !== $sRealFile) { $sUrl = $this->rainloopRepo().$sRealFile; $sTmp = $this->downloadRemotePackageByUrl($sUrl); } if ('' !== $sTmp) { include_once APP_VERSION_ROOT_PATH.'app/libraries/pclzip/pclzip.lib.php'; $oArchive = new \PclZip($sTmp); if ('plugin' === $sType) { $bResult = true; if (\is_dir(APP_PLUGINS_PATH.$sId)) { $bResult = \MailSo\Base\Utils::RecRmDir(APP_PLUGINS_PATH.$sId); if (!$bResult) { $this->Logger()->Write('Cannot remove previous plugin folder: '.$sId, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } } if ($bResult) { $bResult = 0 !== $oArchive->extract(PCLZIP_OPT_PATH, APP_PLUGINS_PATH); if (!$bResult) { $this->Logger()->Write('Cannot extract package files: '.$oArchive->errorInfo(), \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } } } @\unlink($sTmp); } return $this->DefaultResponse(__FUNCTION__, $bResult ? ('plugin' !== $sType ? array('Reload' => true) : true) : false); } /** * @return array */ public function DoAdminPluginList() { $this->IsAdminLoggined(); $aResult = array(); $sEnabledPlugins = $this->Config()->Get('plugins', 'enabled_list', ''); $aEnabledPlugins = \explode(',', \strtolower($sEnabledPlugins)); $aEnabledPlugins = \array_map('trim', $aEnabledPlugins); $aList = $this->Plugins()->InstalledPlugins(); foreach ($aList as $aItem) { $aResult[] = array( 'Name' => $aItem[0], 'Enabled' => \in_array(\strtolower($aItem[0]), $aEnabledPlugins), 'Configured' => false ); } return $this->DefaultResponse(__FUNCTION__, $aResult); } /** * @param string $sName * @param bool $bEnable = true * @return bool */ private function pluginEnable($sName, $bEnable = true) { if (0 === \strlen($sName)) { return false; } $oConfig = $this->Config(); $sEnabledPlugins = $oConfig->Get('plugins', 'enabled_list', ''); $aEnabledPlugins = \explode(',', \strtolower($sEnabledPlugins)); $aEnabledPlugins = \array_map('trim', $aEnabledPlugins); $aNewEnabledPlugins = array(); if ($bEnable) { $aNewEnabledPlugins = $aEnabledPlugins; $aNewEnabledPlugins[] = $sName; } else { foreach ($aEnabledPlugins as $sPlugin) { if ($sName !== $sPlugin && 0 < \strlen($sPlugin)) { $aNewEnabledPlugins[] = $sPlugin; } } } $aNewEnabledPlugins = \array_unique($aNewEnabledPlugins); $oConfig->Set('plugins', 'enabled_list', \trim(\implode(',', $aNewEnabledPlugins), ' ,')); return $oConfig->Save(); } /** * @return array */ public function DoAdminPluginDisable() { $this->IsAdminLoggined(); $sName = (string) $this->GetActionParam('Name', ''); $bDisable = '1' === (string) $this->GetActionParam('Disabled', '1'); if (!$bDisable) { $oPlugin = $this->Plugins()->CreatePluginByName($sName); if ($oPlugin && ($oPlugin instanceof \RainLoop\Plugins\AbstractPlugin)) { $sValue = $oPlugin->Supported(); if (0 < \strlen($sValue)) { return $this->FalseResponse(__FUNCTION__, \RainLoop\Notifications::UnsupportedPluginPackage, $sValue); } } else { return $this->FalseResponse(__FUNCTION__, \RainLoop\Notifications::InvalidPluginPackage); } } return $this->DefaultResponse(__FUNCTION__, $this->pluginEnable($sName, !$bDisable)); } /** * @return array */ public function DoAdminPluginLoad() { $this->IsAdminLoggined(); $mResult = false; $sName = (string) $this->GetActionParam('Name', ''); if (!empty($sName)) { $oPlugin = $this->Plugins()->CreatePluginByName($sName); if ($oPlugin) { $mResult = array( 'Name' => $sName, 'Readme' => file_exists($oPlugin->Path().'/README') ? file_get_contents($oPlugin->Path().'/README') : '', 'Config' => array() ); $aMap = $oPlugin->ConfigMap(); $oConfig = $oPlugin->Config(); if (is_array($aMap) && 0 < count($aMap)) { foreach ($aMap as $oItem) { if ($oItem && ($oItem instanceof \RainLoop\Plugins\Property)) { $aItem = $oItem->ToArray(); $aItem[0] = $oConfig->Get('plugin', $oItem->Name(), ''); if (\RainLoop\Enumerations\PluginPropertyType::PASSWORD === $oItem->Type()) { $aItem[0] = APP_DUMMY; } $mResult['Config'][] = $aItem; } } } } } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * @return array */ public function DoAdminPluginSettingsUpdate() { $this->IsAdminLoggined(); $mResult = false; $sName = (string) $this->GetActionParam('Name', ''); if (!empty($sName)) { $oPlugin = $this->Plugins()->CreatePluginByName($sName); if ($oPlugin) { $oConfig = $oPlugin->Config(); $aMap = $oPlugin->ConfigMap(); if (is_array($aMap) && 0 < count($aMap)) { foreach ($aMap as $oItem) { $sValue = $this->GetActionParam('_'.$oItem->Name(), $oConfig->Get('plugin', $oItem->Name())); if (\RainLoop\Enumerations\PluginPropertyType::PASSWORD !== $oItem->Type() || APP_DUMMY !== $sValue) { $mResultValue = null; switch ($oItem->Type()) { case \RainLoop\Enumerations\PluginPropertyType::INT: $mResultValue = (int) $sValue; break; case \RainLoop\Enumerations\PluginPropertyType::BOOL: $mResultValue = '1' === (string) $sValue; break; case \RainLoop\Enumerations\PluginPropertyType::SELECTION: if (is_array($oItem->DefaultValue()) && in_array($sValue, $oItem->DefaultValue())) { $mResultValue = (string) $sValue; } break; case \RainLoop\Enumerations\PluginPropertyType::PASSWORD: case \RainLoop\Enumerations\PluginPropertyType::STRING: case \RainLoop\Enumerations\PluginPropertyType::STRING_TEXT: $mResultValue = (string) $sValue; break; } if (null !== $mResultValue) { $oConfig->Set('plugin', $oItem->Name(), $mResultValue); } } } } $mResult = $oConfig->Save(); } } if (!$mResult) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSavePluginSettings); } return $this->DefaultResponse(__FUNCTION__, true); } /** * @return array */ public function DoSettingsUpdate() { $self = $this; $oConfig = $this->Config(); $oAccount = $this->getAccountFromToken(); $oSettings = $this->SettingsProvider()->Load($oAccount); $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) { $this->setSettingsFromParams($oSettings, 'Language', 'string', function ($sLanguage) use ($self) { return $self->ValidateLanguage($sLanguage); }); } else { $oSettings->SetConf('Language', $this->ValidateLanguage($oConfig->Get('webmail', 'language', 'en'))); } if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::THEMES, $oAccount)) { $this->setSettingsFromParams($oSettingsLocal, 'Theme', 'string', function ($sTheme) use ($self) { return $self->ValidateTheme($sTheme); }); } else { $oSettingsLocal->SetConf('Theme', $this->ValidateLanguage($oConfig->Get('webmail', 'theme', 'Default'))); } $this->setSettingsFromParams($oSettings, 'MPP', 'int', function ($iValue) { return (int) (\in_array($iValue, array(10, 20, 30, 50, 100, 150, 200, 300)) ? $iValue : 20); }); $this->setSettingsFromParams($oSettings, 'Layout', 'int', function ($iValue) { return (int) (\in_array((int) $iValue, array(\RainLoop\Enumerations\Layout::NO_PREVIW, \RainLoop\Enumerations\Layout::SIDE_PREVIEW, \RainLoop\Enumerations\Layout::BOTTOM_PREVIEW)) ? $iValue : \RainLoop\Enumerations\Layout::SIDE_PREVIEW); }); $this->setSettingsFromParams($oSettings, 'EditorDefaultType', 'string'); $this->setSettingsFromParams($oSettings, 'ShowImages', 'bool'); $this->setSettingsFromParams($oSettings, 'ContactsAutosave', 'bool'); $this->setSettingsFromParams($oSettings, 'DesktopNotifications', 'bool'); $this->setSettingsFromParams($oSettings, 'SoundNotification', 'bool'); $this->setSettingsFromParams($oSettings, 'UseCheckboxesInList', 'bool'); $this->setSettingsFromParams($oSettings, 'AutoLogout', 'int'); $this->setSettingsFromParams($oSettings, 'EnableTwoFactor', 'bool'); $this->setSettingsFromParams($oSettingsLocal, 'UseThreads', 'bool'); $this->setSettingsFromParams($oSettingsLocal, 'ReplySameFolder', 'bool'); return $this->DefaultResponse(__FUNCTION__, $this->SettingsProvider()->Save($oAccount, $oSettings) && $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); } /** * @return array */ public function DoNoop() { $this->initMailClientConnection(); return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoPing() { return $this->DefaultResponse(__FUNCTION__, 'Pong'); } /** * @return array */ public function DoChangePassword() { $oAccount = $this->getAccountFromToken(); if ($oAccount) { try { $this->ChangePasswordProvider()->ChangePassword( $oAccount, $this->GetActionParam('PrevPassword', ''), $this->GetActionParam('NewPassword', '') ); } catch (\Exception $oException) { $this->loginErrorDelay(); $this->Logger()->Write('Error: Can\'t change password for '.$oAccount->Email().' account.', \MailSo\Log\Enumerations\Type::NOTICE); throw $oException; } } return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoJsInfo() { $bIsError = '1' === (string) $this->GetActionParam('IsError', '0'); $mData = $this->GetActionParam('Data', null); $this->Logger()->WriteDump(is_array($mData) ? $mData : array(), $bIsError ? \MailSo\Log\Enumerations\Type::ERROR : \MailSo\Log\Enumerations\Type::INFO, 'JS-INFO'); return $this->DefaultResponse(__FUNCTION__, true); } /** * @return array */ public function DoVersion() { return $this->DefaultResponse(__FUNCTION__, APP_VERSION === (string) $this->GetActionParam('Version', '')); } /** * @return array */ public function DoJsError() { $sMessage = $this->GetActionParam('Message', ''); if (0 < strlen($sMessage)) { $sFileName = $this->GetActionParam('FileName', ''); $sLineNo = $this->GetActionParam('LineNo', ''); $sLocation = $this->GetActionParam('Location', ''); $sHtmlCapa = $this->GetActionParam('HtmlCapa', ''); $sTimeOnPage = $this->GetActionParam('TimeOnPage', ''); $oHttp = $this->Http(); $this->Logger()->Write($sMessage.' ('.$sFileName.' ~ '.$sLineNo.')', \MailSo\Log\Enumerations\Type::ERROR, 'JS'); $this->Logger()->WriteDump(array( 'Location' => $sLocation, 'Capability' => $sHtmlCapa, 'TimeOnPage' => $sTimeOnPage, 'HTTP_USER_AGENT' => $oHttp->GetServer('HTTP_USER_AGENT', ''), 'HTTP_ACCEPT_ENCODING' => $oHttp->GetServer('HTTP_ACCEPT_ENCODING', ''), 'HTTP_ACCEPT_LANGUAGE' => $oHttp->GetServer('HTTP_ACCEPT_LANGUAGE', '') )); } return $this->DefaultResponse(__FUNCTION__, true); } /** * @param \MailSo\Mail\FolderCollection $oFolders * @return array */ private function recFoldersNames($oFolders) { $aResult = array(); if ($oFolders) { $aFolders =& $oFolders->GetAsArray(); foreach ($aFolders as $oFolder) { $aResult[] = $oFolder->FullNameRaw()."|". implode("|", $oFolder->Flags()).($oFolder->IsSubscribed() ? '1' : '0'); $oSub = $oFolder->SubFolders(); if ($oSub && 0 < $oSub->Count()) { $aResult = \array_merge($aResult, $this->recFoldersNames($oSub)); } } } return $aResult; } /** * @staticvar array $aCache * @param \RainLoop\Model\Account $oAccount * * @return array */ private function systemFoldersNames($oAccount) { static $aCache = null; if (null === $aCache) { $aCache = array( 'Sent' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Send' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Sent Item' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Sent Items' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Send Item' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Send Items' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Sent Mail' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Sent Mails' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Send Mail' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Send Mails' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Drafts' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Draft' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Draft Mail' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Draft Mails' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Drafts Mail' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Drafts Mails' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Spam' => \MailSo\Imap\Enumerations\FolderType::JUNK, 'Junk' => \MailSo\Imap\Enumerations\FolderType::JUNK, 'Bulk Mail' => \MailSo\Imap\Enumerations\FolderType::JUNK, 'Bulk Mails' => \MailSo\Imap\Enumerations\FolderType::JUNK, 'Trash' => \MailSo\Imap\Enumerations\FolderType::TRASH, 'Deleted' => \MailSo\Imap\Enumerations\FolderType::TRASH, 'Bin' => \MailSo\Imap\Enumerations\FolderType::TRASH, 'Archive' => \MailSo\Imap\Enumerations\FolderType::ALL, 'All' => \MailSo\Imap\Enumerations\FolderType::ALL, 'All Mail' => \MailSo\Imap\Enumerations\FolderType::ALL, 'All Mails' => \MailSo\Imap\Enumerations\FolderType::ALL, 'AllMail' => \MailSo\Imap\Enumerations\FolderType::ALL, 'AllMails' => \MailSo\Imap\Enumerations\FolderType::ALL, ); $this->Plugins()->RunHook('filter.system-folders-names', array($oAccount, &$aCache)); } return $aCache; } /** * @param \RainLoop\Model\Account $oAccount * @param \MailSo\Mail\FolderCollection $oFolders * @param array $aResult * @param bool $bListFolderTypes = true */ private function recFoldersTypes($oAccount, $oFolders, &$aResult, $bListFolderTypes = true) { if ($oFolders) { $aFolders =& $oFolders->GetAsArray(); if (\is_array($aFolders) && 0 < \count($aFolders)) { if ($bListFolderTypes) { foreach ($aFolders as $oFolder) { $iFolderListType = $oFolder->GetFolderListType(); if (!isset($aResult[$iFolderListType]) && \in_array($iFolderListType, array( \MailSo\Imap\Enumerations\FolderType::SENT, \MailSo\Imap\Enumerations\FolderType::DRAFTS, \MailSo\Imap\Enumerations\FolderType::JUNK, \MailSo\Imap\Enumerations\FolderType::TRASH, \MailSo\Imap\Enumerations\FolderType::ALL ))) { $aResult[$iFolderListType] = $oFolder->FullNameRaw(); } } foreach ($aFolders as $oFolder) { $oSub = $oFolder->SubFolders(); if ($oSub && 0 < $oSub->Count()) { $this->recFoldersTypes($oAccount, $oSub, $aResult, true); } } } $aMap = $this->systemFoldersNames($oAccount); foreach ($aFolders as $oFolder) { $sName = $oFolder->Name(); $sFullName = $oFolder->FullName(); if (isset($aMap[$sName]) || isset($aMap[$sFullName])) { $iFolderType = isset($aMap[$sName]) ? $aMap[$sName] : $aMap[$sFullName]; if (!isset($aResult[$iFolderType]) && \in_array($iFolderType, array( \MailSo\Imap\Enumerations\FolderType::SENT, \MailSo\Imap\Enumerations\FolderType::DRAFTS, \MailSo\Imap\Enumerations\FolderType::JUNK, \MailSo\Imap\Enumerations\FolderType::TRASH, \MailSo\Imap\Enumerations\FolderType::ALL ))) { $aResult[$iFolderType] = $oFolder->FullNameRaw(); } } } foreach ($aFolders as $oFolder) { $oSub = $oFolder->SubFolders(); if ($oSub && 0 < $oSub->Count()) { $this->recFoldersTypes($oAccount, $oSub, $aResult, false); } } } } } /** * @return array */ public function DoFolders() { $sRawKey = (string) $this->GetActionParam('RawKey', ''); if (0 < \strlen($sRawKey)) { $this->verifyCacheByKey($sRawKey); } $oAccount = $this->initMailClientConnection(); $oFolderCollection = null; $this->Plugins()->RunHook('filter.folders-before', array($oAccount, &$oFolderCollection)); if (null === $oFolderCollection) { $oFolderCollection = $this->MailClient()->Folders('', '*', !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true), (int) $this->Config()->Get('labs', 'imap_folder_list_limit', 200) ); } $this->Plugins()->RunHook('filter.folders-post', array($oAccount, &$oFolderCollection)); if ($oFolderCollection instanceof \MailSo\Mail\FolderCollection) { $aSystemFolders = array(); $this->recFoldersTypes($oAccount, $oFolderCollection, $aSystemFolders); $oFolderCollection->SystemFolders = $aSystemFolders; if ($this->Config()->Get('labs', 'autocreate_system_folders', true)) { $bDoItAgain = false; $sNamespace = $oFolderCollection->GetNamespace(); $sParent = empty($sNamespace) ? '' : \substr($sNamespace, 0, -1); $sDelimiter = $oFolderCollection->FindDelimiter(); $aList = array(); $aMap = $this->systemFoldersNames($oAccount); if ('' === $this->GetActionParam('SentFolder', '')) { $aList[] = \MailSo\Imap\Enumerations\FolderType::SENT; } if ('' === $this->GetActionParam('DraftFolder', '')) { $aList[] = \MailSo\Imap\Enumerations\FolderType::DRAFTS; } if ('' === $this->GetActionParam('SpamFolder', '')) { $aList[] = \MailSo\Imap\Enumerations\FolderType::JUNK; } if ('' === $this->GetActionParam('TrashFolder', '')) { $aList[] = \MailSo\Imap\Enumerations\FolderType::TRASH; } if ('' === $this->GetActionParam('ArchiveFolder', '')) { $aList[] = \MailSo\Imap\Enumerations\FolderType::ALL; } $this->Plugins()->RunHook('filter.folders-system-types', array($oAccount, &$aList)); foreach ($aList as $iType) { if (!isset($aSystemFolders[$iType])) { $mFolderNameToCreate = \array_search($iType, $aMap); if (!empty($mFolderNameToCreate)) { $iPos = \strrpos($mFolderNameToCreate, $sDelimiter); if (false !== $iPos) { $mNewParent = \substr($mFolderNameToCreate, 0, $iPos); $mNewFolderNameToCreate = \substr($mFolderNameToCreate, $iPos + 1); if (0 < \strlen($mNewFolderNameToCreate)) { $mFolderNameToCreate = $mNewFolderNameToCreate; } if (0 < \strlen($mNewParent)) { $sParent = 0 < \strlen($sParent) ? $sParent.$sDelimiter.$mNewParent : $mNewParent; } } $sFullNameToCheck = \MailSo\Base\Utils::ConvertEncoding($mFolderNameToCreate, \MailSo\Base\Enumerations\Charset::UTF_8, \MailSo\Base\Enumerations\Charset::UTF_7_IMAP); if (0 < \strlen(\trim($sParent))) { $sFullNameToCheck = $sParent.$sDelimiter.$sFullNameToCheck; } if (!$oFolderCollection->GetByFullNameRaw($sFullNameToCheck)) { try { if ($this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent, true, $sDelimiter)) { $bDoItAgain = true; } } catch (\Exception $oException) { $this->Logger()->WriteException($oException); } } } } } if ($bDoItAgain) { $oFolderCollection = $this->MailClient()->Folders('', '*', !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true), (int) $this->Config()->Get('labs', 'imap_folder_list_limit', 200) ); if ($oFolderCollection) { $aSystemFolders = array(); $this->recFoldersTypes($oAccount, $oFolderCollection, $aSystemFolders); $oFolderCollection->SystemFolders = $aSystemFolders; } } } if ($oFolderCollection) { $oFolderCollection->FoldersHash = \md5(\implode("\x0", $this->recFoldersNames($oFolderCollection))); $this->cacheByKey($sRawKey); } } $this->Plugins()->RunHook('filter.folders-complete', array($oAccount, &$oFolderCollection)); return $this->DefaultResponse(__FUNCTION__, $oFolderCollection); } /** * @return array */ public function DoFolderCreate() { $this->initMailClientConnection(); try { $sFolderNameInUtf = $this->GetActionParam('Folder', ''); $sFolderParentFullNameRaw = $this->GetActionParam('Parent', ''); $this->MailClient()->FolderCreate($sFolderNameInUtf, $sFolderParentFullNameRaw, !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true)); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantCreateFolder, $oException); } return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoFolderSubscribe() { $this->initMailClientConnection(); $sFolderNameInUtf = $this->GetActionParam('Folder', ''); $bSubscribe = '1' === (string) $this->GetActionParam('Subscribe', '0'); try { $this->MailClient()->FolderSubscribe($sFolderNameInUtf, !!$bSubscribe); } catch (\Exception $oException) { if ($bSubscribe) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSubscribeFolder, $oException); } else { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantUnsubscribeFolder, $oException); } } return $this->TrueResponse(__FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFolderRename() { $this->initMailClientConnection(); $sPrevFolderFullNameRaw = $this->GetActionParam('Folder', ''); $sNewTopFolderNameInUtf = $this->GetActionParam('NewFolderName', ''); try { $this->MailClient()->FolderRename($sPrevFolderFullNameRaw, $sNewTopFolderNameInUtf, !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true)); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantRenameFolder, $oException); } return $this->TrueResponse(__FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFolderDelete() { $this->initMailClientConnection(); $sFolderFullNameRaw = $this->GetActionParam('Folder', ''); try { $this->MailClient()->FolderDelete($sFolderFullNameRaw, !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true)); } catch (\MailSo\Mail\Exceptions\NonEmptyFolder $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantDeleteNonEmptyFolder, $oException); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantDeleteFolder, $oException); } return $this->TrueResponse(__FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFolderClear() { $this->initMailClientConnection(); $sFolderFullNameRaw = $this->GetActionParam('Folder', ''); try { $this->MailClient()->FolderClear($sFolderFullNameRaw); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError, $oException); } return $this->TrueResponse(__FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFolderInformation() { $sFolder = $this->GetActionParam('Folder', ''); $sPrevUidNext = $this->GetActionParam('UidNext', ''); $aFlagsUids = array(); $sFlagsUids = (string) $this->GetActionParam('FlagsUids', ''); $aFlagsFilteredUids = array(); if (0 < strlen($sFlagsUids)) { $aFlagsUids = explode(',', $sFlagsUids); $aFlagsFilteredUids = array_filter($aFlagsUids, function (&$sUid) { $sUid = (int) trim($sUid); return 0 < (int) trim($sUid); }); } $this->initMailClientConnection(); $sForwardedFlag = $this->Config()->Get('labs', 'imap_forwarded_flag', ''); $sReadReceiptFlag = $this->Config()->Get('labs', 'imap_read_receipt_flag', ''); try { $aInboxInformation = $this->MailClient()->FolderInformation( $sFolder, $sPrevUidNext, $aFlagsFilteredUids ); if (\is_array($aInboxInformation) && isset($aInboxInformation['Flags']) && \is_array($aInboxInformation['Flags'])) { foreach ($aInboxInformation['Flags'] as $iUid => $aFlags) { $aLowerFlags = array_map('strtolower', $aFlags); $aInboxInformation['Flags'][$iUid] = array( 'IsSeen' => in_array('\\seen', $aLowerFlags), 'IsFlagged' => in_array('\\flagged', $aLowerFlags), 'IsAnswered' => in_array('\\answered', $aLowerFlags), 'IsDeleted' => in_array('\\deleted', $aLowerFlags), 'IsForwarded' => 0 < strlen($sForwardedFlag) && in_array(strtolower($sForwardedFlag), $aLowerFlags), 'IsReadReceipt' => 0 < strlen($sReadReceiptFlag) && in_array(strtolower($sReadReceiptFlag), $aLowerFlags) ); } } } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError, $oException); } if (\is_array($aInboxInformation)) { $aInboxInformation['Version'] = APP_VERSION; } return $this->DefaultResponse(__FUNCTION__, $aInboxInformation); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoFolderInformationMultiply() { $aResult = array( 'List' => array(), 'Version' => APP_VERSION ); $aFolders = $this->GetActionParam('Folders', null); if (\is_array($aFolders)) { $this->initMailClientConnection(); $aFolders = \array_unique($aFolders); foreach ($aFolders as $sFolder) { if (0 < \strlen(\trim($sFolder)) && 'INBOX' !== \strtoupper($sFolder)) { try { $aInboxInformation = $this->MailClient()->FolderInformation($sFolder, '', array()); if (\is_array($aInboxInformation) && isset($aInboxInformation['Folder'])) { $aResult['List'][] = $aInboxInformation; } } catch (\Exception $oException) { $this->Logger()->WriteException($oException); } } } } return $this->DefaultResponse(__FUNCTION__, $aResult); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoMessageList() { // \sleep(2); // throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList); $sFolder = ''; $iOffset = 0; $iLimit = 20; $sSearch = ''; $sUidNext = ''; $bUseThreads = false; $sExpandedThreadUid = ''; $sRawKey = $this->GetActionParam('RawKey', ''); $aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9); if (\is_array($aValues) && 9 === \count($aValues)) { $sFolder =(string) $aValues[0]; $iOffset = (int) $aValues[1]; $iLimit = (int) $aValues[2]; $sSearch = (string) $aValues[3]; $sUidNext = (string) $aValues[6]; $bUseThreads = (bool) $aValues[7]; $sExpandedThreadUid = (string) $aValues[8]; $this->verifyCacheByKey($sRawKey); } else { $sFolder = $this->GetActionParam('Folder', ''); $iOffset = (int) $this->GetActionParam('Offset', 0); $iLimit = (int) $this->GetActionParam('Limit', 10); $sSearch = $this->GetActionParam('Search', ''); $sUidNext = $this->GetActionParam('UidNext', ''); $bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0'); $sExpandedThreadUid = (string) $this->GetActionParam('ExpandedThreadUid', ''); } if (0 === strlen($sFolder)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList); } $this->initMailClientConnection(); try { if (!$this->Config()->Get('labs', 'use_imap_thread', false)) { $bUseThreads = false; $sExpandedThreadUid = ''; } $aExpandedThreadUid = array(); if (0 < \strlen($sExpandedThreadUid)) { $aExpandedThreadUid = \explode(',', $sExpandedThreadUid); $aExpandedThreadUid = \array_map(function ($sValue) { $sValue = \trim($sValue); return \is_numeric($sValue) ? (int) $sValue : 0; }, $aExpandedThreadUid); $aExpandedThreadUid = \array_filter($aExpandedThreadUid, function ($iValue) { return 0 < $iValue; }); } $oMessageList = $this->MailClient()->MessageList( $sFolder, $iOffset, $iLimit, $sSearch, $sUidNext, $this->cacherForUids(), !!$this->Config()->Get('labs', 'use_imap_sort', false), $bUseThreads, $aExpandedThreadUid, !!$this->Config()->Get('labs', 'use_imap_esearch_esort', false) ); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList, $oException); } if ($oMessageList instanceof \MailSo\Mail\MessageCollection) { $this->cacheByKey($sRawKey); } return $this->DefaultResponse(__FUNCTION__, $oMessageList); } /** * @param \RainLoop\Model\Account $oAccount * @param bool $bWithDraftInfo = true * * @return \MailSo\Mime\Message */ private function buildMessage($oAccount, $bWithDraftInfo = true) { $sIdentityID = $this->GetActionParam('IdentityID', ''); $sTo = $this->GetActionParam('To', ''); $sCc = $this->GetActionParam('Cc', ''); $sBcc = $this->GetActionParam('Bcc', ''); $sReplyTo = $this->GetActionParam('ReplyTo', ''); $sSubject = $this->GetActionParam('Subject', ''); $bTextIsHtml = '1' === $this->GetActionParam('TextIsHtml', '0'); $bReadReceiptRequest = '1' === $this->GetActionParam('ReadReceiptRequest', '0'); $bMarkAsImportant = '1' === $this->GetActionParam('MarkAsImportant', '0'); $sText = $this->GetActionParam('Text', ''); $aAttachments = $this->GetActionParam('Attachments', null); $aDraftInfo = $this->GetActionParam('DraftInfo', null); $sInReplyTo = $this->GetActionParam('InReplyTo', ''); $sReferences = $this->GetActionParam('References', ''); $oMessage = \MailSo\Mime\Message::NewInstance(); $oMessage->RegenerateMessageId(); $oMessage->SetXMailer('RainLoop/'.APP_VERSION); $oFromIdentity = $this->GetIdentityByID($oAccount, $sIdentityID); if ($oFromIdentity) { $oMessage->SetFrom(\MailSo\Mime\Email::NewInstance( $oFromIdentity->Email(), $oFromIdentity->Name())); } else { $oMessage->SetFrom(\MailSo\Mime\Email::Parse($oAccount->Email())); } if (!empty($sReplyTo)) { $oReplyTo = \MailSo\Mime\EmailCollection::NewInstance($sReplyTo); if ($oReplyTo && 0 < $oReplyTo->Count()) { $oMessage->SetReplyTo($oReplyTo); } } if ($bReadReceiptRequest) { $oMessage->SetReadReceipt($oAccount->Email()); } if ($bMarkAsImportant) { $oMessage->SetPriority(\MailSo\Mime\Enumerations\MessagePriority::HIGH); } $oMessage->SetSubject($sSubject); $oToEmails = \MailSo\Mime\EmailCollection::NewInstance($sTo); if ($oToEmails && $oToEmails->Count()) { $oMessage->SetTo($oToEmails); } $oCcEmails = \MailSo\Mime\EmailCollection::NewInstance($sCc); if ($oCcEmails && $oCcEmails->Count()) { $oMessage->SetCc($oCcEmails); } $oBccEmails = \MailSo\Mime\EmailCollection::NewInstance($sBcc); if ($oBccEmails && $oBccEmails->Count()) { $oMessage->SetBcc($oBccEmails); } if ($bWithDraftInfo && \is_array($aDraftInfo) && !empty($aDraftInfo[0]) && !empty($aDraftInfo[1]) && !empty($aDraftInfo[2])) { $oMessage->SetDraftInfo($aDraftInfo[0], $aDraftInfo[1], $aDraftInfo[2]); } if (0 < \strlen($sInReplyTo)) { $oMessage->SetInReplyTo($sInReplyTo); } if (0 < \strlen($sReferences)) { $oMessage->SetReferences($sReferences); } $aFoundedCids = array(); $mFoundDataURL = array(); $aFoundedContentLocationUrls = array(); $sTextToAdd = $bTextIsHtml ? \MailSo\Base\HtmlUtils::BuildHtml($sText, $aFoundedCids, $mFoundDataURL, $aFoundedContentLocationUrls) : $sText; $this->Plugins()->RunHook($bTextIsHtml ? 'filter.message-html' : 'filter.message-plain', array($oAccount, &$oMessage, &$sTextToAdd)); if ($bTextIsHtml && 0 < \strlen($sTextToAdd)) { $sTextConverted = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sTextToAdd); $this->Plugins()->RunHook('filter.message-plain', array($oAccount, &$oMessage, &$sTextConverted)); $oMessage->AddText($sTextConverted, false); } $oMessage->AddText($sTextToAdd, $bTextIsHtml); if (\is_array($aAttachments)) { foreach ($aAttachments as $sTempName => $aData) { $sFileName = (string) $aData[0]; $bIsInline = (bool) $aData[1]; $sCID = (string) $aData[2]; $sContentLocation = isset($aData[3]) ? (string) $aData[3] : ''; $rResource = $this->FilesProvider()->GetFile($oAccount, $sTempName); if (\is_resource($rResource)) { $iFileSize = $this->FilesProvider()->FileSize($oAccount, $sTempName); $oMessage->Attachments()->Add( \MailSo\Mime\Attachment::NewInstance($rResource, $sFileName, $iFileSize, $bIsInline, \in_array(trim(trim($sCID), '<>'), $aFoundedCids), $sCID, array(), $sContentLocation ) ); } } } if ($mFoundDataURL && \is_array($mFoundDataURL) && 0 < \count($mFoundDataURL)) { foreach ($mFoundDataURL as $sCidHash => $sDataUrlString) { $aMatch = array(); $sCID = '<'.$sCidHash.'>'; if (\preg_match('/^data:(image\/[a-zA-Z0-9]+);base64,(.+)$/i', $sDataUrlString, $aMatch) && !empty($aMatch[1]) && !empty($aMatch[2])) { $sRaw = \MailSo\Base\Utils::Base64Decode($aMatch[2]); $iFileSize = \strlen($sRaw); if (0 < $iFileSize) { $sFileName = \preg_replace('/[^a-z0-9]+/i', '.', $aMatch[1]); $rResource = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($sRaw); $sRaw = ''; unset($sRaw); unset($aMatch); $oMessage->Attachments()->Add( \MailSo\Mime\Attachment::NewInstance($rResource, $sFileName, $iFileSize, true, true, $sCID) ); } } } } $this->Plugins()->RunHook('filter.build-message', array(&$oMessage)); $this->Plugins()->RunHook('filter.build-message[2]', array(&$oMessage, $oAccount)); return $oMessage; } /** * @param \RainLoop\Model\Account $oAccount * * @return void */ private function deleteMessageAttachmnets($oAccount) { $aAttachments = $this->GetActionParam('Attachments', null); if (\is_array($aAttachments)) { foreach (\array_keys($aAttachments) as $sTempName) { if ($this->FilesProvider()->FileExists($oAccount, $sTempName)) { $this->FilesProvider()->Clear($oAccount, $sTempName); } } } } /** * @param \RainLoop\Model\Account $oAccount * * @return \MailSo\Mime\Message */ private function buildReadReceiptMessage($oAccount) { $sReadReceipt = $this->GetActionParam('ReadReceipt', ''); $sSubject = $this->GetActionParam('Subject', ''); $sText = $this->GetActionParam('Text', ''); $oIdentity = $this->GetAccountIdentity($oAccount); if (empty($sReadReceipt) || empty($sSubject) || empty($sText) || !$oIdentity) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); } $oMessage = \MailSo\Mime\Message::NewInstance(); $oMessage->RegenerateMessageId(); $oMessage->SetXMailer('RainLoop/'.APP_VERSION); $oMessage->SetFrom(\MailSo\Mime\Email::NewInstance($oIdentity->Email(), $oIdentity->Name())); $sReplyTo = $oIdentity->ReplyTo(); if (!empty($sReplyTo)) { $oReplyTo = \MailSo\Mime\EmailCollection::NewInstance($sReplyTo); if ($oReplyTo && $oReplyTo->Count()) { $oMessage->SetReplyTo($oReplyTo); } } $oMessage->SetSubject($sSubject); $oToEmails = \MailSo\Mime\EmailCollection::NewInstance($sReadReceipt); if ($oToEmails && $oToEmails->Count()) { $oMessage->SetTo($oToEmails); } $this->Plugins()->RunHook('filter.read-receipt-message-plain', array($oAccount, &$oMessage, &$sText)); $oMessage->AddText($sText, false); $this->Plugins()->RunHook('filter.build-read-receipt-message', array(&$oMessage, $oAccount)); return $oMessage; } /** * @return array */ public function DoSaveMessage() { $oAccount = $this->initMailClientConnection(); $sMessageFolder = $this->GetActionParam('MessageFolder', ''); $sMessageUid = $this->GetActionParam('MessageUid', ''); $sDraftFolder = $this->GetActionParam('DraftFolder', ''); if (0 === strlen($sDraftFolder)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); } $oMessage = $this->buildMessage($oAccount, true); $this->Plugins() ->RunHook('filter.save-message', array(&$oMessage)) ->RunHook('filter.save-message[2]', array(&$oMessage, $oAccount)) ; $mResult = false; if ($oMessage) { $rMessageStream = \MailSo\Base\ResourceRegistry::CreateMemoryResource(); $iMessageStreamSize = \MailSo\Base\Utils::MultipleStreamWriter( $oMessage->ToStream(false), array($rMessageStream), 8192, true, true); if (false !== $iMessageStreamSize) { $sMessageId = $oMessage->MessageId(); \rewind($rMessageStream); $iNewUid = 0; $this->MailClient()->MessageAppendStream( $rMessageStream, $iMessageStreamSize, $sDraftFolder, array( \MailSo\Imap\Enumerations\MessageFlag::SEEN ), $iNewUid); if (!empty($sMessageId) && (null === $iNewUid || 0 === $iNewUid)) { $iNewUid = $this->MailClient()->FindMessageUidByMessageId($sDraftFolder, $sMessageId); } $mResult = true; if (0 < strlen($sMessageFolder) && 0 < strlen($sMessageUid)) { $this->MailClient()->MessageDelete($sMessageFolder, array($sMessageUid), true, true); } if (null !== $iNewUid && 0 < $iNewUid) { $mResult = array( 'NewFolder' => $sDraftFolder, 'NewUid' => $iNewUid ); } } } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * * @param \RainLoop\Model\Account $oAccount * @param \MailSo\Mime\Message $oMessage * @param resource $rMessageStream * @param bool $bDsn = false * @param bool $bAddHiddenRcpt = true * * @throws \RainLoop\Exceptions\ClientException * @throws \MailSo\Net\Exceptions\ConnectionException */ private function smtpSendMessage($oAccount, $oMessage, &$rMessageStream, &$iMessageStreamSize, $bDsn = false, $bAddHiddenRcpt = true) { $oRcpt = $oMessage->GetRcpt(); if ($oRcpt && 0 < $oRcpt->Count()) { $this->Plugins()->RunHook('filter.smtp-message-stream', array($oAccount, &$rMessageStream, &$iMessageStreamSize)); $this->Plugins()->RunHook('filter.message-rcpt', array($oAccount, &$oRcpt)); try { $oFrom = $oMessage->GetFrom(); $sFrom = $oFrom instanceof \MailSo\Mime\Email ? $oFrom->GetEmail() : ''; $sFrom = empty($sFrom) ? $oAccount->Email() : $sFrom; $this->Plugins()->RunHook('filter.smtp-from', array($oAccount, $oMessage, &$sFrom)); $aHiddenRcpt = array(); if ($bAddHiddenRcpt) { $this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt)); } $bUsePhpMail = $oAccount->Domain()->OutUsePhpMail(); $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); $bLoggined = $oAccount->OutConnectAndLoginHelper($this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail); if ($bUsePhpMail) { if (\MailSo\Base\Utils::FunctionExistsAndEnabled('mail')) { $aToCollection = $oMessage->GetTo(); if ($aToCollection && $oFrom) { $sRawBody = @\stream_get_contents($rMessageStream); if (!empty($sRawBody)) { $sMailTo = \trim($aToCollection->ToString(true)); $sMailSubject = \trim($oMessage->GetSubject()); $sMailSubject = 0 === \strlen($sMailSubject) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, $sMailSubject); $sMailHeaders = $sMailBody = ''; list($sMailHeaders, $sMailBody) = \explode("\r\n\r\n", $sRawBody, 2); unset($sRawBody); // $this->Logger()->WriteDump(array( // $sMailTo, $sMailSubject, $sMailBody, $sMailHeaders // )); if (!\mail($sMailTo, $sMailSubject, $sMailBody, $sMailHeaders/*, '-f'.$oFrom->GetEmail()*/)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage); } } } } else { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage); } } else if ($oSmtpClient->IsConnected()) { if (!empty($sFrom)) { $oSmtpClient->MailFrom($sFrom, '', $bDsn); } $aRcpt =& $oRcpt->GetAsArray(); foreach ($aRcpt as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) { $oSmtpClient->Rcpt($oEmail->GetEmail(), $bDsn); } if ($bAddHiddenRcpt && \is_array($aHiddenRcpt) && 0 < \count($aHiddenRcpt)) { foreach ($aHiddenRcpt as $sEmail) { if (\preg_match('/^[^@\s]+@[^@\s]+$/', $sEmail)) { $oSmtpClient->Rcpt($sEmail); } } } $oSmtpClient->DataWithStream($rMessageStream); if ($bLoggined) { $oSmtpClient->Logout(); } $oSmtpClient->Disconnect(); } } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { if ($this->Config()->Get('labs', 'smtp_show_server_errors')) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ClientViewError, $oException); } else { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); } } catch (\MailSo\Smtp\Exceptions\LoginException $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException); } catch (\Exception $oException) { if ($this->Config()->Get('labs', 'smtp_show_server_errors')) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ClientViewError, $oException); } else { throw $oException; } } } else { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidRecipients); } } /** * @return array */ public function DoSendMessage() { $oAccount = $this->initMailClientConnection(); $oConfig = $this->Config(); $sDraftFolder = $this->GetActionParam('MessageFolder', ''); $sDraftUid = $this->GetActionParam('MessageUid', ''); $sSentFolder = $this->GetActionParam('SentFolder', ''); $aDraftInfo = $this->GetActionParam('DraftInfo', null); $bDsn = '1' === (string) $this->GetActionParam('Dsn', '0'); $oMessage = $this->buildMessage($oAccount, false); $this->Plugins() ->RunHook('filter.send-message', array(&$oMessage)) ->RunHook('filter.send-message[2]', array(&$oMessage, $oAccount)) ; $mResult = false; try { if ($oMessage) { $rMessageStream = \MailSo\Base\ResourceRegistry::CreateMemoryResource(); $iMessageStreamSize = \MailSo\Base\Utils::MultipleStreamWriter( $oMessage->ToStream(true), array($rMessageStream), 8192, true, true, true); if (false !== $iMessageStreamSize) { $this->smtpSendMessage($oAccount, $oMessage, $rMessageStream, $iMessageStreamSize, $bDsn, true); if (\is_array($aDraftInfo) && 3 === \count($aDraftInfo)) { $sDraftInfoType = $aDraftInfo[0]; $sDraftInfoUid = $aDraftInfo[1]; $sDraftInfoFolder = $aDraftInfo[2]; try { switch (\strtolower($sDraftInfoType)) { case 'reply': case 'reply-all': $this->MailClient()->MessageSetFlag($sDraftInfoFolder, array($sDraftInfoUid), true, \MailSo\Imap\Enumerations\MessageFlag::ANSWERED, true); break; case 'forward': $sForwardedFlag = $this->Config()->Get('labs', 'imap_forwarded_flag', ''); if (0 < strlen($sForwardedFlag)) { $this->MailClient()->MessageSetFlag($sDraftInfoFolder, array($sDraftInfoUid), true, $sForwardedFlag, true); } break; } } catch (\Exception $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); } } if (0 < \strlen($sSentFolder)) { try { if (!$oMessage->GetBcc()) { if (\is_resource($rMessageStream)) { \rewind($rMessageStream); } $this->Plugins()->RunHook('filter.sent-message-stream', array($oAccount, &$rMessageStream, &$iMessageStreamSize)); $this->MailClient()->MessageAppendStream( $rMessageStream, $iMessageStreamSize, $sSentFolder, array( \MailSo\Imap\Enumerations\MessageFlag::SEEN ) ); } else { $rAppendMessageStream = \MailSo\Base\ResourceRegistry::CreateMemoryResource(); $iAppendMessageStreamSize = \MailSo\Base\Utils::MultipleStreamWriter( $oMessage->ToStream(false), array($rAppendMessageStream), 8192, true, true, true); $this->Plugins()->RunHook('filter.sent-message-stream', array($oAccount, &$rAppendMessageStream, &$iAppendMessageStreamSize)); $this->MailClient()->MessageAppendStream( $rAppendMessageStream, $iAppendMessageStreamSize, $sSentFolder, array( \MailSo\Imap\Enumerations\MessageFlag::SEEN )); if (\is_resource($rAppendMessageStream)) { @fclose($rAppendMessageStream); } } } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSaveMessage, $oException); } } if (\is_resource($rMessageStream)) { @\fclose($rMessageStream); } $this->deleteMessageAttachmnets($oAccount); if (0 < \strlen($sDraftFolder) && 0 < \strlen($sDraftUid)) { try { $this->MailClient()->MessageDelete($sDraftFolder, array($sDraftUid), true, true); } catch (\Exception $oException) { $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); } } $mResult = true; } } } catch (\RainLoop\Exceptions\ClientException $oException) { throw $oException; } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage, $oException); } if (false === $mResult) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage); } try { if ($oMessage && $this->AddressBookProvider($oAccount)->IsActive()) { $aArrayToFrec = array(); $oToCollection = $oMessage->GetTo(); if ($oToCollection) { $aTo =& $oToCollection->GetAsArray(); foreach ($aTo as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) { $aArrayToFrec[$oEmail->GetEmail(true)] = $oEmail->ToString(false, true); } } if (0 < \count($aArrayToFrec)) { $oSettings = $this->SettingsProvider()->Load($oAccount); $this->AddressBookProvider($oAccount)->IncFrec( $oAccount->ParentEmailHelper(), \array_values($aArrayToFrec), !!$oSettings->GetConf('ContactsAutosave', !!$oConfig->Get('defaults', 'contacts_autosave', true))); } } } catch (\Exception $oException) { $this->Logger()->WriteException($oException); } return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoSendReadReceiptMessage() { $oAccount = $this->initMailClientConnection(); $oMessage = $this->buildReadReceiptMessage($oAccount); $this->Plugins()->RunHook('filter.send-read-receipt-message', array(&$oMessage, $oAccount)); $mResult = false; try { if ($oMessage) { $rMessageStream = \MailSo\Base\ResourceRegistry::CreateMemoryResource(); $iMessageStreamSize = \MailSo\Base\Utils::MultipleStreamWriter( $oMessage->ToStream(true), array($rMessageStream), 8192, true, true, true); if (false !== $iMessageStreamSize) { $this->smtpSendMessage($oAccount, $oMessage, $rMessageStream, $iMessageStreamSize, false, false); if (\is_resource($rMessageStream)) { @\fclose($rMessageStream); } $mResult = true; $sReadReceiptFlag = $this->Config()->Get('labs', 'imap_read_receipt_flag', ''); if (!empty($sReadReceiptFlag)) { $sFolderFullName = $this->GetActionParam('MessageFolder', ''); $sUid = $this->GetActionParam('MessageUid', ''); $this->Cacher($oAccount)->Set(\RainLoop\KeyPathHelper::ReadReceiptCache($oAccount->Email(), $sFolderFullName, $sUid), '1'); if (0 < \strlen($sFolderFullName) && 0 < \strlen($sUid)) { try { $this->MailClient()->MessageSetFlag($sFolderFullName, array($sUid), true, $sReadReceiptFlag, true, true); } catch (\Exception $oException) {} } } } } } catch (\RainLoop\Exceptions\ClientException $oException) { throw $oException; } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage, $oException); } if (false === $mResult) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage); } return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoQuota() { $this->initMailClientConnection(); try { $aQuota = $this->MailClient()->Quota(); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError, $oException); } return $this->DefaultResponse(__FUNCTION__, $aQuota); } private function getContactsSyncData($oAccount) { $mResult = null; $sData = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'contacts_sync' ); if (!empty($sData)) { $mData = \RainLoop\Utils::DecodeKeyValues($sData); if (\is_array($mData)) { $mResult = array( 'Enable' => isset($mData['Enable']) ? !!$mData['Enable'] : false, 'Url' => isset($mData['Url']) ? \trim($mData['Url']) : '', 'User' => isset($mData['User']) ? \trim($mData['User']) : '', 'Password' => isset($mData['Password']) ? $mData['Password'] : '' ); } } return $mResult; } /** * @return array */ public function DoSaveContactsSyncData() { $oAccount = $this->getAccountFromToken(); $oAddressBookProvider = $this->AddressBookProvider($oAccount); if (!$oAddressBookProvider || !$oAddressBookProvider->IsActive()) { return $this->FalseResponse(__FUNCTION__); } $bEnabled = '1' === (string) $this->GetActionParam('Enable', '0'); $sUrl = $this->GetActionParam('Url', ''); $sUser = $this->GetActionParam('User', ''); $sPassword = $this->GetActionParam('Password', ''); $mData = $this->getContactsSyncData($oAccount); $bResult = $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'contacts_sync', \RainLoop\Utils::EncodeKeyValues(array( 'Enable' => $bEnabled, 'User' => $sUser, 'Password' => APP_DUMMY === $sPassword && isset($mData['Password']) ? $mData['Password'] : (APP_DUMMY === $sPassword ? '' : $sPassword), 'Url' => $sUrl )) ); return $this->DefaultResponse(__FUNCTION__, $bResult); } /** * @return array */ public function DoContactsSync() { $bResult = false; $oAccount = $this->getAccountFromToken(); $oAddressBookProvider = $this->AddressBookProvider($oAccount); if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { $mData = $this->getContactsSyncData($oAccount); if (\is_array($mData) && isset($mData['Enable'], $mData['User'], $mData['Password'], $mData['Url']) && $mData['Enable']) { $bResult = $oAddressBookProvider->Sync( $oAccount->ParentEmailHelper(), $mData['Url'], $mData['User'], $mData['Password']); } } if (!$bResult) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError); } return $this->TrueResponse(__FUNCTION__); } private function getTwoFactorInfo($oAccount, $bRemoveSecret = false) { $sEmail = $oAccount->ParentEmailHelper(); $mData = null; $aResult = array( 'User' => '', 'IsSet' => false, 'Enable' => false, 'Secret' => '', 'Url' => '', 'BackupCodes' => '' ); if (!empty($sEmail)) { $aResult['User'] = $sEmail; $sData = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'two_factor' ); if ($sData) { $mData = \RainLoop\Utils::DecodeKeyValues($sData); } } if (\is_array($mData) && !empty($aResult['User']) && !empty($mData['User']) && !empty($mData['Secret']) && !empty($mData['BackupCodes']) && $sEmail === $mData['User']) { $aResult['IsSet'] = true; $aResult['Enable'] = isset($mData['Enable']) ? !!$mData['Enable'] : false; $aResult['Secret'] = $mData['Secret']; $aResult['BackupCodes'] = $mData['BackupCodes']; $aResult['Url'] = $this->TwoFactorAuthProvider()->GetQRCodeGoogleUrl( $aResult['User'], $aResult['Secret'], $this->Config()->Get('webmail', 'title', '')); } if ($bRemoveSecret) { if (isset($aResult['Secret'])) { unset($aResult['Secret']); } if (isset($aResult['Url'])) { unset($aResult['Url']); } if (isset($aResult['BackupCodes'])) { unset($aResult['BackupCodes']); } } return $aResult; } /** * @param \RainLoop\Model\Account $oAccount * @param string $sCode * * @return bool */ private function removeBackupCodeFromTwoFactorInfo($oAccount, $sCode) { if (!$oAccount || empty($sCode)) { return false; } $sData = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'two_factor' ); if ($sData) { $mData = \RainLoop\Utils::DecodeKeyValues($sData); if (!empty($mData['BackupCodes'])) { $sBackupCodes = \preg_replace('/[^\d]+/', ' ', ' '.$mData['BackupCodes'].' '); $sBackupCodes = \str_replace(' '.$sCode.' ', '', $sBackupCodes); $mData['BackupCodes'] = \trim(\preg_replace('/[^\d]+/', ' ', $sBackupCodes)); return $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'two_factor', \RainLoop\Utils::EncodeKeyValues($mData) ); } } return false; } /** * @return array */ public function DoGetTwoFactorInfo() { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } return $this->DefaultResponse(__FUNCTION__, $this->getTwoFactorInfo($oAccount, true)); } /** * @return array */ public function DoCreateTwoFactorSecret() { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sEmail = $oAccount->ParentEmailHelper(); $sSecret = $this->TwoFactorAuthProvider()->CreateSecret(); $aCodes = array(); for ($iIndex = 9; $iIndex > 0; $iIndex--) { $aCodes[] = \rand(100000000, 900000000); } $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'two_factor', \RainLoop\Utils::EncodeKeyValues(array( 'User' => $sEmail, 'Enable' => false, 'Secret' => $sSecret, 'BackupCodes' => \implode(' ', $aCodes) )) ); \sleep(1); return $this->DefaultResponse(__FUNCTION__, $this->getTwoFactorInfo($oAccount)); } /** * @return array */ public function DoShowTwoFactorSecret() { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $aResult = $this->getTwoFactorInfo($oAccount); if (\is_array($aResult)) { unset($aResult['BackupCodes']); } return $this->DefaultResponse(__FUNCTION__, $aResult); } /** * @return array */ public function DoEnableTwoFactor() { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sEmail = $oAccount->ParentEmailHelper(); $bResult = false; $mData = $this->getTwoFactorInfo($oAccount); if (isset($mData['Secret'], $mData['BackupCodes'])) { $bResult = $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'two_factor', \RainLoop\Utils::EncodeKeyValues(array( 'User' => $sEmail, 'Enable' => '1' === \trim($this->GetActionParam('Enable', '0')), 'Secret' => $mData['Secret'], 'BackupCodes' => $mData['BackupCodes'] )) ); } return $this->DefaultResponse(__FUNCTION__, $bResult); } /** * @return array */ public function DoTestTwoFactorInfo() { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sCode = \trim($this->GetActionParam('Code', '')); $aData = $this->getTwoFactorInfo($oAccount); $sSecret = !empty($aData['Secret']) ? $aData['Secret'] : ''; // $this->Logger()->WriteDump(array( // $sCode, $sSecret, $aData, // $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode) // )); \sleep(1); return $this->DefaultResponse(__FUNCTION__, $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode)); } /** * @return array */ public function DoClearTwoFactorInfo() { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $this->StorageProvider()->Clear($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'two_factor' ); return $this->DefaultResponse(__FUNCTION__, $this->getTwoFactorInfo($oAccount, true)); } /** * @return array */ public function DoContacts() { $oAccount = $this->getAccountFromToken(); $sSearch = \trim($this->GetActionParam('Search', '')); $iOffset = (int) $this->GetActionParam('Offset', 0); $iLimit = (int) $this->GetActionParam('Limit', 20); $iOffset = 0 > $iOffset ? 0 : $iOffset; $iLimit = 0 > $iLimit ? 20 : $iLimit; $iResultCount = 0; $mResult = array(); $oAbp = $this->AddressBookProvider($oAccount); if ($oAbp->IsActive()) { $iResultCount = 0; $mResult = $oAbp->GetContacts($oAccount->ParentEmailHelper(), $iOffset, $iLimit, $sSearch, $iResultCount); } return $this->DefaultResponse(__FUNCTION__, array( 'Offset' => $iOffset, 'Limit' => $iLimit, 'Count' => $iResultCount, 'Search' => $sSearch, 'List' => $mResult )); } /** * @return array */ public function DoContactsDelete() { $oAccount = $this->getAccountFromToken(); $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); $aFilteredUids = \array_filter($aUids, function (&$mUid) { $mUid = (int) \trim($mUid); return 0 < $mUid; }); $bResult = false; if (0 < \count($aFilteredUids) && $this->AddressBookProvider($oAccount)->IsActive()) { $bResult = $this->AddressBookProvider($oAccount)->DeleteContacts($oAccount->ParentEmailHelper(), $aFilteredUids); } return $this->DefaultResponse(__FUNCTION__, $bResult); } /** * @return array */ public function DoContactSave() { $oAccount = $this->getAccountFromToken(); $bResult = false; $oAddressBookProvider = $this->AddressBookProvider($oAccount); $sRequestUid = \trim($this->GetActionParam('RequestUid', '')); if ($oAddressBookProvider && $oAddressBookProvider->IsActive() && 0 < \strlen($sRequestUid)) { $sUid = \trim($this->GetActionParam('Uid', '')); $oContact = null; if (0 < \strlen($sUid)) { $oContact = $oAddressBookProvider->GetContactByID($oAccount->ParentEmailHelper(), $sUid); } if (!$oContact) { $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); if (0 < \strlen($sUid)) { $oContact->IdContact = $sUid; } } $oContact->Properties = array(); $aProperties = $this->GetActionParam('Properties', array()); if (\is_array($aProperties)) { foreach ($aProperties as $aItem) { if ($aItem && isset($aItem[0], $aItem[1]) && \is_numeric($aItem[0])) { $oProp = new \RainLoop\Providers\AddressBook\Classes\Property(); $oProp->Type = (int) $aItem[0]; $oProp->Value = $aItem[1]; $oProp->TypeStr = empty($aItem[2]) ? '': $aItem[2]; $oContact->Properties[] = $oProp; } } } if (!empty($oContact->Etag)) { $oContact->Etag = \md5($oContact->ToVCard()); } $bResult = $oAddressBookProvider->ContactSave($oAccount->ParentEmailHelper(), $oContact); } return $this->DefaultResponse(__FUNCTION__, array( 'RequestUid' => $sRequestUid, 'ResultID' => $bResult ? $oContact->IdContact : '', 'Result' => $bResult )); } /** * @return array */ public function DoSuggestions() { $oAccount = $this->getAccountFromToken(); $sQuery = \trim($this->GetActionParam('Query', '')); $iLimit = (int) $this->Config()->Get('contacts', 'suggestions_limit', 20); $aResult = array(); $this->Plugins()->RunHook('ajax.suggestions-input-parameters', array(&$sQuery, &$iLimit, $oAccount)); $iLimit = (int) $iLimit; if (5 > $iLimit) { $iLimit = 5; } $this->Plugins()->RunHook('ajax.suggestions-pre', array(&$aResult, $sQuery, $oAccount, $iLimit)); if ($iLimit > \count($aResult) && 0 < \strlen($sQuery)) { try { // Address Book $oAddressBookProvider = $this->AddressBookProvider($oAccount); if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { $aSuggestions = $oAddressBookProvider->GetSuggestions($oAccount->ParentEmailHelper(), $sQuery, $iLimit); if (0 === \count($aResult)) { $aResult = $aSuggestions; } else { $aResult = \array_merge($aResult, $aSuggestions); } } } catch (\Exception $oException) { $this->Logger()->WriteException($oException); } } if ($iLimit > \count($aResult) && 0 < \strlen($sQuery)) { $oSuggestionsProvider = $this->SuggestionsProvider(); if ($oSuggestionsProvider && $oSuggestionsProvider->IsActive()) { $iSuggestionLimit = $iLimit - \count($aResult); $aSuggestionsProviderResult = $oSuggestionsProvider->Process($oAccount, $sQuery, $iSuggestionLimit); if (\is_array($aSuggestionsProviderResult) && 0 < \count($aSuggestionsProviderResult)) { $aResult = \array_merge($aResult, $aSuggestionsProviderResult); } } } if ($iLimit < \count($aResult)) { $aResult = \array_slice($aResult, 0, $iLimit); } $this->Plugins()->RunHook('ajax.suggestions-post', array(&$aResult, $sQuery, $oAccount, $iLimit)); if ($iLimit < \count($aResult)) { $aResult = \array_slice($aResult, 0, $iLimit); } return $this->DefaultResponse(__FUNCTION__, $aResult); } /** * @return array */ private function messageSetFlag($sActionFunction, $sResponseFunction) { $this->initMailClientConnection(); $sFolder = $this->GetActionParam('Folder', ''); $bSetAction = '1' === (string) $this->GetActionParam('SetAction', '0'); $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); $aFilteredUids = \array_filter($aUids, function (&$sUid) { $sUid = (int) \trim($sUid); return 0 < $sUid; }); try { $this->MailClient()->{$sActionFunction}($sFolder, $aFilteredUids, true, $bSetAction, true); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError, $oException); } return $this->TrueResponse($sResponseFunction); } /** * @return array */ public function DoMessageSetSeen() { return $this->messageSetFlag('MessageSetSeen', __FUNCTION__); } /** * @return array */ public function DoMessageSetSeenToAll() { $this->initMailClientConnection(); $sFolder = $this->GetActionParam('Folder', ''); $bSetAction = '1' === (string) $this->GetActionParam('SetAction', '0'); try { $this->MailClient()->MessageSetSeenToAll($sFolder, $bSetAction); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError, $oException); } return $this->TrueResponse(__FUNCTION__); } /** * @return array */ public function DoMessageSetFlagged() { return $this->messageSetFlag('MessageSetFlagged', __FUNCTION__); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoMessage() { $sRawKey = (string) $this->GetActionParam('RawKey', ''); $sFolder = ''; $iUid = 0; $aValues = $this->getDecodedClientRawKeyValue($sRawKey, 4); if (is_array($aValues) && 4 === count($aValues)) { $sFolder = (string) $aValues[0]; $iUid = (int) $aValues[1]; $this->verifyCacheByKey($sRawKey); } else { $sFolder = $this->GetActionParam('Folder', ''); $iUid = (int) $this->GetActionParam('Uid', 0); } $oAccount = $this->initMailClientConnection(); try { $oMessage = $this->MailClient()->Message($sFolder, $iUid, true, $this->cacherForThreads(), (int) $this->Config()->Get('labs', 'imap_body_text_limit', 0) ); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessage, $oException); } if ($oMessage instanceof \MailSo\Mail\Message) { $this->Plugins() ->RunHook('filter.result-message', array(&$oMessage)) ->RunHook('filter.result-message[2]', array(&$oMessage, $oAccount)) ; $this->cacheByKey($sRawKey); } return $this->DefaultResponse(__FUNCTION__, $oMessage); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoMessageDelete() { $this->initMailClientConnection(); $sFolder = $this->GetActionParam('Folder', ''); $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); $aFilteredUids = \array_filter($aUids, function (&$sUid) { $sUid = (int) \trim($sUid); return 0 < $sUid; }); try { $this->MailClient()->MessageDelete($sFolder, $aFilteredUids, true, true, !!$this->Config()->Get('labs', 'use_imap_expunge_all_on_delete', false)); $sHash = $this->MailClient()->FolderHash($sFolder); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantDeleteMessage, $oException); } return $this->DefaultResponse(__FUNCTION__, '' === $sHash ? false : array($sFolder, $sHash)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoMessageMove() { $this->initMailClientConnection(); $sFromFolder = $this->GetActionParam('FromFolder', ''); $sToFolder = $this->GetActionParam('ToFolder', ''); $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); $aFilteredUids = \array_filter($aUids, function (&$mUid) { $mUid = (int) \trim($mUid); return 0 < $mUid; }); try { $this->MailClient()->MessageMove($sFromFolder, $sToFolder, $aFilteredUids, true, !!$this->Config()->Get('labs', 'use_imap_move', true), !!$this->Config()->Get('labs', 'use_imap_expunge_all_on_delete', false) ); $sHash = $this->MailClient()->FolderHash($sFromFolder); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantMoveMessage, $oException); } return $this->DefaultResponse(__FUNCTION__, '' === $sHash ? false : array($sFromFolder, $sHash)); } /** * @return array * * @throws \MailSo\Base\Exceptions\Exception */ public function DoMessageCopy() { $this->initMailClientConnection(); $sFromFolder = $this->GetActionParam('FromFolder', ''); $sToFolder = $this->GetActionParam('ToFolder', ''); $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); $aFilteredUids = \array_filter($aUids, function (&$mUid) { $mUid = (int) \trim($mUid); return 0 < $mUid; }); try { $this->MailClient()->MessageCopy($sFromFolder, $sToFolder, $aFilteredUids, true); $sHash = $this->MailClient()->FolderHash($sFromFolder); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantCopyMessage, $oException); } return $this->DefaultResponse(__FUNCTION__, '' === $sHash ? false : array($sFromFolder, $sHash)); } /** * @param string $sFileName * @param string $sContentType * @param string $sMimeIndex * @param int $iMaxLength = 250 * * @return string */ public function MainClearFileName($sFileName, $sContentType, $sMimeIndex, $iMaxLength = 250) { $sFileName = 0 === \strlen($sFileName) ? \preg_replace('/[^a-zA-Z0-9]/', '.', (empty($sMimeIndex) ? '' : $sMimeIndex.'.').$sContentType) : $sFileName; $sClearedFileName = \preg_replace('/[\s]+/', ' ', \preg_replace('/[\.]+/', '.', $sFileName)); $sExt = \MailSo\Base\Utils::GetFileExtension($sClearedFileName); if (10 < $iMaxLength && $iMaxLength < \strlen($sClearedFileName) - \strlen($sExt)) { $sClearedFileName = \substr($sClearedFileName, 0, $iMaxLength).(empty($sExt) ? '' : '.'.$sExt); } return \MailSo\Base\Utils::ClearFileName(\MailSo\Base\Utils::Utf8Clear($sClearedFileName)); } /** * @return array */ public function DoMessageUploadAttachments() { $oAccount = $this->initMailClientConnection(); $mResult = false; $self = $this; try { $aAttachments = $this->GetActionParam('Attachments', array()); if (is_array($aAttachments) && 0 < count($aAttachments)) { $mResult = array(); foreach ($aAttachments as $sAttachment) { $aValues = \RainLoop\Utils::DecodeKeyValues($sAttachment); if (is_array($aValues)) { $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; $iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0; $sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''; $sTempName = md5($sAttachment); if (!$this->FilesProvider()->FileExists($oAccount, $sTempName)) { $this->MailClient()->MessageMimeStream( function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($oAccount, &$mResult, $sTempName, $sAttachment, $self) { if (is_resource($rResource)) { $sContentType = (empty($sFileName)) ? 'text/plain' : \MailSo\Base\Utils::MimeContentType($sFileName); $sFileName = $self->MainClearFileName($sFileName, $sContentType, $sMimeIndex); if ($self->FilesProvider()->PutFile($oAccount, $sTempName, $rResource)) { $mResult[$sTempName] = $sAttachment; } } }, $sFolder, $iUid, true, $sMimeIndex); } else { $mResult[$sTempName] = $sAttachment; } } } } } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError, $oException); } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * @return array */ public function DoComposeUploadExternals() { $oAccount = $this->getAccountFromToken(); $mResult = false; $aExternals = $this->GetActionParam('Externals', array()); if (\is_array($aExternals) && 0 < \count($aExternals)) { $oHttp = \MailSo\Base\Http::SingletonInstance(); $mResult = array(); foreach ($aExternals as $sUrl) { $mResult[$sUrl] = ''; $sTempName = \md5($sUrl); $iCode = 0; $sContentType = ''; $rFile = $this->FilesProvider()->GetFile($oAccount, $sTempName, 'wb+'); if ($rFile && $oHttp->SaveUrlToFile($sUrl, $rFile, '', $sContentType, $iCode, $this->Logger(), 60, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''))) { $mResult[$sUrl] = $sTempName; } if (\is_resource($rFile)) { @\fclose($rFile); } } } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * @return array */ public function DoComposeUploadDrive() { $oAccount = $this->getAccountFromToken(); $mResult = false; $sUrl = $this->GetActionParam('Url', ''); $sAccessToken = $this->GetActionParam('AccessToken', ''); $sGoogleApiKey = (string) $this->Config()->Get('social', 'google_api_key', ''); if (0 < \strlen($sUrl) && 0 < \strlen($sAccessToken) && 0 < \strlen($sGoogleApiKey)) { $oHttp = \MailSo\Base\Http::SingletonInstance(); $mResult[$sUrl] = false; $sTempName = \md5($sUrl); $iCode = 0; $sContentType = ''; $rFile = $this->FilesProvider()->GetFile($oAccount, $sTempName, 'wb+'); if ($rFile && $oHttp->SaveUrlToFile($sUrl.'&key='.$sGoogleApiKey, $rFile, '', $sContentType, $iCode, $this->Logger(), 60, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), array('Authorization: Bearer '.$sAccessToken))) { $mResult[$sUrl] = array($sTempName, 0); } if (\is_resource($rFile)) { @\fclose($rFile); } if (isset($mResult[$sUrl][1])) { $mResult[$sUrl][1] = $rFile = $this->FilesProvider()->FileSize($oAccount, $sTempName); } } return $this->DefaultResponse(__FUNCTION__, $mResult); } /** * @return array */ public function DoSocialUsers() { $oAccount = $this->getAccountFromToken(); $oSettings = $this->SettingsProvider()->Load($oAccount); $aData = array( 'Google' => '', 'Facebook' => '', 'Twitter' => '' ); if ($oSettings) { $aData['Google'] = $oSettings->GetConf('GoogleSocialName', ''); $aData['Facebook'] = $oSettings->GetConf('FacebookSocialName', ''); $aData['Twitter'] = $oSettings->GetConf('TwitterSocialName', ''); } return $this->DefaultResponse(__FUNCTION__, $aData); } /** * @return array */ public function DoSocialGoogleDisconnect() { $oAccount = $this->getAccountFromToken(); return $this->DefaultResponse(__FUNCTION__, $this->Social()->GoogleDisconnect($oAccount)); } /** * @return array */ public function DoSocialTwitterDisconnect() { $oAccount = $this->getAccountFromToken(); return $this->DefaultResponse(__FUNCTION__, $this->Social()->TwitterDisconnect($oAccount)); } /** * @return array */ public function DoSocialFacebookDisconnect() { $oAccount = $this->getAccountFromToken(); return $this->DefaultResponse(__FUNCTION__, $this->Social()->FacebookDisconnect($oAccount)); } /** * @param int $iError * @param int $iClientError * * @return string */ private function getUploadErrorMessageByCode($iError, &$iClientError) { $sError = ''; $iClientError = UploadClientError::NORMAL; switch($iError) { case UPLOAD_ERR_OK: break; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: case UploadError::CONFIG_SIZE: case UploadError::EMPTY_FILES_DATA: $sError = 'File is too big'; $iClientError = UploadClientError::FILE_IS_TOO_BIG; break; case UPLOAD_ERR_PARTIAL: $sError = 'File partially uploaded'; $iClientError = UploadClientError::FILE_PARTIALLY_UPLOADED; break; case UPLOAD_ERR_NO_FILE: $sError = 'No file uploaded'; $iClientError = UploadClientError::FILE_NO_UPLOADED; break; case UPLOAD_ERR_NO_TMP_DIR: case UPLOAD_ERR_CANT_WRITE: case UPLOAD_ERR_EXTENSION: $sError = 'Missing temp folder'; $iClientError = UploadClientError::MISSING_TEMP_FOLDER; break; case UploadError::ON_SAVING: $sError = 'Error on saving file'; $iClientError = UploadClientError::FILE_ON_SAVING_ERROR; break; case UploadError::FILE_TYPE: $sError = 'Invalid file type'; $iClientError = UploadClientError::FILE_TYPE; break; case UploadError::UNKNOWN: default: $sError = 'Unknown error'; $iClientError = UploadClientError::UNKNOWN; break; } return $sError; } /** * @return array */ public function Upload() { $oAccount = $this->getAccountFromToken(); $aResponse = array(); $aFile = $this->GetActionParam('File', null); $iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN); if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) { $sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']); if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) { $iError = \RainLoop\Enumerations\UploadError::ON_SAVING; } else { $sUploadName = $aFile['name']; $iSize = $aFile['size']; $sMimeType = $aFile['type']; $aResponse['Attachment'] = array( 'Name' => $sUploadName, 'TempName' => $sSavedName, 'MimeType' => $sMimeType, 'Size' => (int) $iSize ); } } if (UPLOAD_ERR_OK !== $iError) { $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); if (!empty($sError)) { $aResponse['ErrorCode'] = $iClientError; $aResponse['Error'] = $sError; } } return $this->DefaultResponse(__FUNCTION__, $aResponse); } /** * @return array */ public function DoClearUserBackground() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $oSettings = $this->SettingsProvider()->Load($oAccount); if ($oAccount && $oSettings) { $this->StorageProvider()->Clear($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'background' ); $oSettings->SetConf('UserBackgroundName', ''); $oSettings->SetConf('UserBackgroundHash', ''); } return $this->DefaultResponse(__FUNCTION__, $oAccount && $oSettings ? $this->SettingsProvider()->Save($oAccount, $oSettings) : false); } /** * @return array */ public function UploadBackground() { $oAccount = $this->getAccountFromToken(); if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } $sName = ''; $sHash = ''; $aFile = $this->GetActionParam('File', null); $iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN); if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) { $sMimeType = \strtolower(\MailSo\Base\Utils::MimeContentType($aFile['name'])); if (\in_array($sMimeType, array('image/png', 'image/jpg', 'image/jpeg'))) { $sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']); if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) { $iError = \RainLoop\Enumerations\UploadError::ON_SAVING; } else { $rData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); if (@\is_resource($rData)) { $sData = @\stream_get_contents($rData); if (!empty($sData) && 0 < \strlen($sData)) { $sName = $aFile['name']; if (empty($sName)) { $sName = '_'; } if ($this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'background', \json_encode(array( 'Name' => $aFile['name'], 'ContentType' => $sMimeType, 'Raw' => \base64_encode($sData) )) )) { $oSettings = $this->SettingsProvider()->Load($oAccount); if ($oSettings) { $sHash = \MailSo\Base\Utils::Md5Rand($sName.APP_VERSION.APP_SALT); $oSettings->SetConf('UserBackgroundName', $sName); $oSettings->SetConf('UserBackgroundHash', $sHash); $this->SettingsProvider()->Save($oAccount, $oSettings); } } } unset($sData); } if (@\is_resource($rData)) { @\fclose($rData); } unset($rData); } $this->FilesProvider()->Clear($oAccount, $sSavedName); } else { $iError = \RainLoop\Enumerations\UploadError::FILE_TYPE; } } if (UPLOAD_ERR_OK !== $iError) { $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); if (!empty($sError)) { return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); } } return $this->DefaultResponse(__FUNCTION__, !empty($sName) && !empty($sHash) ? array( 'Name' => $sName, 'Hash' => $sHash ) : false); } /** * @return array */ public function UploadContacts() { $oAccount = $this->getAccountFromToken(); $mResponse = false; $aFile = $this->GetActionParam('File', null); $iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN); if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) { $sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']); if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) { $iError = \RainLoop\Enumerations\UploadError::ON_SAVING; } else { @\ini_set('auto_detect_line_endings', true); $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); if ($mData) { $sFileStart = @\fread($mData, 20); \rewind($mData); if (false !== $sFileStart) { $sFileStart = \trim($sFileStart); if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) { $mResponse = $this->importContactsFromVcfFile($oAccount, $mData); } else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) { $mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart); } } } if (\is_resource($mData)) { @\fclose($mData); } unset($mData); $this->FilesProvider()->Clear($oAccount, $sSavedName); @\ini_set('auto_detect_line_endings', false); } } if (UPLOAD_ERR_OK !== $iError) { $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); if (!empty($sError)) { return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); } } return $this->DefaultResponse(__FUNCTION__, $mResponse); } /** * @param \RainLoop\Model\Account $oAccount * @param resource $rFile * @param string $sFileStart * * @return int */ private function importContactsFromCsvFile($oAccount, $rFile, $sFileStart) { $iCount = 0; $aHeaders = null; $aData = array(); if ($oAccount && \is_resource($rFile)) { $oAddressBookProvider = $this->AddressBookProvider($oAccount); if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { $sDelimiter = ((int) \strpos($sFileStart, ',') > (int) \strpos($sFileStart, ';')) ? ',' : ';'; @\setlocale(LC_CTYPE, 'en_US.UTF-8'); while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) { if (null === $aHeaders) { if (3 >= \count($mRow)) { return 0; } $aHeaders = $mRow; foreach ($aHeaders as $iIndex => $sHeaderValue) { $aHeaders[$iIndex] = \MailSo\Base\Utils::Utf8Clear($sHeaderValue); } } else { $aNewItem = array(); foreach ($aHeaders as $iIndex => $sHeaderValue) { $aNewItem[$sHeaderValue] = isset($mRow[$iIndex]) ? $mRow[$iIndex] : ''; } $aData[] = $aNewItem; } } if (\is_array($aData) && 0 < \count($aData)) { $this->Logger()->Write('Import contacts from csv'); $iCount = $oAddressBookProvider->ImportCsvArray($oAccount->ParentEmailHelper(), $aData); } } } return $iCount; } /** * @param \RainLoop\Model\Account $oAccount * @param resource $rFile * * @return int */ private function importContactsFromVcfFile($oAccount, $rFile) { $iCount = 0; if ($oAccount && \is_resource($rFile)) { $oAddressBookProvider = $this->AddressBookProvider($oAccount); if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { $sFile = \stream_get_contents($rFile); if (\is_resource($rFile)) { \fclose($rFile); } if (is_string($sFile) && 5 < \strlen($sFile)) { $this->Logger()->Write('Import contacts from vcf'); $iCount = $oAddressBookProvider->ImportVcfFile($oAccount->ParentEmailHelper(), $sFile); } } } return $iCount; } /** * @return bool * * @throws \MailSo\Base\Exceptions\Exception */ public function RawViewAsPlain() { $this->initMailClientConnection(); $sRawKey = (string) $this->GetActionParam('RawKey', ''); $aValues = $this->getDecodedRawKeyValue($sRawKey); $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; $iUid = (int) (isset($aValues['Uid']) ? $aValues['Uid'] : 0); $sMimeIndex = (string) (isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''); header('Content-Type: text/plain', true); return $this->MailClient()->MessageMimeStream(function ($rResource) { if (is_resource($rResource)) { \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); } }, $sFolder, $iUid, true, $sMimeIndex); } /** * @return string */ public function RawFramedView() { $oAccount = $this->getAccountFromToken(false); if ($oAccount) { $sRawKey = (string) $this->GetActionParam('RawKey', ''); $aParams = $this->GetActionParam('Params', null); $this->Http()->ServerNoCache(); $aData = \RainLoop\Utils::DecodeKeyValues($sRawKey); if (isset($aParams[0], $aParams[1], $aParams[2]) && 'Raw' === $aParams[0] && 'FramedView' === $aParams[2] && isset($aData['Framed']) && $aData['Framed'] && $aData['FileName']) { if ($this->isFileHasFramedPreview($aData['FileName'])) { $sNewSpecAuthToken = $this->GetShortLifeSpecAuthToken(); if (!empty($sNewSpecAuthToken)) { $aParams[1] = '_'.$sNewSpecAuthToken; $aParams[2] = 'View'; \array_shift($aParams); $sLast = \array_pop($aParams); $sUrl = $this->Http()->GetFullUrl().'?/Raw/&s=/'.implode('/', $aParams).'/&ss=/'.$sLast; $sFullUrl = 'https://docs.google.com/viewer?embedded=true&url='.urlencode($sUrl); @\header('Content-Type: text/html; charset=utf-8'); echo ''. ''. ''. ''; } } } } return true; } /** * @return bool * * @throws \MailSo\Base\Exceptions\Exception */ public function Append() { $oAccount = $this->initMailClientConnection(); $sFolderFullNameRaw = $this->GetActionParam('Folder', ''); $_FILES = isset($_FILES) ? $_FILES : null; if ($oAccount instanceof \RainLoop\Model\Account && $this->Config()->Get('labs', 'allow_message_append', false) && isset($_FILES, $_FILES['AppendFile'], $_FILES['AppendFile']['name'], $_FILES['AppendFile']['tmp_name'], $_FILES['AppendFile']['size'])) { if (is_string($_FILES['AppendFile']['tmp_name']) && 0 < strlen($_FILES['AppendFile']['tmp_name'])) { if (\UPLOAD_ERR_OK === (int) $_FILES['AppendFile']['error'] && !empty($sFolderFullNameRaw)) { $sSavedName = 'append-post-'.md5($sFolderFullNameRaw.$_FILES['AppendFile']['name'].$_FILES['AppendFile']['tmp_name']); if ($this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $_FILES['AppendFile']['tmp_name'])) { $iMessageStreamSize = $this->FilesProvider()->FileSize($oAccount, $sSavedName); $rMessageStream = $this->FilesProvider()->GetFile($oAccount, $sSavedName); $this->MailClient()->MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderFullNameRaw); $this->FilesProvider()->Clear($oAccount, $sSavedName); } } } } return $this->DefaultResponse(__FUNCTION__, true); } /** * @param bool $bAdmin * @param \RainLoop\Model\Account $oAccount = null * * @return array */ public function Capa($bAdmin, $oAccount = null) { $oConfig = $this->Config(); $aResult = array(); if ($oConfig->Get('capa', 'filters', false)) { $aResult[] = \RainLoop\Enumerations\Capa::FILTERS; if ($bAdmin || ($oAccount && $oAccount->Domain()->UseSieve())) { $aResult[] = \RainLoop\Enumerations\Capa::SIEVE; } } // if ($oConfig->Get('capa', 'templates', true)) // { // $aResult[] = \RainLoop\Enumerations\Capa::TEMPLATES; // } if ($oConfig->Get('webmail', 'allow_additional_accounts', false)) { $aResult[] = \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS; } if ($oConfig->Get('security', 'allow_two_factor_auth', false) && ($bAdmin || ($oAccount && !$oAccount->IsAdditionalAccount()))) { $aResult[] = \RainLoop\Enumerations\Capa::TWO_FACTOR; } if ($oConfig->Get('labs', 'allow_gravatar', false)) { $aResult[] = \RainLoop\Enumerations\Capa::GRAVATAR; } if ($oConfig->Get('interface', 'show_attachment_thumbnail', true)) { $aResult[] = \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS; } if ($oConfig->Get('labs', 'allow_prefetch', false)) { $aResult[] = \RainLoop\Enumerations\Capa::PREFETCH; } if ($oConfig->Get('webmail', 'allow_themes', false)) { $aResult[] = \RainLoop\Enumerations\Capa::THEMES; } if ($oConfig->Get('webmail', 'allow_user_background', false)) { $aResult[] = \RainLoop\Enumerations\Capa::USER_BACKGROUND; } if ($oConfig->Get('security', 'openpgp', false)) { $aResult[] = \RainLoop\Enumerations\Capa::OPEN_PGP; } if (!\RainLoop\Utils::IsOwnCloud()) { $aResult[] = \RainLoop\Enumerations\Capa::AUTOLOGOUT; } return $aResult; } /** * @param bool $bAdmin * @param string $sName * @param \RainLoop\Model\Account $oAccount = null * * @return bool */ public function GetCapa($bAdmin, $sName, $oAccount = null) { return \in_array($sName, $this->Capa($bAdmin, $oAccount)); } /** * @param string $sKey * @param bool $bForce = false * * @return bool */ public function cacheByKey($sKey, $bForce = false) { $bResult = false; if (!empty($sKey) && ($bForce || $this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true))) { $this->oHttp->ServerUseCache( \md5('Etag:'.\md5($sKey.\md5($this->Config()->Get('cache', 'index', '')))), 1382478804, 2002478804); $bResult = true; } return $bResult; } /** * @param string $sKey * @param bool $bForce = false * * @return void */ public function verifyCacheByKey($sKey, $bForce = false) { if (!empty($sKey) && ($bForce || $this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true))) { $sIfModifiedSince = $this->Http()->GetHeader('If-Modified-Since', ''); $sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', ''); if (!empty($sIfModifiedSince) || !empty($sIfNoneMatch)) { $this->Http()->StatusHeader(304); $this->cacheByKey($sKey); exit(); } } } /** * @param bool $bDownload * @param bool $bThumbnail = false * * @return bool */ private function rawSmart($bDownload, $bThumbnail = false) { $sRawKey = (string) $this->GetActionParam('RawKey', ''); $aValues = $this->getDecodedRawKeyValue($sRawKey); $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; $iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0; $sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''; $sContentTypeIn = (string) isset($aValues['MimeType']) ? $aValues['MimeType'] : ''; $sFileNameIn = (string) isset($aValues['FileName']) ? $aValues['FileName'] : ''; if (!empty($sFolder) && 0 < $iUid) { $this->verifyCacheByKey($sRawKey); } $oAccount = $this->initMailClientConnection(); $self = $this; return $this->MailClient()->MessageMimeStream( function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail) { if ($oAccount && \is_resource($rResource)) { $sContentTypeOut = $sContentTypeIn; if (empty($sContentTypeOut)) { $sContentTypeOut = $sContentType; if (empty($sContentTypeOut)) { $sContentTypeOut = (empty($sFileName)) ? 'text/plain' : \MailSo\Base\Utils::MimeContentType($sFileName); } } $sFileNameOut = $sFileNameIn; if (empty($sFileNameOut)) { $sFileNameOut = $sFileName; } $sFileNameOut = $self->MainClearFileName($sFileNameOut, $sContentTypeOut, $sMimeIndex); $self->cacheByKey($sRawKey); $bDone = false; if ($bThumbnail && !$bDownload) { \MailSo\Base\StreamWrappers\TempFile::Reg(); $sFileName = 'mailsotempfile://'.\MailSo\Base\Utils::Md5Rand($sFileNameOut); $rTempResource = @\fopen($sFileName, 'r+b'); if (@\is_resource($rTempResource)) { \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource)); @\fclose($rTempResource); $oThumb =@ new \PHPThumb\GD($sFileName); if ($oThumb) { $oThumb->adaptiveResize(60, 60)->show(); $bDone = true; } } } if (!$bDone) { \header('Content-Type: '.$sContentTypeOut); \header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '. \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); \header('Accept-Ranges: none', true); \header('Content-Transfer-Encoding: binary'); \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); } } }, $sFolder, $iUid, true, $sMimeIndex); } /** * @return bool */ public function RawDownload() { return $this->rawSmart(true); } /** * @return bool */ public function RawView() { return $this->rawSmart(false); } /** * @return bool */ public function RawViewThumbnail() { return $this->rawSmart(false, true); } /** * @return bool */ public function RawUserBackground() { $sRawKey = (string) $this->GetActionParam('RawKey', ''); $this->verifyCacheByKey($sRawKey); $oAccount = $this->getAccountFromToken(); $sData = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'background' ); if (!empty($sData)) { $aData = \json_decode($sData, true); unset($sData); if (!empty($aData['ContentType']) && !empty($aData['Raw']) && \in_array($aData['ContentType'], array('image/png', 'image/jpg', 'image/jpeg'))) { $this->cacheByKey($sRawKey); @\header('Content-Type: '.$aData['ContentType']); echo \base64_decode($aData['Raw']); unset($aData); return true; } } return false; } /** * @return bool */ public function RawPublic() { $sRawKey = (string) $this->GetActionParam('RawKey', ''); $this->verifyCacheByKey($sRawKey); $sHash = $sRawKey; $sData = ''; if (!empty($sHash)) { $sData = $this->StorageProvider()->Get(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, \RainLoop\KeyPathHelper::PublicFile($sHash) ); } $aMatch = array(); if (!empty($sData) && 0 === \strpos($sData, 'data:') && \preg_match('/^data:([^:]+):/', $sData, $aMatch) && !empty($aMatch[1])) { $sContentType = \trim($aMatch[1]); if (\in_array($sContentType, array('image/png', 'image/jpg', 'image/jpeg'))) { $this->cacheByKey($sRawKey); @\header('Content-Type: '.$sContentType); echo \preg_replace('/^data:[^:]+:/', '', $sData); unset($sData); return true; } } return false; } /** * @param string $sFileName * * @return bool */ public function isFileHasFramedPreview($sFileName) { $sExt = \MailSo\Base\Utils::GetFileExtension($sFileName); return \in_array($sExt, array('doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx')); } /** * @param string $sFileName * * @return bool */ public function isFileHasThumbnail($sFileName) { static $aCache = array(); $sExt = \MailSo\Base\Utils::GetFileExtension($sFileName); if (isset($aCache[$sExt])) { return $aCache[$sExt]; } $bResult = \function_exists('gd_info'); if ($bResult) { $bResult = false; switch ($sExt) { case 'png': $bResult = \function_exists('imagecreatefrompng'); break; case 'gif': $bResult = \function_exists('imagecreatefromgif'); break; case 'jpg': case 'jpeg': $bResult = \function_exists('imagecreatefromjpeg'); break; } } $aCache[$sExt] = $bResult; return $bResult; } /** * @return bool */ public function RawAvatar() { $sData = ''; $sRawKey = (string) $this->GetActionParam('RawKey', ''); $sRawKey = \urldecode($sRawKey); $this->verifyCacheByKey($sRawKey); if (0 < \strlen($sRawKey) && \preg_match('/^[^@]+@([^@]+)$/', $sRawKey)) { $sEmail = \MailSo\Base\Utils::IdnToAscii($sRawKey, true); $iCode = 0; $sContentType = ''; $sData = $this->Http()->GetUrlAsString('http://gravatar.com/avatar/'.\md5($sEmail).'.jpg?s=80&d=404', null, $sContentType, $iCode, $this->Logger(), 5, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')); $sContentType = \strtolower(\trim($sContentType)); if (200 !== $iCode || empty($sData) || !\in_array($sContentType, array('image/jpeg', 'image/jpg', 'image/png'))) { $sData = ''; $aMatch = array(); if (\preg_match('/^[^@]+@([a-z0-9\-\.]+)$/', $sEmail, $aMatch) && !empty($aMatch[1])) { $sDomain = $aMatch[1]; if (\file_exists(APP_VERSION_ROOT_PATH.'app/resources/images/services/'.$sDomain.'.png')) { $sContentType = 'image/png'; $sData = \file_get_contents(APP_VERSION_ROOT_PATH.'app/resources/images/services/'.$sDomain.'.png'); } } } } if (empty($sData) || empty($sContentType)) { $sContentType = 'image/png'; $sData = \file_get_contents(APP_VERSION_ROOT_PATH.'app/resources/images/empty-contact.png'); } $this->cacheByKey($sRawKey); \header('Content-Type: '.$sContentType); echo $sData; return true; } /** * @return bool */ public function RawContactsVcf() { $oAccount = $this->getAccountFromToken(); \header('Content-Type: text/x-vcard; charset=UTF-8'); \header('Content-Disposition: attachment; filename="contacts.vcf"', true); \header('Accept-Ranges: none', true); \header('Content-Transfer-Encoding: binary'); $this->oHttp->ServerNoCache(); return $this->AddressBookProvider($oAccount)->IsActive() ? $this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'vcf') : false; } /** * @return bool */ public function RawContactsCsv() { $oAccount = $this->getAccountFromToken(); \header('Content-Type: text/csv; charset=UTF-8'); \header('Content-Disposition: attachment; filename="contacts.csv"', true); \header('Accept-Ranges: none', true); \header('Content-Transfer-Encoding: binary'); $this->oHttp->ServerNoCache(); return $this->AddressBookProvider($oAccount)->IsActive() ? $this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'csv') : false; } /** * @return \RainLoop\Model\Account|bool */ private function initMailClientConnection() { $oAccount = null; if (!$this->MailClient()->IsLoggined()) { $oAccount = $this->getAccountFromToken(); try { $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException); } $this->MailClient()->ImapClient()->__FORCE_SELECT_ON_EXAMINE__ = !!$this->Config()->Get('labs', 'use_imap_force_selection'); } return $oAccount; } /** * @param string $sRawKey * * @return array | false */ private function getDecodedRawKeyValue($sRawKey) { $bResult = false; if (!empty($sRawKey)) { $aValues = \RainLoop\Utils::DecodeKeyValues($sRawKey); if (is_array($aValues)) { $bResult = $aValues; } } return $bResult; } /** * @param string $sRawKey * @param int | null $iLenCache * @return array | bool */ private function getDecodedClientRawKeyValue($sRawKey, $iLenCache = null) { $mResult = false; if (!empty($sRawKey)) { $sRawKey = \MailSo\Base\Utils::UrlSafeBase64Decode($sRawKey); $aValues = explode("\x0", $sRawKey); if (is_array($aValues) && (null === $iLenCache || $iLenCache === count($aValues))) { $mResult = $aValues; } } return $mResult; } /** * @param string $sTheme * * @return string */ public function ValidateTheme($sTheme) { return \in_array($sTheme, $this->GetThemes()) ? $sTheme : $this->Config()->Get('themes', 'default', 'Default'); } /** * @param string $sLanguage * * @return string */ public function ValidateLanguage($sLanguage) { return \in_array($sLanguage, $this->GetLanguages()) ? $sLanguage : $this->Config()->Get('i18n', 'default', 'en'); } /** * @param string $sType * * @return string */ public function ValidateContactPdoType($sType) { return \in_array($sType, array('mysql', 'pgsql', 'sqlite')) ? $sType : 'sqlite'; } /** * @staticvar array $aCache * * @return array */ public function GetThemes() { static $aCache = null; if (\is_array($aCache)) { return $aCache; } $bClear = false; $bDefault = false; $sList = array(); $sDir = APP_VERSION_ROOT_PATH.'themes'; if (@\is_dir($sDir)) { $rDirH = \opendir($sDir); if ($rDirH) { while (($sFile = \readdir($rDirH)) !== false) { if ('.' !== $sFile{0} && \is_dir($sDir.'/'.$sFile) && \file_exists($sDir.'/'.$sFile.'/styles.less')) { if ('Default' !== $sFile && 'Clear' !== $sFile) { $sList[] = $sFile; } else { if ('Default' === $sFile) { $bDefault = true; } else if ('Clear' === $sFile) { $bClear = true; } } } } @closedir($rDirH); } } $sDir = APP_INDEX_ROOT_PATH.'themes'; // custom user themes if (@\is_dir($sDir)) { $rDirH = \opendir($sDir); if ($rDirH) { while (($sFile = \readdir($rDirH)) !== false) { if ('.' !== $sFile{0} && \is_dir($sDir.'/'.$sFile) && \file_exists($sDir.'/'.$sFile.'/styles.less')) { $sList[] = $sFile.'@custom'; } } @\closedir($rDirH); } } $sList = \array_unique($sList); \sort($sList); if ($bDefault) { \array_unshift($sList, 'Default'); } if ($bClear) { \array_push($sList, 'Clear'); } $aCache = $sList; return $sList; } /** * @staticvar array $aCache * * @return array */ public function GetLanguages() { static $aCache = null; if (\is_array($aCache)) { return $aCache; } $bEn = false; $sList = array(); $sDir = APP_VERSION_ROOT_PATH.'langs/'; if (@\is_dir($sDir)) { $rDirH = opendir($sDir); if ($rDirH) { while (($sFile = \readdir($rDirH)) !== false) { if ('.' !== $sFile{0} && \is_file($sDir.'/'.$sFile) && '.ini' === \substr($sFile, -4)) { $sLang = \strtolower(\substr($sFile, 0, -4)); if (0 < \strlen($sLang) && 'always' !== $sLang) { if (\in_array($sLang, array('en'))) { $bEn = true; } else { \array_push($sList, $sLang); } } } } @\closedir($rDirH); } } \sort($sList); if ($bEn) { \array_unshift($sList, 'en'); } $aCache = $sList; return $sList; } /** * @param string $sName * @param string $sHtml * * @return string */ public function ProcessTemplate($sName, $sHtml) { $sHtml = $this->Plugins()->ProcessTemplate($sName, $sHtml); $sHtml = \preg_replace('/\{\{INCLUDE\/([a-zA-Z]+)\/PLACE\}\}/', '', $sHtml); return \RainLoop\Utils::ClearHtmlOutput($sHtml); } /** * @staticvar bool $bOnce * @param string $sName * @param array $aData = array() * * @return bool */ public function KeenIO($sName, $aData = array()) { static $bOnce = null; if (false === $bOnce) { return false; } if (APP_VERSION === APP_DEV_VERSION || \in_array(APP_SITE, \explode(' ', \base64_decode('bG9jYWxob3N0IHJhaW5sb29wLmRlIHJhaW5sb29wLm5ldCBkZW1vLnJhaW5sb29wLm5ldCBkZW1vLnJhaW5sb29wLmRl')))) { return true; } if (null === $bOnce && (!\MailSo\Base\Utils::FunctionExistsAndEnabled('curl_init') || !\MailSo\Base\Utils::FunctionExistsAndEnabled('json_encode'))) { $bOnce = false; return false; } $aOptions = array( CURLOPT_URL => \base64_decode('aHR0cHM6Ly9hcGkua2Vlbi5pby8zLjAvcHJvamVjdHMvNTE2NmRmOGUzODQzMzE3Y2QzMDAwMDA2L2V2ZW50cy8=').$sName, CURLOPT_HEADER => false, CURLOPT_FAILONERROR => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => array( 'Content-Type: application/json' ), CURLOPT_POST => true, CURLOPT_POSTFIELDS => \json_encode(\array_merge($aData, array( 'version' => APP_VERSION, 'uid' => \md5(APP_SITE.APP_SALT), 'site' => APP_SITE, 'date' => array( 'month' => \gmdate('m.Y'), 'day' => \gmdate('d.m.Y') ) ))), CURLOPT_TIMEOUT => 10 ); $sProxy = $this->Config()->Get('labs', 'curl_proxy', ''); if (0 < \strlen($sProxy)) { $aOptions[CURLOPT_PROXY] = $sProxy; $sProxyAuth = $this->Config()->Get('labs', 'curl_proxy_auth', ''); if (0 < \strlen($sProxyAuth)) { $aOptions[CURLOPT_PROXYUSERPWD] = $sProxyAuth; } } $oCurl = \curl_init(); \curl_setopt_array($oCurl, $aOptions); $mResult = \curl_exec($oCurl); if (\is_resource($oCurl)) { \curl_close($oCurl); } return !!$mResult; } /** * @param string $sActionName * @param mixed $mResult = false * @param array $aAdditionalParams = array() * * @return array */ private function mainDefaultResponse($sActionName, $mResult = false, $aAdditionalParams = array()) { $sActionName = 'Do' === substr($sActionName, 0, 2) ? substr($sActionName, 2) : $sActionName; $aResult = array( 'Action' => $sActionName, 'Result' => $this->responseObject($mResult, $sActionName) ); if (\is_array($aAdditionalParams)) { foreach ($aAdditionalParams as $sKey => $mValue) { $aResult[$sKey] = $mValue; } } return $aResult; } /** * @param string $sActionName * @param mixed $mResult = false * @param array $aAdditionalParams = array() * * @return array */ public function DefaultResponse($sActionName, $mResult = false, $aAdditionalParams = array()) { $this->Plugins()->RunHook('main.default-response-data', array($sActionName, &$mResult)); $aResponseItem = $this->mainDefaultResponse($sActionName, $mResult, $aAdditionalParams); $this->Plugins()->RunHook('main.default-response', array($sActionName, &$aResponseItem)); return $aResponseItem; } /** * @param string $sActionName * @param array $aAdditionalParams = array() * * @return array */ public function TrueResponse($sActionName, $aAdditionalParams = array()) { $mResult = true; $this->Plugins()->RunHook('main.default-response-data', array($sActionName, &$mResult)); $aResponseItem = $this->mainDefaultResponse($sActionName, $mResult, $aAdditionalParams); $this->Plugins()->RunHook('main.default-response', array($sActionName, &$aResponseItem)); return $aResponseItem; } /** * @param string $sActionName * @param int $iErrorCode = null * @param string $sErrorMessage = null * @param string $sAdditionalErrorMessage = null * * @return array */ public function FalseResponse($sActionName, $iErrorCode = null, $sErrorMessage = null, $sAdditionalErrorMessage = null) { $mResult = false; $this->Plugins() ->RunHook('main.default-response-data', array($sActionName, &$mResult)) ->RunHook('main.default-response-error-data', array($sActionName, &$iErrorCode, &$sErrorMessage)) ; $aAdditionalParams = array(); if (null !== $iErrorCode) { $aAdditionalParams['ErrorCode'] = (int) $iErrorCode; $aAdditionalParams['ErrorMessage'] = null === $sErrorMessage ? '' : (string) $sErrorMessage; $aAdditionalParams['ErrorMessageAdditional'] = null === $sAdditionalErrorMessage ? '' : (string) $sAdditionalErrorMessage; } $aResponseItem = $this->mainDefaultResponse($sActionName, $mResult, $aAdditionalParams); $this->Plugins()->RunHook('main.default-response', array($sActionName, &$aResponseItem)); return $aResponseItem; } /** * @param string $sActionName * @param \Exception $oException * * @return array */ public function ExceptionResponse($sActionName, $oException) { $iErrorCode = null; $sErrorMessage = null; $sErrorMessageAdditional = null; if ($oException instanceof \RainLoop\Exceptions\ClientException) { $iErrorCode = $oException->getCode(); $sErrorMessage = null; if ($iErrorCode === \RainLoop\Notifications::ClientViewError) { $sErrorMessage = $oException->getMessage(); } $sErrorMessageAdditional = $oException->getAdditionalMessage(); if (empty($sErrorMessageAdditional)) { $sErrorMessageAdditional = null; } } else { $iErrorCode = \RainLoop\Notifications::UnknownError; $sErrorMessage = $oException->getCode().' - '.$oException->getMessage(); } $oPrevious = $oException->getPrevious(); if ($oPrevious) { $this->Logger()->WriteException($oPrevious); } else { $this->Logger()->WriteException($oException); } return $this->FalseResponse($sActionName, $iErrorCode, $sErrorMessage, $sErrorMessageAdditional); } /** * @param array $aCurrentActionParams * @param string $sMethodName = '' * * @return \RainLoop\Actions */ public function SetActionParams($aCurrentActionParams, $sMethodName = '') { $this->Plugins()->RunHook('filter.action-params', array($sMethodName, &$aCurrentActionParams)); $this->aCurrentActionParams = $aCurrentActionParams; return $this; } /** * @param string $sKey * @param mixed $mDefault = null * * @return mixed */ public function GetActionParam($sKey, $mDefault = null) { return is_array($this->aCurrentActionParams) && isset($this->aCurrentActionParams[$sKey]) ? $this->aCurrentActionParams[$sKey] : $mDefault; } /** * @return mixed */ public function GetActionParams() { return $this->aCurrentActionParams; } /** * @param string $sKey * * @return bool */ public function HasActionParam($sKey) { return isset($this->aCurrentActionParams[$sKey]); } /** * @param array $aKeys * * @return bool */ public function HasOneOfActionParams($aKeys) { foreach ($aKeys as $sKey) { if ($this->HasActionParam($sKey)) { return true; } } return false; } /** * @param string $sUrl */ public function Location($sUrl) { $this->Logger()->Write('Location: '.$sUrl); @\header('Location: '.$sUrl); } /** * @param object $oData * @param string $sParent * @param array $aParameters = array() * * @return array | false */ private function objectData($oData, $sParent, $aParameters = array()) { $mResult = false; if (is_object($oData)) { $aNames = explode('\\', get_class($oData)); $mResult = array( '@Object' => end($aNames) ); if ($oData instanceof \MailSo\Base\Collection) { $mResult['@Object'] = 'Collection/'.$mResult['@Object']; $mResult['@Count'] = $oData->Count(); $mResult['@Collection'] = $this->responseObject($oData->GetAsArray(), $sParent, $aParameters); } else { $mResult['@Object'] = 'Object/'.$mResult['@Object']; } } return $mResult; } /** * @param string $sFolderFullName * * @return string */ private function hashFolderFullName($sFolderFullName) { return \in_array(\strtolower($sFolderFullName), array('inbox', 'sent', 'send', 'drafts', 'spam', 'junk', 'bin', 'trash', 'archive', 'allmail')) ? $sFolderFullName : \md5($sFolderFullName); } /** * @return array */ public function GetLanguageAndTheme() { $oAccount = $this->GetAccount(); $sLanguage = $this->Config()->Get('webmail', 'language', 'en'); $sTheme = $this->Config()->Get('webmail', 'theme', 'Default'); if ($oAccount instanceof \RainLoop\Model\Account) { $oSettings = $this->SettingsProvider()->Load($oAccount); if ($oSettings instanceof \RainLoop\Settings) { $sLanguage = $oSettings->GetConf('Language', $sLanguage); } $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); if ($oSettingsLocal instanceof \RainLoop\Settings) { $sTheme = $oSettingsLocal->GetConf('Theme', $sTheme); } } $sLanguage = $this->ValidateLanguage($sLanguage); $sTheme = $this->ValidateTheme($sTheme); return array($sLanguage, $sTheme); } /** * @param string $sKey * * @return string */ public function StaticI18N($sKey) { static $sLang = null; static $aLang = null; if (null === $sLang) { $aList = $this->GetLanguageAndTheme(); $sLang = $aList[0]; } if (null === $aLang) { $aLang = array(); \RainLoop\Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'app/i18n/langs.ini', $aLang); \RainLoop\Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'langs/'.$sLang.'.ini', $aLang); $this->Plugins()->ReadLang($sLang, $aLang); } return isset($aLang[$sKey]) ? $aLang[$sKey] : $sKey; } /** * @return MailSo\Cache\CacheClient|null */ private function cacherForUids() { $oAccount = $this->getAccountFromToken(false); return ($this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'server_uids', false)) ? $this->Cacher($oAccount) : null; } /** * @return MailSo\Cache\CacheClient|null */ private function cacherForThreads() { $oAccount = $this->getAccountFromToken(false); return !!$this->Config()->Get('labs', 'use_imap_thread', false) ? $this->Cacher($oAccount) : null; } /** * @param string $sSubject * * @return array */ private function explodeSubject($sSubject) { $aResult = array('', ''); if (0 < \strlen($sSubject)) { $bDrop = false; $aPrefix = array(); $aSuffix = array(); $aParts = \explode(':', $sSubject); foreach ($aParts as $sPart) { if (!$bDrop && (\preg_match('/^(RE|FWD)$/i', \trim($sPart)) || \preg_match('/^(RE|FWD)[\[\(][\d]+[\]\)]$/i', \trim($sPart)))) { $aPrefix[] = $sPart; } else { $aSuffix[] = $sPart; $bDrop = true; } } if (0 < \count($aPrefix)) { $aResult[0] = \rtrim(\trim(\implode(':', $aPrefix)), ':').': '; } if (0 < \count($aSuffix)) { $aResult[1] = \trim(\implode(':', $aSuffix)); } if (0 === \strlen($aResult[1])) { $aResult = array('', $sSubject); } } return $aResult; } /** * @param mixed $mResponse * @param string $sParent * @param array $aParameters = array() * * @return mixed */ protected function responseObject($mResponse, $sParent = '', $aParameters = array()) { $mResult = $mResponse; if (\is_object($mResponse)) { $bHook = true; $sClassName = \get_class($mResponse); $bHasSimpleJsonFunc = \method_exists($mResponse, 'ToSimpleJSON'); $bThumb = $this->GetCapa(false, \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS); if ($bHasSimpleJsonFunc) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), $mResponse->ToSimpleJSON(true)); } else if ('MailSo\Mail\Message' === $sClassName) { $oAccount = $this->getAccountFromToken(false); $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'Folder' => $mResponse->Folder(), 'Uid' => (string) $mResponse->Uid(), 'Subject' => \trim(\MailSo\Base\Utils::Utf8Clear($mResponse->Subject())), 'MessageId' => $mResponse->MessageId(), 'Size' => $mResponse->Size(), 'DateTimeStampInUTC' => !!$this->Config()->Get('labs', 'date_from_headers', false) ? $mResponse->HeaderTimeStampInUTC() : $mResponse->InternalTimeStampInUTC(), 'ReplyTo' => $this->responseObject($mResponse->ReplyTo(), $sParent, $aParameters), 'From' => $this->responseObject($mResponse->From(), $sParent, $aParameters), 'To' => $this->responseObject($mResponse->To(), $sParent, $aParameters), 'Cc' => $this->responseObject($mResponse->Cc(), $sParent, $aParameters), 'Bcc' => $this->responseObject($mResponse->Bcc(), $sParent, $aParameters), 'Sender' => $this->responseObject($mResponse->Sender(), $sParent, $aParameters), 'DeliveredTo' => $this->responseObject($mResponse->DeliveredTo(), $sParent, $aParameters), 'Priority' => $mResponse->Priority(), 'Threads' => $mResponse->Threads(), 'ThreadsLen' => $mResponse->ThreadsLen(), 'ParentThread' => $mResponse->ParentThread(), 'Sensitivity' => $mResponse->Sensitivity(), 'ExternalProxy' => false, 'ReadReceipt' => '' )); $mResult['SubjectParts'] = $this->explodeSubject($mResult['Subject']); $oAttachments = $mResponse->Attachments(); $iAttachmentsCount = $oAttachments ? $oAttachments->Count() : 0; $mResult['HasAttachments'] = 0 < $iAttachmentsCount; $mResult['AttachmentsMainType'] = ''; if (0 < $iAttachmentsCount) { switch (true) { case $iAttachmentsCount === $oAttachments->ImageCount(): $mResult['AttachmentsMainType'] = 'image'; break; case $iAttachmentsCount === $oAttachments->ArchiveCount(): $mResult['AttachmentsMainType'] = 'archive'; break; case $iAttachmentsCount === $oAttachments->PdfCount(): $mResult['AttachmentsMainType'] = 'pdf'; break; case $iAttachmentsCount === $oAttachments->DocCount(): $mResult['AttachmentsMainType'] = 'doc'; break; case $iAttachmentsCount === $oAttachments->CertificateCount(): $mResult['AttachmentsMainType'] = 'certificate'; break; } } $sSubject = $mResult['Subject']; $mResult['Hash'] = \md5($mResult['Folder'].$mResult['Uid']); $mResult['RequestHash'] = \RainLoop\Utils::EncodeKeyValues(array( 'V' => APP_VERSION, 'Account' => $oAccount ? \md5($oAccount->Hash()) : '', 'Folder' => $mResult['Folder'], 'Uid' => $mResult['Uid'], 'MimeType' => 'message/rfc822', 'FileName' => (0 === \strlen($sSubject) ? 'message-'.$mResult['Uid'] : \MailSo\Base\Utils::ClearXss($sSubject)).'.eml' )); // Flags $aFlags = $mResponse->FlagsLowerCase(); $mResult['IsSeen'] = \in_array('\\seen', $aFlags); $mResult['IsFlagged'] = \in_array('\\flagged', $aFlags); $mResult['IsAnswered'] = \in_array('\\answered', $aFlags); $mResult['IsDeleted'] = \in_array('\\deleted', $aFlags); $sForwardedFlag = $this->Config()->Get('labs', 'imap_forwarded_flag', ''); $sReadReceiptFlag = $this->Config()->Get('labs', 'imap_read_receipt_flag', ''); $mResult['IsForwarded'] = 0 < \strlen($sForwardedFlag) && \in_array(\strtolower($sForwardedFlag), $aFlags); $mResult['IsReadReceipt'] = 0 < \strlen($sReadReceiptFlag) && \in_array(\strtolower($sReadReceiptFlag), $aFlags); $mResult['TextPartIsTrimmed'] = false; if ('Message' === $sParent) { $oAttachments = /* @var \MailSo\Mail\AttachmentCollection */ $mResponse->Attachments(); $bHasExternals = false; $mFoundedCIDs = array(); $aContentLocationUrls = array(); $mFoundedContentLocationUrls = array(); if ($oAttachments && 0 < $oAttachments->Count()) { $aList =& $oAttachments->GetAsArray(); foreach ($aList as /* @var \MailSo\Mail\Attachment */ $oAttachment) { if ($oAttachment) { $sContentLocation = $oAttachment->ContentLocation(); if ($sContentLocation && 0 < \strlen($sContentLocation)) { $aContentLocationUrls[] = $oAttachment->ContentLocation(); } } } } $sPlain = ''; $sHtml = \trim($mResponse->Html()); if (0 === \strlen($sHtml)) { $sPlain = \trim($mResponse->Plain()); } $mResult['DraftInfo'] = $mResponse->DraftInfo(); $mResult['InReplyTo'] = $mResponse->InReplyTo(); $mResult['References'] = $mResponse->References(); $fAdditionalExternalFilter = null; if (!!$this->Config()->Get('labs', 'use_local_proxy_for_external_images', false)) { $fAdditionalExternalFilter = function ($sUrl) { return './?/ProxyExternal/'.\RainLoop\Utils::EncodeKeyValues(array( 'Rnd' => \md5(\microtime(true)), 'Token' => \RainLoop\Utils::GetConnectionToken(), 'Url' => $sUrl )).'/'; }; } $mResult['Html'] = 0 === \strlen($sHtml) ? '' : \MailSo\Base\HtmlUtils::ClearHtml( $sHtml, $bHasExternals, $mFoundedCIDs, $aContentLocationUrls, $mFoundedContentLocationUrls, false, false, $fAdditionalExternalFilter); $mResult['ExternalProxy'] = null !== $fAdditionalExternalFilter; $mResult['Plain'] = $sPlain; // $mResult['Plain'] = 0 === \strlen($sPlain) ? '' : \MailSo\Base\HtmlUtils::ConvertPlainToHtml($sPlain); $mResult['TextHash'] = \md5($mResult['Html'].$mResult['Plain']); $mResult['TextPartIsTrimmed'] = $mResponse->TextPartIsTrimmed(); $mResult['PgpSigned'] = $mResponse->PgpSigned(); $mResult['PgpEncrypted'] = $mResponse->PgpEncrypted(); $mResult['PgpSignature'] = $mResponse->PgpSignature(); unset($sHtml, $sPlain); $mResult['HasExternals'] = $bHasExternals; $mResult['HasInternals'] = (\is_array($mFoundedCIDs) && 0 < \count($mFoundedCIDs)) || (\is_array($mFoundedContentLocationUrls) && 0 < \count($mFoundedContentLocationUrls)); $mResult['FoundedCIDs'] = $mFoundedCIDs; $mResult['Attachments'] = $this->responseObject($oAttachments, $sParent, \array_merge($aParameters, array( 'FoundedCIDs' => $mFoundedCIDs, 'FoundedContentLocationUrls' => $mFoundedContentLocationUrls ))); $mResult['ReadReceipt'] = $mResponse->ReadReceipt(); if (0 < \strlen($mResult['ReadReceipt']) && !$mResult['IsReadReceipt']) { if (0 < \strlen($mResult['ReadReceipt'])) { try { $oReadReceipt = \MailSo\Mime\Email::Parse($mResult['ReadReceipt']); if (!$oReadReceipt) { $mResult['ReadReceipt'] = ''; } } catch (\Exception $oException) { unset($oException); } } if (0 < \strlen($mResult['ReadReceipt']) && '1' === $this->Cacher($oAccount)->Get( \RainLoop\KeyPathHelper::ReadReceiptCache($oAccount->Email(), $mResult['Folder'], $mResult['Uid']), '0')) { $mResult['ReadReceipt'] = ''; } } } } else if ('MailSo\Mime\Email' === $sClassName) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'Name' => \MailSo\Base\Utils::Utf8Clear($mResponse->GetDisplayName()), 'Email' => \MailSo\Base\Utils::Utf8Clear($mResponse->GetEmail(true)), 'DkimStatus' => $mResponse->GetDkimStatus(), 'DkimValue' => $mResponse->GetDkimValue() )); } else if ('RainLoop\Providers\AddressBook\Classes\Contact' === $sClassName) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( /* @var $mResponse \RainLoop\Providers\AddressBook\Classes\Contact */ 'IdContact' => $mResponse->IdContact, 'Display' => \MailSo\Base\Utils::Utf8Clear($mResponse->Display), 'ReadOnly' => $mResponse->ReadOnly, 'IdPropertyFromSearch' => $mResponse->IdPropertyFromSearch, 'Properties' => $this->responseObject($mResponse->Properties, $sParent, $aParameters) )); } else if ('RainLoop\Providers\AddressBook\Classes\Tag' === $sClassName) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( /* @var $mResponse \RainLoop\Providers\AddressBook\Classes\Tag */ 'IdContactTag' => $mResponse->IdContactTag, 'Name' => \MailSo\Base\Utils::Utf8Clear($mResponse->Name), 'ReadOnly' => $mResponse->ReadOnly )); } else if ('RainLoop\Providers\AddressBook\Classes\Property' === $sClassName) { // Simple hack if ($mResponse && $mResponse->IsWeb()) { $mResponse->Value = \preg_replace('/(skype|ftp|http[s]?)\\\:\/\//i', '$1://', $mResponse->Value); } $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( /* @var $mResponse \RainLoop\Providers\AddressBook\Classes\Property */ 'IdProperty' => $mResponse->IdProperty, 'Type' => $mResponse->Type, 'TypeStr' => $mResponse->TypeStr, 'Value' => \MailSo\Base\Utils::Utf8Clear($mResponse->Value) )); } else if ('MailSo\Mail\Attachment' === $sClassName) { $oAccount = $this->getAccountFromToken(false); $mFoundedCIDs = isset($aParameters['FoundedCIDs']) && \is_array($aParameters['FoundedCIDs']) && 0 < \count($aParameters['FoundedCIDs']) ? $aParameters['FoundedCIDs'] : null; $mFoundedContentLocationUrls = isset($aParameters['FoundedContentLocationUrls']) && \is_array($aParameters['FoundedContentLocationUrls']) && 0 < \count($aParameters['FoundedContentLocationUrls']) ? $aParameters['FoundedContentLocationUrls'] : null; if ($mFoundedCIDs || $mFoundedContentLocationUrls) { $mFoundedCIDs = \array_merge($mFoundedCIDs ? $mFoundedCIDs : array(), $mFoundedContentLocationUrls ? $mFoundedContentLocationUrls : array()); $mFoundedCIDs = 0 < \count($mFoundedCIDs) ? $mFoundedCIDs : null; } $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'Folder' => $mResponse->Folder(), 'Uid' => (string) $mResponse->Uid(), 'Framed' => false, 'MimeIndex' => (string) $mResponse->MimeIndex(), 'MimeType' => $mResponse->MimeType(), 'FileName' => \MailSo\Base\Utils::ClearFileName( \MailSo\Base\Utils::ClearXss($mResponse->FileName(true))), 'EstimatedSize' => $mResponse->EstimatedSize(), 'CID' => $mResponse->Cid(), 'ContentLocation' => $mResponse->ContentLocation(), 'IsInline' => $mResponse->IsInline(), 'IsThumbnail' => $bThumb, 'IsLinked' => ($mFoundedCIDs && \in_array(\trim(\trim($mResponse->Cid()), '<>'), $mFoundedCIDs)) || ($mFoundedContentLocationUrls && \in_array(\trim($mResponse->ContentLocation()), $mFoundedContentLocationUrls)) )); $mResult['Framed'] = $this->isFileHasFramedPreview($mResult['FileName']); if ($mResult['IsThumbnail']) { $mResult['IsThumbnail'] = $this->isFileHasThumbnail($mResult['FileName']); } $mResult['Download'] = \RainLoop\Utils::EncodeKeyValues(array( 'V' => APP_VERSION, 'Account' => $oAccount ? \md5($oAccount->Hash()) : '', 'Folder' => $mResult['Folder'], 'Uid' => $mResult['Uid'], 'MimeIndex' => $mResult['MimeIndex'], 'MimeType' => $mResult['MimeType'], 'FileName' => $mResult['FileName'], 'Framed' => $mResult['Framed'] )); } else if ('MailSo\Mail\Folder' === $sClassName) { $aExtended = null; $mStatus = $mResponse->Status(); if (\is_array($mStatus) && isset($mStatus['MESSAGES'], $mStatus['UNSEEN'], $mStatus['UIDNEXT'])) { $aExtended = array( 'MessageCount' => (int) $mStatus['MESSAGES'], 'MessageUnseenCount' => (int) $mStatus['UNSEEN'], 'UidNext' => (string) $mStatus['UIDNEXT'], 'Hash' => \MailSo\Mail\MailClient::GenerateHash( $mResponse->FullNameRaw(), $mStatus['MESSAGES'], $mStatus['UNSEEN'], $mStatus['UIDNEXT']) ); } $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'Name' => $mResponse->Name(), 'FullName' => $mResponse->FullName(), 'FullNameRaw' => $mResponse->FullNameRaw(), 'FullNameHash' => $this->hashFolderFullName($mResponse->FullNameRaw(), $mResponse->FullName()), 'Delimiter' => (string) $mResponse->Delimiter(), 'HasVisibleSubFolders' => $mResponse->HasVisibleSubFolders(), 'IsSubscribed' => $mResponse->IsSubscribed(), 'IsExists' => $mResponse->IsExists(), 'IsSelectable' => $mResponse->IsSelectable(), 'Flags' => $mResponse->FlagsLowerCase(), 'Extended' => $aExtended, 'SubFolders' => $this->responseObject($mResponse->SubFolders(), $sParent, $aParameters) )); } else if ('MailSo\Mail\MessageCollection' === $sClassName) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'MessageCount' => $mResponse->MessageCount, 'MessageUnseenCount' => $mResponse->MessageUnseenCount, 'MessageResultCount' => $mResponse->MessageResultCount, 'Folder' => $mResponse->FolderName, 'FolderHash' => $mResponse->FolderHash, 'UidNext' => $mResponse->UidNext, 'NewMessages' => $this->responseObject($mResponse->NewMessages), 'LastCollapsedThreadUids' => $mResponse->LastCollapsedThreadUids, 'Offset' => $mResponse->Offset, 'Limit' => $mResponse->Limit, 'Search' => $mResponse->Search )); } else if ('MailSo\Mail\AttachmentCollection' === $sClassName) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'InlineCount' => $mResponse->InlineCount() )); } else if ('MailSo\Mail\FolderCollection' === $sClassName) { $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( 'Namespace' => $mResponse->GetNamespace(), 'FoldersHash' => isset($mResponse->FoldersHash) ? $mResponse->FoldersHash : '', 'IsThreadsSupported' => $mResponse->IsThreadsSupported, 'Optimized' => $mResponse->Optimized, 'SystemFolders' => isset($mResponse->SystemFolders) && \is_array($mResponse->SystemFolders) ? $mResponse->SystemFolders : array() )); } else if ($mResponse instanceof \MailSo\Base\Collection) { $aList =& $mResponse->GetAsArray(); if (100 < \count($aList) && $mResponse instanceof \MailSo\Mime\EmailCollection) { $aList = \array_slice($aList, 0, 100); } $mResult = $this->responseObject($aList, $sParent, $aParameters); $bHook = false; } else { $mResult = '["'.\get_class($mResponse).'"]'; $bHook = false; } if ($bHook) { $this->Plugins()->RunHook('filter.response-object', array($sClassName, $mResult), false); } } else if (\is_array($mResponse)) { foreach ($mResponse as $iKey => $oItem) { $mResponse[$iKey] = $this->responseObject($oItem, $sParent, $aParameters); } $mResult = $mResponse; } unset($mResponse); return $mResult; } }