HEX
Server: Apache
System: Linux 185.122.168.184.host.secureserver.net 5.14.0-570.52.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Oct 15 06:39:08 EDT 2025 x86_64
User: barbeatleanalyti (1024)
PHP: 8.1.33
Disabled: NONE
Upload Files
File: /home/barbeatleanalyti/public_html/public_html/webmail/modules/Mail/Managers/Sieve/Manager.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\Modules\Mail\Managers\Sieve;

/**
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2019, Afterlogic Corp.
 */
class Manager extends \Aurora\System\Managers\AbstractManager
{
	/**
	 * @var bool
	 */
	const AutoSave = true;

	/**
	 * @var CApiSieveProtocol
	 */
	protected $oSieve;

	/**
	 * @var string
	 */
	protected $sSieveFileName;

	/**
	 * @var array
	 */
	protected $aSectionsData;

	/**
	 * @var array
	 */
	protected $aSectionsOrders;

	/**
	 * @var array
	 */
	protected $aSieves;

	/**
	 * @var string
	 */
	protected $sGeneralPassword;

	/**
	 * @param \Aurora\System\Module\AbstractModule $oModule
	 */
	public function __construct(\Aurora\System\Module\AbstractModule $oModule)
	{
		parent::__construct($oModule);

		$this->aSieves = array();
		$this->sGeneralPassword = '';
		$this->sSieveFileName = $oModule->getConfig('SieveFileName', 'sieve');
		$this->sSieveFolderCharset = $oModule->getConfig('SieveFiltersFolderCharset', 'utf-8');
		$this->bSectionsParsed = false;
		$this->aSectionsData = array();
		$this->aSectionsOrders = array(
			'forward',
			'autoresponder',
			'filters'
		);
	}

	/**
	 * @param string $sValue
	 * @return string
	 */
	private function _quoteValue($sValue)
	{
		return str_replace('"', '\\"', trim($sValue));
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @return array
	 */
	public function getAutoresponder($oAccount)
	{
		$this->_parseSectionsData($oAccount);
		$sData = $this->_getSectionData('autoresponder');

		$bEnabled = false;
		$sSubject = '';
		$sText = '';

		$aMatch = array();
		if (!empty($sData) && preg_match('/#data=([\d])~([^\n]+)/', $sData, $aMatch) && isset($aMatch[1]) && isset($aMatch[2]))
		{
			$bEnabled = '1' === (string) $aMatch[1];
			$aParts = explode("\x0", base64_decode($aMatch[2]), 2);
			if (is_array($aParts) && 2 === count($aParts))
			{
				$sSubject = $aParts[0];
				$sText = $aParts[1];
			}
		}

		return array(
			'Enable' => $bEnabled,
			'Subject' => $sSubject,
			'Message' => $sText
		);
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @param string $sSubject
	 * @param string $sText
	 * @param bool $bEnabled
	 * @return bool
	 */
	public function setAutoresponder($oAccount, $sSubject, $sText, $bEnabled = true)
	{
		$sSubject = str_replace(array("\r", "\n", "\t"), ' ', trim($sSubject));
		$sText = str_replace(array("\r"), '', trim($sText));

		$sData = '#data='.($bEnabled ? '1' : '0').'~'.base64_encode($sSubject."\x0".$sText)."\n";
		$sScriptText = 'vacation :days 1 :subject "'.$this->_quoteValue($sSubject).'" "'.$this->_quoteValue($sText).'";';

		if ($bEnabled)
		{
			$sData .= $sScriptText;
		}
		else
		{
			$sData .= '#'.implode("\n#", explode("\n", $sScriptText));
		}

		$this->_parseSectionsData($oAccount);
		$this->_setSectionData('autoresponder', $sData);

		if (self::AutoSave)
		{
			return $this->_resaveSectionsData($oAccount);
		}

		return true;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * 
	 * @return bool
	 */
//	public function disableAutoresponder($oAccount)
//	{
//		$aData = $this->getAutoresponder($oAccount);
//
//		$sText = '';
//		$sSubject = '';
//
//		if ($aData && isset($aData[1], $aData[2]))
//		{
//			$sText = $aData[2];
//			$sSubject = $aData[1];
//		}
//
//		return $this->setAutoresponder($oAccount, $sText, $sSubject, false);
//	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * 
	 * @return array
	 */
	public function getForward($oAccount)
	{
		$this->_parseSectionsData($oAccount);
		$sData = $this->_getSectionData('forward');

		$bEnabled = false;
		$sForward = '';

		$aMatch = array();
		if (!empty($sData) && preg_match('/#data=([\d])~([^\n]+)/', $sData, $aMatch) && isset($aMatch[1]) && isset($aMatch[2]))
		{
			$bEnabled = '1' === (string) $aMatch[1];
			$sForward = base64_decode($aMatch[2]);
		}
		
		return array(
			'Enable' => $bEnabled,
			'Email' => $sForward
		);
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @param string $sForward
	 * @param bool $bEnabled Default true
	 * 
	 * @return bool
	 */
	public function setForward($oAccount, $sForward, $bEnabled = true)
	{
		$sData =
			'#data='.($bEnabled ? '1' : '0').'~'.base64_encode($sForward)."\n".
			($bEnabled ? '' : '#').'redirect :copy "'.$this->_quoteValue($sForward).'";'."\n";

		$this->_parseSectionsData($oAccount);
		$this->_setSectionData('forward', $sData);

		if (self::AutoSave)
		{
			return $this->_resaveSectionsData($oAccount);
		}

		return true;
	}
	
	public function createFilterInstance(\Aurora\Modules\Mail\Classes\Account $oAccount, $aData)
	{
		$oFilter = null;
		
		if (is_array($aData))
		{
			$oFilter = new \Aurora\Modules\Mail\Classes\SieveFilter($oAccount);
		
			$oFilter->Enable = (bool) trim($aData['Enable']);
			$oFilter->Field = (int) trim($aData['Field']);
			$oFilter->Condition = (int) trim($aData['Condition']);
			$oFilter->Action = (int) trim($aData['Action']);
			$oFilter->Filter = (string) trim($aData['Filter']);

			if (\Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder === $oFilter->Action && isset($aData['FolderFullName']))
			{
				$oFilter->FolderFullName = \Aurora\System\Utils::ConvertEncoding($aData['FolderFullName'],
					$this->sSieveFolderCharset, 'utf7-imap');
			}
		}
		
		return $oFilter;
	}
	
	/**
	 * @param CAcount $oAccount
	 *
	 * @return array|false
	 */
	public function getSieveFilters($oAccount)
	{
		$mResult = false;
		$sScript = $this->getFiltersRawData($oAccount);
		
		if (false !== $sScript)
		{
			$mResult = array();
			
			$aFilters = explode("\n", $sScript);

			foreach ($aFilters as $sFilter)
			{
				$sPattern = '#sieve_filter:';
				if (strpos($sFilter, $sPattern) !== false)
				{
					$sFilter = substr($sFilter, strlen($sPattern));

					$aFilter = explode(";", $sFilter);

//					if (is_array($aFilter) && 5 < count($aFilter))
					if (is_array($aFilter))
					{
						$aFilterData = array(
							'Enable' => $aFilter[0],
							'Field' => $aFilter[2],
							'Condition' => $aFilter[1],
							'Action' => $aFilter[4],
							'Filter' => $aFilter[3],
							'FolderFullName' => $aFilter[5]
						);
						
						$oFilter = $this->createFilterInstance($oAccount, $aFilterData);
						
						if ($oFilter)
						{
							$mResult[] = $oFilter;
						}
					}

					unset($oFilter);
				}
			}
		}

		return $mResult;
	}

	/**
	 * @param CAcount $oAccount
	 * @param array $aFilters
	 *
	 * @return bool
	 */
	public function updateSieveFilters($oAccount, $aFilters)
	{
		$sFilters = "#sieve filter\n\n";

		if ($oAccount)
		{
			foreach ($aFilters as /* @var $oFilter SieveFilter */ $oFilter)
			{
				if  ('' === trim($oFilter->Filter))
				{
					continue;
				}

				if  (\Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder === $oFilter->Action && '' === trim($oFilter->FolderFullName))
				{
					continue;
				}

				$aFields = array();
				switch($oFilter->Field)
				{
					default :
					case \Aurora\Modules\Mail\Enums\FilterFields::From:
						$aFields[] = 'From';
						break;
					case \Aurora\Modules\Mail\Enums\FilterFields::To:
						$aFields[] = 'To';
						$aFields[] = 'CC';
						break;
					case \Aurora\Modules\Mail\Enums\FilterFields::Subject:
						$aFields[] = 'Subject';
						break;
				}

				// condition
				foreach ($aFields as $iIndex => $sField)
				{
					$aFields[$iIndex] = '"'.$this->_quoteValue($sField).'"';
				}

				$sCondition = '';
				$sFields = implode(',', $aFields);
				switch ($oFilter->Condition)
				{
					case \Aurora\Modules\Mail\Enums\FilterCondition::ContainSubstring:
						$sCondition = 'if header :contains ['.$sFields.'] "'.$this->_quoteValue($oFilter->Filter).'" {';
						break;
					case \Aurora\Modules\Mail\Enums\FilterCondition::ContainExactPhrase:
						$sCondition = 'if header :is ['.$sFields.'] "'.$this->_quoteValue($oFilter->Filter).'" {';
						break;
					case \Aurora\Modules\Mail\Enums\FilterCondition::NotContainSubstring:
						$sCondition = 'if not header :contains ['.$sFields.'] "'.$this->_quoteValue($oFilter->Filter).'" {';
						break;
				}

				// folder
				$sFolderFullName = '';
				if (\Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder === $oFilter->Action)
				{
					$sFolderFullName = \Aurora\System\Utils::ConvertEncoding($oFilter->FolderFullName,
						'utf7-imap', $this->sSieveFolderCharset);
				}

				// action
				$sAction = '';
				switch($oFilter->Action)
				{
					case \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately:
						$sAction = 'discard ;';
						$sAction .= 'stop ;';
						break;
					case \Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder:
						$sAction = 'fileinto "'.$this->_quoteValue($sFolderFullName).'" ;'."\n";
						$sAction .= 'stop ;';
						break;
				}

				$sEnd = '}';

				if (!$oFilter->Enable)
				{
					$sCondition = '#'.$sCondition;
					$sAction = '#'.$sAction;
					$sEnd = '#'.$sEnd;
				}

				$sFilters .= "\n".'#sieve_filter:'.implode(';', array(
					$oFilter->Enable ? '1' : '0', $oFilter->Condition, $oFilter->Field,
					$oFilter->Filter, $oFilter->Action, $sFolderFullName))."\n";

				$sFilters .= $sCondition."\n";
				$sFilters .= $sAction."\n";
				$sFilters .= $sEnd."\n";
			}

			$sFilters = $sFilters."\n".'#end sieve filter'."\n";
			
			return $this->setFiltersRawData($oAccount, $sFilters);
		}

		return false;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @param string $sForward
	 * @param bool $bEnabled = true
	 * 
	 * @return bool
	 */
	public function disableForward($oAccount)
	{
		$sForward = '';
		$aData = $this->getForward($oAccount);

		if ($aData && isset($aData[1]))
		{
			$sForward = $aData[1];
		}

		return $this->setForward($oAccount, $sForward, false);
	}

	/**
	 * @depricated
	 * 
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @param string $sSectionName Default ''
	 * @param string $sSectionData Default ''
	 * 
	 * @return bool
	 */
	public function resave($oAccount, $sSectionName = '', $sSectionData = '')
	{
		$this->_parseSectionsData($oAccount);
		if (!empty($sSectionName) && !empty($sSectionData))
		{
			$this->_setSectionData($sSectionName, $sSectionData);
		}

		return $this->_resaveSectionsData($oAccount);
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @return string
	 */
	public function getFiltersRawData($oAccount)
	{
		$this->_parseSectionsData($oAccount);
		return $this->_getSectionData('filters');
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @param string $sFiltersRawData
	 * @return bool
	 */
	public function setFiltersRawData($oAccount, $sFiltersRawData)
	{
		$this->_parseSectionsData($oAccount);
		$this->_setSectionData('filters', $sFiltersRawData);

		if (self::AutoSave)
		{
			return $this->_resaveSectionsData($oAccount);
		}
		return true;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @return \MailSo\Sieve\ManageSieveClient|false
	 */
	protected function _getSieveDriver(\Aurora\Modules\Mail\Classes\Account $oAccount)
	{
		$oSieve = false;
		if ($oAccount instanceof \Aurora\Modules\Mail\Classes\Account)
		{
			if (!isset($this->aSieves[$oAccount->Email]))
			{
				$oSieve = \MailSo\Sieve\ManageSieveClient::NewInstance();
				$oSieve->SetLogger(\Aurora\System\Api::SystemLogger());

				$this->aSieves[$oAccount->Email] = $oSieve;
			}
			else
			{
				$oSieve = $this->aSieves[$oAccount->Email];
			}
		}

		return $oSieve;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * 
	 * @return \MailSo\Sieve\ManageSieveClient|false
	 */
	protected function _connectSieve($oAccount)
	{
		$bResult = false;
		$oSieve = $this->_getSieveDriver($oAccount);

		if ($oSieve)
		{
			if (!$oSieve->IsConnected())
			{
				$oMailModule = \Aurora\System\Api::GetModule('Mail');
				$sGeneralPassword = $oMailModule->getConfig('SieveGeneralPassword', '');
				
				$oServer = $oMailModule->getServersManager()->getServer($oAccount->ServerId);
				
				$sHost = $oServer->IncomingServer;
				$iPort = $oServer->SievePort;
				$sPassword = 0 === strlen($sGeneralPassword) ? $oAccount->getPassword() : $sGeneralPassword;
				$bUseStarttls = $this->GetModule()->getConfig('SieveUseStarttls', false);
				$bResult = $oSieve
					->Connect($sHost, $iPort, $bUseStarttls ? \MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS : \MailSo\Net\Enumerations\ConnectionSecurityType::NONE)
					->Login($oAccount->IncomingLogin, $sPassword)
				;
			}
			else
			{
				$bResult = true;
			}

			if ($oSieve)
			{
				return $oSieve;
			}
		}

		return $bResult;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * 
	 * @return string|false
	 */
	protected function _getSieveFile($oAccount)
	{
		$sResult = false;
		
		try
		{
			$oSieve = $this->_connectSieve($oAccount);
			if ($oSieve)
			{
				if ($oSieve->IsActiveScript($this->sSieveFileName))
				{
					$sResult = $oSieve->GetScript($this->sSieveFileName);
				}
			}
		}
		catch (\Exception $oException)
		{
			$sResult = false;
		}

		return is_string($sResult) ? str_replace("\r", '', $sResult) : false;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * @param string $sText
	 * 
	 * @return bool
	 */
	protected function _setSieveFile($oAccount, $sText)
	{
		$sText = str_replace("\r", '', $sText);
		$sText = rtrim(str_replace("\n", "\r\n", $sText));
		$bResult = false;
		
		try
		{
			$oSieve = $this->_connectSieve($oAccount);
			
			if ($oSieve)
			{
				$oSieve->CheckScript($sText);
				
				$oSieve->PutScript($this->sSieveFileName, $sText);
				$oSieve->SetActiveScript($this->sSieveFileName);

				$bResult = true;
			}
			else
			{
				throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::UserManager_AccountUpdateFailed);
			}
		}
		catch (\Exception $oException)
		{
			throw $oException;
		}

		return $bResult;
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * 
	 * @return bool
	 */
	protected function _resaveSectionsData($oAccount)
	{
		$this->bSectionsParsed = false;
		return $this->_setSieveFile($oAccount, $this->_selectionsDataToString());
	}

	/**
	 * @param \Aurora\Modules\StandardAuth\Classes\Account $oAccount
	 * 
	 * @param bool $bForced Default false
	 */
	protected function _parseSectionsData($oAccount, $bForced = false)
	{
		if (!$this->bSectionsParsed || $bForced)
		{
			$sText = $this->_getSieveFile($oAccount);
			if (false !== $sText)
			{
				if (is_array($this->aSectionsOrders))
				{
					foreach ($this->aSectionsOrders as $sSectionName)
					{
						$aParams = $this->_getSectionParams($sSectionName, $sText);
						if ($aParams)
						{
							$this->aSectionsData[$sSectionName] = trim(substr($sText,
								$aParams[0] + strlen($aParams[2]),
								$aParams[1] - $aParams[0] - strlen($aParams[2])
							));
						}
					}
				}
			}
		}
	}

	/**
	 * @return string
	 */
	protected function _selectionsDataToString()
	{
		$sResult = '';
		if (is_array($this->aSectionsOrders))
		{
			foreach ($this->aSectionsOrders as $sSectionName)
			{
				if (!empty($this->aSectionsData[$sSectionName]))
				{
					$sResult .= "\n".
						$this->_getComment($sSectionName, true)."\n".
						$this->aSectionsData[$sSectionName]."\n".
						$this->_getComment($sSectionName, false)."\n";
				};
			}
		}

		$sResult = 'require ["fileinto", "copy", "vacation"] ;'."\n".$sResult;
		$sResult = "# Sieve filter\n".$sResult;
		$sResult .= "keep ;\n";
		return $sResult;
	}

	/**
	 * @param string $sSectionName
	 * 
	 * @return string
	 */
	protected function _getSectionData($sSectionName)
	{
		if (in_array($sSectionName, $this->aSectionsOrders) && !empty($this->aSectionsData[$sSectionName]))
		{
			  return $this->aSectionsData[$sSectionName];
		}

		return '';
	}

	/**
	 * @param string $sSectionName
	 * @param string $sData
	 */
	protected function _setSectionData($sSectionName, $sData)
	{
		if (in_array($sSectionName, $this->aSectionsOrders))
		{
			$this->aSectionsData[$sSectionName] = $sData;
		}
	}

	/**
	 * 
	 * @param type $sSectionName
	 * @param type $bIsBeginComment Default true
	 * 
	 * @return string
	 */
	protected function _getComment($sSectionName, $bIsBeginComment = true)
	{
		return '#'.($bIsBeginComment ? 'begin' : 'end').' = '.$sSectionName.' =';
	}

	/**
	 * 
	 * @param string $sSectionName
	 * @param string $sText
	 * 
	 * @return array|false
	 */
	protected function _getSectionParams($sSectionName, $sText)
	{
		$aResult = false;

		if (!empty($sText))
		{
			$sBeginComment = $this->_getComment($sSectionName, true);
			$sEndComment = $this->_getComment($sSectionName, false);

			$iBegin = strpos($sText, $sBeginComment);
			if (false !== $iBegin)
			{
				$iEnd = strpos($sText, $sEndComment, $iBegin);
				if (false !== $iEnd)
				{
					$aResult = array($iBegin, $iEnd, $sBeginComment, $sEndComment);
				}
			}
		}

		return $aResult;
	}

}