diff --git a/dev/Common/Base64.js b/dev/Common/Base64.js index 8d739a6fe..b3b801e19 100644 --- a/dev/Common/Base64.js +++ b/dev/Common/Base64.js @@ -9,7 +9,7 @@ const Base64 = { // public method for urlsafe encoding urlsafe_encode: (input) => Base64.encode(input) - .replace(/[+]/g, '-').replace(/[\/]/g, '_').replace(/[=]/g, '.'), + .replace(/[+]/g, '-').replace(/[\/]/g, '_').replace(/[=]/g, ''), // public method for encoding encode: (input) => { diff --git a/dev/Styles/Login.less b/dev/Styles/Login.less index 3b6ec0db3..84cf4f32e 100644 --- a/dev/Styles/Login.less +++ b/dev/Styles/Login.less @@ -182,3 +182,13 @@ z-index: 1052; } } + +.btn-submit-icon-wrp { + border: none; + background: none; + display: inline-block; + margin: 0; + padding: 0; + outline: none; + cursor: pointer; +} \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 88916fe1e..04740a893 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3125,9 +3125,9 @@ } }, "moment": { - "version": "2.15.0", - "from": "moment@2.15.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.15.0.tgz" + "version": "2.15.1", + "from": "moment@2.15.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.15.1.tgz" }, "ms": { "version": "0.7.1", diff --git a/package.json b/package.json index 09a88ae7f..19a9cedc7 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Simple, modern & fast web-based email client", "private": true, "version": "1.10.4", - "release": "180", - "ownCloudPackageVersion": "4.24", + "release": "181", + "ownCloudPackageVersion": "4.25", "homepage": "http://rainloop.net", "main": "gulpfile.js", "author": { @@ -99,7 +99,7 @@ "knockout-sortable": "^0.14.1", "lightgallery": "^1.2.21", "matchmedia-polyfill": "^0.3.0", - "moment": "^2.15.0", + "moment": "^2.15.1", "node-fs": "^0.1.7", "node-notifier": "4.6.1", "normalize.css": "^4.2.0", diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Crypt.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Crypt.php index 3ef6fc5f8..f21bdbca9 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Crypt.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Crypt.php @@ -1,189 +1,189 @@ -> 2 & 3; - for ($iPIndex = 0; $iPIndex < $iN; $iPIndex++) - { - $iY = $aV[$iPIndex + 1]; - $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + - (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); - $iZ = $aV[$iPIndex] = self::int32($aV[$iPIndex] + $iMx); - } - $iY = $aV[0]; - $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + - (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); - $iZ = $aV[$iN] = self::int32($aV[$iN] + $iMx); - } - - return self::long2str($aV, false); - } - - /** - * @param string $sEncriptedString - * @param string $sKey - * - * @return string - */ - public static function XxteaDecrypt($sEncriptedString, $sKey) - { - if (0 === \strlen($sEncriptedString)) - { - return ''; - } - - $aV = self::str2long($sEncriptedString, false); - $aK = self::str2long($sKey, false); - - if (\count($aK) < 4) - { - for ($iIndex = \count($aK); $iIndex < 4; $iIndex++) - { - $aK[$iIndex] = 0; - } - } - - $iN = \count($aV) - 1; - - $iZ = $aV[$iN]; - $iY = $aV[0]; - $iDelta = 0x9E3779B9; - $iQ = \floor(6 + 52 / ($iN + 1)); - $iSum = self::int32($iQ * $iDelta); - while ($iSum != 0) - { - $iE = $iSum >> 2 & 3; - for ($iPIndex = $iN; $iPIndex > 0; $iPIndex--) - { - $iZ = $aV[$iPIndex - 1]; - $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + - (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); - $iY = $aV[$iPIndex] = self::int32($aV[$iPIndex] - $iMx); - } - $iZ = $aV[$iN]; - $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + - (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); - $iY = $aV[0] = self::int32($aV[0] - $iMx); - $iSum = self::int32($iSum - $iDelta); - } - - return self::long2str($aV, true); - } - - /** - * @param array $aV - * @param array $aW - * - * @return string - */ - private static function long2str($aV, $aW) - { - $iLen = \count($aV); - $iN = ($iLen - 1) << 2; - if ($aW) - { - $iM = $aV[$iLen - 1]; - if (($iM < $iN - 3) || ($iM > $iN)) - { - return false; - } - $iN = $iM; - } - $aS = array(); - for ($iIndex = 0; $iIndex < $iLen; $iIndex++) - { - $aS[$iIndex] = \pack('V', $aV[$iIndex]); - } - if ($aW) - { - return \substr(\join('', $aS), 0, $iN); - } - else - { - return \join('', $aS); - } - } - - /** - * @param string $sS - * @param string $sW - * - * @return array - */ - private static function str2long($sS, $sW) - { - $aV = \unpack('V*', $sS . \str_repeat("\0", (4 - \strlen($sS) % 4) & 3)); - $aV = \array_values($aV); - if ($sW) - { - $aV[\count($aV)] = \strlen($sS); - } - return $aV; - } - - /** - * @param int $iN - * - * @return int - */ - private static function int32($iN) - { - while ($iN >= 2147483648) - { - $iN -= 4294967296; - } - while ($iN <= -2147483649) - { - $iN += 4294967296; - } - return (int) $iN; - } +> 2 & 3; + for ($iPIndex = 0; $iPIndex < $iN; $iPIndex++) + { + $iY = $aV[$iPIndex + 1]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iZ = $aV[$iPIndex] = self::int32($aV[$iPIndex] + $iMx); + } + $iY = $aV[0]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iZ = $aV[$iN] = self::int32($aV[$iN] + $iMx); + } + + return self::long2str($aV, false); + } + + /** + * @param string $sEncriptedString + * @param string $sKey + * + * @return string + */ + public static function XxteaDecrypt($sEncriptedString, $sKey) + { + if (0 === \strlen($sEncriptedString)) + { + return ''; + } + + $aV = self::str2long($sEncriptedString, false); + $aK = self::str2long($sKey, false); + + if (\count($aK) < 4) + { + for ($iIndex = \count($aK); $iIndex < 4; $iIndex++) + { + $aK[$iIndex] = 0; + } + } + + $iN = \count($aV) - 1; + + $iZ = $aV[$iN]; + $iY = $aV[0]; + $iDelta = 0x9E3779B9; + $iQ = \floor(6 + 52 / ($iN + 1)); + $iSum = self::int32($iQ * $iDelta); + while ($iSum != 0) + { + $iE = $iSum >> 2 & 3; + for ($iPIndex = $iN; $iPIndex > 0; $iPIndex--) + { + $iZ = $aV[$iPIndex - 1]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iY = $aV[$iPIndex] = self::int32($aV[$iPIndex] - $iMx); + } + $iZ = $aV[$iN]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iY = $aV[0] = self::int32($aV[0] - $iMx); + $iSum = self::int32($iSum - $iDelta); + } + + return self::long2str($aV, true); + } + + /** + * @param array $aV + * @param array $aW + * + * @return string + */ + private static function long2str($aV, $aW) + { + $iLen = \count($aV); + $iN = ($iLen - 1) << 2; + if ($aW) + { + $iM = $aV[$iLen - 1]; + if (($iM < $iN - 3) || ($iM > $iN)) + { + return false; + } + $iN = $iM; + } + $aS = array(); + for ($iIndex = 0; $iIndex < $iLen; $iIndex++) + { + $aS[$iIndex] = \pack('V', $aV[$iIndex]); + } + if ($aW) + { + return \substr(\join('', $aS), 0, $iN); + } + else + { + return \join('', $aS); + } + } + + /** + * @param string $sS + * @param string $sW + * + * @return array + */ + private static function str2long($sS, $sW) + { + $aV = \unpack('V*', $sS . \str_repeat("\0", (4 - \strlen($sS) % 4) & 3)); + $aV = \array_values($aV); + if ($sW) + { + $aV[\count($aV)] = \strlen($sS); + } + return $aV; + } + + /** + * @param int $iN + * + * @return int + */ + private static function int32($iN) + { + while ($iN >= 2147483648) + { + $iN -= 4294967296; + } + while ($iN <= -2147483649) + { + $iN += 4294967296; + } + return (int) $iN; + } } \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php index b439b965a..ea9343705 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -1721,7 +1721,7 @@ END; */ public static function UrlSafeBase64Encode($sValue) { - return \rtrim(\strtr(\base64_encode(\trim($sValue)), '+/', '-_'), '='); + return \rtrim(\strtr(\base64_encode($sValue), '+/', '-_'), '='); } /** @@ -1732,7 +1732,7 @@ END; public static function UrlSafeBase64Decode($sValue) { $sValue = \rtrim(\strtr($sValue, '-_.', '+/='), '='); - return \MailSo\Base\Utils::Base64Decode(\str_pad($sValue, \strlen($sValue) % 4, '=', STR_PAD_RIGHT)); + return \MailSo\Base\Utils::Base64Decode(\str_pad($sValue, \strlen($sValue) + (\strlen($sValue) % 4), '=', STR_PAD_RIGHT)); } /** diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index 19aa9a51e..9e01e2c12 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -1,2667 +1,2667 @@ -iTagCount = 0; - $this->aCapabilityItems = null; - $this->oCurrentFolderInfo = null; - $this->aFetchCallbacks = null; - $this->iResponseBufParsedPos = 0; - - $this->aLastResponse = array(); - $this->bNeedNext = true; - $this->aPartialResponses = array(); - - $this->aTagTimeouts = array(); - - $this->bIsLoggined = false; - $this->bIsSelected = false; - $this->sLogginedUser = ''; - - $this->__FORCE_SELECT_ON_EXAMINE__ = false; - - @\ini_set('xdebug.max_nesting_level', 500); - } - - /** - * @return \MailSo\Imap\ImapClient - */ - public static function NewInstance() - { - return new self(); - } - - /** - * @return string - */ - public function GetLogginedUser() - { - return $this->sLogginedUser; - } - - /** - * @param string $sServerName - * @param int $iPort = 143 - * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT - * @param bool $bVerifySsl = false - * @param bool $bAllowSelfSigned = true - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function Connect($sServerName, $iPort = 143, - $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, - $bVerifySsl = false, $bAllowSelfSigned = true) - { - $this->aTagTimeouts['*'] = \microtime(true); - - parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); - - $this->parseResponseWithValidation('*', true); - - if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( - $this->IsSupported('STARTTLS'), $this->iSecurityType)) - { - $this->SendRequestWithCheck('STARTTLS'); - $this->EnableCrypto(); - - $this->aCapabilityItems = null; - } - else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType) - { - $this->writeLogException( - new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - return $this; - } - - protected function _xor($string, $string2) - { - $result = ''; - $size = strlen($string); - for ($i=0; $i<$size; $i++) { - $result .= chr(ord($string[$i]) ^ ord($string2[$i])); - } - return $result; - } - - /** - * @param string $sLogin - * @param string $sPassword - * @param string $sProxyAuthUser = '' - * @param bool $bUseAuthPlainIfSupported = true - * @param bool $bUseAuthCramMd5IfSupported = true - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function Login($sLogin, $sPassword, $sProxyAuthUser = '', - $bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true) - { - if (!\MailSo\Base\Validator::NotEmptyString($sLogin, true) || - !\MailSo\Base\Validator::NotEmptyString($sPassword, true)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); - - $sPassword = $sPassword; - - $this->sLogginedUser = $sLogin; - - try - { - if ($bUseAuthCramMd5IfSupported && $this->IsSupported('AUTH=CRAM-MD5')) - { - $this->SendRequest('AUTHENTICATE', array('CRAM-MD5')); - - $aResponse = $this->parseResponseWithValidation(); - if ($aResponse && \is_array($aResponse) && 0 < \count($aResponse) && - \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $aResponse[\count($aResponse) - 1]->ResponseType) - { - $oContinuationResponse = null; - foreach ($aResponse as $oResponse) - { - if ($oResponse && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oResponse->ResponseType) - { - $oContinuationResponse = $oResponse; - } - } - - if ($oContinuationResponse && !empty($oContinuationResponse->ResponseList[1])) - { - $sTicket = @\base64_decode($oContinuationResponse->ResponseList[1]); - $this->oLogger->Write('ticket: '.$sTicket); - - $sToken = \base64_encode($sLogin.' '.\MailSo\Base\Utils::Hmac($sTicket, $sPassword)); - - if ($this->oLogger) - { - $this->oLogger->AddSecret($sToken); - } - - $this->sendRaw($sToken, true, '*******'); - $this->parseResponseWithValidation(); - } - else - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\LoginException(), - \MailSo\Log\Enumerations\Type::NOTICE, true); - } - } - else - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\LoginException(), - \MailSo\Log\Enumerations\Type::NOTICE, true); - } - } - else if ($bUseAuthPlainIfSupported && $this->IsSupported('AUTH=PLAIN')) - { - $sToken = \base64_encode("\0".$sLogin."\0".$sPassword); - if ($this->oLogger) - { - $this->oLogger->AddSecret($sToken); - } - - if ($this->IsSupported('AUTH=SASL-IR') && false) - { - $this->SendRequestWithCheck('AUTHENTICATE', array('PLAIN', $sToken)); - } - else - { - $this->SendRequest('AUTHENTICATE', array('PLAIN')); - $this->parseResponseWithValidation(); - - $this->sendRaw($sToken, true, '*******'); - $this->parseResponseWithValidation(); - } - } - else - { - if ($this->oLogger) - { - $this->oLogger->AddSecret($this->EscapeString($sPassword)); - } - - $this->SendRequestWithCheck('LOGIN', - array( - $this->EscapeString($sLogin), - $this->EscapeString($sPassword) - )); - } -// else -// { -// $this->writeLogException( -// new \MailSo\Imap\Exceptions\LoginBadMethodException(), -// \MailSo\Log\Enumerations\Type::NOTICE, true); -// } - - if (0 < \strlen($sProxyAuthUser)) - { - $this->SendRequestWithCheck('PROXYAUTH', array($this->EscapeString($sProxyAuthUser))); - } - } - catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\LoginBadCredentialsException( - $oException->GetResponses(), '', 0, $oException), - \MailSo\Log\Enumerations\Type::NOTICE, true); - } - - $this->bIsLoggined = true; - $this->aCapabilityItems = null; - - return $this; - } - - /** - * @param string $sXOAuth2Token - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function LoginWithXOauth2($sXOAuth2Token) - { - if (!\MailSo\Base\Validator::NotEmptyString($sXOAuth2Token, true)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - if (!$this->IsSupported('AUTH=XOAUTH2')) - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\LoginBadMethodException(), - \MailSo\Log\Enumerations\Type::NOTICE, true); - } - - try - { - $this->SendRequest('AUTHENTICATE', array('XOAUTH2', \trim($sXOAuth2Token))); - $aR = $this->parseResponseWithValidation(); - - if (\is_array($aR) && 0 < \count($aR) && isset($aR[\count($aR) - 1])) - { - $oR = $aR[\count($aR) - 1]; - if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oR->ResponseType) - { - if (!empty($oR->ResponseList[1]) && preg_match('/^[a-zA-Z0-9=+\/]+$/', $oR->ResponseList[1])) - { - $this->Logger()->Write(\base64_decode($oR->ResponseList[1]), - \MailSo\Log\Enumerations\Type::WARNING); - } - - $this->sendRaw(''); - $this->parseResponseWithValidation(); - } - } - } - catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\LoginBadCredentialsException( - $oException->GetResponses(), '', 0, $oException), - \MailSo\Log\Enumerations\Type::NOTICE, true); - } - - $this->bIsLoggined = true; - $this->aCapabilityItems = null; - - return $this; - } - - /** - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Net\Exceptions\Exception - */ - public function Logout() - { - if ($this->bIsLoggined) - { - $this->bIsLoggined = false; - $this->SendRequestWithCheck('LOGOUT', array()); - } - - return $this; - } - - /** - * @return \MailSo\Imap\ImapClient - */ - public function ForceCloseConnection() - { - $this->Disconnect(); - - return $this; - } - - /** - * @return bool - */ - public function IsLoggined() - { - return $this->IsConnected() && $this->bIsLoggined; - } - - /** - * @return bool - */ - public function IsSelected() - { - return $this->IsLoggined() && $this->bIsSelected; - } - - /** - * @return array|null - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function Capability() - { - $this->SendRequestWithCheck('CAPABILITY', array(), true); - return $this->aCapabilityItems; - } - - /** - * @param string $sExtentionName - * @return bool - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function IsSupported($sExtentionName) - { - $bResult = \MailSo\Base\Validator::NotEmptyString($sExtentionName, true); - if ($bResult && null === $this->aCapabilityItems) - { - $this->aCapabilityItems = $this->Capability(); - } - - return $bResult && \is_array($this->aCapabilityItems) && - \in_array(\strtoupper($sExtentionName), $this->aCapabilityItems); - } - - /** - * @return \MailSo\Imap\NamespaceResult|null - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function GetNamespace() - { - if (!$this->IsSupported('NAMESPACE')) - { - return null; - } - - $oReturn = false; - - $this->SendRequest('NAMESPACE'); - $aResult = $this->parseResponseWithValidation(); - - $oImapResponse = null; - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && - 'NAMESPACE' === $oImapResponse->StatusOrIndex) - { - $oReturn = NamespaceResult::NewInstance(); - $oReturn->InitByImapResponse($oImapResponse); - break; - } - } - - if (false === $oReturn) - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\ResponseException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - return $oReturn; - } - - /** - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function Noop() - { - return $this->SendRequestWithCheck('NOOP'); - } - - /** - * @param string $sFolderName - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderCreate($sFolderName) - { - return $this->SendRequestWithCheck('CREATE', - array($this->EscapeString($sFolderName))); - } - - /** - * @param string $sFolderName - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderDelete($sFolderName) - { - return $this->SendRequestWithCheck('DELETE', - array($this->EscapeString($sFolderName))); - } - - /** - * @param string $sFolderName - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderSubscribe($sFolderName) - { - return $this->SendRequestWithCheck('SUBSCRIBE', - array($this->EscapeString($sFolderName))); - } - - /** - * @param string $sFolderName - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderUnSubscribe($sFolderName) - { - return $this->SendRequestWithCheck('UNSUBSCRIBE', - array($this->EscapeString($sFolderName))); - } - - /** - * @param string $sOldFolderName - * @param string $sNewFolderName - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderRename($sOldFolderName, $sNewFolderName) - { - return $this->SendRequestWithCheck('RENAME', array( - $this->EscapeString($sOldFolderName), - $this->EscapeString($sNewFolderName))); - } - - /** - * @param array $aResult - * - * @return array - */ - protected function getStatusFolderInformation($aResult) - { - $aReturn = array(); - - if (\is_array($aResult)) - { - $oImapResponse = null; - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && - 'STATUS' === $oImapResponse->StatusOrIndex && isset($oImapResponse->ResponseList[3]) && - \is_array($oImapResponse->ResponseList[3])) - { - $sName = null; - foreach ($oImapResponse->ResponseList[3] as $sArrayItem) - { - if (null === $sName) - { - $sName = $sArrayItem; - } - else - { - $aReturn[$sName] = $sArrayItem; - $sName = null; - } - } - } - } - } - - return $aReturn; - } - - /** - * @param string $sFolderName - * @param array $aStatusItems - * - * @return array|bool - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderStatus($sFolderName, array $aStatusItems) - { - $aResult = false; - if (\count($aStatusItems) > 0) - { - $this->SendRequest('STATUS', - array($this->EscapeString($sFolderName), $aStatusItems)); - - $aResult = $this->getStatusFolderInformation( - $this->parseResponseWithValidation()); - } - - return $aResult; - } - - /** - * @param array $aResult - * @param string $sStatus - * @param bool $bUseListStatus = false - * - * @return array - */ - private function getFoldersFromResult(array $aResult, $sStatus, $bUseListStatus = false) - { - $aReturn = array(); - - $sDelimiter = ''; - $bInbox = false; - - $oImapResponse = null; - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && - $sStatus === $oImapResponse->StatusOrIndex && 5 === count($oImapResponse->ResponseList)) - { - try - { - $oFolder = Folder::NewInstance($oImapResponse->ResponseList[4], - $oImapResponse->ResponseList[3], $oImapResponse->ResponseList[2]); - - if ($oFolder->IsInbox()) - { - $bInbox = true; - } - - if (empty($sDelimiter)) - { - $sDelimiter = $oFolder->Delimiter(); - } - - $aReturn[] = $oFolder; - } - catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) - { - $this->writeLogException($oException, \MailSo\Log\Enumerations\Type::WARNING, false); - } - } - } - - if (!$bInbox && !empty($sDelimiter)) - { - $aReturn[] = Folder::NewInstance('INBOX', $sDelimiter); - } - - if ($bUseListStatus) - { - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && - 'STATUS' === $oImapResponse->StatusOrIndex && - isset($oImapResponse->ResponseList[2]) && - isset($oImapResponse->ResponseList[3]) && - \is_array($oImapResponse->ResponseList[3])) - { - $sFolderNameRaw = $oImapResponse->ResponseList[2]; - - $oCurrentFolder = null; - foreach ($aReturn as &$oFolder) - { - if ($oFolder && $sFolderNameRaw === $oFolder->FullNameRaw()) - { - $oCurrentFolder =& $oFolder; - break; - } - } - - if (null !== $oCurrentFolder) - { - $sName = null; - $aStatus = array(); - - foreach ($oImapResponse->ResponseList[3] as $sArrayItem) - { - if (null === $sName) - { - $sName = $sArrayItem; - } - else - { - $aStatus[$sName] = $sArrayItem; - $sName = null; - } - } - - if (0 < count($aStatus)) - { - $oCurrentFolder->SetExtended('STATUS', $aStatus); - } - } - - unset($oCurrentFolder); - } - } - } - - return $aReturn; - } - - /** - * @param bool $bIsSubscribeList - * @param string $sParentFolderName = '' - * @param string $sListPattern = '*' - * @param bool $bUseListStatus = false - * - * @return array - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - private function specificFolderList($bIsSubscribeList, $sParentFolderName = '', $sListPattern = '*', $bUseListStatus = false) - { - $sCmd = 'LSUB'; - if (!$bIsSubscribeList) - { - $sCmd = 'LIST'; - } - - $sListPattern = 0 === strlen(trim($sListPattern)) ? '*' : $sListPattern; - - $aParameters = array( - $this->EscapeString($sParentFolderName), - $this->EscapeString($sListPattern) - ); - - if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS')) - { - $aL = array( - \MailSo\Imap\Enumerations\FolderStatus::MESSAGES, - \MailSo\Imap\Enumerations\FolderStatus::UNSEEN, - \MailSo\Imap\Enumerations\FolderStatus::UIDNEXT - ); - -// if ($this->IsSupported('CONDSTORE')) -// { -// $aL[] = \MailSo\Imap\Enumerations\FolderStatus::HIGHESTMODSEQ; -// } - - $aParameters[] = 'RETURN'; - $aParameters[] = array('STATUS', $aL); - } - else - { - $bUseListStatus = false; - } - - $this->SendRequest($sCmd, $aParameters); - - return $this->getFoldersFromResult( - $this->parseResponseWithValidation(), $sCmd, $bUseListStatus); - } - - /** - * @param string $sParentFolderName = '' - * @param string $sListPattern = '*' - * - * @return array - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderList($sParentFolderName = '', $sListPattern = '*') - { - return $this->specificFolderList(false, $sParentFolderName, $sListPattern); - } - - /** - * @param string $sParentFolderName = '' - * @param string $sListPattern = '*' - * - * @return array - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderSubscribeList($sParentFolderName = '', $sListPattern = '*') - { - return $this->specificFolderList(true, $sParentFolderName, $sListPattern); - } - - /** - * @param string $sParentFolderName = '' - * @param string $sListPattern = '*' - * - * @return array - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderStatusList($sParentFolderName = '', $sListPattern = '*') - { - return $this->specificFolderList(false, $sParentFolderName, $sListPattern, true); - } - - /** - * @param array $aResult - * @param string $sFolderName - * @param bool $bIsWritable - * - * @return void - */ - protected function initCurrentFolderInformation($aResult, $sFolderName, $bIsWritable) - { - if (\is_array($aResult)) - { - $oImapResponse = null; - $oResult = FolderInformation::NewInstance($sFolderName, $bIsWritable); - - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType) - { - if (\count($oImapResponse->ResponseList) > 2 && - 'FLAGS' === $oImapResponse->ResponseList[1] && \is_array($oImapResponse->ResponseList[2])) - { - $oResult->Flags = $oImapResponse->ResponseList[2]; - } - - if (is_array($oImapResponse->OptionalResponse) && \count($oImapResponse->OptionalResponse) > 1) - { - if ('PERMANENTFLAGS' === $oImapResponse->OptionalResponse[0] && - is_array($oImapResponse->OptionalResponse[1])) - { - $oResult->PermanentFlags = $oImapResponse->OptionalResponse[1]; - } - else if ('UIDVALIDITY' === $oImapResponse->OptionalResponse[0] && - isset($oImapResponse->OptionalResponse[1])) - { - $oResult->Uidvalidity = $oImapResponse->OptionalResponse[1]; - } - else if ('UNSEEN' === $oImapResponse->OptionalResponse[0] && - isset($oImapResponse->OptionalResponse[1]) && - is_numeric($oImapResponse->OptionalResponse[1])) - { - $oResult->Unread = (int) $oImapResponse->OptionalResponse[1]; - } - else if ('UIDNEXT' === $oImapResponse->OptionalResponse[0] && - isset($oImapResponse->OptionalResponse[1])) - { - $oResult->Uidnext = $oImapResponse->OptionalResponse[1]; - } - else if ('HIGHESTMODSEQ' === $oImapResponse->OptionalResponse[0] && - isset($oImapResponse->OptionalResponse[1]) && - \is_numeric($oImapResponse->OptionalResponse[1])) - { - $oResult->HighestModSeq = \trim($oImapResponse->OptionalResponse[1]); - } - } - - if (\count($oImapResponse->ResponseList) > 2 && - \is_string($oImapResponse->ResponseList[2]) && - \is_numeric($oImapResponse->ResponseList[1])) - { - switch($oImapResponse->ResponseList[2]) - { - case 'EXISTS': - $oResult->Exists = (int) $oImapResponse->ResponseList[1]; - break; - case 'RECENT': - $oResult->Recent = (int) $oImapResponse->ResponseList[1]; - break; - } - } - } - } - - $this->oCurrentFolderInfo = $oResult; - } - } - - /** - * @param string $sFolderName - * @param bool $bIsWritable - * @param bool $bReSelectSameFolders - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - protected function selectOrExamineFolder($sFolderName, $bIsWritable, $bReSelectSameFolders) - { - if (!$bReSelectSameFolders) - { - if ($this->oCurrentFolderInfo && - $sFolderName === $this->oCurrentFolderInfo->FolderName && - $bIsWritable === $this->oCurrentFolderInfo->IsWritable) - { - return $this; - } - } - - if (!\MailSo\Base\Validator::NotEmptyString($sFolderName, true)) - { - throw new \MailSo\Base\Exceptions\InvalidArgumentException(); - } - - $this->SendRequest(($bIsWritable) ? 'SELECT' : 'EXAMINE', - array($this->EscapeString($sFolderName))); - - $this->initCurrentFolderInformation( - $this->parseResponseWithValidation(), $sFolderName, $bIsWritable); - - $this->bIsSelected = true; - - return $this; - } - - /** - * @param string $sFolderName - * @param bool $bReSelectSameFolders = false - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderSelect($sFolderName, $bReSelectSameFolders = false) - { - return $this->selectOrExamineFolder($sFolderName, true, $bReSelectSameFolders); - } - - /** - * @param string $sFolderName - * @param bool $bReSelectSameFolders = false - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderExamine($sFolderName, $bReSelectSameFolders = false) - { - return $this->selectOrExamineFolder($sFolderName, $this->__FORCE_SELECT_ON_EXAMINE__, $bReSelectSameFolders); - } - - /** - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function FolderUnSelect() - { - if ($this->IsSelected() && $this->IsSupported('UNSELECT')) - { - $this->SendRequestWithCheck('UNSELECT'); - $this->bIsSelected = false; - } - - return $this; - } - - /** - * @param array $aInputFetchItems - * @param string $sIndexRange - * @param bool $bIndexIsUid - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function Fetch(array $aInputFetchItems, $sIndexRange, $bIndexIsUid) - { - $sIndexRange = (string) $sIndexRange; - if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $aFetchItems = \MailSo\Imap\Enumerations\FetchType::ChangeFetchItemsBefourRequest($aInputFetchItems); - foreach ($aFetchItems as $sName => $mItem) - { - if (0 < \strlen($sName) && '' !== $mItem) - { - if (null === $this->aFetchCallbacks) - { - $this->aFetchCallbacks = array(); - } - - $this->aFetchCallbacks[$sName] = $mItem; - } - } - - $this->SendRequest((($bIndexIsUid) ? 'UID ' : '').'FETCH', array($sIndexRange, \array_keys($aFetchItems))); - $aResult = $this->validateResponse($this->parseResponse()); - $this->aFetchCallbacks = null; - - $aReturn = array(); - $oImapResponse = null; - foreach ($aResult as $oImapResponse) - { - if (FetchResponse::IsValidFetchImapResponse($oImapResponse)) - { - if (FetchResponse::IsNotEmptyFetchImapResponse($oImapResponse)) - { - $aReturn[] = FetchResponse::NewInstance($oImapResponse); - } - else - { - if ($this->oLogger) - { - $this->oLogger->Write('Skipped Imap Response! ['.$oImapResponse->ToLine().']', \MailSo\Log\Enumerations\Type::NOTICE); - } - } - } - } - - return $aReturn; - } - - - /** - * @return array|false - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function Quota() - { - $aReturn = false; - if ($this->IsSupported('QUOTA')) - { - $this->SendRequest('GETQUOTAROOT "INBOX"'); - $aResult = $this->parseResponseWithValidation(); - - $aReturn = array(0, 0); - $oImapResponse = null; - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && 'QUOTA' === $oImapResponse->StatusOrIndex - && \is_array($oImapResponse->ResponseList) - && isset($oImapResponse->ResponseList[3]) - && \is_array($oImapResponse->ResponseList[3]) - && 2 < \count($oImapResponse->ResponseList[3]) - && 'STORAGE' === \strtoupper($oImapResponse->ResponseList[3][0]) - && \is_numeric($oImapResponse->ResponseList[3][1]) - && \is_numeric($oImapResponse->ResponseList[3][2]) - ) - { - $aReturn = array( - (int) $oImapResponse->ResponseList[3][1], - (int) $oImapResponse->ResponseList[3][2], - 0, - 0 - ); - - if (5 < \count($oImapResponse->ResponseList[3]) - && 'MESSAGE' === \strtoupper($oImapResponse->ResponseList[3][3]) - && \is_numeric($oImapResponse->ResponseList[3][4]) - && \is_numeric($oImapResponse->ResponseList[3][5]) - ) - { - $aReturn[2] = (int) $oImapResponse->ResponseList[3][4]; - $aReturn[3] = (int) $oImapResponse->ResponseList[3][5]; - } - } - } - } - - return $aReturn; - } - - /** - * @param array $aSortTypes - * @param string $sSearchCriterias - * @param bool $bReturnUid - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageSimpleSort($aSortTypes, $sSearchCriterias = 'ALL', $bReturnUid = true) - { - $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; - $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias - ? 'ALL' : $sSearchCriterias; - - if (!\is_array($aSortTypes) || 0 === \count($aSortTypes)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - else if (!$this->IsSupported('SORT')) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $aRequest = array(); - $aRequest[] = $aSortTypes; - $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; - $aRequest[] = $sSearchCriterias; - - $sCmd = 'SORT'; - - $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); - $aResult = $this->parseResponseWithValidation(); - - $aReturn = array(); - $oImapResponse = null; - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && ($sCmd === $oImapResponse->StatusOrIndex || - ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && - $sCmd === $oImapResponse->ResponseList[2]) - && \is_array($oImapResponse->ResponseList) - && 2 < \count($oImapResponse->ResponseList)) - { - $iStart = 2; - if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && - !empty($oImapResponse->ResponseList[2]) && - $sCmd === $oImapResponse->ResponseList[2]) - { - $iStart = 3; - } - - for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) - { - $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; - } - } - } - - return $aReturn; - } - - /** - * @param bool $bSort = false - * @param string $sSearchCriterias = 'ALL' - * @param array $aSearchOrSortReturn = null - * @param bool $bReturnUid = true - * @param string $sLimit = '' - * @param string $sCharset = '' - * @param array $aSortTypes = null - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - private function simpleESearchOrESortHelper($bSort = false, $sSearchCriterias = 'ALL', $aSearchOrSortReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '', $aSortTypes = null) - { - $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; - $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias - ? 'ALL' : $sSearchCriterias; - - $sCmd = $bSort ? 'SORT': 'SEARCH'; - if ($bSort && (!\is_array($aSortTypes) || 0 === \count($aSortTypes) || !$this->IsSupported('SORT'))) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - if (!$this->IsSupported($bSort ? 'ESORT' : 'ESEARCH')) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - if (!\is_array($aSearchOrSortReturn) || 0 === \count($aSearchOrSortReturn)) - { - $aSearchOrSortReturn = array('ALL'); - } - - $aRequest = array(); - if ($bSort) - { - $aRequest[] = 'RETURN'; - $aRequest[] = $aSearchOrSortReturn; - - $aRequest[] = $aSortTypes; - $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; - } - else - { - if (0 < \strlen($sCharset)) - { - $aRequest[] = 'CHARSET'; - $aRequest[] = \strtoupper($sCharset); - } - - $aRequest[] = 'RETURN'; - $aRequest[] = $aSearchOrSortReturn; - } - - $aRequest[] = $sSearchCriterias; - - if (0 < \strlen($sLimit)) - { - $aRequest[] = $sLimit; - } - - $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); - $sRequestTag = $this->getCurrentTag(); - - $aResult = array(); - $aResponse = $this->parseResponseWithValidation(); - - if (\is_array($aResponse)) - { - $oImapResponse = null; - foreach ($aResponse as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && ('ESEARCH' === $oImapResponse->StatusOrIndex || 'ESORT' === $oImapResponse->StatusOrIndex) - && \is_array($oImapResponse->ResponseList) - && isset($oImapResponse->ResponseList[2], $oImapResponse->ResponseList[2][0], $oImapResponse->ResponseList[2][1]) - && 'TAG' === $oImapResponse->ResponseList[2][0] && $sRequestTag === $oImapResponse->ResponseList[2][1] - && (!$bReturnUid || ($bReturnUid && !empty($oImapResponse->ResponseList[3]) && 'UID' === $oImapResponse->ResponseList[3])) - ) - { - $iStart = 3; - foreach ($oImapResponse->ResponseList as $iIndex => $mItem) - { - if ($iIndex >= $iStart) - { - switch ($mItem) - { - case 'ALL': - case 'MAX': - case 'MIN': - case 'COUNT': - if (isset($oImapResponse->ResponseList[$iIndex + 1])) - { - $aResult[$mItem] = $oImapResponse->ResponseList[$iIndex + 1]; - } - break; - } - } - } - } - } - } - - return $aResult; - } - - /** - * @param string $sSearchCriterias = 'ALL' - * @param array $aSearchReturn = null - * @param bool $bReturnUid = true - * @param string $sLimit = '' - * @param string $sCharset = '' - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageSimpleESearch($sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '') - { - return $this->simpleESearchOrESortHelper(false, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, $sCharset); - } - - /** - * @param array $aSortTypes - * @param string $sSearchCriterias = 'ALL' - * @param array $aSearchReturn = null - * @param bool $bReturnUid = true - * @param string $sLimit = '' - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageSimpleESort($aSortTypes, $sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '') - { - return $this->simpleESearchOrESortHelper(true, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, '', $aSortTypes); - } - - /** - * @param array $aResult - * @return \MailSo\Imap\Response - */ - private function findLastResponse($aResult) - { - $oResult = null; - if (\is_array($aResult) && 0 < \count($aResult)) - { - $oResult = $aResult[\count($aResult) - 1]; - if (!($oResult instanceof \MailSo\Imap\Response)) - { - $oResult = null; - } - } - - return $oResult; - } - - /** - * @param string $sSearchCriterias - * @param bool $bReturnUid = true - * @param string $sCharset = '' - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageSimpleSearch($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = '') - { - $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; - $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias - ? 'ALL' : $sSearchCriterias; - - $aRequest = array(); - if (0 < \strlen($sCharset)) - { - $aRequest[] = 'CHARSET'; - $aRequest[] = \strtoupper($sCharset); - } - - $aRequest[] = $sSearchCriterias; - - $sCmd = 'SEARCH'; - - $sCont = $this->SendRequest($sCommandPrefix.$sCmd, $aRequest, true); - if ('' !== $sCont) - { - $aResult = $this->parseResponseWithValidation(); - $oItem = $this->findLastResponse($aResult); - - if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) - { - $aParts = explode("\r\n", $sCont); - foreach ($aParts as $sLine) - { - $this->sendRaw($sLine); - - $aResult = $this->parseResponseWithValidation(); - $oItem = $this->findLastResponse($aResult); - if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) - { - continue; - } - } - } - } - else - { - $aResult = $this->parseResponseWithValidation(); - } - - $aReturn = array(); - $oImapResponse = null; - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && ($sCmd === $oImapResponse->StatusOrIndex || - ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && - $sCmd === $oImapResponse->ResponseList[2]) - && \is_array($oImapResponse->ResponseList) - && 2 < count($oImapResponse->ResponseList)) - { - $iStart = 2; - if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && - !empty($oImapResponse->ResponseList[2]) && - $sCmd === $oImapResponse->ResponseList[2]) - { - $iStart = 3; - } - - for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) - { - $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; - } - } - } - - $aReturn = \array_reverse($aReturn); - return $aReturn; - } - - /** - * @param mixed $aValue - * - * @return mixed - */ - private function validateThreadItem($aValue) - { - $mResult = false; - if (\is_numeric($aValue)) - { - $mResult = (int) $aValue; - if (0 >= $mResult) - { - $mResult = false; - } - } - else if (\is_array($aValue)) - { - if (1 === \count($aValue) && \is_numeric($aValue[0])) - { - $mResult = (int) $aValue[0]; - if (0 >= $mResult) - { - $mResult = false; - } - } - else - { - $mResult = array(); - foreach ($aValue as $aValueItem) - { - $mTemp = $this->validateThreadItem($aValueItem); - if (false !== $mTemp) - { - $mResult[] = $mTemp; - } - } - } - } - - return $mResult; - } - - /** - * @param string $sSearchCriterias = 'ALL' - * @param bool $bReturnUid = true - * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 - * - * @return array - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageSimpleThread($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) - { - $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; - $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias - ? 'ALL' : $sSearchCriterias; - - $sThreadType = ''; - switch (true) - { - case $this->IsSupported('THREAD=REFS'): - $sThreadType = 'REFS'; - break; - case $this->IsSupported('THREAD=REFERENCES'): - $sThreadType = 'REFERENCES'; - break; - case $this->IsSupported('THREAD=ORDEREDSUBJECT'): - $sThreadType = 'ORDEREDSUBJECT'; - break; - default: - $this->writeLogException( - new Exceptions\RuntimeException('Thread is not supported'), - \MailSo\Log\Enumerations\Type::ERROR, true); - break; - } - - $aRequest = array(); - $aRequest[] = $sThreadType; - $aRequest[] = \strtoupper($sCharset); - $aRequest[] = $sSearchCriterias; - - $sCmd = 'THREAD'; - - $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); - $aResult = $this->parseResponseWithValidation(); - - $aReturn = array(); - $oImapResponse = null; - - foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && ($sCmd === $oImapResponse->StatusOrIndex || - ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && - $sCmd === $oImapResponse->ResponseList[2]) - && \is_array($oImapResponse->ResponseList) - && 2 < \count($oImapResponse->ResponseList)) - { - $iStart = 2; - if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && - !empty($oImapResponse->ResponseList[2]) && - $sCmd === $oImapResponse->ResponseList[2]) - { - $iStart = 3; - } - - for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) - { - $aNewValue = $this->validateThreadItem($oImapResponse->ResponseList[$iIndex]); - if (false !== $aNewValue) - { - $aReturn[] = $aNewValue; - } - } - } - } - - return $aReturn; - } - - /** - * @param string $sToFolder - * @param string $sIndexRange - * @param bool $bIndexIsUid - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageCopy($sToFolder, $sIndexRange, $bIndexIsUid) - { - if (0 === \strlen($sIndexRange)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; - return $this->SendRequestWithCheck($sCommandPrefix.'COPY', - array($sIndexRange, $this->EscapeString($sToFolder))); - } - - /** - * @param string $sToFolder - * @param string $sIndexRange - * @param bool $bIndexIsUid - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageMove($sToFolder, $sIndexRange, $bIndexIsUid) - { - if (0 === \strlen($sIndexRange)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - if (!$this->IsSupported('MOVE')) - { - $this->writeLogException( - new Exceptions\RuntimeException('Move is not supported'), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; - return $this->SendRequestWithCheck($sCommandPrefix.'MOVE', - array($sIndexRange, $this->EscapeString($sToFolder))); - } - - /** - * @param string $sUidRangeIfSupported = '' - * @param bool $bForceUidExpunge = false - * @param bool $bExpungeAll = false - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageExpunge($sUidRangeIfSupported = '', $bForceUidExpunge = false, $bExpungeAll = false) - { - $sUidRangeIfSupported = \trim($sUidRangeIfSupported); - - $sCmd = 'EXPUNGE'; - $aArguments = array(); - - if (!$bExpungeAll && $bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS')) - { - $sCmd = 'UID '.$sCmd; - $aArguments = array($sUidRangeIfSupported); - } - - return $this->SendRequestWithCheck($sCmd, $aArguments); - } - - /** - * @param string $sIndexRange - * @param bool $bIndexIsUid - * @param array $aInputStoreItems - * @param string $sStoreAction - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageStoreFlag($sIndexRange, $bIndexIsUid, $aInputStoreItems, $sStoreAction) - { - if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true) || - !\MailSo\Base\Validator::NotEmptyString($sStoreAction, true) || - 0 === \count($aInputStoreItems)) - { - return false; - } - - $sCmd = ($bIndexIsUid) ? 'UID STORE' : 'STORE'; - return $this->SendRequestWithCheck($sCmd, array($sIndexRange, $sStoreAction, $aInputStoreItems)); - } - - /** - * @param string $sFolderName - * @param resource $rMessageAppendStream - * @param int $iStreamSize - * @param array $aAppendFlags = null - * @param int $iUid = null - * @param int $sDateTime = 0 - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function MessageAppendStream($sFolderName, $rMessageAppendStream, $iStreamSize, $aAppendFlags = null, &$iUid = null, $sDateTime = 0) - { - $aData = array($this->EscapeString($sFolderName), $aAppendFlags); - if (0 < $sDateTime) - { - $aData[] = $this->EscapeString(\gmdate('d-M-Y H:i:s', $sDateTime).' +0000'); - } - - $aData[] = '{'.$iStreamSize.'}'; - - $this->SendRequest('APPEND', $aData); - $this->parseResponseWithValidation(); - - $this->writeLog('Write to connection stream', \MailSo\Log\Enumerations\Type::NOTE); - - \MailSo\Base\Utils::MultipleStreamWriter($rMessageAppendStream, array($this->rConnect)); - - $this->sendRaw(''); - $this->parseResponseWithValidation(); - - if (null !== $iUid) - { - $aLastResponse = $this->GetLastResponse(); - if (\is_array($aLastResponse) && 0 < \count($aLastResponse) && $aLastResponse[\count($aLastResponse) - 1]) - { - $oLast = $aLastResponse[count($aLastResponse) - 1]; - if ($oLast && \MailSo\Imap\Enumerations\ResponseType::TAGGED === $oLast->ResponseType && \is_array($oLast->OptionalResponse)) - { - if (0 < \strlen($oLast->OptionalResponse[0]) && - 0 < \strlen($oLast->OptionalResponse[2]) && - 'APPENDUID' === strtoupper($oLast->OptionalResponse[0]) && - \is_numeric($oLast->OptionalResponse[2]) - ) - { - $iUid = (int) $oLast->OptionalResponse[2]; - } - } - } - } - - return $this; - } - - /** - * @return \MailSo\Imap\FolderInformation - */ - public function FolderCurrentInformation() - { - return $this->oCurrentFolderInfo; - } - - /** - * @param string $sCommand - * @param array $aParams = array() - * @param bool $bBreakOnLiteral = false - * - * @return string - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - */ - public function SendRequest($sCommand, $aParams = array(), $bBreakOnLiteral = false) - { - if (!\MailSo\Base\Validator::NotEmptyString($sCommand, true) || !\is_array($aParams)) - { - $this->writeLogException( - new \MailSo\Base\Exceptions\InvalidArgumentException(), - \MailSo\Log\Enumerations\Type::ERROR, true); - } - - $this->IsConnected(true); - - $sTag = $this->getNewTag(); - - $sCommand = \trim($sCommand); - $sRealCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aParams); - - $sFakeCommand = ''; - $aFakeParams = $this->secureRequestParams($sCommand, $aParams); - if (null !== $aFakeParams) - { - $sFakeCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aFakeParams); - } - - $this->aTagTimeouts[$sTag] = \microtime(true); - - if ($bBreakOnLiteral && !\preg_match('/\d\+\}\r\n/', $sRealCommand)) - { - $iPos = \strpos($sRealCommand, "}\r\n"); - if (false !== $iPos) - { - $iFakePos = \strpos($sFakeCommand, "}\r\n"); - - $this->sendRaw(\substr($sRealCommand, 0, $iPos + 1), true, - false !== $iFakePos ? \substr($sFakeCommand, 0, $iFakePos + 3) : ''); - - return \substr($sRealCommand, $iPos + 3); - } - } - - $this->sendRaw($sRealCommand, true, $sFakeCommand); - return ''; - } - - /** - * @param string $sCommand - * @param array $aParams - * - * @return array|null - */ - private function secureRequestParams($sCommand, $aParams) - { - $aResult = null; - switch ($sCommand) - { - case 'LOGIN': - $aResult = $aParams; - if (\is_array($aResult) && 2 === count($aResult)) - { - $aResult[1] = '"********"'; - } - break; - } - - return $aResult; - } - - /** - * @param string $sCommand - * @param array $aParams = array() - * @param bool $bFindCapa = false - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - * @throws \MailSo\Net\Exceptions\Exception - * @throws \MailSo\Imap\Exceptions\Exception - */ - public function SendRequestWithCheck($sCommand, $aParams = array(), $bFindCapa = false) - { - $this->SendRequest($sCommand, $aParams); - $this->parseResponseWithValidation(null, $bFindCapa); - - return $this; - } - - /** - * @return array - */ - public function GetLastResponse() - { - return $this->aLastResponse; - } - - /** - * @param mixed $aResult - * - * @return array - * - * @throws \MailSo\Imap\Exceptions\ResponseNotFoundException - * @throws \MailSo\Imap\Exceptions\InvalidResponseException - * @throws \MailSo\Imap\Exceptions\NegativeResponseException - */ - private function validateResponse($aResult) - { - if (!\is_array($aResult) || 0 === $iCnt = \count($aResult)) - { - $this->writeLogException( - new Exceptions\ResponseNotFoundException(), - \MailSo\Log\Enumerations\Type::WARNING, true); - } - - if ($aResult[$iCnt - 1]->ResponseType !== \MailSo\Imap\Enumerations\ResponseType::CONTINUATION) - { - if (!$aResult[$iCnt - 1]->IsStatusResponse) - { - $this->writeLogException( - new Exceptions\InvalidResponseException($aResult), - \MailSo\Log\Enumerations\Type::WARNING, true); - } - - if (\MailSo\Imap\Enumerations\ResponseStatus::OK !== $aResult[$iCnt - 1]->StatusOrIndex) - { - $this->writeLogException( - new Exceptions\NegativeResponseException($aResult), - \MailSo\Log\Enumerations\Type::WARNING, true); - } - } - - return $aResult; - } - - /** - * @param string $sEndTag = null - * @param bool $bFindCapa = false - * - * @return array|bool - */ - protected function parseResponse($sEndTag = null, $bFindCapa = false) - { - if (\is_resource($this->rConnect)) - { - $oImapResponse = null; - $sEndTag = (null === $sEndTag) ? $this->getCurrentTag() : $sEndTag; - - while (true) - { - $oImapResponse = Response::NewInstance(); - - $this->partialParseResponseBranch($oImapResponse); - - if ($oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNKNOWN === $oImapResponse->ResponseType) - { - return false; - } - - if ($bFindCapa) - { - $this->initCapabilityImapResponse($oImapResponse); - } - - $this->aPartialResponses[] = $oImapResponse; - if ($sEndTag === $oImapResponse->Tag || \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) - { - if (isset($this->aTagTimeouts[$sEndTag])) - { - $this->writeLog((\microtime(true) - $this->aTagTimeouts[$sEndTag]).' ('.$sEndTag.')', - \MailSo\Log\Enumerations\Type::TIME); - - unset($this->aTagTimeouts[$sEndTag]); - } - - break; - } - } - else - { - return false; - } - - unset($oImapResponse); - } - } - - $this->iResponseBufParsedPos = 0; - $this->aLastResponse = $this->aPartialResponses; - $this->aPartialResponses = array(); - - return $this->aLastResponse; - } - - /** - * @param string $sEndTag = null - * @param bool $bFindCapa = false - * - * @return array - */ - private function parseResponseWithValidation($sEndTag = null, $bFindCapa = false) - { - return $this->validateResponse($this->parseResponse($sEndTag, $bFindCapa)); - } - - /** - * @param \MailSo\Imap\Response $oImapResponse - * - * @return void - */ - private function initCapabilityImapResponse($oImapResponse) - { - if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && \is_array($oImapResponse->ResponseList)) - { - $aList = null; - if (isset($oImapResponse->ResponseList[1]) && \is_string($oImapResponse->ResponseList[1]) && - 'CAPABILITY' === \strtoupper($oImapResponse->ResponseList[1])) - { - $aList = \array_slice($oImapResponse->ResponseList, 2); - } - else if ($oImapResponse->OptionalResponse && \is_array($oImapResponse->OptionalResponse) && - 1 < \count($oImapResponse->OptionalResponse) && \is_string($oImapResponse->OptionalResponse[0]) && - 'CAPABILITY' === \strtoupper($oImapResponse->OptionalResponse[0])) - { - $aList = \array_slice($oImapResponse->OptionalResponse, 1); - } - - if (\is_array($aList) && 0 < \count($aList)) - { - $this->aCapabilityItems = \array_map('strtoupper', $aList); - } - } - } - - /** - * @return array|string - * - * @throws \MailSo\Net\Exceptions\Exception - */ - private function partialParseResponseBranch(&$oImapResponse, $iStackIndex = -1, - $bTreatAsAtom = false, $sParentToken = '', $sOpenBracket = '') - { - $mNull = null; - - $iStackIndex++; - $iPos = $this->iResponseBufParsedPos; - - $sPreviousAtomUpperCase = null; - $bIsEndOfList = false; - $bIsClosingBracketSquare = false; - $iLiteralLen = 0; - $iBufferEndIndex = 0; - $iDebugCount = 0; - - $rImapLiteralStream = null; - - $bIsGotoDefault = false; - $bIsGotoLiteral = false; - $bIsGotoLiteralEnd = false; - $bIsGotoAtomBracket = false; - $bIsGotoNotAtomBracket = false; - - $bCountOneInited = false; - $bCountTwoInited = false; - - $sAtomBuilder = $bTreatAsAtom ? '' : null; - $aList = array(); - if (null !== $oImapResponse) - { - $aList =& $oImapResponse->ResponseList; - } - - while (!$bIsEndOfList) - { - $iDebugCount++; - if (100000 === $iDebugCount) - { - $this->Logger()->Write('PartialParseOver: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); - } - - if ($this->bNeedNext) - { - $iPos = 0; - $this->getNextBuffer(); - $this->iResponseBufParsedPos = $iPos; - $this->bNeedNext = false; - } - - $sChar = null; - if ($bIsGotoDefault) - { - $sChar = 'GOTO_DEFAULT'; - $bIsGotoDefault = false; - } - else if ($bIsGotoLiteral) - { - $bIsGotoLiteral = false; - $bIsGotoLiteralEnd = true; - - if ($this->partialResponseLiteralCallbackCallable( - $sParentToken, null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $this->rConnect, $iLiteralLen)) - { - if (!$bTreatAsAtom) - { - $aList[] = ''; - } - } - else - { - $sLiteral = ''; - $iRead = $iLiteralLen; - - while (0 < $iRead) - { - $sAddRead = \fread($this->rConnect, $iRead); - if (false === $sAddRead) - { - $sLiteral = false; - break; - } - - $sLiteral .= $sAddRead; - $iRead -= \strlen($sAddRead); - - \MailSo\Base\Utils::ResetTimeLimit(); - } - - if (false !== $sLiteral) - { - $iLiteralSize = \strlen($sLiteral); - \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralSize); - if ($iLiteralLen !== $iLiteralSize) - { - $this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '. - $iLiteralLen.'" bytes', \MailSo\Log\Enumerations\Type::WARNING); - } - - if (!$bTreatAsAtom) - { - $aList[] = $sLiteral; - - if (\MailSo\Config::$LogSimpleLiterals) - { - $this->writeLog('{'.\strlen($sLiteral).'} '.$sLiteral, \MailSo\Log\Enumerations\Type::INFO); - } - } - } - else - { - $this->writeLog('Can\'t read imap stream', \MailSo\Log\Enumerations\Type::NOTE); - } - - unset($sLiteral); - } - - continue; - } - else if ($bIsGotoLiteralEnd) - { - $rImapLiteralStream = null; - $sPreviousAtomUpperCase = null; - $this->bNeedNext = true; - $bIsGotoLiteralEnd = false; - - continue; - } - else if ($bIsGotoAtomBracket) - { - if ($bTreatAsAtom) - { - $sAtomBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, - null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); - - $sAtomBuilder .= $sAtomBlock; - $iPos = $this->iResponseBufParsedPos; - $sAtomBuilder .= ($bIsClosingBracketSquare) ? ']' : ')'; - } - - $sPreviousAtomUpperCase = null; - $bIsGotoAtomBracket = false; - - continue; - } - else if ($bIsGotoNotAtomBracket) - { - $aSubItems = $this->partialParseResponseBranch($mNull, $iStackIndex, false, - null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); - - $aList[] = $aSubItems; - $iPos = $this->iResponseBufParsedPos; - $sPreviousAtomUpperCase = null; - if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) - { - $oImapResponse->OptionalResponse = $aSubItems; - - $bIsGotoDefault = true; - $bIsGotoNotAtomBracket = false; - continue; - } - $bIsGotoNotAtomBracket = false; - - continue; - } - else - { - $iBufferEndIndex = \strlen($this->sResponseBuffer) - 3; - $this->bResponseBufferChanged = false; - - if ($iPos > $iBufferEndIndex) - { - break; - } - - $sChar = $this->sResponseBuffer[$iPos]; - } - - switch (true) - { - case ']' === $sChar: - $iPos++; - $sPreviousAtomUpperCase = null; - $bIsEndOfList = true; - break; - case ')' === $sChar: - $iPos++; - $sPreviousAtomUpperCase = null; - $bIsEndOfList = true; - break; - case ' ' === $sChar: - if ($bTreatAsAtom) - { - $sAtomBuilder .= ' '; - } - $iPos++; - break; - case '[' === $sChar: - $bIsClosingBracketSquare = true; - case '(' === $sChar: - if ('(' === $sChar) - { - $bIsClosingBracketSquare = false; - } - - if ($bTreatAsAtom) - { - $sAtomBuilder .= $bIsClosingBracketSquare ? '[' : '('; - } - $iPos++; - - $this->iResponseBufParsedPos = $iPos; - if ($bTreatAsAtom) - { - $bIsGotoAtomBracket = true; - $sOpenBracket = $bIsClosingBracketSquare ? '[' : '('; - } - else - { - $bIsGotoNotAtomBracket = true; - $sOpenBracket = $bIsClosingBracketSquare ? '[' : '('; - } - break; - case '{' === $sChar: - $bIsLiteralParsed = false; - $mLiteralEndPos = \strpos($this->sResponseBuffer, '}', $iPos); - if (false !== $mLiteralEndPos && $mLiteralEndPos > $iPos) - { - $sLiteralLenAsString = \substr($this->sResponseBuffer, $iPos + 1, $mLiteralEndPos - $iPos - 1); - if (\is_numeric($sLiteralLenAsString)) - { - $iLiteralLen = (int) $sLiteralLenAsString; - $bIsLiteralParsed = true; - $iPos = $mLiteralEndPos + 3; - $bIsGotoLiteral = true; - break; - } - } - if (!$bIsLiteralParsed) - { - $iPos = $iBufferEndIndex; - } - $sPreviousAtomUpperCase = null; - break; - case '"' === $sChar: - $bIsQuotedParsed = false; - while (true) - { - $iClosingPos = $iPos + 1; - if ($iClosingPos > $iBufferEndIndex) - { - break; - } - - while (true) - { - $iClosingPos = \strpos($this->sResponseBuffer, '"', $iClosingPos); - if (false === $iClosingPos) - { - break; - } - - // TODO - $iClosingPosNext = $iClosingPos + 1; - if ( - isset($this->sResponseBuffer[$iClosingPosNext]) && - ' ' !== $this->sResponseBuffer[$iClosingPosNext] && - "\r" !== $this->sResponseBuffer[$iClosingPosNext] && - "\n" !== $this->sResponseBuffer[$iClosingPosNext] && - ']' !== $this->sResponseBuffer[$iClosingPosNext] && - ')' !== $this->sResponseBuffer[$iClosingPosNext] - ) - { - $iClosingPos++; - continue; - } - - $iSlashCount = 0; - while ('\\' === $this->sResponseBuffer[$iClosingPos - $iSlashCount - 1]) - { - $iSlashCount++; - } - - if ($iSlashCount % 2 == 1) - { - $iClosingPos++; - continue; - } - else - { - break; - } - } - - if (false === $iClosingPos) - { - break; - } - else - { -// $iSkipClosingPos = 0; - $bIsQuotedParsed = true; - if ($bTreatAsAtom) - { - $sAtomBuilder .= \strtr( - \substr($this->sResponseBuffer, $iPos, $iClosingPos - $iPos + 1), - array('\\\\' => '\\', '\\"' => '"') - ); - } - else - { - $aList[] = \strtr( - \substr($this->sResponseBuffer, $iPos + 1, $iClosingPos - $iPos - 1), - array('\\\\' => '\\', '\\"' => '"') - ); - } - - $iPos = $iClosingPos + 1; - break; - } - } - - if (!$bIsQuotedParsed) - { - $iPos = $iBufferEndIndex; - } - - $sPreviousAtomUpperCase = null; - break; - - case 'GOTO_DEFAULT' === $sChar: - default: - $iCharBlockStartPos = $iPos; - - if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) - { - $iPos = $iBufferEndIndex; - - while ($iPos > $iCharBlockStartPos && $this->sResponseBuffer[$iCharBlockStartPos] === ' ') - { - $iCharBlockStartPos++; - } - } - - $bIsAtomDone = false; - while (!$bIsAtomDone && ($iPos <= $iBufferEndIndex)) - { - $sCharDef = $this->sResponseBuffer[$iPos]; - switch (true) - { - case '[' === $sCharDef: - if (null === $sAtomBuilder) - { - $sAtomBuilder = ''; - } - - $sAtomBuilder .= \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos + 1); - - $iPos++; - $this->iResponseBufParsedPos = $iPos; - - $sListBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, - null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), '['); - - if (null !== $sListBlock) - { - $sAtomBuilder .= $sListBlock.']'; - } - - $iPos = $this->iResponseBufParsedPos; - $iCharBlockStartPos = $iPos; - break; - case ' ' === $sCharDef: - case ')' === $sCharDef && '(' === $sOpenBracket: - case ']' === $sCharDef && '[' === $sOpenBracket: - $bIsAtomDone = true; - break; - default: - $iPos++; - break; - } - } - - if ($iPos > $iCharBlockStartPos || null !== $sAtomBuilder) - { - $sLastCharBlock = \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos); - if (null === $sAtomBuilder) - { - $aList[] = $sLastCharBlock; - $sPreviousAtomUpperCase = $sLastCharBlock; - } - else - { - $sAtomBuilder .= $sLastCharBlock; - - if (!$bTreatAsAtom) - { - $aList[] = $sAtomBuilder; - $sPreviousAtomUpperCase = $sAtomBuilder; - $sAtomBuilder = null; - } - } - - if (null !== $oImapResponse) - { -// if (1 === \count($aList)) - if (!$bCountOneInited && 1 === \count($aList)) -// if (isset($aList[0]) && !isset($aList[1])) // fast 1 === \count($aList) - { - $bCountOneInited = true; - - $oImapResponse->Tag = $aList[0]; - if ('+' === $oImapResponse->Tag) - { - $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::CONTINUATION; - } - else if ('*' === $oImapResponse->Tag) - { - $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNTAGGED; - } - else if ($this->getCurrentTag() === $oImapResponse->Tag) - { - $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::TAGGED; - } - else - { - $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNKNOWN; - } - } -// else if (2 === \count($aList)) - else if (!$bCountTwoInited && 2 === \count($aList)) -// else if (isset($aList[1]) && !isset($aList[2])) // fast 2 === \count($aList) - { - $bCountTwoInited = true; - - $oImapResponse->StatusOrIndex = strtoupper($aList[1]); - - if ($oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::OK || - $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::NO || - $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BAD || - $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BYE || - $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::PREAUTH) - { - $oImapResponse->IsStatusResponse = true; - } - } - else if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) - { - $oImapResponse->HumanReadable = $sLastCharBlock; - } - else if ($oImapResponse->IsStatusResponse) - { - $oImapResponse->HumanReadable = $sLastCharBlock; - } - } - } - } - } - - $this->iResponseBufParsedPos = $iPos; - if (null !== $oImapResponse) - { - $this->bNeedNext = true; - $this->iResponseBufParsedPos = 0; - } - - if (100000 < $iDebugCount) - { - $this->Logger()->Write('PartialParseOverResult: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); - } - - return $bTreatAsAtom ? $sAtomBuilder : $aList; - } - - /** - * @param string $sParent - * @param string $sLiteralAtomUpperCase - * @param resource $rImapStream - * @param int $iLiteralLen - * - * @return bool - */ - private function partialResponseLiteralCallbackCallable($sParent, $sLiteralAtomUpperCase, $rImapStream, $iLiteralLen) - { - $sLiteralAtomUpperCasePeek = ''; - if (0 === \strpos($sLiteralAtomUpperCase, 'BODY')) - { - $sLiteralAtomUpperCasePeek = \str_replace('BODY', 'BODY.PEEK', $sLiteralAtomUpperCase); - } - - $sFetchKey = ''; - if (\is_array($this->aFetchCallbacks)) - { - if (0 < \strlen($sLiteralAtomUpperCasePeek) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCasePeek])) - { - $sFetchKey = $sLiteralAtomUpperCasePeek; - } - else if (0 < \strlen($sLiteralAtomUpperCase) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCase])) - { - $sFetchKey = $sLiteralAtomUpperCase; - } - } - - $bResult = false; - if (0 < \strlen($sFetchKey) && '' !== $this->aFetchCallbacks[$sFetchKey] && - \is_callable($this->aFetchCallbacks[$sFetchKey])) - { - $rImapLiteralStream = - \MailSo\Base\StreamWrappers\Literal::CreateStream($rImapStream, $iLiteralLen); - - $bResult = true; - $this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. - ' - try to read '.$iLiteralLen.' bytes.', \MailSo\Log\Enumerations\Type::NOTE); - - $this->bRunningCallback = true; - - try - { - \call_user_func($this->aFetchCallbacks[$sFetchKey], - $sParent, $sLiteralAtomUpperCase, $rImapLiteralStream); - } - catch (\Exception $oException) - { - $this->writeLog('Callback Exception', \MailSo\Log\Enumerations\Type::NOTICE); - $this->writeLogException($oException); - } - - if (\is_resource($rImapLiteralStream)) - { - $iNotReadLiteralLen = 0; - - $bFeof = \feof($rImapLiteralStream); - $this->writeLog('End Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. - ' - feof = '.($bFeof ? 'good' : 'BAD'), $bFeof ? - \MailSo\Log\Enumerations\Type::NOTE : \MailSo\Log\Enumerations\Type::WARNING); - - if (!$bFeof) - { - while (!@\feof($rImapLiteralStream)) - { - $sBuf = @\fread($rImapLiteralStream, 1024 * 1024); - if (false === $sBuf || 0 === \strlen($sBuf) || null === $sBuf) - { - break; - } - - \MailSo\Base\Utils::ResetTimeLimit(); - $iNotReadLiteralLen += \strlen($sBuf); - } - - if (\is_resource($rImapLiteralStream) && !@\feof($rImapLiteralStream)) - { - @\stream_get_contents($rImapLiteralStream); - } - } - - if (\is_resource($rImapLiteralStream)) - { - @\fclose($rImapLiteralStream); - } - - if ($iNotReadLiteralLen > 0) - { - $this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', - \MailSo\Log\Enumerations\Type::WARNING); - } - } - else - { - $this->writeLog('Literal stream is not resource after callback.', - \MailSo\Log\Enumerations\Type::WARNING); - } - - \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralLen); - - $this->bRunningCallback = false; - } - - return $bResult; - } - - /** - * @param array $aParams = null - * - * @return string - */ - private function prepearParamLine($aParams = array()) - { - $sReturn = ''; - if (\is_array($aParams) && 0 < \count($aParams)) - { - foreach ($aParams as $mParamItem) - { - if (\is_array($mParamItem) && 0 < \count($mParamItem)) - { - $sReturn .= ' ('.\trim($this->prepearParamLine($mParamItem)).')'; - } - else if (\is_string($mParamItem)) - { - $sReturn .= ' '.$mParamItem; - } - } - } - return $sReturn; - } - - /** - * @return string - */ - private function getNewTag() - { - $this->iTagCount++; - return $this->getCurrentTag(); - } - - /** - * @return string - */ - private function getCurrentTag() - { - return self::TAG_PREFIX.$this->iTagCount; - } - - /** - * @param string $sStringForEscape - * - * @return string - */ - public function EscapeString($sStringForEscape) - { - return '"'.\str_replace(array('\\', '"'), array('\\\\', '\\"'), $sStringForEscape).'"'; - } - - /** - * @return string - */ - protected function getLogName() - { - return 'IMAP'; - } - - /** - * @param \MailSo\Log\Logger $oLogger - * - * @return \MailSo\Imap\ImapClient - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - */ - public function SetLogger($oLogger) - { - parent::SetLogger($oLogger); - - return $this; - } - - /** - * @param resource $rConnect - * @param array $aCapabilityItems = array() - * - * @return \MailSo\Imap\ImapClient - */ - public function TestSetValues($rConnect, $aCapabilityItems = array()) - { - $this->rConnect = $rConnect; - $this->aCapabilityItems = $aCapabilityItems; - - return $this; - } - - /** - * @param string $sEndTag = null - * @param string $bFindCapa = false - * - * @return array - */ - public function TestParseResponseWithValidationProxy($sEndTag = null, $bFindCapa = false) - { - return $this->parseResponseWithValidation($sEndTag, $bFindCapa); - } -} +iTagCount = 0; + $this->aCapabilityItems = null; + $this->oCurrentFolderInfo = null; + $this->aFetchCallbacks = null; + $this->iResponseBufParsedPos = 0; + + $this->aLastResponse = array(); + $this->bNeedNext = true; + $this->aPartialResponses = array(); + + $this->aTagTimeouts = array(); + + $this->bIsLoggined = false; + $this->bIsSelected = false; + $this->sLogginedUser = ''; + + $this->__FORCE_SELECT_ON_EXAMINE__ = false; + + @\ini_set('xdebug.max_nesting_level', 500); + } + + /** + * @return \MailSo\Imap\ImapClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return string + */ + public function GetLogginedUser() + { + return $this->sLogginedUser; + } + + /** + * @param string $sServerName + * @param int $iPort = 143 + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Connect($sServerName, $iPort = 143, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) + { + $this->aTagTimeouts['*'] = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $this->parseResponseWithValidation('*', true); + + if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( + $this->IsSupported('STARTTLS'), $this->iSecurityType)) + { + $this->SendRequestWithCheck('STARTTLS'); + $this->EnableCrypto(); + + $this->aCapabilityItems = null; + } + else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType) + { + $this->writeLogException( + new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $this; + } + + protected function _xor($string, $string2) + { + $result = ''; + $size = strlen($string); + for ($i=0; $i<$size; $i++) { + $result .= chr(ord($string[$i]) ^ ord($string2[$i])); + } + return $result; + } + + /** + * @param string $sLogin + * @param string $sPassword + * @param string $sProxyAuthUser = '' + * @param bool $bUseAuthPlainIfSupported = true + * @param bool $bUseAuthCramMd5IfSupported = true + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Login($sLogin, $sPassword, $sProxyAuthUser = '', + $bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true) + { + if (!\MailSo\Base\Validator::NotEmptyString($sLogin, true) || + !\MailSo\Base\Validator::NotEmptyString($sPassword, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); + + $sPassword = $sPassword; + + $this->sLogginedUser = $sLogin; + + try + { + if ($bUseAuthCramMd5IfSupported && $this->IsSupported('AUTH=CRAM-MD5')) + { + $this->SendRequest('AUTHENTICATE', array('CRAM-MD5')); + + $aResponse = $this->parseResponseWithValidation(); + if ($aResponse && \is_array($aResponse) && 0 < \count($aResponse) && + \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $aResponse[\count($aResponse) - 1]->ResponseType) + { + $oContinuationResponse = null; + foreach ($aResponse as $oResponse) + { + if ($oResponse && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oResponse->ResponseType) + { + $oContinuationResponse = $oResponse; + } + } + + if ($oContinuationResponse && !empty($oContinuationResponse->ResponseList[1])) + { + $sTicket = @\base64_decode($oContinuationResponse->ResponseList[1]); + $this->oLogger->Write('ticket: '.$sTicket); + + $sToken = \base64_encode($sLogin.' '.\MailSo\Base\Utils::Hmac($sTicket, $sPassword)); + + if ($this->oLogger) + { + $this->oLogger->AddSecret($sToken); + } + + $this->sendRaw($sToken, true, '*******'); + $this->parseResponseWithValidation(); + } + else + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else if ($bUseAuthPlainIfSupported && $this->IsSupported('AUTH=PLAIN')) + { + $sToken = \base64_encode("\0".$sLogin."\0".$sPassword); + if ($this->oLogger) + { + $this->oLogger->AddSecret($sToken); + } + + if ($this->IsSupported('AUTH=SASL-IR') && false) + { + $this->SendRequestWithCheck('AUTHENTICATE', array('PLAIN', $sToken)); + } + else + { + $this->SendRequest('AUTHENTICATE', array('PLAIN')); + $this->parseResponseWithValidation(); + + $this->sendRaw($sToken, true, '*******'); + $this->parseResponseWithValidation(); + } + } + else + { + if ($this->oLogger) + { + $this->oLogger->AddSecret($this->EscapeString($sPassword)); + } + + $this->SendRequestWithCheck('LOGIN', + array( + $this->EscapeString($sLogin), + $this->EscapeString($sPassword) + )); + } +// else +// { +// $this->writeLogException( +// new \MailSo\Imap\Exceptions\LoginBadMethodException(), +// \MailSo\Log\Enumerations\Type::NOTICE, true); +// } + + if (0 < \strlen($sProxyAuthUser)) + { + $this->SendRequestWithCheck('PROXYAUTH', array($this->EscapeString($sProxyAuthUser))); + } + } + catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + $this->aCapabilityItems = null; + + return $this; + } + + /** + * @param string $sXOAuth2Token + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function LoginWithXOauth2($sXOAuth2Token) + { + if (!\MailSo\Base\Validator::NotEmptyString($sXOAuth2Token, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$this->IsSupported('AUTH=XOAUTH2')) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginBadMethodException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + try + { + $this->SendRequest('AUTHENTICATE', array('XOAUTH2', \trim($sXOAuth2Token))); + $aR = $this->parseResponseWithValidation(); + + if (\is_array($aR) && 0 < \count($aR) && isset($aR[\count($aR) - 1])) + { + $oR = $aR[\count($aR) - 1]; + if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oR->ResponseType) + { + if (!empty($oR->ResponseList[1]) && preg_match('/^[a-zA-Z0-9=+\/]+$/', $oR->ResponseList[1])) + { + $this->Logger()->Write(\base64_decode($oR->ResponseList[1]), + \MailSo\Log\Enumerations\Type::WARNING); + } + + $this->sendRaw(''); + $this->parseResponseWithValidation(); + } + } + } + catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + $this->aCapabilityItems = null; + + return $this; + } + + /** + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function Logout() + { + if ($this->bIsLoggined) + { + $this->bIsLoggined = false; + $this->SendRequestWithCheck('LOGOUT', array()); + } + + return $this; + } + + /** + * @return \MailSo\Imap\ImapClient + */ + public function ForceCloseConnection() + { + $this->Disconnect(); + + return $this; + } + + /** + * @return bool + */ + public function IsLoggined() + { + return $this->IsConnected() && $this->bIsLoggined; + } + + /** + * @return bool + */ + public function IsSelected() + { + return $this->IsLoggined() && $this->bIsSelected; + } + + /** + * @return array|null + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Capability() + { + $this->SendRequestWithCheck('CAPABILITY', array(), true); + return $this->aCapabilityItems; + } + + /** + * @param string $sExtentionName + * @return bool + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function IsSupported($sExtentionName) + { + $bResult = \MailSo\Base\Validator::NotEmptyString($sExtentionName, true); + if ($bResult && null === $this->aCapabilityItems) + { + $this->aCapabilityItems = $this->Capability(); + } + + return $bResult && \is_array($this->aCapabilityItems) && + \in_array(\strtoupper($sExtentionName), $this->aCapabilityItems); + } + + /** + * @return \MailSo\Imap\NamespaceResult|null + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function GetNamespace() + { + if (!$this->IsSupported('NAMESPACE')) + { + return null; + } + + $oReturn = false; + + $this->SendRequest('NAMESPACE'); + $aResult = $this->parseResponseWithValidation(); + + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + 'NAMESPACE' === $oImapResponse->StatusOrIndex) + { + $oReturn = NamespaceResult::NewInstance(); + $oReturn->InitByImapResponse($oImapResponse); + break; + } + } + + if (false === $oReturn) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\ResponseException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $oReturn; + } + + /** + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Noop() + { + return $this->SendRequestWithCheck('NOOP'); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderCreate($sFolderName) + { + return $this->SendRequestWithCheck('CREATE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderDelete($sFolderName) + { + return $this->SendRequestWithCheck('DELETE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderSubscribe($sFolderName) + { + return $this->SendRequestWithCheck('SUBSCRIBE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderUnSubscribe($sFolderName) + { + return $this->SendRequestWithCheck('UNSUBSCRIBE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sOldFolderName + * @param string $sNewFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderRename($sOldFolderName, $sNewFolderName) + { + return $this->SendRequestWithCheck('RENAME', array( + $this->EscapeString($sOldFolderName), + $this->EscapeString($sNewFolderName))); + } + + /** + * @param array $aResult + * + * @return array + */ + protected function getStatusFolderInformation($aResult) + { + $aReturn = array(); + + if (\is_array($aResult)) + { + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + 'STATUS' === $oImapResponse->StatusOrIndex && isset($oImapResponse->ResponseList[3]) && + \is_array($oImapResponse->ResponseList[3])) + { + $sName = null; + foreach ($oImapResponse->ResponseList[3] as $sArrayItem) + { + if (null === $sName) + { + $sName = $sArrayItem; + } + else + { + $aReturn[$sName] = $sArrayItem; + $sName = null; + } + } + } + } + } + + return $aReturn; + } + + /** + * @param string $sFolderName + * @param array $aStatusItems + * + * @return array|bool + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderStatus($sFolderName, array $aStatusItems) + { + $aResult = false; + if (\count($aStatusItems) > 0) + { + $this->SendRequest('STATUS', + array($this->EscapeString($sFolderName), $aStatusItems)); + + $aResult = $this->getStatusFolderInformation( + $this->parseResponseWithValidation()); + } + + return $aResult; + } + + /** + * @param array $aResult + * @param string $sStatus + * @param bool $bUseListStatus = false + * + * @return array + */ + private function getFoldersFromResult(array $aResult, $sStatus, $bUseListStatus = false) + { + $aReturn = array(); + + $sDelimiter = ''; + $bInbox = false; + + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + $sStatus === $oImapResponse->StatusOrIndex && 5 === count($oImapResponse->ResponseList)) + { + try + { + $oFolder = Folder::NewInstance($oImapResponse->ResponseList[4], + $oImapResponse->ResponseList[3], $oImapResponse->ResponseList[2]); + + if ($oFolder->IsInbox()) + { + $bInbox = true; + } + + if (empty($sDelimiter)) + { + $sDelimiter = $oFolder->Delimiter(); + } + + $aReturn[] = $oFolder; + } + catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) + { + $this->writeLogException($oException, \MailSo\Log\Enumerations\Type::WARNING, false); + } + } + } + + if (!$bInbox && !empty($sDelimiter)) + { + $aReturn[] = Folder::NewInstance('INBOX', $sDelimiter); + } + + if ($bUseListStatus) + { + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + 'STATUS' === $oImapResponse->StatusOrIndex && + isset($oImapResponse->ResponseList[2]) && + isset($oImapResponse->ResponseList[3]) && + \is_array($oImapResponse->ResponseList[3])) + { + $sFolderNameRaw = $oImapResponse->ResponseList[2]; + + $oCurrentFolder = null; + foreach ($aReturn as &$oFolder) + { + if ($oFolder && $sFolderNameRaw === $oFolder->FullNameRaw()) + { + $oCurrentFolder =& $oFolder; + break; + } + } + + if (null !== $oCurrentFolder) + { + $sName = null; + $aStatus = array(); + + foreach ($oImapResponse->ResponseList[3] as $sArrayItem) + { + if (null === $sName) + { + $sName = $sArrayItem; + } + else + { + $aStatus[$sName] = $sArrayItem; + $sName = null; + } + } + + if (0 < count($aStatus)) + { + $oCurrentFolder->SetExtended('STATUS', $aStatus); + } + } + + unset($oCurrentFolder); + } + } + } + + return $aReturn; + } + + /** + * @param bool $bIsSubscribeList + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * @param bool $bUseListStatus = false + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + private function specificFolderList($bIsSubscribeList, $sParentFolderName = '', $sListPattern = '*', $bUseListStatus = false) + { + $sCmd = 'LSUB'; + if (!$bIsSubscribeList) + { + $sCmd = 'LIST'; + } + + $sListPattern = 0 === strlen(trim($sListPattern)) ? '*' : $sListPattern; + + $aParameters = array( + $this->EscapeString($sParentFolderName), + $this->EscapeString($sListPattern) + ); + + if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS')) + { + $aL = array( + \MailSo\Imap\Enumerations\FolderStatus::MESSAGES, + \MailSo\Imap\Enumerations\FolderStatus::UNSEEN, + \MailSo\Imap\Enumerations\FolderStatus::UIDNEXT + ); + +// if ($this->IsSupported('CONDSTORE')) +// { +// $aL[] = \MailSo\Imap\Enumerations\FolderStatus::HIGHESTMODSEQ; +// } + + $aParameters[] = 'RETURN'; + $aParameters[] = array('STATUS', $aL); + } + else + { + $bUseListStatus = false; + } + + $this->SendRequest($sCmd, $aParameters); + + return $this->getFoldersFromResult( + $this->parseResponseWithValidation(), $sCmd, $bUseListStatus); + } + + /** + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderList($sParentFolderName = '', $sListPattern = '*') + { + return $this->specificFolderList(false, $sParentFolderName, $sListPattern); + } + + /** + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderSubscribeList($sParentFolderName = '', $sListPattern = '*') + { + return $this->specificFolderList(true, $sParentFolderName, $sListPattern); + } + + /** + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderStatusList($sParentFolderName = '', $sListPattern = '*') + { + return $this->specificFolderList(false, $sParentFolderName, $sListPattern, true); + } + + /** + * @param array $aResult + * @param string $sFolderName + * @param bool $bIsWritable + * + * @return void + */ + protected function initCurrentFolderInformation($aResult, $sFolderName, $bIsWritable) + { + if (\is_array($aResult)) + { + $oImapResponse = null; + $oResult = FolderInformation::NewInstance($sFolderName, $bIsWritable); + + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType) + { + if (\count($oImapResponse->ResponseList) > 2 && + 'FLAGS' === $oImapResponse->ResponseList[1] && \is_array($oImapResponse->ResponseList[2])) + { + $oResult->Flags = $oImapResponse->ResponseList[2]; + } + + if (is_array($oImapResponse->OptionalResponse) && \count($oImapResponse->OptionalResponse) > 1) + { + if ('PERMANENTFLAGS' === $oImapResponse->OptionalResponse[0] && + is_array($oImapResponse->OptionalResponse[1])) + { + $oResult->PermanentFlags = $oImapResponse->OptionalResponse[1]; + } + else if ('UIDVALIDITY' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1])) + { + $oResult->Uidvalidity = $oImapResponse->OptionalResponse[1]; + } + else if ('UNSEEN' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1]) && + is_numeric($oImapResponse->OptionalResponse[1])) + { + $oResult->Unread = (int) $oImapResponse->OptionalResponse[1]; + } + else if ('UIDNEXT' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1])) + { + $oResult->Uidnext = $oImapResponse->OptionalResponse[1]; + } + else if ('HIGHESTMODSEQ' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1]) && + \is_numeric($oImapResponse->OptionalResponse[1])) + { + $oResult->HighestModSeq = \trim($oImapResponse->OptionalResponse[1]); + } + } + + if (\count($oImapResponse->ResponseList) > 2 && + \is_string($oImapResponse->ResponseList[2]) && + \is_numeric($oImapResponse->ResponseList[1])) + { + switch($oImapResponse->ResponseList[2]) + { + case 'EXISTS': + $oResult->Exists = (int) $oImapResponse->ResponseList[1]; + break; + case 'RECENT': + $oResult->Recent = (int) $oImapResponse->ResponseList[1]; + break; + } + } + } + } + + $this->oCurrentFolderInfo = $oResult; + } + } + + /** + * @param string $sFolderName + * @param bool $bIsWritable + * @param bool $bReSelectSameFolders + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + protected function selectOrExamineFolder($sFolderName, $bIsWritable, $bReSelectSameFolders) + { + if (!$bReSelectSameFolders) + { + if ($this->oCurrentFolderInfo && + $sFolderName === $this->oCurrentFolderInfo->FolderName && + $bIsWritable === $this->oCurrentFolderInfo->IsWritable) + { + return $this; + } + } + + if (!\MailSo\Base\Validator::NotEmptyString($sFolderName, true)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->SendRequest(($bIsWritable) ? 'SELECT' : 'EXAMINE', + array($this->EscapeString($sFolderName))); + + $this->initCurrentFolderInformation( + $this->parseResponseWithValidation(), $sFolderName, $bIsWritable); + + $this->bIsSelected = true; + + return $this; + } + + /** + * @param string $sFolderName + * @param bool $bReSelectSameFolders = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderSelect($sFolderName, $bReSelectSameFolders = false) + { + return $this->selectOrExamineFolder($sFolderName, true, $bReSelectSameFolders); + } + + /** + * @param string $sFolderName + * @param bool $bReSelectSameFolders = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderExamine($sFolderName, $bReSelectSameFolders = false) + { + return $this->selectOrExamineFolder($sFolderName, $this->__FORCE_SELECT_ON_EXAMINE__, $bReSelectSameFolders); + } + + /** + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderUnSelect() + { + if ($this->IsSelected() && $this->IsSupported('UNSELECT')) + { + $this->SendRequestWithCheck('UNSELECT'); + $this->bIsSelected = false; + } + + return $this; + } + + /** + * @param array $aInputFetchItems + * @param string $sIndexRange + * @param bool $bIndexIsUid + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Fetch(array $aInputFetchItems, $sIndexRange, $bIndexIsUid) + { + $sIndexRange = (string) $sIndexRange; + if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $aFetchItems = \MailSo\Imap\Enumerations\FetchType::ChangeFetchItemsBefourRequest($aInputFetchItems); + foreach ($aFetchItems as $sName => $mItem) + { + if (0 < \strlen($sName) && '' !== $mItem) + { + if (null === $this->aFetchCallbacks) + { + $this->aFetchCallbacks = array(); + } + + $this->aFetchCallbacks[$sName] = $mItem; + } + } + + $this->SendRequest((($bIndexIsUid) ? 'UID ' : '').'FETCH', array($sIndexRange, \array_keys($aFetchItems))); + $aResult = $this->validateResponse($this->parseResponse()); + $this->aFetchCallbacks = null; + + $aReturn = array(); + $oImapResponse = null; + foreach ($aResult as $oImapResponse) + { + if (FetchResponse::IsValidFetchImapResponse($oImapResponse)) + { + if (FetchResponse::IsNotEmptyFetchImapResponse($oImapResponse)) + { + $aReturn[] = FetchResponse::NewInstance($oImapResponse); + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Skipped Imap Response! ['.$oImapResponse->ToLine().']', \MailSo\Log\Enumerations\Type::NOTICE); + } + } + } + } + + return $aReturn; + } + + + /** + * @return array|false + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Quota() + { + $aReturn = false; + if ($this->IsSupported('QUOTA')) + { + $this->SendRequest('GETQUOTAROOT "INBOX"'); + $aResult = $this->parseResponseWithValidation(); + + $aReturn = array(0, 0); + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && 'QUOTA' === $oImapResponse->StatusOrIndex + && \is_array($oImapResponse->ResponseList) + && isset($oImapResponse->ResponseList[3]) + && \is_array($oImapResponse->ResponseList[3]) + && 2 < \count($oImapResponse->ResponseList[3]) + && 'STORAGE' === \strtoupper($oImapResponse->ResponseList[3][0]) + && \is_numeric($oImapResponse->ResponseList[3][1]) + && \is_numeric($oImapResponse->ResponseList[3][2]) + ) + { + $aReturn = array( + (int) $oImapResponse->ResponseList[3][1], + (int) $oImapResponse->ResponseList[3][2], + 0, + 0 + ); + + if (5 < \count($oImapResponse->ResponseList[3]) + && 'MESSAGE' === \strtoupper($oImapResponse->ResponseList[3][3]) + && \is_numeric($oImapResponse->ResponseList[3][4]) + && \is_numeric($oImapResponse->ResponseList[3][5]) + ) + { + $aReturn[2] = (int) $oImapResponse->ResponseList[3][4]; + $aReturn[3] = (int) $oImapResponse->ResponseList[3][5]; + } + } + } + } + + return $aReturn; + } + + /** + * @param array $aSortTypes + * @param string $sSearchCriterias + * @param bool $bReturnUid + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleSort($aSortTypes, $sSearchCriterias = 'ALL', $bReturnUid = true) + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + if (!\is_array($aSortTypes) || 0 === \count($aSortTypes)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + else if (!$this->IsSupported('SORT')) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $aRequest = array(); + $aRequest[] = $aSortTypes; + $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; + $aRequest[] = $sSearchCriterias; + + $sCmd = 'SORT'; + + $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); + $aResult = $this->parseResponseWithValidation(); + + $aReturn = array(); + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ($sCmd === $oImapResponse->StatusOrIndex || + ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + && \is_array($oImapResponse->ResponseList) + && 2 < \count($oImapResponse->ResponseList)) + { + $iStart = 2; + if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && + !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + { + $iStart = 3; + } + + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) + { + $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; + } + } + } + + return $aReturn; + } + + /** + * @param bool $bSort = false + * @param string $sSearchCriterias = 'ALL' + * @param array $aSearchOrSortReturn = null + * @param bool $bReturnUid = true + * @param string $sLimit = '' + * @param string $sCharset = '' + * @param array $aSortTypes = null + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + private function simpleESearchOrESortHelper($bSort = false, $sSearchCriterias = 'ALL', $aSearchOrSortReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '', $aSortTypes = null) + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + $sCmd = $bSort ? 'SORT': 'SEARCH'; + if ($bSort && (!\is_array($aSortTypes) || 0 === \count($aSortTypes) || !$this->IsSupported('SORT'))) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$this->IsSupported($bSort ? 'ESORT' : 'ESEARCH')) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!\is_array($aSearchOrSortReturn) || 0 === \count($aSearchOrSortReturn)) + { + $aSearchOrSortReturn = array('ALL'); + } + + $aRequest = array(); + if ($bSort) + { + $aRequest[] = 'RETURN'; + $aRequest[] = $aSearchOrSortReturn; + + $aRequest[] = $aSortTypes; + $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; + } + else + { + if (0 < \strlen($sCharset)) + { + $aRequest[] = 'CHARSET'; + $aRequest[] = \strtoupper($sCharset); + } + + $aRequest[] = 'RETURN'; + $aRequest[] = $aSearchOrSortReturn; + } + + $aRequest[] = $sSearchCriterias; + + if (0 < \strlen($sLimit)) + { + $aRequest[] = $sLimit; + } + + $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); + $sRequestTag = $this->getCurrentTag(); + + $aResult = array(); + $aResponse = $this->parseResponseWithValidation(); + + if (\is_array($aResponse)) + { + $oImapResponse = null; + foreach ($aResponse as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ('ESEARCH' === $oImapResponse->StatusOrIndex || 'ESORT' === $oImapResponse->StatusOrIndex) + && \is_array($oImapResponse->ResponseList) + && isset($oImapResponse->ResponseList[2], $oImapResponse->ResponseList[2][0], $oImapResponse->ResponseList[2][1]) + && 'TAG' === $oImapResponse->ResponseList[2][0] && $sRequestTag === $oImapResponse->ResponseList[2][1] + && (!$bReturnUid || ($bReturnUid && !empty($oImapResponse->ResponseList[3]) && 'UID' === $oImapResponse->ResponseList[3])) + ) + { + $iStart = 3; + foreach ($oImapResponse->ResponseList as $iIndex => $mItem) + { + if ($iIndex >= $iStart) + { + switch ($mItem) + { + case 'ALL': + case 'MAX': + case 'MIN': + case 'COUNT': + if (isset($oImapResponse->ResponseList[$iIndex + 1])) + { + $aResult[$mItem] = $oImapResponse->ResponseList[$iIndex + 1]; + } + break; + } + } + } + } + } + } + + return $aResult; + } + + /** + * @param string $sSearchCriterias = 'ALL' + * @param array $aSearchReturn = null + * @param bool $bReturnUid = true + * @param string $sLimit = '' + * @param string $sCharset = '' + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleESearch($sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '') + { + return $this->simpleESearchOrESortHelper(false, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, $sCharset); + } + + /** + * @param array $aSortTypes + * @param string $sSearchCriterias = 'ALL' + * @param array $aSearchReturn = null + * @param bool $bReturnUid = true + * @param string $sLimit = '' + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleESort($aSortTypes, $sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '') + { + return $this->simpleESearchOrESortHelper(true, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, '', $aSortTypes); + } + + /** + * @param array $aResult + * @return \MailSo\Imap\Response + */ + private function findLastResponse($aResult) + { + $oResult = null; + if (\is_array($aResult) && 0 < \count($aResult)) + { + $oResult = $aResult[\count($aResult) - 1]; + if (!($oResult instanceof \MailSo\Imap\Response)) + { + $oResult = null; + } + } + + return $oResult; + } + + /** + * @param string $sSearchCriterias + * @param bool $bReturnUid = true + * @param string $sCharset = '' + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleSearch($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = '') + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + $aRequest = array(); + if (0 < \strlen($sCharset)) + { + $aRequest[] = 'CHARSET'; + $aRequest[] = \strtoupper($sCharset); + } + + $aRequest[] = $sSearchCriterias; + + $sCmd = 'SEARCH'; + + $sCont = $this->SendRequest($sCommandPrefix.$sCmd, $aRequest, true); + if ('' !== $sCont) + { + $aResult = $this->parseResponseWithValidation(); + $oItem = $this->findLastResponse($aResult); + + if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) + { + $aParts = explode("\r\n", $sCont); + foreach ($aParts as $sLine) + { + $this->sendRaw($sLine); + + $aResult = $this->parseResponseWithValidation(); + $oItem = $this->findLastResponse($aResult); + if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) + { + continue; + } + } + } + } + else + { + $aResult = $this->parseResponseWithValidation(); + } + + $aReturn = array(); + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ($sCmd === $oImapResponse->StatusOrIndex || + ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + && \is_array($oImapResponse->ResponseList) + && 2 < count($oImapResponse->ResponseList)) + { + $iStart = 2; + if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && + !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + { + $iStart = 3; + } + + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) + { + $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; + } + } + } + + $aReturn = \array_reverse($aReturn); + return $aReturn; + } + + /** + * @param mixed $aValue + * + * @return mixed + */ + private function validateThreadItem($aValue) + { + $mResult = false; + if (\is_numeric($aValue)) + { + $mResult = (int) $aValue; + if (0 >= $mResult) + { + $mResult = false; + } + } + else if (\is_array($aValue)) + { + if (1 === \count($aValue) && \is_numeric($aValue[0])) + { + $mResult = (int) $aValue[0]; + if (0 >= $mResult) + { + $mResult = false; + } + } + else + { + $mResult = array(); + foreach ($aValue as $aValueItem) + { + $mTemp = $this->validateThreadItem($aValueItem); + if (false !== $mTemp) + { + $mResult[] = $mTemp; + } + } + } + } + + return $mResult; + } + + /** + * @param string $sSearchCriterias = 'ALL' + * @param bool $bReturnUid = true + * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleThread($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + $sThreadType = ''; + switch (true) + { + case $this->IsSupported('THREAD=REFS'): + $sThreadType = 'REFS'; + break; + case $this->IsSupported('THREAD=REFERENCES'): + $sThreadType = 'REFERENCES'; + break; + case $this->IsSupported('THREAD=ORDEREDSUBJECT'): + $sThreadType = 'ORDEREDSUBJECT'; + break; + default: + $this->writeLogException( + new Exceptions\RuntimeException('Thread is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + break; + } + + $aRequest = array(); + $aRequest[] = $sThreadType; + $aRequest[] = \strtoupper($sCharset); + $aRequest[] = $sSearchCriterias; + + $sCmd = 'THREAD'; + + $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); + $aResult = $this->parseResponseWithValidation(); + + $aReturn = array(); + $oImapResponse = null; + + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ($sCmd === $oImapResponse->StatusOrIndex || + ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + && \is_array($oImapResponse->ResponseList) + && 2 < \count($oImapResponse->ResponseList)) + { + $iStart = 2; + if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && + !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + { + $iStart = 3; + } + + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) + { + $aNewValue = $this->validateThreadItem($oImapResponse->ResponseList[$iIndex]); + if (false !== $aNewValue) + { + $aReturn[] = $aNewValue; + } + } + } + } + + return $aReturn; + } + + /** + * @param string $sToFolder + * @param string $sIndexRange + * @param bool $bIndexIsUid + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageCopy($sToFolder, $sIndexRange, $bIndexIsUid) + { + if (0 === \strlen($sIndexRange)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; + return $this->SendRequestWithCheck($sCommandPrefix.'COPY', + array($sIndexRange, $this->EscapeString($sToFolder))); + } + + /** + * @param string $sToFolder + * @param string $sIndexRange + * @param bool $bIndexIsUid + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageMove($sToFolder, $sIndexRange, $bIndexIsUid) + { + if (0 === \strlen($sIndexRange)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$this->IsSupported('MOVE')) + { + $this->writeLogException( + new Exceptions\RuntimeException('Move is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; + return $this->SendRequestWithCheck($sCommandPrefix.'MOVE', + array($sIndexRange, $this->EscapeString($sToFolder))); + } + + /** + * @param string $sUidRangeIfSupported = '' + * @param bool $bForceUidExpunge = false + * @param bool $bExpungeAll = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageExpunge($sUidRangeIfSupported = '', $bForceUidExpunge = false, $bExpungeAll = false) + { + $sUidRangeIfSupported = \trim($sUidRangeIfSupported); + + $sCmd = 'EXPUNGE'; + $aArguments = array(); + + if (!$bExpungeAll && $bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS')) + { + $sCmd = 'UID '.$sCmd; + $aArguments = array($sUidRangeIfSupported); + } + + return $this->SendRequestWithCheck($sCmd, $aArguments); + } + + /** + * @param string $sIndexRange + * @param bool $bIndexIsUid + * @param array $aInputStoreItems + * @param string $sStoreAction + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageStoreFlag($sIndexRange, $bIndexIsUid, $aInputStoreItems, $sStoreAction) + { + if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true) || + !\MailSo\Base\Validator::NotEmptyString($sStoreAction, true) || + 0 === \count($aInputStoreItems)) + { + return false; + } + + $sCmd = ($bIndexIsUid) ? 'UID STORE' : 'STORE'; + return $this->SendRequestWithCheck($sCmd, array($sIndexRange, $sStoreAction, $aInputStoreItems)); + } + + /** + * @param string $sFolderName + * @param resource $rMessageAppendStream + * @param int $iStreamSize + * @param array $aAppendFlags = null + * @param int $iUid = null + * @param int $sDateTime = 0 + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageAppendStream($sFolderName, $rMessageAppendStream, $iStreamSize, $aAppendFlags = null, &$iUid = null, $sDateTime = 0) + { + $aData = array($this->EscapeString($sFolderName), $aAppendFlags); + if (0 < $sDateTime) + { + $aData[] = $this->EscapeString(\gmdate('d-M-Y H:i:s', $sDateTime).' +0000'); + } + + $aData[] = '{'.$iStreamSize.'}'; + + $this->SendRequest('APPEND', $aData); + $this->parseResponseWithValidation(); + + $this->writeLog('Write to connection stream', \MailSo\Log\Enumerations\Type::NOTE); + + \MailSo\Base\Utils::MultipleStreamWriter($rMessageAppendStream, array($this->rConnect)); + + $this->sendRaw(''); + $this->parseResponseWithValidation(); + + if (null !== $iUid) + { + $aLastResponse = $this->GetLastResponse(); + if (\is_array($aLastResponse) && 0 < \count($aLastResponse) && $aLastResponse[\count($aLastResponse) - 1]) + { + $oLast = $aLastResponse[count($aLastResponse) - 1]; + if ($oLast && \MailSo\Imap\Enumerations\ResponseType::TAGGED === $oLast->ResponseType && \is_array($oLast->OptionalResponse)) + { + if (0 < \strlen($oLast->OptionalResponse[0]) && + 0 < \strlen($oLast->OptionalResponse[2]) && + 'APPENDUID' === strtoupper($oLast->OptionalResponse[0]) && + \is_numeric($oLast->OptionalResponse[2]) + ) + { + $iUid = (int) $oLast->OptionalResponse[2]; + } + } + } + } + + return $this; + } + + /** + * @return \MailSo\Imap\FolderInformation + */ + public function FolderCurrentInformation() + { + return $this->oCurrentFolderInfo; + } + + /** + * @param string $sCommand + * @param array $aParams = array() + * @param bool $bBreakOnLiteral = false + * + * @return string + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + public function SendRequest($sCommand, $aParams = array(), $bBreakOnLiteral = false) + { + if (!\MailSo\Base\Validator::NotEmptyString($sCommand, true) || !\is_array($aParams)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $sTag = $this->getNewTag(); + + $sCommand = \trim($sCommand); + $sRealCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aParams); + + $sFakeCommand = ''; + $aFakeParams = $this->secureRequestParams($sCommand, $aParams); + if (null !== $aFakeParams) + { + $sFakeCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aFakeParams); + } + + $this->aTagTimeouts[$sTag] = \microtime(true); + + if ($bBreakOnLiteral && !\preg_match('/\d\+\}\r\n/', $sRealCommand)) + { + $iPos = \strpos($sRealCommand, "}\r\n"); + if (false !== $iPos) + { + $iFakePos = \strpos($sFakeCommand, "}\r\n"); + + $this->sendRaw(\substr($sRealCommand, 0, $iPos + 1), true, + false !== $iFakePos ? \substr($sFakeCommand, 0, $iFakePos + 3) : ''); + + return \substr($sRealCommand, $iPos + 3); + } + } + + $this->sendRaw($sRealCommand, true, $sFakeCommand); + return ''; + } + + /** + * @param string $sCommand + * @param array $aParams + * + * @return array|null + */ + private function secureRequestParams($sCommand, $aParams) + { + $aResult = null; + switch ($sCommand) + { + case 'LOGIN': + $aResult = $aParams; + if (\is_array($aResult) && 2 === count($aResult)) + { + $aResult[1] = '"********"'; + } + break; + } + + return $aResult; + } + + /** + * @param string $sCommand + * @param array $aParams = array() + * @param bool $bFindCapa = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function SendRequestWithCheck($sCommand, $aParams = array(), $bFindCapa = false) + { + $this->SendRequest($sCommand, $aParams); + $this->parseResponseWithValidation(null, $bFindCapa); + + return $this; + } + + /** + * @return array + */ + public function GetLastResponse() + { + return $this->aLastResponse; + } + + /** + * @param mixed $aResult + * + * @return array + * + * @throws \MailSo\Imap\Exceptions\ResponseNotFoundException + * @throws \MailSo\Imap\Exceptions\InvalidResponseException + * @throws \MailSo\Imap\Exceptions\NegativeResponseException + */ + private function validateResponse($aResult) + { + if (!\is_array($aResult) || 0 === $iCnt = \count($aResult)) + { + $this->writeLogException( + new Exceptions\ResponseNotFoundException(), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + if ($aResult[$iCnt - 1]->ResponseType !== \MailSo\Imap\Enumerations\ResponseType::CONTINUATION) + { + if (!$aResult[$iCnt - 1]->IsStatusResponse) + { + $this->writeLogException( + new Exceptions\InvalidResponseException($aResult), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + if (\MailSo\Imap\Enumerations\ResponseStatus::OK !== $aResult[$iCnt - 1]->StatusOrIndex) + { + $this->writeLogException( + new Exceptions\NegativeResponseException($aResult), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + } + + return $aResult; + } + + /** + * @param string $sEndTag = null + * @param bool $bFindCapa = false + * + * @return array|bool + */ + protected function parseResponse($sEndTag = null, $bFindCapa = false) + { + if (\is_resource($this->rConnect)) + { + $oImapResponse = null; + $sEndTag = (null === $sEndTag) ? $this->getCurrentTag() : $sEndTag; + + while (true) + { + $oImapResponse = Response::NewInstance(); + + $this->partialParseResponseBranch($oImapResponse); + + if ($oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNKNOWN === $oImapResponse->ResponseType) + { + return false; + } + + if ($bFindCapa) + { + $this->initCapabilityImapResponse($oImapResponse); + } + + $this->aPartialResponses[] = $oImapResponse; + if ($sEndTag === $oImapResponse->Tag || \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) + { + if (isset($this->aTagTimeouts[$sEndTag])) + { + $this->writeLog((\microtime(true) - $this->aTagTimeouts[$sEndTag]).' ('.$sEndTag.')', + \MailSo\Log\Enumerations\Type::TIME); + + unset($this->aTagTimeouts[$sEndTag]); + } + + break; + } + } + else + { + return false; + } + + unset($oImapResponse); + } + } + + $this->iResponseBufParsedPos = 0; + $this->aLastResponse = $this->aPartialResponses; + $this->aPartialResponses = array(); + + return $this->aLastResponse; + } + + /** + * @param string $sEndTag = null + * @param bool $bFindCapa = false + * + * @return array + */ + private function parseResponseWithValidation($sEndTag = null, $bFindCapa = false) + { + return $this->validateResponse($this->parseResponse($sEndTag, $bFindCapa)); + } + + /** + * @param \MailSo\Imap\Response $oImapResponse + * + * @return void + */ + private function initCapabilityImapResponse($oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && \is_array($oImapResponse->ResponseList)) + { + $aList = null; + if (isset($oImapResponse->ResponseList[1]) && \is_string($oImapResponse->ResponseList[1]) && + 'CAPABILITY' === \strtoupper($oImapResponse->ResponseList[1])) + { + $aList = \array_slice($oImapResponse->ResponseList, 2); + } + else if ($oImapResponse->OptionalResponse && \is_array($oImapResponse->OptionalResponse) && + 1 < \count($oImapResponse->OptionalResponse) && \is_string($oImapResponse->OptionalResponse[0]) && + 'CAPABILITY' === \strtoupper($oImapResponse->OptionalResponse[0])) + { + $aList = \array_slice($oImapResponse->OptionalResponse, 1); + } + + if (\is_array($aList) && 0 < \count($aList)) + { + $this->aCapabilityItems = \array_map('strtoupper', $aList); + } + } + } + + /** + * @return array|string + * + * @throws \MailSo\Net\Exceptions\Exception + */ + private function partialParseResponseBranch(&$oImapResponse, $iStackIndex = -1, + $bTreatAsAtom = false, $sParentToken = '', $sOpenBracket = '') + { + $mNull = null; + + $iStackIndex++; + $iPos = $this->iResponseBufParsedPos; + + $sPreviousAtomUpperCase = null; + $bIsEndOfList = false; + $bIsClosingBracketSquare = false; + $iLiteralLen = 0; + $iBufferEndIndex = 0; + $iDebugCount = 0; + + $rImapLiteralStream = null; + + $bIsGotoDefault = false; + $bIsGotoLiteral = false; + $bIsGotoLiteralEnd = false; + $bIsGotoAtomBracket = false; + $bIsGotoNotAtomBracket = false; + + $bCountOneInited = false; + $bCountTwoInited = false; + + $sAtomBuilder = $bTreatAsAtom ? '' : null; + $aList = array(); + if (null !== $oImapResponse) + { + $aList =& $oImapResponse->ResponseList; + } + + while (!$bIsEndOfList) + { + $iDebugCount++; + if (100000 === $iDebugCount) + { + $this->Logger()->Write('PartialParseOver: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); + } + + if ($this->bNeedNext) + { + $iPos = 0; + $this->getNextBuffer(); + $this->iResponseBufParsedPos = $iPos; + $this->bNeedNext = false; + } + + $sChar = null; + if ($bIsGotoDefault) + { + $sChar = 'GOTO_DEFAULT'; + $bIsGotoDefault = false; + } + else if ($bIsGotoLiteral) + { + $bIsGotoLiteral = false; + $bIsGotoLiteralEnd = true; + + if ($this->partialResponseLiteralCallbackCallable( + $sParentToken, null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $this->rConnect, $iLiteralLen)) + { + if (!$bTreatAsAtom) + { + $aList[] = ''; + } + } + else + { + $sLiteral = ''; + $iRead = $iLiteralLen; + + while (0 < $iRead) + { + $sAddRead = \fread($this->rConnect, $iRead); + if (false === $sAddRead) + { + $sLiteral = false; + break; + } + + $sLiteral .= $sAddRead; + $iRead -= \strlen($sAddRead); + + \MailSo\Base\Utils::ResetTimeLimit(); + } + + if (false !== $sLiteral) + { + $iLiteralSize = \strlen($sLiteral); + \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralSize); + if ($iLiteralLen !== $iLiteralSize) + { + $this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '. + $iLiteralLen.'" bytes', \MailSo\Log\Enumerations\Type::WARNING); + } + + if (!$bTreatAsAtom) + { + $aList[] = $sLiteral; + + if (\MailSo\Config::$LogSimpleLiterals) + { + $this->writeLog('{'.\strlen($sLiteral).'} '.$sLiteral, \MailSo\Log\Enumerations\Type::INFO); + } + } + } + else + { + $this->writeLog('Can\'t read imap stream', \MailSo\Log\Enumerations\Type::NOTE); + } + + unset($sLiteral); + } + + continue; + } + else if ($bIsGotoLiteralEnd) + { + $rImapLiteralStream = null; + $sPreviousAtomUpperCase = null; + $this->bNeedNext = true; + $bIsGotoLiteralEnd = false; + + continue; + } + else if ($bIsGotoAtomBracket) + { + if ($bTreatAsAtom) + { + $sAtomBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, + null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); + + $sAtomBuilder .= $sAtomBlock; + $iPos = $this->iResponseBufParsedPos; + $sAtomBuilder .= ($bIsClosingBracketSquare) ? ']' : ')'; + } + + $sPreviousAtomUpperCase = null; + $bIsGotoAtomBracket = false; + + continue; + } + else if ($bIsGotoNotAtomBracket) + { + $aSubItems = $this->partialParseResponseBranch($mNull, $iStackIndex, false, + null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); + + $aList[] = $aSubItems; + $iPos = $this->iResponseBufParsedPos; + $sPreviousAtomUpperCase = null; + if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) + { + $oImapResponse->OptionalResponse = $aSubItems; + + $bIsGotoDefault = true; + $bIsGotoNotAtomBracket = false; + continue; + } + $bIsGotoNotAtomBracket = false; + + continue; + } + else + { + $iBufferEndIndex = \strlen($this->sResponseBuffer) - 3; + $this->bResponseBufferChanged = false; + + if ($iPos > $iBufferEndIndex) + { + break; + } + + $sChar = $this->sResponseBuffer[$iPos]; + } + + switch (true) + { + case ']' === $sChar: + $iPos++; + $sPreviousAtomUpperCase = null; + $bIsEndOfList = true; + break; + case ')' === $sChar: + $iPos++; + $sPreviousAtomUpperCase = null; + $bIsEndOfList = true; + break; + case ' ' === $sChar: + if ($bTreatAsAtom) + { + $sAtomBuilder .= ' '; + } + $iPos++; + break; + case '[' === $sChar: + $bIsClosingBracketSquare = true; + case '(' === $sChar: + if ('(' === $sChar) + { + $bIsClosingBracketSquare = false; + } + + if ($bTreatAsAtom) + { + $sAtomBuilder .= $bIsClosingBracketSquare ? '[' : '('; + } + $iPos++; + + $this->iResponseBufParsedPos = $iPos; + if ($bTreatAsAtom) + { + $bIsGotoAtomBracket = true; + $sOpenBracket = $bIsClosingBracketSquare ? '[' : '('; + } + else + { + $bIsGotoNotAtomBracket = true; + $sOpenBracket = $bIsClosingBracketSquare ? '[' : '('; + } + break; + case '{' === $sChar: + $bIsLiteralParsed = false; + $mLiteralEndPos = \strpos($this->sResponseBuffer, '}', $iPos); + if (false !== $mLiteralEndPos && $mLiteralEndPos > $iPos) + { + $sLiteralLenAsString = \substr($this->sResponseBuffer, $iPos + 1, $mLiteralEndPos - $iPos - 1); + if (\is_numeric($sLiteralLenAsString)) + { + $iLiteralLen = (int) $sLiteralLenAsString; + $bIsLiteralParsed = true; + $iPos = $mLiteralEndPos + 3; + $bIsGotoLiteral = true; + break; + } + } + if (!$bIsLiteralParsed) + { + $iPos = $iBufferEndIndex; + } + $sPreviousAtomUpperCase = null; + break; + case '"' === $sChar: + $bIsQuotedParsed = false; + while (true) + { + $iClosingPos = $iPos + 1; + if ($iClosingPos > $iBufferEndIndex) + { + break; + } + + while (true) + { + $iClosingPos = \strpos($this->sResponseBuffer, '"', $iClosingPos); + if (false === $iClosingPos) + { + break; + } + + // TODO + $iClosingPosNext = $iClosingPos + 1; + if ( + isset($this->sResponseBuffer[$iClosingPosNext]) && + ' ' !== $this->sResponseBuffer[$iClosingPosNext] && + "\r" !== $this->sResponseBuffer[$iClosingPosNext] && + "\n" !== $this->sResponseBuffer[$iClosingPosNext] && + ']' !== $this->sResponseBuffer[$iClosingPosNext] && + ')' !== $this->sResponseBuffer[$iClosingPosNext] + ) + { + $iClosingPos++; + continue; + } + + $iSlashCount = 0; + while ('\\' === $this->sResponseBuffer[$iClosingPos - $iSlashCount - 1]) + { + $iSlashCount++; + } + + if ($iSlashCount % 2 == 1) + { + $iClosingPos++; + continue; + } + else + { + break; + } + } + + if (false === $iClosingPos) + { + break; + } + else + { +// $iSkipClosingPos = 0; + $bIsQuotedParsed = true; + if ($bTreatAsAtom) + { + $sAtomBuilder .= \strtr( + \substr($this->sResponseBuffer, $iPos, $iClosingPos - $iPos + 1), + array('\\\\' => '\\', '\\"' => '"') + ); + } + else + { + $aList[] = \strtr( + \substr($this->sResponseBuffer, $iPos + 1, $iClosingPos - $iPos - 1), + array('\\\\' => '\\', '\\"' => '"') + ); + } + + $iPos = $iClosingPos + 1; + break; + } + } + + if (!$bIsQuotedParsed) + { + $iPos = $iBufferEndIndex; + } + + $sPreviousAtomUpperCase = null; + break; + + case 'GOTO_DEFAULT' === $sChar: + default: + $iCharBlockStartPos = $iPos; + + if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) + { + $iPos = $iBufferEndIndex; + + while ($iPos > $iCharBlockStartPos && $this->sResponseBuffer[$iCharBlockStartPos] === ' ') + { + $iCharBlockStartPos++; + } + } + + $bIsAtomDone = false; + while (!$bIsAtomDone && ($iPos <= $iBufferEndIndex)) + { + $sCharDef = $this->sResponseBuffer[$iPos]; + switch (true) + { + case '[' === $sCharDef: + if (null === $sAtomBuilder) + { + $sAtomBuilder = ''; + } + + $sAtomBuilder .= \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos + 1); + + $iPos++; + $this->iResponseBufParsedPos = $iPos; + + $sListBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, + null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), '['); + + if (null !== $sListBlock) + { + $sAtomBuilder .= $sListBlock.']'; + } + + $iPos = $this->iResponseBufParsedPos; + $iCharBlockStartPos = $iPos; + break; + case ' ' === $sCharDef: + case ')' === $sCharDef && '(' === $sOpenBracket: + case ']' === $sCharDef && '[' === $sOpenBracket: + $bIsAtomDone = true; + break; + default: + $iPos++; + break; + } + } + + if ($iPos > $iCharBlockStartPos || null !== $sAtomBuilder) + { + $sLastCharBlock = \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos); + if (null === $sAtomBuilder) + { + $aList[] = $sLastCharBlock; + $sPreviousAtomUpperCase = $sLastCharBlock; + } + else + { + $sAtomBuilder .= $sLastCharBlock; + + if (!$bTreatAsAtom) + { + $aList[] = $sAtomBuilder; + $sPreviousAtomUpperCase = $sAtomBuilder; + $sAtomBuilder = null; + } + } + + if (null !== $oImapResponse) + { +// if (1 === \count($aList)) + if (!$bCountOneInited && 1 === \count($aList)) +// if (isset($aList[0]) && !isset($aList[1])) // fast 1 === \count($aList) + { + $bCountOneInited = true; + + $oImapResponse->Tag = $aList[0]; + if ('+' === $oImapResponse->Tag) + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::CONTINUATION; + } + else if ('*' === $oImapResponse->Tag) + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNTAGGED; + } + else if ($this->getCurrentTag() === $oImapResponse->Tag) + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::TAGGED; + } + else + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNKNOWN; + } + } +// else if (2 === \count($aList)) + else if (!$bCountTwoInited && 2 === \count($aList)) +// else if (isset($aList[1]) && !isset($aList[2])) // fast 2 === \count($aList) + { + $bCountTwoInited = true; + + $oImapResponse->StatusOrIndex = strtoupper($aList[1]); + + if ($oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::OK || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::NO || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BAD || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BYE || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::PREAUTH) + { + $oImapResponse->IsStatusResponse = true; + } + } + else if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) + { + $oImapResponse->HumanReadable = $sLastCharBlock; + } + else if ($oImapResponse->IsStatusResponse) + { + $oImapResponse->HumanReadable = $sLastCharBlock; + } + } + } + } + } + + $this->iResponseBufParsedPos = $iPos; + if (null !== $oImapResponse) + { + $this->bNeedNext = true; + $this->iResponseBufParsedPos = 0; + } + + if (100000 < $iDebugCount) + { + $this->Logger()->Write('PartialParseOverResult: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); + } + + return $bTreatAsAtom ? $sAtomBuilder : $aList; + } + + /** + * @param string $sParent + * @param string $sLiteralAtomUpperCase + * @param resource $rImapStream + * @param int $iLiteralLen + * + * @return bool + */ + private function partialResponseLiteralCallbackCallable($sParent, $sLiteralAtomUpperCase, $rImapStream, $iLiteralLen) + { + $sLiteralAtomUpperCasePeek = ''; + if (0 === \strpos($sLiteralAtomUpperCase, 'BODY')) + { + $sLiteralAtomUpperCasePeek = \str_replace('BODY', 'BODY.PEEK', $sLiteralAtomUpperCase); + } + + $sFetchKey = ''; + if (\is_array($this->aFetchCallbacks)) + { + if (0 < \strlen($sLiteralAtomUpperCasePeek) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCasePeek])) + { + $sFetchKey = $sLiteralAtomUpperCasePeek; + } + else if (0 < \strlen($sLiteralAtomUpperCase) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCase])) + { + $sFetchKey = $sLiteralAtomUpperCase; + } + } + + $bResult = false; + if (0 < \strlen($sFetchKey) && '' !== $this->aFetchCallbacks[$sFetchKey] && + \is_callable($this->aFetchCallbacks[$sFetchKey])) + { + $rImapLiteralStream = + \MailSo\Base\StreamWrappers\Literal::CreateStream($rImapStream, $iLiteralLen); + + $bResult = true; + $this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. + ' - try to read '.$iLiteralLen.' bytes.', \MailSo\Log\Enumerations\Type::NOTE); + + $this->bRunningCallback = true; + + try + { + \call_user_func($this->aFetchCallbacks[$sFetchKey], + $sParent, $sLiteralAtomUpperCase, $rImapLiteralStream); + } + catch (\Exception $oException) + { + $this->writeLog('Callback Exception', \MailSo\Log\Enumerations\Type::NOTICE); + $this->writeLogException($oException); + } + + if (\is_resource($rImapLiteralStream)) + { + $iNotReadLiteralLen = 0; + + $bFeof = \feof($rImapLiteralStream); + $this->writeLog('End Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. + ' - feof = '.($bFeof ? 'good' : 'BAD'), $bFeof ? + \MailSo\Log\Enumerations\Type::NOTE : \MailSo\Log\Enumerations\Type::WARNING); + + if (!$bFeof) + { + while (!@\feof($rImapLiteralStream)) + { + $sBuf = @\fread($rImapLiteralStream, 1024 * 1024); + if (false === $sBuf || 0 === \strlen($sBuf) || null === $sBuf) + { + break; + } + + \MailSo\Base\Utils::ResetTimeLimit(); + $iNotReadLiteralLen += \strlen($sBuf); + } + + if (\is_resource($rImapLiteralStream) && !@\feof($rImapLiteralStream)) + { + @\stream_get_contents($rImapLiteralStream); + } + } + + if (\is_resource($rImapLiteralStream)) + { + @\fclose($rImapLiteralStream); + } + + if ($iNotReadLiteralLen > 0) + { + $this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', + \MailSo\Log\Enumerations\Type::WARNING); + } + } + else + { + $this->writeLog('Literal stream is not resource after callback.', + \MailSo\Log\Enumerations\Type::WARNING); + } + + \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralLen); + + $this->bRunningCallback = false; + } + + return $bResult; + } + + /** + * @param array $aParams = null + * + * @return string + */ + private function prepearParamLine($aParams = array()) + { + $sReturn = ''; + if (\is_array($aParams) && 0 < \count($aParams)) + { + foreach ($aParams as $mParamItem) + { + if (\is_array($mParamItem) && 0 < \count($mParamItem)) + { + $sReturn .= ' ('.\trim($this->prepearParamLine($mParamItem)).')'; + } + else if (\is_string($mParamItem)) + { + $sReturn .= ' '.$mParamItem; + } + } + } + return $sReturn; + } + + /** + * @return string + */ + private function getNewTag() + { + $this->iTagCount++; + return $this->getCurrentTag(); + } + + /** + * @return string + */ + private function getCurrentTag() + { + return self::TAG_PREFIX.$this->iTagCount; + } + + /** + * @param string $sStringForEscape + * + * @return string + */ + public function EscapeString($sStringForEscape) + { + return '"'.\str_replace(array('\\', '"'), array('\\\\', '\\"'), $sStringForEscape).'"'; + } + + /** + * @return string + */ + protected function getLogName() + { + return 'IMAP'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } + + /** + * @param resource $rConnect + * @param array $aCapabilityItems = array() + * + * @return \MailSo\Imap\ImapClient + */ + public function TestSetValues($rConnect, $aCapabilityItems = array()) + { + $this->rConnect = $rConnect; + $this->aCapabilityItems = $aCapabilityItems; + + return $this; + } + + /** + * @param string $sEndTag = null + * @param string $bFindCapa = false + * + * @return array + */ + public function TestParseResponseWithValidationProxy($sEndTag = null, $bFindCapa = false) + { + return $this->parseResponseWithValidation($sEndTag, $bFindCapa); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php b/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php index 733fe476c..53469eda3 100644 --- a/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php +++ b/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php @@ -1,1417 +1,1417 @@ - - * Copyright (c) 2009, Ian Selby/Gen X Design - * - * Author(s): Ian Selby - * - * Licensed under the MIT License - * Redistributions of files must retain the above copyright notice. - * - * @author Ian Selby - * @copyright Copyright (c) 2009 Gen X Design - * @link http://phpthumb.gxdlabs.com - * @license http://www.opensource.org/licenses/mit-license.php The MIT License - */ - -class GD extends PHPThumb -{ - /** - * The prior image (before manipulation) - * - * @var resource - */ - protected $oldImage; - - /** - * The working image (used during manipulation) - * - * @var resource - */ - protected $workingImage; - - /** - * The current dimensions of the image - * - * @var array - */ - protected $currentDimensions; - - /** - * The new, calculated dimensions of the image - * - * @var array - */ - protected $newDimensions; - - /** - * The options for this class - * - * This array contains various options that determine the behavior in - * various functions throughout the class. Functions note which specific - * option key / values are used in their documentation - * - * @var array - */ - protected $options; - - /** - * The maximum width an image can be after resizing (in pixels) - * - * @var int - */ - protected $maxWidth; - - /** - * The maximum height an image can be after resizing (in pixels) - * - * @var int - */ - protected $maxHeight; - - /** - * The percentage to resize the image by - * - * @var int - */ - protected $percent; - - /** - * @param string $fileName - * @param array $options - * @param array $plugins - */ - public function __construct($fileName, $options = array(), array $plugins = array()) - { - parent::__construct($fileName, $options, $plugins); - - $this->determineFormat(); - $this->verifyFormatCompatiblity(); - - switch ($this->format) { - case 'GIF': - $this->oldImage = @imagecreatefromgif($this->fileName); - break; - case 'JPG': - $this->oldImage = @imagecreatefromjpeg($this->fileName); - break; - case 'PNG': - $this->oldImage = @imagecreatefrompng($this->fileName); - break; - case 'STRING': - $this->oldImage = @imagecreatefromstring($this->fileName); - break; - } - - if (!is_resource($this->oldImage)) - { - throw new \Exception('Invalid image file'); - } - else - { - $this->currentDimensions = array ( - 'width' => imagesx($this->oldImage), - 'height' => imagesy($this->oldImage) - ); - } - } - - public function __destruct() - { - if (is_resource($this->oldImage)) { - imagedestroy($this->oldImage); - } - - if (is_resource($this->workingImage)) { - imagedestroy($this->workingImage); - } - } - - /** - * Pad an image to desired dimensions. Moves the image into the center and fills the rest with $color. - * @param $width - * @param $height - * @param array $color - * @return GD - */ - public function pad($width, $height, $color = array(255, 255, 255)) - { - // no resize - woohoo! - if ($width == $this->currentDimensions['width'] && $height == $this->currentDimensions['height']) { - return $this; - } - - // create the working image - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($width, $height); - } else { - $this->workingImage = imagecreate($width, $height); - } - - // create the fill color - $fillColor = imagecolorallocate( - $this->workingImage, - $color[0], - $color[1], - $color[2] - ); - - // fill our working image with the fill color - imagefill( - $this->workingImage, - 0, - 0, - $fillColor - ); - - // copy the image into the center of our working image - imagecopyresampled( - $this->workingImage, - $this->oldImage, - intval(($width-$this->currentDimensions['width']) / 2), - intval(($height-$this->currentDimensions['height']) / 2), - 0, - 0, - $this->currentDimensions['width'], - $this->currentDimensions['height'], - $this->currentDimensions['width'], - $this->currentDimensions['height'] - ); - - // update all the variables and resources to be correct - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $width; - $this->currentDimensions['height'] = $height; - - return $this; - } - - /** - * Resizes an image to be no larger than $maxWidth or $maxHeight - * - * If either param is set to zero, then that dimension will not be considered as a part of the resize. - * Additionally, if $this->options['resizeUp'] is set to true (false by default), then this function will - * also scale the image up to the maximum dimensions provided. - * - * @param int $maxWidth The maximum width of the image in pixels - * @param int $maxHeight The maximum height of the image in pixels - * @return \PHPThumb\GD - */ - public function resize($maxWidth = 0, $maxHeight = 0) - { - // make sure our arguments are valid - if (!is_numeric($maxWidth)) { - throw new \InvalidArgumentException('$maxWidth must be numeric'); - } - - if (!is_numeric($maxHeight)) { - throw new \InvalidArgumentException('$maxHeight must be numeric'); - } - - // make sure we're not exceeding our image size if we're not supposed to - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($maxHeight) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $maxHeight; - $this->maxWidth = (intval($maxWidth) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $maxWidth; - } else { - $this->maxHeight = intval($maxHeight); - $this->maxWidth = intval($maxWidth); - } - - // get the new dimensions... - $this->calcImageSize($this->currentDimensions['width'], $this->currentDimensions['height']); - - // create the working image - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - } else { - $this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - } - - $this->preserveAlpha(); - - // and create the newly sized image - imagecopyresampled( - $this->workingImage, - $this->oldImage, - 0, - 0, - 0, - 0, - $this->newDimensions['newWidth'], - $this->newDimensions['newHeight'], - $this->currentDimensions['width'], - $this->currentDimensions['height'] - ); - - // update all the variables and resources to be correct - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $this->newDimensions['newWidth']; - $this->currentDimensions['height'] = $this->newDimensions['newHeight']; - - return $this; - } - - /** - * Adaptively Resizes the Image - * - * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the - * remaining overflow (from the center) to get the image to be the size specified - * - * @param int $maxWidth - * @param int $maxHeight - * @return \PHPThumb\GD - */ - public function adaptiveResize($width, $height) - { - // make sure our arguments are valid - if ((!is_numeric($width) || $width == 0) && (!is_numeric($height) || $height == 0)) { - throw new \InvalidArgumentException('$width and $height must be numeric and greater than zero'); - } - - if (!is_numeric($width) || $width == 0) { - $width = ($height * $this->currentDimensions['width']) / $this->currentDimensions['height']; - } - - if (!is_numeric($height) || $height == 0) { - $height = ($width * $this->currentDimensions['height']) / $this->currentDimensions['width']; - } - - // make sure we're not exceeding our image size if we're not supposed to - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; - $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; - } else { - $this->maxHeight = intval($height); - $this->maxWidth = intval($width); - } - - $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); - - // resize the image to be close to our desired dimensions - $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - - // reset the max dimensions... - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; - $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; - } else { - $this->maxHeight = intval($height); - $this->maxWidth = intval($width); - } - - // create the working image - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); - } else { - $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); - } - - $this->preserveAlpha(); - - $cropWidth = $this->maxWidth; - $cropHeight = $this->maxHeight; - $cropX = 0; - $cropY = 0; - - // now, figure out how to crop the rest of the image... - if ($this->currentDimensions['width'] > $this->maxWidth) { - $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2); - } elseif ($this->currentDimensions['height'] > $this->maxHeight) { - $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2); - } - - imagecopyresampled( - $this->workingImage, - $this->oldImage, - 0, - 0, - $cropX, - $cropY, - $cropWidth, - $cropHeight, - $cropWidth, - $cropHeight - ); - - // update all the variables and resources to be correct - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $this->maxWidth; - $this->currentDimensions['height'] = $this->maxHeight; - - return $this; - } - - /** - * Adaptively Resizes the Image and Crops Using a Percentage - * - * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the - * remaining overflow using a provided percentage to get the image to be the size specified. - * - * The percentage mean different things depending on the orientation of the original image. - * - * For Landscape images: - * --------------------- - * - * A percentage of 1 would crop the image all the way to the left, which would be the same as - * using adaptiveResizeQuadrant() with $quadrant = 'L' - * - * A percentage of 50 would crop the image to the center which would be the same as using - * adaptiveResizeQuadrant() with $quadrant = 'C', or even the original adaptiveResize() - * - * A percentage of 100 would crop the image to the image all the way to the right, etc, etc. - * Note that you can use any percentage between 1 and 100. - * - * For Portrait images: - * -------------------- - * - * This works the same as for Landscape images except that a percentage of 1 means top and 100 means bottom - * - * @param int $maxWidth - * @param int $maxHeight - * @param int $percent - * @return \PHPThumb\GD - */ - public function adaptiveResizePercent($width, $height, $percent = 50) - { - // make sure our arguments are valid - if (!is_numeric($width) || $width == 0) { - throw new \InvalidArgumentException('$width must be numeric and greater than zero'); - } - - if (!is_numeric($height) || $height == 0) { - throw new \InvalidArgumentException('$height must be numeric and greater than zero'); - } - - // make sure we're not exceeding our image size if we're not supposed to - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; - $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; - } else { - $this->maxHeight = intval($height); - $this->maxWidth = intval($width); - } - - $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); - - // resize the image to be close to our desired dimensions - $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - - // reset the max dimensions... - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; - $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; - } else { - $this->maxHeight = intval($height); - $this->maxWidth = intval($width); - } - - // create the working image - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); - } else { - $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); - } - - $this->preserveAlpha(); - - $cropWidth = $this->maxWidth; - $cropHeight = $this->maxHeight; - $cropX = 0; - $cropY = 0; - - // Crop the rest of the image using the quadrant - - if ($percent > 100) { - $percent = 100; - } elseif ($percent < 1) { - $percent = 1; - } - - if ($this->currentDimensions['width'] > $this->maxWidth) { - // Image is landscape - $maxCropX = $this->currentDimensions['width'] - $this->maxWidth; - $cropX = intval(($percent / 100) * $maxCropX); - - } elseif ($this->currentDimensions['height'] > $this->maxHeight) { - // Image is portrait - $maxCropY = $this->currentDimensions['height'] - $this->maxHeight; - $cropY = intval(($percent / 100) * $maxCropY); - } - - imagecopyresampled( - $this->workingImage, - $this->oldImage, - 0, - 0, - $cropX, - $cropY, - $cropWidth, - $cropHeight, - $cropWidth, - $cropHeight - ); - - // update all the variables and resources to be correct - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $this->maxWidth; - $this->currentDimensions['height'] = $this->maxHeight; - - return $this; - } - - /** - * Adaptively Resizes the Image and Crops Using a Quadrant - * - * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the - * remaining overflow using the quadrant to get the image to be the size specified. - * - * The quadrants available are Top, Bottom, Center, Left, and Right: - * - * - * +---+---+---+ - * | | T | | - * +---+---+---+ - * | L | C | R | - * +---+---+---+ - * | | B | | - * +---+---+---+ - * - * Note that if your image is Landscape and you choose either of the Top or Bottom quadrants (which won't - * make sence since only the Left and Right would be available, then the Center quadrant will be used - * to crop. This would have exactly the same result as using adaptiveResize(). - * The same goes if your image is portrait and you choose either the Left or Right quadrants. - * - * @param int $maxWidth - * @param int $maxHeight - * @param string $quadrant T, B, C, L, R - * @return \PHPThumb\GD - */ - public function adaptiveResizeQuadrant($width, $height, $quadrant = 'C') - { - // make sure our arguments are valid - if (!is_numeric($width) || $width == 0) { - throw new \InvalidArgumentException('$width must be numeric and greater than zero'); - } - - if (!is_numeric($height) || $height == 0) { - throw new \InvalidArgumentException('$height must be numeric and greater than zero'); - } - - // make sure we're not exceeding our image size if we're not supposed to - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; - $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; - } else { - $this->maxHeight = intval($height); - $this->maxWidth = intval($width); - } - - $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); - - // resize the image to be close to our desired dimensions - $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - - // reset the max dimensions... - if ($this->options['resizeUp'] === false) { - $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; - $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; - } else { - $this->maxHeight = intval($height); - $this->maxWidth = intval($width); - } - - // create the working image - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); - } else { - $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); - } - - $this->preserveAlpha(); - - $cropWidth = $this->maxWidth; - $cropHeight = $this->maxHeight; - $cropX = 0; - $cropY = 0; - - // Crop the rest of the image using the quadrant - - if ($this->currentDimensions['width'] > $this->maxWidth) { - // Image is landscape - switch ($quadrant) { - case 'L': - $cropX = 0; - break; - case 'R': - $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth)); - break; - case 'C': - default: - $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2); - break; - } - } elseif ($this->currentDimensions['height'] > $this->maxHeight) { - // Image is portrait - switch ($quadrant) { - case 'T': - $cropY = 0; - break; - case 'B': - $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight)); - break; - case 'C': - default: - $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2); - break; - } - } - - imagecopyresampled( - $this->workingImage, - $this->oldImage, - 0, - 0, - $cropX, - $cropY, - $cropWidth, - $cropHeight, - $cropWidth, - $cropHeight - ); - - // update all the variables and resources to be correct - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $this->maxWidth; - $this->currentDimensions['height'] = $this->maxHeight; - - return $this; - } - - /** - * Resizes an image by a given percent uniformly, - * Percentage should be whole number representation (i.e. 1-100) - * - * @param int $percent - * @return GD - * @throws \InvalidArgumentException - */ - public function resizePercent($percent = 0) - { - if (!is_numeric($percent)) { - throw new \InvalidArgumentException ('$percent must be numeric'); - } - - $this->percent = intval($percent); - - $this->calcImageSizePercent($this->currentDimensions['width'], $this->currentDimensions['height']); - - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - } else { - $this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); - } - - $this->preserveAlpha(); - - imagecopyresampled( - $this->workingImage, - $this->oldImage, - 0, - 0, - 0, - 0, - $this->newDimensions['newWidth'], - $this->newDimensions['newHeight'], - $this->currentDimensions['width'], - $this->currentDimensions['height'] - ); - - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $this->newDimensions['newWidth']; - $this->currentDimensions['height'] = $this->newDimensions['newHeight']; - - return $this; - } - - /** - * Crops an image from the center with provided dimensions - * - * If no height is given, the width will be used as a height, thus creating a square crop - * - * @param int $cropWidth - * @param int $cropHeight - * @return \PHPThumb\GD - */ - public function cropFromCenter($cropWidth, $cropHeight = null) - { - if (!is_numeric($cropWidth)) { - throw new \InvalidArgumentException('$cropWidth must be numeric'); - } - - if ($cropHeight !== null && !is_numeric($cropHeight)) { - throw new \InvalidArgumentException('$cropHeight must be numeric'); - } - - if ($cropHeight === null) { - $cropHeight = $cropWidth; - } - - $cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth; - $cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight; - - $cropX = intval(($this->currentDimensions['width'] - $cropWidth) / 2); - $cropY = intval(($this->currentDimensions['height'] - $cropHeight) / 2); - - $this->crop($cropX, $cropY, $cropWidth, $cropHeight); - - return $this; - } - - /** - * Vanilla Cropping - Crops from x,y with specified width and height - * - * @param int $startX - * @param int $startY - * @param int $cropWidth - * @param int $cropHeight - * @return \PHPThumb\GD - */ - public function crop($startX, $startY, $cropWidth, $cropHeight) - { - // validate input - if (!is_numeric($startX)) { - throw new \InvalidArgumentException('$startX must be numeric'); - } - - if (!is_numeric($startY)) { - throw new \InvalidArgumentException('$startY must be numeric'); - } - - if (!is_numeric($cropWidth)) { - throw new \InvalidArgumentException('$cropWidth must be numeric'); - } - - if (!is_numeric($cropHeight)) { - throw new \InvalidArgumentException('$cropHeight must be numeric'); - } - - // do some calculations - $cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth; - $cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight; - - // ensure everything's in bounds - if (($startX + $cropWidth) > $this->currentDimensions['width']) { - $startX = ($this->currentDimensions['width'] - $cropWidth); - } - - if (($startY + $cropHeight) > $this->currentDimensions['height']) { - $startY = ($this->currentDimensions['height'] - $cropHeight); - } - - if ($startX < 0) { - $startX = 0; - } - - if ($startY < 0) { - $startY = 0; - } - - // create the working image - if (function_exists('imagecreatetruecolor')) { - $this->workingImage = imagecreatetruecolor($cropWidth, $cropHeight); - } else { - $this->workingImage = imagecreate($cropWidth, $cropHeight); - } - - $this->preserveAlpha(); - - imagecopyresampled( - $this->workingImage, - $this->oldImage, - 0, - 0, - $startX, - $startY, - $cropWidth, - $cropHeight, - $cropWidth, - $cropHeight - ); - - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $cropWidth; - $this->currentDimensions['height'] = $cropHeight; - - return $this; - } - - /** - * Rotates image either 90 degrees clockwise or counter-clockwise - * - * @param string $direction - * @retunrn \PHPThumb\GD - */ - public function rotateImage($direction = 'CW') - { - if ($direction == 'CW') { - $this->rotateImageNDegrees(90); - } else { - $this->rotateImageNDegrees(-90); - } - - return $this; - } - - /** - * Rotates image specified number of degrees - * - * @param int $degrees - * @return \PHPThumb\GD - */ - public function rotateImageNDegrees($degrees) - { - if (!is_numeric($degrees)) { - throw new \InvalidArgumentException('$degrees must be numeric'); - } - - if (!function_exists('imagerotate')) { - throw new \RuntimeException('Your version of GD does not support image rotation'); - } - - $this->workingImage = imagerotate($this->oldImage, $degrees, 0); - - $newWidth = $this->currentDimensions['height']; - $newHeight = $this->currentDimensions['width']; - $this->oldImage = $this->workingImage; - $this->currentDimensions['width'] = $newWidth; - $this->currentDimensions['height'] = $newHeight; - - return $this; - } - - /** - * Applies a filter to the image - * - * @param int $filter - * @return \PHPThumb\GD - */ - public function imageFilter($filter, $arg1 = false, $arg2 = false, $arg3 = false, $arg4 = false) - { - if (!is_numeric($filter)) { - throw new \InvalidArgumentException('$filter must be numeric'); - } - - if (!function_exists('imagefilter')) { - throw new \RuntimeException('Your version of GD does not support image filters'); - } - - $result = false; - if ($arg1 === false) { - $result = imagefilter($this->oldImage, $filter); - } elseif ($arg2 === false) { - $result = imagefilter($this->oldImage, $filter, $arg1); - } elseif ($arg3 === false) { - $result = imagefilter($this->oldImage, $filter, $arg1, $arg2); - } elseif ($arg4 === false) { - $result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3); - } else { - $result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3, $arg4); - } - - if (!$result) { - throw new \RuntimeException('GD imagefilter failed'); - } - - $this->workingImage = $this->oldImage; - - return $this; - } - - /** - * Shows an image - * - * This function will show the current image by first sending the appropriate header - * for the format, and then outputting the image data. If headers have already been sent, - * a runtime exception will be thrown - * - * @param bool $rawData Whether or not the raw image stream should be output - * @return \PHPThumb\GD - */ - public function show($rawData = false) - { - //Execute any plugins - if ($this->plugins) { - foreach ($this->plugins as $plugin) { - /* @var $plugin \PHPThumb\PluginInterface */ - $plugin->execute($this); - } - } - - if (headers_sent() && php_sapi_name() != 'cli') { - throw new \RuntimeException('Cannot show image, headers have already been sent'); - } - - // When the interlace option equals true or false call imageinterlace else leave it to default - if ($this->options['interlace'] === true) { - imageinterlace($this->oldImage, 1); - } elseif ($this->options['interlace'] === false) { - imageinterlace($this->oldImage, 0); - } - - switch ($this->format) { - case 'GIF': - if ($rawData === false) { - header('Content-type: image/gif'); - } - imagegif($this->oldImage); - break; - case 'JPG': - if ($rawData === false) { - header('Content-type: image/jpeg'); - } - imagejpeg($this->oldImage, null, $this->options['jpegQuality']); - break; - case 'PNG': - case 'STRING': - if ($rawData === false) { - header('Content-type: image/png'); - } - imagepng($this->oldImage); - break; - } - - return $this; - } - - /** - * Returns the Working Image as a String - * - * This function is useful for getting the raw image data as a string for storage in - * a database, or other similar things. - * - * @return string - */ - public function getImageAsString() - { - $data = null; - ob_start(); - $this->show(true); - $data = ob_get_contents(); - ob_end_clean(); - - return $data; - } - - /** - * Saves an image - * - * This function will make sure the target directory is writeable, and then save the image. - * - * If the target directory is not writeable, the function will try to correct the permissions (if allowed, this - * is set as an option ($this->options['correctPermissions']). If the target cannot be made writeable, then a - * \RuntimeException is thrown. - * - * @param string $fileName The full path and filename of the image to save - * @param string $format The format to save the image in (optional, must be one of [GIF,JPG,PNG] - * @return \PHPThumb\GD - */ - public function save($fileName, $format = null) - { - $validFormats = array('GIF', 'JPG', 'PNG'); - $format = ($format !== null) ? strtoupper($format) : $this->format; - - if (!in_array($format, $validFormats)) { - throw new \InvalidArgumentException("Invalid format type specified in save function: {$format}"); - } - - // make sure the directory is writeable - if (!is_writeable(dirname($fileName))) { - // try to correct the permissions - if ($this->options['correctPermissions'] === true) { - @chmod(dirname($fileName), 0777); - - // throw an exception if not writeable - if (!is_writeable(dirname($fileName))) { - throw new \RuntimeException("File is not writeable, and could not correct permissions: {$fileName}"); - } - } else { // throw an exception if not writeable - throw new \RuntimeException("File not writeable: {$fileName}"); - } - } - - // When the interlace option equals true or false call imageinterlace else leave it to default - if ($this->options['interlace'] === true) { - imageinterlace($this->oldImage, 1); - } elseif ($this->options['interlace'] === false) { - imageinterlace($this->oldImage, 0); - } - - switch ($format) { - case 'GIF': - imagegif($this->oldImage, $fileName); - break; - case 'JPG': - imagejpeg($this->oldImage, $fileName, $this->options['jpegQuality']); - break; - case 'PNG': - imagepng($this->oldImage, $fileName); - break; - } - - return $this; - } - - ################################# - # ----- GETTERS / SETTERS ----- # - ################################# - - /** - * Sets options for all operations. - * @param array $options - * @return GD - */ - public function setOptions(array $options = array()) - { - // we've yet to init the default options, so create them here - if (sizeof($this->options) == 0) { - $defaultOptions = array( - 'resizeUp' => false, - 'jpegQuality' => 100, - 'correctPermissions' => false, - 'preserveAlpha' => true, - 'alphaMaskColor' => array (255, 255, 255), - 'preserveTransparency' => true, - 'transparencyMaskColor' => array (0, 0, 0), - 'interlace' => null - ); - } else { // otherwise, let's use what we've got already - $defaultOptions = $this->options; - } - - $this->options = array_merge($defaultOptions, $options); - - return $this; - } - - /** - * Returns $currentDimensions. - * - * @see \PHPThumb\GD::$currentDimensions - */ - public function getCurrentDimensions() - { - return $this->currentDimensions; - } - - /** - * @param $currentDimensions - * @return GD - */ - public function setCurrentDimensions($currentDimensions) - { - $this->currentDimensions = $currentDimensions; - - return $this; - } - - /** - * @return int - */ - public function getMaxHeight() - { - return $this->maxHeight; - } - - /** - * @param $maxHeight - * @return GD - */ - public function setMaxHeight($maxHeight) - { - $this->maxHeight = $maxHeight; - - return $this; - } - - /** - * @return int - */ - public function getMaxWidth() - { - return $this->maxWidth; - } - - /** - * @param $maxWidth - * @return GD - */ - public function setMaxWidth($maxWidth) - { - $this->maxWidth = $maxWidth; - - return $this; - } - - /** - * Returns $newDimensions. - * - * @see \PHPThumb\GD::$newDimensions - */ - public function getNewDimensions() - { - return $this->newDimensions; - } - - /** - * Sets $newDimensions. - * - * @param object $newDimensions - * @see \PHPThumb\GD::$newDimensions - */ - public function setNewDimensions($newDimensions) - { - $this->newDimensions = $newDimensions; - - return $this; - } - - /** - * Returns $options. - * - * @see \PHPThumb\GD::$options - */ - public function getOptions() - { - return $this->options; - } - - /** - * Returns $percent. - * - * @see \PHPThumb\GD::$percent - */ - public function getPercent() - { - return $this->percent; - } - - /** - * Sets $percent. - * - * @param object $percent - * @see \PHPThumb\GD::$percent - */ - public function setPercent($percent) - { - $this->percent = $percent; - - return $this; - } - - /** - * Returns $oldImage. - * - * @see \PHPThumb\GD::$oldImage - */ - public function getOldImage() - { - return $this->oldImage; - } - - /** - * Sets $oldImage. - * - * @param object $oldImage - * @see \PHPThumb\GD::$oldImage - */ - public function setOldImage($oldImage) - { - $this->oldImage = $oldImage; - - return $this; - } - - /** - * Returns $workingImage. - * - * @see \PHPThumb\GD::$workingImage - */ - public function getWorkingImage() - { - return $this->workingImage; - } - - /** - * Sets $workingImage. - * - * @param object $workingImage - * @see \PHPThumb\GD::$workingImage - */ - public function setWorkingImage($workingImage) - { - $this->workingImage = $workingImage; - - return $this; - } - - - ################################# - # ----- UTILITY FUNCTIONS ----- # - ################################# - - /** - * Calculates a new width and height for the image based on $this->maxWidth and the provided dimensions - * - * @return array - * @param int $width - * @param int $height - */ - protected function calcWidth($width, $height) - { - $newWidthPercentage = (100 * $this->maxWidth) / $width; - $newHeight = ($height * $newWidthPercentage) / 100; - - return array( - 'newWidth' => intval($this->maxWidth), - 'newHeight' => intval($newHeight) - ); - } - - /** - * Calculates a new width and height for the image based on $this->maxWidth and the provided dimensions - * - * @return array - * @param int $width - * @param int $height - */ - protected function calcHeight($width, $height) - { - $newHeightPercentage = (100 * $this->maxHeight) / $height; - $newWidth = ($width * $newHeightPercentage) / 100; - - return array( - 'newWidth' => ceil($newWidth), - 'newHeight' => ceil($this->maxHeight) - ); - } - - /** - * Calculates a new width and height for the image based on $this->percent and the provided dimensions - * - * @return array - * @param int $width - * @param int $height - */ - protected function calcPercent($width, $height) - { - $newWidth = ($width * $this->percent) / 100; - $newHeight = ($height * $this->percent) / 100; - - return array( - 'newWidth' => ceil($newWidth), - 'newHeight' => ceil($newHeight) - ); - } - - /** - * Calculates the new image dimensions - * - * These calculations are based on both the provided dimensions and $this->maxWidth and $this->maxHeight - * - * @param int $width - * @param int $height - */ - protected function calcImageSize($width, $height) - { - $newSize = array( - 'newWidth' => $width, - 'newHeight' => $height - ); - - if ($this->maxWidth > 0) { - $newSize = $this->calcWidth($width, $height); - - if ($this->maxHeight > 0 && $newSize['newHeight'] > $this->maxHeight) { - $newSize = $this->calcHeight($newSize['newWidth'], $newSize['newHeight']); - } - } - - if ($this->maxHeight > 0) { - $newSize = $this->calcHeight($width, $height); - - if ($this->maxWidth > 0 && $newSize['newWidth'] > $this->maxWidth) { - $newSize = $this->calcWidth($newSize['newWidth'], $newSize['newHeight']); - } - } - - $this->newDimensions = $newSize; - } - - /** - * Calculates new image dimensions, not allowing the width and height to be less than either the max width or height - * - * @param int $width - * @param int $height - */ - protected function calcImageSizeStrict($width, $height) - { - // first, we need to determine what the longest resize dimension is.. - if ($this->maxWidth >= $this->maxHeight) { - // and determine the longest original dimension - if ($width > $height) { - $newDimensions = $this->calcHeight($width, $height); - - if ($newDimensions['newWidth'] < $this->maxWidth) { - $newDimensions = $this->calcWidth($width, $height); - } - } elseif ($height >= $width) { - $newDimensions = $this->calcWidth($width, $height); - - if ($newDimensions['newHeight'] < $this->maxHeight) { - $newDimensions = $this->calcHeight($width, $height); - } - } - } elseif ($this->maxHeight > $this->maxWidth) { - if ($width >= $height) { - $newDimensions = $this->calcWidth($width, $height); - - if ($newDimensions['newHeight'] < $this->maxHeight) { - $newDimensions = $this->calcHeight($width, $height); - } - } elseif ($height > $width) { - $newDimensions = $this->calcHeight($width, $height); - - if ($newDimensions['newWidth'] < $this->maxWidth) { - $newDimensions = $this->calcWidth($width, $height); - } - } - } - - $this->newDimensions = $newDimensions; - } - - /** - * Calculates new dimensions based on $this->percent and the provided dimensions - * - * @param int $width - * @param int $height - */ - protected function calcImageSizePercent($width, $height) - { - if ($this->percent > 0) { - $this->newDimensions = $this->calcPercent($width, $height); - } - } - - /** - * Determines the file format by mime-type - * - * This function will throw exceptions for invalid images / mime-types - * - */ - protected function determineFormat() - { - $formatInfo = getimagesize($this->fileName); - - // non-image files will return false - if ($formatInfo === false) { - if ($this->remoteImage) { - throw new \Exception("Could not determine format of remote image: {$this->fileName}"); - } else { - throw new \Exception("File is not a valid image: {$this->fileName}"); - } - } - - $mimeType = isset($formatInfo['mime']) ? $formatInfo['mime'] : null; - - switch ($mimeType) { - case 'image/gif': - $this->format = 'GIF'; - break; - case 'image/jpeg': - $this->format = 'JPG'; - break; - case 'image/png': - $this->format = 'PNG'; - break; - default: - throw new \Exception("Image format not supported: {$mimeType}"); - } - } - - /** - * Makes sure the correct GD implementation exists for the file type - * - */ - protected function verifyFormatCompatiblity() - { - $isCompatible = true; - $gdInfo = gd_info(); - - switch ($this->format) { - case 'GIF': - $isCompatible = isset($gdInfo['GIF Create Support']); - break; - case 'JPG': - $isCompatible = (isset($gdInfo['JPG Support']) || isset($gdInfo['JPEG Support'])) ? true : false; - break; - case 'PNG': - $isCompatible = isset($gdInfo[$this->format . ' Support']); - break; - default: - $isCompatible = false; - } - - if (!$isCompatible) { - // one last check for "JPEG" instead - $isCompatible = isset($gdInfo['JPEG Support']); - - if (!$isCompatible) { - throw new \Exception("Your GD installation does not support {$this->format} image types"); - } - } - } - - /** - * Preserves the alpha or transparency for PNG and GIF files - * - * Alpha / transparency will not be preserved if the appropriate options are set to false. - * Also, the GIF transparency is pretty skunky (the results aren't awesome), but it works like a - * champ... that's the nature of GIFs tho, so no huge surprise. - * - * This functionality was originally suggested by commenter Aimi (no links / site provided) - Thanks! :) - * - */ - protected function preserveAlpha() - { - if ($this->format == 'PNG' && $this->options['preserveAlpha'] === true) { - imagealphablending($this->workingImage, false); - - $colorTransparent = imagecolorallocatealpha( - $this->workingImage, - $this->options['alphaMaskColor'][0], - $this->options['alphaMaskColor'][1], - $this->options['alphaMaskColor'][2], - 0 - ); - - imagefill($this->workingImage, 0, 0, $colorTransparent); - imagesavealpha($this->workingImage, true); - } - // preserve transparency in GIFs... this is usually pretty rough tho - if ($this->format == 'GIF' && $this->options['preserveTransparency'] === true) { - $colorTransparent = imagecolorallocate( - $this->workingImage, - $this->options['transparencyMaskColor'][0], - $this->options['transparencyMaskColor'][1], - $this->options['transparencyMaskColor'][2] - ); - - imagecolortransparent($this->workingImage, $colorTransparent); - imagetruecolortopalette($this->workingImage, true, 256); - } - } -} + + * Copyright (c) 2009, Ian Selby/Gen X Design + * + * Author(s): Ian Selby + * + * Licensed under the MIT License + * Redistributions of files must retain the above copyright notice. + * + * @author Ian Selby + * @copyright Copyright (c) 2009 Gen X Design + * @link http://phpthumb.gxdlabs.com + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +class GD extends PHPThumb +{ + /** + * The prior image (before manipulation) + * + * @var resource + */ + protected $oldImage; + + /** + * The working image (used during manipulation) + * + * @var resource + */ + protected $workingImage; + + /** + * The current dimensions of the image + * + * @var array + */ + protected $currentDimensions; + + /** + * The new, calculated dimensions of the image + * + * @var array + */ + protected $newDimensions; + + /** + * The options for this class + * + * This array contains various options that determine the behavior in + * various functions throughout the class. Functions note which specific + * option key / values are used in their documentation + * + * @var array + */ + protected $options; + + /** + * The maximum width an image can be after resizing (in pixels) + * + * @var int + */ + protected $maxWidth; + + /** + * The maximum height an image can be after resizing (in pixels) + * + * @var int + */ + protected $maxHeight; + + /** + * The percentage to resize the image by + * + * @var int + */ + protected $percent; + + /** + * @param string $fileName + * @param array $options + * @param array $plugins + */ + public function __construct($fileName, $options = array(), array $plugins = array()) + { + parent::__construct($fileName, $options, $plugins); + + $this->determineFormat(); + $this->verifyFormatCompatiblity(); + + switch ($this->format) { + case 'GIF': + $this->oldImage = @imagecreatefromgif($this->fileName); + break; + case 'JPG': + $this->oldImage = @imagecreatefromjpeg($this->fileName); + break; + case 'PNG': + $this->oldImage = @imagecreatefrompng($this->fileName); + break; + case 'STRING': + $this->oldImage = @imagecreatefromstring($this->fileName); + break; + } + + if (!is_resource($this->oldImage)) + { + throw new \Exception('Invalid image file'); + } + else + { + $this->currentDimensions = array ( + 'width' => imagesx($this->oldImage), + 'height' => imagesy($this->oldImage) + ); + } + } + + public function __destruct() + { + if (is_resource($this->oldImage)) { + imagedestroy($this->oldImage); + } + + if (is_resource($this->workingImage)) { + imagedestroy($this->workingImage); + } + } + + /** + * Pad an image to desired dimensions. Moves the image into the center and fills the rest with $color. + * @param $width + * @param $height + * @param array $color + * @return GD + */ + public function pad($width, $height, $color = array(255, 255, 255)) + { + // no resize - woohoo! + if ($width == $this->currentDimensions['width'] && $height == $this->currentDimensions['height']) { + return $this; + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($width, $height); + } else { + $this->workingImage = imagecreate($width, $height); + } + + // create the fill color + $fillColor = imagecolorallocate( + $this->workingImage, + $color[0], + $color[1], + $color[2] + ); + + // fill our working image with the fill color + imagefill( + $this->workingImage, + 0, + 0, + $fillColor + ); + + // copy the image into the center of our working image + imagecopyresampled( + $this->workingImage, + $this->oldImage, + intval(($width-$this->currentDimensions['width']) / 2), + intval(($height-$this->currentDimensions['height']) / 2), + 0, + 0, + $this->currentDimensions['width'], + $this->currentDimensions['height'], + $this->currentDimensions['width'], + $this->currentDimensions['height'] + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $width; + $this->currentDimensions['height'] = $height; + + return $this; + } + + /** + * Resizes an image to be no larger than $maxWidth or $maxHeight + * + * If either param is set to zero, then that dimension will not be considered as a part of the resize. + * Additionally, if $this->options['resizeUp'] is set to true (false by default), then this function will + * also scale the image up to the maximum dimensions provided. + * + * @param int $maxWidth The maximum width of the image in pixels + * @param int $maxHeight The maximum height of the image in pixels + * @return \PHPThumb\GD + */ + public function resize($maxWidth = 0, $maxHeight = 0) + { + // make sure our arguments are valid + if (!is_numeric($maxWidth)) { + throw new \InvalidArgumentException('$maxWidth must be numeric'); + } + + if (!is_numeric($maxHeight)) { + throw new \InvalidArgumentException('$maxHeight must be numeric'); + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($maxHeight) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $maxHeight; + $this->maxWidth = (intval($maxWidth) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $maxWidth; + } else { + $this->maxHeight = intval($maxHeight); + $this->maxWidth = intval($maxWidth); + } + + // get the new dimensions... + $this->calcImageSize($this->currentDimensions['width'], $this->currentDimensions['height']); + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } else { + $this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } + + $this->preserveAlpha(); + + // and create the newly sized image + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + 0, + 0, + $this->newDimensions['newWidth'], + $this->newDimensions['newHeight'], + $this->currentDimensions['width'], + $this->currentDimensions['height'] + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->newDimensions['newWidth']; + $this->currentDimensions['height'] = $this->newDimensions['newHeight']; + + return $this; + } + + /** + * Adaptively Resizes the Image + * + * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the + * remaining overflow (from the center) to get the image to be the size specified + * + * @param int $maxWidth + * @param int $maxHeight + * @return \PHPThumb\GD + */ + public function adaptiveResize($width, $height) + { + // make sure our arguments are valid + if ((!is_numeric($width) || $width == 0) && (!is_numeric($height) || $height == 0)) { + throw new \InvalidArgumentException('$width and $height must be numeric and greater than zero'); + } + + if (!is_numeric($width) || $width == 0) { + $width = ($height * $this->currentDimensions['width']) / $this->currentDimensions['height']; + } + + if (!is_numeric($height) || $height == 0) { + $height = ($width * $this->currentDimensions['height']) / $this->currentDimensions['width']; + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); + + // resize the image to be close to our desired dimensions + $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + + // reset the max dimensions... + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); + } else { + $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); + } + + $this->preserveAlpha(); + + $cropWidth = $this->maxWidth; + $cropHeight = $this->maxHeight; + $cropX = 0; + $cropY = 0; + + // now, figure out how to crop the rest of the image... + if ($this->currentDimensions['width'] > $this->maxWidth) { + $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2); + } elseif ($this->currentDimensions['height'] > $this->maxHeight) { + $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2); + } + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $cropX, + $cropY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->maxWidth; + $this->currentDimensions['height'] = $this->maxHeight; + + return $this; + } + + /** + * Adaptively Resizes the Image and Crops Using a Percentage + * + * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the + * remaining overflow using a provided percentage to get the image to be the size specified. + * + * The percentage mean different things depending on the orientation of the original image. + * + * For Landscape images: + * --------------------- + * + * A percentage of 1 would crop the image all the way to the left, which would be the same as + * using adaptiveResizeQuadrant() with $quadrant = 'L' + * + * A percentage of 50 would crop the image to the center which would be the same as using + * adaptiveResizeQuadrant() with $quadrant = 'C', or even the original adaptiveResize() + * + * A percentage of 100 would crop the image to the image all the way to the right, etc, etc. + * Note that you can use any percentage between 1 and 100. + * + * For Portrait images: + * -------------------- + * + * This works the same as for Landscape images except that a percentage of 1 means top and 100 means bottom + * + * @param int $maxWidth + * @param int $maxHeight + * @param int $percent + * @return \PHPThumb\GD + */ + public function adaptiveResizePercent($width, $height, $percent = 50) + { + // make sure our arguments are valid + if (!is_numeric($width) || $width == 0) { + throw new \InvalidArgumentException('$width must be numeric and greater than zero'); + } + + if (!is_numeric($height) || $height == 0) { + throw new \InvalidArgumentException('$height must be numeric and greater than zero'); + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); + + // resize the image to be close to our desired dimensions + $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + + // reset the max dimensions... + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); + } else { + $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); + } + + $this->preserveAlpha(); + + $cropWidth = $this->maxWidth; + $cropHeight = $this->maxHeight; + $cropX = 0; + $cropY = 0; + + // Crop the rest of the image using the quadrant + + if ($percent > 100) { + $percent = 100; + } elseif ($percent < 1) { + $percent = 1; + } + + if ($this->currentDimensions['width'] > $this->maxWidth) { + // Image is landscape + $maxCropX = $this->currentDimensions['width'] - $this->maxWidth; + $cropX = intval(($percent / 100) * $maxCropX); + + } elseif ($this->currentDimensions['height'] > $this->maxHeight) { + // Image is portrait + $maxCropY = $this->currentDimensions['height'] - $this->maxHeight; + $cropY = intval(($percent / 100) * $maxCropY); + } + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $cropX, + $cropY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->maxWidth; + $this->currentDimensions['height'] = $this->maxHeight; + + return $this; + } + + /** + * Adaptively Resizes the Image and Crops Using a Quadrant + * + * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the + * remaining overflow using the quadrant to get the image to be the size specified. + * + * The quadrants available are Top, Bottom, Center, Left, and Right: + * + * + * +---+---+---+ + * | | T | | + * +---+---+---+ + * | L | C | R | + * +---+---+---+ + * | | B | | + * +---+---+---+ + * + * Note that if your image is Landscape and you choose either of the Top or Bottom quadrants (which won't + * make sence since only the Left and Right would be available, then the Center quadrant will be used + * to crop. This would have exactly the same result as using adaptiveResize(). + * The same goes if your image is portrait and you choose either the Left or Right quadrants. + * + * @param int $maxWidth + * @param int $maxHeight + * @param string $quadrant T, B, C, L, R + * @return \PHPThumb\GD + */ + public function adaptiveResizeQuadrant($width, $height, $quadrant = 'C') + { + // make sure our arguments are valid + if (!is_numeric($width) || $width == 0) { + throw new \InvalidArgumentException('$width must be numeric and greater than zero'); + } + + if (!is_numeric($height) || $height == 0) { + throw new \InvalidArgumentException('$height must be numeric and greater than zero'); + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); + + // resize the image to be close to our desired dimensions + $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + + // reset the max dimensions... + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); + } else { + $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); + } + + $this->preserveAlpha(); + + $cropWidth = $this->maxWidth; + $cropHeight = $this->maxHeight; + $cropX = 0; + $cropY = 0; + + // Crop the rest of the image using the quadrant + + if ($this->currentDimensions['width'] > $this->maxWidth) { + // Image is landscape + switch ($quadrant) { + case 'L': + $cropX = 0; + break; + case 'R': + $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth)); + break; + case 'C': + default: + $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2); + break; + } + } elseif ($this->currentDimensions['height'] > $this->maxHeight) { + // Image is portrait + switch ($quadrant) { + case 'T': + $cropY = 0; + break; + case 'B': + $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight)); + break; + case 'C': + default: + $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2); + break; + } + } + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $cropX, + $cropY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->maxWidth; + $this->currentDimensions['height'] = $this->maxHeight; + + return $this; + } + + /** + * Resizes an image by a given percent uniformly, + * Percentage should be whole number representation (i.e. 1-100) + * + * @param int $percent + * @return GD + * @throws \InvalidArgumentException + */ + public function resizePercent($percent = 0) + { + if (!is_numeric($percent)) { + throw new \InvalidArgumentException ('$percent must be numeric'); + } + + $this->percent = intval($percent); + + $this->calcImageSizePercent($this->currentDimensions['width'], $this->currentDimensions['height']); + + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } else { + $this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } + + $this->preserveAlpha(); + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + 0, + 0, + $this->newDimensions['newWidth'], + $this->newDimensions['newHeight'], + $this->currentDimensions['width'], + $this->currentDimensions['height'] + ); + + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->newDimensions['newWidth']; + $this->currentDimensions['height'] = $this->newDimensions['newHeight']; + + return $this; + } + + /** + * Crops an image from the center with provided dimensions + * + * If no height is given, the width will be used as a height, thus creating a square crop + * + * @param int $cropWidth + * @param int $cropHeight + * @return \PHPThumb\GD + */ + public function cropFromCenter($cropWidth, $cropHeight = null) + { + if (!is_numeric($cropWidth)) { + throw new \InvalidArgumentException('$cropWidth must be numeric'); + } + + if ($cropHeight !== null && !is_numeric($cropHeight)) { + throw new \InvalidArgumentException('$cropHeight must be numeric'); + } + + if ($cropHeight === null) { + $cropHeight = $cropWidth; + } + + $cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth; + $cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight; + + $cropX = intval(($this->currentDimensions['width'] - $cropWidth) / 2); + $cropY = intval(($this->currentDimensions['height'] - $cropHeight) / 2); + + $this->crop($cropX, $cropY, $cropWidth, $cropHeight); + + return $this; + } + + /** + * Vanilla Cropping - Crops from x,y with specified width and height + * + * @param int $startX + * @param int $startY + * @param int $cropWidth + * @param int $cropHeight + * @return \PHPThumb\GD + */ + public function crop($startX, $startY, $cropWidth, $cropHeight) + { + // validate input + if (!is_numeric($startX)) { + throw new \InvalidArgumentException('$startX must be numeric'); + } + + if (!is_numeric($startY)) { + throw new \InvalidArgumentException('$startY must be numeric'); + } + + if (!is_numeric($cropWidth)) { + throw new \InvalidArgumentException('$cropWidth must be numeric'); + } + + if (!is_numeric($cropHeight)) { + throw new \InvalidArgumentException('$cropHeight must be numeric'); + } + + // do some calculations + $cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth; + $cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight; + + // ensure everything's in bounds + if (($startX + $cropWidth) > $this->currentDimensions['width']) { + $startX = ($this->currentDimensions['width'] - $cropWidth); + } + + if (($startY + $cropHeight) > $this->currentDimensions['height']) { + $startY = ($this->currentDimensions['height'] - $cropHeight); + } + + if ($startX < 0) { + $startX = 0; + } + + if ($startY < 0) { + $startY = 0; + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($cropWidth, $cropHeight); + } else { + $this->workingImage = imagecreate($cropWidth, $cropHeight); + } + + $this->preserveAlpha(); + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $startX, + $startY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $cropWidth; + $this->currentDimensions['height'] = $cropHeight; + + return $this; + } + + /** + * Rotates image either 90 degrees clockwise or counter-clockwise + * + * @param string $direction + * @retunrn \PHPThumb\GD + */ + public function rotateImage($direction = 'CW') + { + if ($direction == 'CW') { + $this->rotateImageNDegrees(90); + } else { + $this->rotateImageNDegrees(-90); + } + + return $this; + } + + /** + * Rotates image specified number of degrees + * + * @param int $degrees + * @return \PHPThumb\GD + */ + public function rotateImageNDegrees($degrees) + { + if (!is_numeric($degrees)) { + throw new \InvalidArgumentException('$degrees must be numeric'); + } + + if (!function_exists('imagerotate')) { + throw new \RuntimeException('Your version of GD does not support image rotation'); + } + + $this->workingImage = imagerotate($this->oldImage, $degrees, 0); + + $newWidth = $this->currentDimensions['height']; + $newHeight = $this->currentDimensions['width']; + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $newWidth; + $this->currentDimensions['height'] = $newHeight; + + return $this; + } + + /** + * Applies a filter to the image + * + * @param int $filter + * @return \PHPThumb\GD + */ + public function imageFilter($filter, $arg1 = false, $arg2 = false, $arg3 = false, $arg4 = false) + { + if (!is_numeric($filter)) { + throw new \InvalidArgumentException('$filter must be numeric'); + } + + if (!function_exists('imagefilter')) { + throw new \RuntimeException('Your version of GD does not support image filters'); + } + + $result = false; + if ($arg1 === false) { + $result = imagefilter($this->oldImage, $filter); + } elseif ($arg2 === false) { + $result = imagefilter($this->oldImage, $filter, $arg1); + } elseif ($arg3 === false) { + $result = imagefilter($this->oldImage, $filter, $arg1, $arg2); + } elseif ($arg4 === false) { + $result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3); + } else { + $result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3, $arg4); + } + + if (!$result) { + throw new \RuntimeException('GD imagefilter failed'); + } + + $this->workingImage = $this->oldImage; + + return $this; + } + + /** + * Shows an image + * + * This function will show the current image by first sending the appropriate header + * for the format, and then outputting the image data. If headers have already been sent, + * a runtime exception will be thrown + * + * @param bool $rawData Whether or not the raw image stream should be output + * @return \PHPThumb\GD + */ + public function show($rawData = false) + { + //Execute any plugins + if ($this->plugins) { + foreach ($this->plugins as $plugin) { + /* @var $plugin \PHPThumb\PluginInterface */ + $plugin->execute($this); + } + } + + if (headers_sent() && php_sapi_name() != 'cli') { + throw new \RuntimeException('Cannot show image, headers have already been sent'); + } + + // When the interlace option equals true or false call imageinterlace else leave it to default + if ($this->options['interlace'] === true) { + imageinterlace($this->oldImage, 1); + } elseif ($this->options['interlace'] === false) { + imageinterlace($this->oldImage, 0); + } + + switch ($this->format) { + case 'GIF': + if ($rawData === false) { + header('Content-type: image/gif'); + } + imagegif($this->oldImage); + break; + case 'JPG': + if ($rawData === false) { + header('Content-type: image/jpeg'); + } + imagejpeg($this->oldImage, null, $this->options['jpegQuality']); + break; + case 'PNG': + case 'STRING': + if ($rawData === false) { + header('Content-type: image/png'); + } + imagepng($this->oldImage); + break; + } + + return $this; + } + + /** + * Returns the Working Image as a String + * + * This function is useful for getting the raw image data as a string for storage in + * a database, or other similar things. + * + * @return string + */ + public function getImageAsString() + { + $data = null; + ob_start(); + $this->show(true); + $data = ob_get_contents(); + ob_end_clean(); + + return $data; + } + + /** + * Saves an image + * + * This function will make sure the target directory is writeable, and then save the image. + * + * If the target directory is not writeable, the function will try to correct the permissions (if allowed, this + * is set as an option ($this->options['correctPermissions']). If the target cannot be made writeable, then a + * \RuntimeException is thrown. + * + * @param string $fileName The full path and filename of the image to save + * @param string $format The format to save the image in (optional, must be one of [GIF,JPG,PNG] + * @return \PHPThumb\GD + */ + public function save($fileName, $format = null) + { + $validFormats = array('GIF', 'JPG', 'PNG'); + $format = ($format !== null) ? strtoupper($format) : $this->format; + + if (!in_array($format, $validFormats)) { + throw new \InvalidArgumentException("Invalid format type specified in save function: {$format}"); + } + + // make sure the directory is writeable + if (!is_writeable(dirname($fileName))) { + // try to correct the permissions + if ($this->options['correctPermissions'] === true) { + @chmod(dirname($fileName), 0777); + + // throw an exception if not writeable + if (!is_writeable(dirname($fileName))) { + throw new \RuntimeException("File is not writeable, and could not correct permissions: {$fileName}"); + } + } else { // throw an exception if not writeable + throw new \RuntimeException("File not writeable: {$fileName}"); + } + } + + // When the interlace option equals true or false call imageinterlace else leave it to default + if ($this->options['interlace'] === true) { + imageinterlace($this->oldImage, 1); + } elseif ($this->options['interlace'] === false) { + imageinterlace($this->oldImage, 0); + } + + switch ($format) { + case 'GIF': + imagegif($this->oldImage, $fileName); + break; + case 'JPG': + imagejpeg($this->oldImage, $fileName, $this->options['jpegQuality']); + break; + case 'PNG': + imagepng($this->oldImage, $fileName); + break; + } + + return $this; + } + + ################################# + # ----- GETTERS / SETTERS ----- # + ################################# + + /** + * Sets options for all operations. + * @param array $options + * @return GD + */ + public function setOptions(array $options = array()) + { + // we've yet to init the default options, so create them here + if (sizeof($this->options) == 0) { + $defaultOptions = array( + 'resizeUp' => false, + 'jpegQuality' => 100, + 'correctPermissions' => false, + 'preserveAlpha' => true, + 'alphaMaskColor' => array (255, 255, 255), + 'preserveTransparency' => true, + 'transparencyMaskColor' => array (0, 0, 0), + 'interlace' => null + ); + } else { // otherwise, let's use what we've got already + $defaultOptions = $this->options; + } + + $this->options = array_merge($defaultOptions, $options); + + return $this; + } + + /** + * Returns $currentDimensions. + * + * @see \PHPThumb\GD::$currentDimensions + */ + public function getCurrentDimensions() + { + return $this->currentDimensions; + } + + /** + * @param $currentDimensions + * @return GD + */ + public function setCurrentDimensions($currentDimensions) + { + $this->currentDimensions = $currentDimensions; + + return $this; + } + + /** + * @return int + */ + public function getMaxHeight() + { + return $this->maxHeight; + } + + /** + * @param $maxHeight + * @return GD + */ + public function setMaxHeight($maxHeight) + { + $this->maxHeight = $maxHeight; + + return $this; + } + + /** + * @return int + */ + public function getMaxWidth() + { + return $this->maxWidth; + } + + /** + * @param $maxWidth + * @return GD + */ + public function setMaxWidth($maxWidth) + { + $this->maxWidth = $maxWidth; + + return $this; + } + + /** + * Returns $newDimensions. + * + * @see \PHPThumb\GD::$newDimensions + */ + public function getNewDimensions() + { + return $this->newDimensions; + } + + /** + * Sets $newDimensions. + * + * @param object $newDimensions + * @see \PHPThumb\GD::$newDimensions + */ + public function setNewDimensions($newDimensions) + { + $this->newDimensions = $newDimensions; + + return $this; + } + + /** + * Returns $options. + * + * @see \PHPThumb\GD::$options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns $percent. + * + * @see \PHPThumb\GD::$percent + */ + public function getPercent() + { + return $this->percent; + } + + /** + * Sets $percent. + * + * @param object $percent + * @see \PHPThumb\GD::$percent + */ + public function setPercent($percent) + { + $this->percent = $percent; + + return $this; + } + + /** + * Returns $oldImage. + * + * @see \PHPThumb\GD::$oldImage + */ + public function getOldImage() + { + return $this->oldImage; + } + + /** + * Sets $oldImage. + * + * @param object $oldImage + * @see \PHPThumb\GD::$oldImage + */ + public function setOldImage($oldImage) + { + $this->oldImage = $oldImage; + + return $this; + } + + /** + * Returns $workingImage. + * + * @see \PHPThumb\GD::$workingImage + */ + public function getWorkingImage() + { + return $this->workingImage; + } + + /** + * Sets $workingImage. + * + * @param object $workingImage + * @see \PHPThumb\GD::$workingImage + */ + public function setWorkingImage($workingImage) + { + $this->workingImage = $workingImage; + + return $this; + } + + + ################################# + # ----- UTILITY FUNCTIONS ----- # + ################################# + + /** + * Calculates a new width and height for the image based on $this->maxWidth and the provided dimensions + * + * @return array + * @param int $width + * @param int $height + */ + protected function calcWidth($width, $height) + { + $newWidthPercentage = (100 * $this->maxWidth) / $width; + $newHeight = ($height * $newWidthPercentage) / 100; + + return array( + 'newWidth' => intval($this->maxWidth), + 'newHeight' => intval($newHeight) + ); + } + + /** + * Calculates a new width and height for the image based on $this->maxWidth and the provided dimensions + * + * @return array + * @param int $width + * @param int $height + */ + protected function calcHeight($width, $height) + { + $newHeightPercentage = (100 * $this->maxHeight) / $height; + $newWidth = ($width * $newHeightPercentage) / 100; + + return array( + 'newWidth' => ceil($newWidth), + 'newHeight' => ceil($this->maxHeight) + ); + } + + /** + * Calculates a new width and height for the image based on $this->percent and the provided dimensions + * + * @return array + * @param int $width + * @param int $height + */ + protected function calcPercent($width, $height) + { + $newWidth = ($width * $this->percent) / 100; + $newHeight = ($height * $this->percent) / 100; + + return array( + 'newWidth' => ceil($newWidth), + 'newHeight' => ceil($newHeight) + ); + } + + /** + * Calculates the new image dimensions + * + * These calculations are based on both the provided dimensions and $this->maxWidth and $this->maxHeight + * + * @param int $width + * @param int $height + */ + protected function calcImageSize($width, $height) + { + $newSize = array( + 'newWidth' => $width, + 'newHeight' => $height + ); + + if ($this->maxWidth > 0) { + $newSize = $this->calcWidth($width, $height); + + if ($this->maxHeight > 0 && $newSize['newHeight'] > $this->maxHeight) { + $newSize = $this->calcHeight($newSize['newWidth'], $newSize['newHeight']); + } + } + + if ($this->maxHeight > 0) { + $newSize = $this->calcHeight($width, $height); + + if ($this->maxWidth > 0 && $newSize['newWidth'] > $this->maxWidth) { + $newSize = $this->calcWidth($newSize['newWidth'], $newSize['newHeight']); + } + } + + $this->newDimensions = $newSize; + } + + /** + * Calculates new image dimensions, not allowing the width and height to be less than either the max width or height + * + * @param int $width + * @param int $height + */ + protected function calcImageSizeStrict($width, $height) + { + // first, we need to determine what the longest resize dimension is.. + if ($this->maxWidth >= $this->maxHeight) { + // and determine the longest original dimension + if ($width > $height) { + $newDimensions = $this->calcHeight($width, $height); + + if ($newDimensions['newWidth'] < $this->maxWidth) { + $newDimensions = $this->calcWidth($width, $height); + } + } elseif ($height >= $width) { + $newDimensions = $this->calcWidth($width, $height); + + if ($newDimensions['newHeight'] < $this->maxHeight) { + $newDimensions = $this->calcHeight($width, $height); + } + } + } elseif ($this->maxHeight > $this->maxWidth) { + if ($width >= $height) { + $newDimensions = $this->calcWidth($width, $height); + + if ($newDimensions['newHeight'] < $this->maxHeight) { + $newDimensions = $this->calcHeight($width, $height); + } + } elseif ($height > $width) { + $newDimensions = $this->calcHeight($width, $height); + + if ($newDimensions['newWidth'] < $this->maxWidth) { + $newDimensions = $this->calcWidth($width, $height); + } + } + } + + $this->newDimensions = $newDimensions; + } + + /** + * Calculates new dimensions based on $this->percent and the provided dimensions + * + * @param int $width + * @param int $height + */ + protected function calcImageSizePercent($width, $height) + { + if ($this->percent > 0) { + $this->newDimensions = $this->calcPercent($width, $height); + } + } + + /** + * Determines the file format by mime-type + * + * This function will throw exceptions for invalid images / mime-types + * + */ + protected function determineFormat() + { + $formatInfo = getimagesize($this->fileName); + + // non-image files will return false + if ($formatInfo === false) { + if ($this->remoteImage) { + throw new \Exception("Could not determine format of remote image: {$this->fileName}"); + } else { + throw new \Exception("File is not a valid image: {$this->fileName}"); + } + } + + $mimeType = isset($formatInfo['mime']) ? $formatInfo['mime'] : null; + + switch ($mimeType) { + case 'image/gif': + $this->format = 'GIF'; + break; + case 'image/jpeg': + $this->format = 'JPG'; + break; + case 'image/png': + $this->format = 'PNG'; + break; + default: + throw new \Exception("Image format not supported: {$mimeType}"); + } + } + + /** + * Makes sure the correct GD implementation exists for the file type + * + */ + protected function verifyFormatCompatiblity() + { + $isCompatible = true; + $gdInfo = gd_info(); + + switch ($this->format) { + case 'GIF': + $isCompatible = isset($gdInfo['GIF Create Support']); + break; + case 'JPG': + $isCompatible = (isset($gdInfo['JPG Support']) || isset($gdInfo['JPEG Support'])) ? true : false; + break; + case 'PNG': + $isCompatible = isset($gdInfo[$this->format . ' Support']); + break; + default: + $isCompatible = false; + } + + if (!$isCompatible) { + // one last check for "JPEG" instead + $isCompatible = isset($gdInfo['JPEG Support']); + + if (!$isCompatible) { + throw new \Exception("Your GD installation does not support {$this->format} image types"); + } + } + } + + /** + * Preserves the alpha or transparency for PNG and GIF files + * + * Alpha / transparency will not be preserved if the appropriate options are set to false. + * Also, the GIF transparency is pretty skunky (the results aren't awesome), but it works like a + * champ... that's the nature of GIFs tho, so no huge surprise. + * + * This functionality was originally suggested by commenter Aimi (no links / site provided) - Thanks! :) + * + */ + protected function preserveAlpha() + { + if ($this->format == 'PNG' && $this->options['preserveAlpha'] === true) { + imagealphablending($this->workingImage, false); + + $colorTransparent = imagecolorallocatealpha( + $this->workingImage, + $this->options['alphaMaskColor'][0], + $this->options['alphaMaskColor'][1], + $this->options['alphaMaskColor'][2], + 0 + ); + + imagefill($this->workingImage, 0, 0, $colorTransparent); + imagesavealpha($this->workingImage, true); + } + // preserve transparency in GIFs... this is usually pretty rough tho + if ($this->format == 'GIF' && $this->options['preserveTransparency'] === true) { + $colorTransparent = imagecolorallocate( + $this->workingImage, + $this->options['transparencyMaskColor'][0], + $this->options['transparencyMaskColor'][1], + $this->options['transparencyMaskColor'][2] + ); + + imagecolortransparent($this->workingImage, $colorTransparent); + imagetruecolortopalette($this->workingImage, true, 256); + } + } +} diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html index 2c161b0a2..18b4f9804 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html @@ -26,9 +26,11 @@ style="padding-right: 35px;" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" data-bind="textInput: password, disable: submitRequest" /> - + - + diff --git a/rainloop/v/0.0.0/app/templates/Views/User/Login.html b/rainloop/v/0.0.0/app/templates/Views/User/Login.html index b0e79aca1..bf0ddbc59 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/Login.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/Login.html @@ -46,7 +46,9 @@ data-bind="textInput: password, hasFocus: passwordFocus, disable: submitRequest" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" /> - + diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html index c681bac63..86548e338 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -80,22 +80,21 @@