commit a93599babe83d2acbf8bb828154da0cf4ef5e0a9
Author: Bram Prieshof (PLACEHOLDERDATA) Welcome to BackupMail!
+Getting to older revision
+
+
The system syncs each wensday at 1:00am with the online mail server
+The revisions are managed by the EXTERNAL version managmentDemos's mail
+
+
+
+
+
+ Password: Bemyguest123
+
+ Original address Internalname
+demouser@outlook.com demooutlook Accessing this backup
+
Using mail client
+
+
+
+ SSL/TLS:
+ no
+
+
+ Protocol:
+ imap only
+
+
+ Port:
+ 143
+
+
+ Authentication:
+ plain password
+
+
+ Hostname:
+ 192.168.2.185
+
+
+Username:
+ listed above as internal name, do not add a @domain to the name
+
+if you are missing folders please check your client if it is subscibed to them
+
+
]*><\/p>/i', + '/]*>/i', + '/<\?xml [^>]*\?>/i' + ), '', $sHtml); + } + + /** + * @param mixed $oDom + */ + public static function ClearComments(&$oDom) + { + $aRemove = array(); + + $oXpath = new \DOMXpath($oDom); + $oComments = $oXpath->query('//comment()'); + if ($oComments) + { + foreach ($oComments as $oComment) + { + $aRemove[] = $oComment; + } + } + + unset($oXpath, $oComments); + + foreach ($aRemove as /* @var $oElement \DOMElement */ $oElement) + { + if (isset($oElement->parentNode)) + { + @$oElement->parentNode->removeChild($oElement); + } + } + } + + /** + * @param mixed $oDom + * @param bool $bClearStyleAndHead = true + */ + public static function ClearTags(&$oDom, $bClearStyleAndHead = true) + { + $aRemoveTags = array( + 'svg', 'link', 'base', 'meta', 'title', 'x-script', 'script', 'bgsound', 'keygen', 'source', + 'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio', 'area', 'map' + ); + + if ($bClearStyleAndHead) + { + $aRemoveTags[] = 'head'; + $aRemoveTags[] = 'style'; + } + + $aHtmlAllowedTags = isset(\MailSo\Config::$HtmlStrictAllowedTags) && + \is_array(\MailSo\Config::$HtmlStrictAllowedTags) && 0 < \count(\MailSo\Config::$HtmlStrictAllowedTags) ? + \MailSo\Config::$HtmlStrictAllowedTags : null; + + $aRemove = array(); + $aNodes = $oDom->getElementsByTagName('*'); + foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement) + { + if ($oElement) + { + $sTagNameLower = \trim(\strtolower($oElement->tagName)); + if ('' !== $sTagNameLower) + { + if (\in_array($sTagNameLower, $aRemoveTags) || ($aHtmlAllowedTags && !\in_array($sTagNameLower, $aHtmlAllowedTags))) + { + $aRemove[] = @$oElement; + } + } + } + } + + foreach ($aRemove as /* @var $oElement \DOMElement */ $oElement) + { + if (isset($oElement->parentNode)) + { + @$oElement->parentNode->removeChild($oElement); + } + } + } + +/* +// public static function ClearStyleUrlValueParserHelper($oUrlValue, $oRule, $oRuleSet, +// $oElem = null, +// &$bHasExternals = false, &$aFoundCIDs = array(), +// $aContentLocationUrls = array(), &$aFoundedContentLocationUrls = array(), +// $bDoNotReplaceExternalUrl = false, $fAdditionalExternalFilter = null +// ) +// { +// if ($oUrlValue instanceof \Sabberworm\CSS\Value\URL) +// { +// $oNewRule = new \Sabberworm\CSS\Rule\Rule('x-rl-orig-'.$oRule->getRule()); +// $oNewRule->setValue((string) $oRule->getValue()); +// $oNewRule->setIsImportant($oRule->getIsImportant()); +// +// $oRuleSet->addRule($oNewRule); +// +// $oUrl = $oUrlValue->getURL(); +// $sUrl = $oUrl ? $oUrl->getString() : ''; +// +// if ('cid:' === \strtolower(\substr($sUrl, 0, 4))) +// { +// $aFoundCIDs[] = \substr($sUrl, 4); +// +// $oRule->setRule('x-rl-mod-'.$oRule->getRule()); +// +// if ($oElem) +// { +// $oElem->setAttribute('data-x-style-mod', '1'); +// } +// } +// else +// { +// if (\preg_match('/http[s]?:\/\//i', $sUrl) || '//' === \substr($sUrl, 0, 2)) +// { +// $oRule->setRule('x-rl-mod-'.$oRule->getRule()); +// +// if (\in_array($sUrl, $aContentLocationUrls)) +// { +// $aFoundedContentLocationUrls[] = $sUrl; +// } +// else +// { +// $bHasExternals = true; +// if (!$bDoNotReplaceExternalUrl) +// { +// if ($fAdditionalExternalFilter) +// { +// $sAdditionalResult = \call_user_func($fAdditionalExternalFilter, $sUrl); +// if (0 < \strlen($sAdditionalResult) && $oUrl) +// { +// $oUrl->setString($sAdditionalResult); +// } +// } +// } +// } +// +// if ($oElem) +// { +// $oElem->setAttribute('data-x-style-mod', '1'); +// } +// } +// else if ('data:image/' !== \strtolower(\substr(\trim($sUrl), 0, 11))) +// { +// $oRuleSet->removeRule($oRule); +// } +// } +// } +// else if ($oRule instanceof \Sabberworm\CSS\Rule\Rule) +// { +// if ('x-rl-' !== \substr($oRule->getRule(), 0, 5)) +// { +// $oValue = $oRule->getValue(); +// if ($oValue instanceof \Sabberworm\CSS\Value\URL) +// { +// \MailSo\Base\HtmlUtils::ClearStyleUrlValueParserHelper($oValue, $oRule, $oRuleSet, $oElem, +// $bHasExternals, $aFoundCIDs, +// $aContentLocationUrls, $aFoundedContentLocationUrls, +// $bDoNotReplaceExternalUrl, $fAdditionalExternalFilter); +// } +// else if ($oValue instanceof \Sabberworm\CSS\Value\RuleValueList) +// { +// $aComps = $oValue->getListComponents(); +// foreach ($aComps as $oValue) +// { +// if ($oValue instanceof \Sabberworm\CSS\Value\URL) +// { +// \MailSo\Base\HtmlUtils::ClearStyleUrlValueParserHelper($oValue, $oRule, $oRuleSet, $oElem, +// $bHasExternals, $aFoundCIDs, +// $aContentLocationUrls, $aFoundedContentLocationUrls, +// $bDoNotReplaceExternalUrl, $fAdditionalExternalFilter); +// } +// } +// } +// } +// } +// } +// +// public static function ClearStyleSmart($sStyle, $oElement = null, +// &$bHasExternals = false, &$aFoundCIDs = array(), +// $aContentLocationUrls = array(), &$aFoundedContentLocationUrls = array(), +// $bDoNotReplaceExternalUrl = false, $fAdditionalExternalFilter = null, +// $sSelectorPrefix = '') +// { +// $mResult = false; +// $oCss = null; +// +// if (!\class_exists('Sabberworm\CSS\Parser')) +// { +// return $mResult; +// } +// +// $sStyle = \trim($sStyle); +// if (empty($sStyle)) +// { +// return ''; +// } +// +// $sStyle = \trim(\preg_replace('/[\r\n\t\s]+/', ' ', $sStyle)); +// +// try +// { +// $oSettings = \Sabberworm\CSS\Settings::create(); +// $oSettings->beStrict(); +// $oSettings->withMultibyteSupport(false); +// +// $oCssParser = new \Sabberworm\CSS\Parser($sStyle, $oSettings); +// $oCss = $oCssParser->parse(); +// } +// catch (\Exception $oEception) +// { +// unset($oEception); +// $mResult = false; +// } +// +// if ($oCss) +// { +// foreach ($oCss->getAllDeclarationBlocks() as $oBlock) +// { +// foreach($oBlock->getSelectors() as $oSelector) +// { +// $sS = ' '.\trim($oSelector->getSelector()).' '; +// $sS = \preg_replace('/ body([\.# ])/i', ' [data-x-div-type="body"]$1', $sS); +// $sS = \preg_replace('/ html([\.# ])/i', ' [data-x-div-type="html"]$1', $sS); +// +// if (0 < \strlen($sSelectorPrefix)) +// { +// $sS = \trim($sSelectorPrefix.' '.\trim($sS)); +// } +// +// $oSelector->setSelector(\trim($sS)); +// } +// } +// +// $aRulesToRemove = array( +// 'pointer-events', 'content', 'behavior', 'cursor', +// ); +// +// foreach($oCss->getAllRuleSets() as $oRuleSet) +// { +// foreach ($aRulesToRemove as $sRuleToRemove) +// { +// $oRuleSet->removeRule($sRuleToRemove); +// } +// +// // position: fixed -> position: fixed -> absolute +// $aRules = $oRuleSet->getRules('position'); +// if (\is_array($aRules)) +// { +// foreach ($aRules as $oRule) +// { +// $mValue = $oRule->getValue(); +// if (\is_string($mValue) && 'fixed' === \trim(\strtolower($mValue))) +// { +// $oRule->setValue('absolute'); +// } +// } +// } +// } +// +// foreach($oCss->getAllDeclarationBlocks() as $oRuleSet) +// { +// if ($oRuleSet instanceof \Sabberworm\CSS\RuleSet\RuleSet) +// { +// if ($oRuleSet instanceof \Sabberworm\CSS\RuleSet\DeclarationBlock) +// { +// $oRuleSet->expandBackgroundShorthand(); +// $oRuleSet->expandListStyleShorthand(); +// } +// +// $aRules = $oRuleSet->getRules(); +// if (\is_array($aRules) && 0 < \count($aRules)) +// { +// foreach ($aRules as $oRule) +// { +// if ($oRule instanceof \Sabberworm\CSS\Rule\Rule) +// { +// \MailSo\Base\HtmlUtils::ClearStyleUrlValueParserHelper(null, $oRule, $oRuleSet, +// $oElement, +// $bHasExternals, $aFoundCIDs, +// $aContentLocationUrls, $aFoundedContentLocationUrls, +// $bDoNotReplaceExternalUrl, $fAdditionalExternalFilter +// ); +// } +// } +// } +// } +// } +// +// try +// { +// $mResult = $oCss->render(\Sabberworm\CSS\OutputFormat::createCompact()); +// } +// catch (\Exception $oEception) +// { +// unset($oEception); +// $mResult = false; +// } +// } +// +// return $mResult; +// } +*/ + + /** + * + * @param string $sStyle + * @param \DOMElement $oElement + * @param bool $bHasExternals + * @param array $aFoundCIDs + * @param array $aContentLocationUrls + * @param array $aFoundedContentLocationUrls + * @param bool $bDoNotReplaceExternalUrl = false + * @param callback|null $fAdditionalExternalFilter = null + * + * @return string + */ + public static function ClearStyle($sStyle, $oElement, &$bHasExternals, &$aFoundCIDs, + $aContentLocationUrls, &$aFoundedContentLocationUrls, $bDoNotReplaceExternalUrl = false, $fAdditionalExternalFilter = null) + { + $sStyle = \trim($sStyle); + $aOutStyles = array(); + $aStyles = \explode(';', $sStyle); + + if ($fAdditionalExternalFilter && !\is_callable($fAdditionalExternalFilter)) + { + $fAdditionalExternalFilter = null; + } + + $aMatch = array(); + foreach ($aStyles as $sStyleItem) + { + $aStyleValue = \explode(':', $sStyleItem, 2); + $sName = \trim(\strtolower($aStyleValue[0])); + $sValue = isset($aStyleValue[1]) ? \trim($aStyleValue[1]) : ''; + + if ('position' === $sName && 'fixed' === \strtolower($sValue)) + { + $sValue = 'absolute'; + } + + if (0 === \strlen($sName) || 0 === \strlen($sValue)) + { + continue; + } + + $sStyleItem = $sName.': '.$sValue; + $aStyleValue = array($sName, $sValue); + + /*if (\in_array($sName, array('position', 'left', 'right', 'top', 'bottom', 'behavior', 'cursor'))) + { + // skip + } + else */if (\in_array($sName, array('behavior', 'pointer-events')) || + ('cursor' === $sName && !\in_array(\strtolower($sValue), array('none', 'cursor'))) || + ('display' === $sName && 'none' === \strtolower($sValue)) || + \preg_match('/expression/i', $sValue) || + ('text-indent' === $sName && '-' === \substr(trim($sValue), 0, 1)) + ) + { + // skip + } + else if (\in_array($sName, array('background-image', 'background', 'list-style', 'list-style-image', 'content')) + && \preg_match('/url[\s]?\(([^)]+)\)/im', $sValue, $aMatch) && !empty($aMatch[1])) + { + $sFullUrl = \trim($aMatch[0], '"\' '); + $sUrl = \trim($aMatch[1], '"\' '); + $sStyleValue = \trim(\preg_replace('/[\s]+/', ' ', \str_replace($sFullUrl, '', $sValue))); + $sStyleItem = empty($sStyleValue) ? '' : $sName.': '.$sStyleValue; + + if ('cid:' === \strtolower(\substr($sUrl, 0, 4))) + { + if ($oElement) + { + $oElement->setAttribute('data-x-style-cid-name', + 'background' === $sName ? 'background-image' : $sName); + + $oElement->setAttribute('data-x-style-cid', \substr($sUrl, 4)); + + $aFoundCIDs[] = \substr($sUrl, 4); + } + } + else + { + if ($oElement) + { + if (\preg_match('/http[s]?:\/\//i', $sUrl) || '//' === \substr($sUrl, 0, 2)) + { + $bHasExternals = true; + if (!$bDoNotReplaceExternalUrl) + { + if (\in_array($sName, array('background-image', 'list-style-image', 'content'))) + { + $sStyleItem = ''; + } + + $sTemp = ''; + if ($oElement->hasAttribute('data-x-style-url')) + { + $sTemp = \trim($oElement->getAttribute('data-x-style-url')); + } + + $sTemp = empty($sTemp) ? '' : (';' === \substr($sTemp, -1) ? $sTemp.' ' : $sTemp.'; '); + + $oElement->setAttribute('data-x-style-url', \trim($sTemp. + ('background' === $sName ? 'background-image' : $sName).': '.$sFullUrl, ' ;')); + + if ($fAdditionalExternalFilter) + { + $sAdditionalResult = \call_user_func($fAdditionalExternalFilter, $sUrl); + if (0 < \strlen($sAdditionalResult)) + { + $oElement->setAttribute('data-x-additional-style-url', + ('background' === $sName ? 'background-image' : $sName).': url('.$sAdditionalResult.')'); + } + } + } + } + else if ('data:image/' !== \strtolower(\substr(\trim($sUrl), 0, 11))) + { + $oElement->setAttribute('data-x-broken-style-src', $sFullUrl); + } + } + } + + if (!empty($sStyleItem)) + { + $aOutStyles[] = $sStyleItem; + } + } + else if ('height' === $sName) + { +// $aOutStyles[] = 'min-'.ltrim($sStyleItem); + $aOutStyles[] = $sStyleItem; + } + else + { + $aOutStyles[] = $sStyleItem; + } + } + + return \implode(';', $aOutStyles); + } + + /** + * @param \DOMDocument $oDom + */ + public static function FindLinksInDOM(&$oDom) + { + $aNodes = $oDom->getElementsByTagName('*'); + foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement) + { + $sTagNameLower = \strtolower($oElement->tagName); + $sParentTagNameLower = isset($oElement->parentNode) && isset($oElement->parentNode->tagName) ? + \strtolower($oElement->parentNode->tagName) : ''; + + if (!\in_array($sTagNameLower, array('html', 'meta', 'head', 'style', 'script', 'img', 'button', 'input', 'textarea', 'a')) && + 'a' !== $sParentTagNameLower && $oElement->childNodes && 0 < $oElement->childNodes->length) + { + $oSubItem = null; + $aTextNodes = array(); + $iIndex = $oElement->childNodes->length - 1; + while ($iIndex > -1) + { + $oSubItem = $oElement->childNodes->item($iIndex); + if ($oSubItem && XML_TEXT_NODE === $oSubItem->nodeType) + { + $aTextNodes[] = $oSubItem; + } + + $iIndex--; + } + + unset($oSubItem); + + foreach ($aTextNodes as $oTextNode) + { + if ($oTextNode && 0 < \strlen($oTextNode->wholeText)/* && \preg_match('/http[s]?:\/\//i', $oTextNode->wholeText)*/) + { + $sText = \MailSo\Base\LinkFinder::NewInstance() + ->Text($oTextNode->wholeText) + ->UseDefaultWrappers(true) + ->CompileText() + ; + + $oSubDom = \MailSo\Base\HtmlUtils::GetDomFromText($sText); + if ($oSubDom) + { + $oBodyNodes = $oSubDom->getElementsByTagName('body'); + if ($oBodyNodes && 0 < $oBodyNodes->length) + { + $oBodyChildNodes = $oBodyNodes->item(0)->childNodes; + if ($oBodyChildNodes && $oBodyChildNodes->length) + { + for ($iIndex = 0, $iLen = $oBodyChildNodes->length; $iIndex < $iLen; $iIndex++) + { + $oSubItem = $oBodyChildNodes->item($iIndex); + if ($oSubItem) + { + if (XML_ELEMENT_NODE === $oSubItem->nodeType && + 'a' === \strtolower($oSubItem->tagName)) + { + $oLink = $oDom->createElement('a', + \str_replace(':', \MailSo\Base\HtmlUtils::$KOS, \htmlspecialchars($oSubItem->nodeValue))); + + $sHref = $oSubItem->getAttribute('href'); + if ($sHref) + { + $oLink->setAttribute('href', $sHref); + } + + $oElement->insertBefore($oLink, $oTextNode); + } + else + { + $oElement->insertBefore($oDom->importNode($oSubItem), $oTextNode); + } + } + } + + $oElement->removeChild($oTextNode); + } + } + + unset($oBodyNodes); + } + + unset($oSubDom, $sText); + } + } + } + } + + unset($aNodes); + } + + /** + * @param string $sHtml + * @param bool $bDoNotReplaceExternalUrl = false + * @param bool $bFindLinksInHtml = false + * @param bool $bWrapByFakeHtmlAndBodyDiv = true + * + * @return string + */ + public static function ClearHtmlSimple($sHtml, $bDoNotReplaceExternalUrl = false, $bFindLinksInHtml = false, $bWrapByFakeHtmlAndBodyDiv = true) + { + $bHasExternals = false; + $aFoundCIDs = array(); + $aContentLocationUrls = array(); + $aFoundedContentLocationUrls = array(); + $fAdditionalExternalFilter = null; + $fAdditionalDomReader = null; + $bTryToDetectHiddenImages = false; + + return \MailSo\Base\HtmlUtils::ClearHtml($sHtml, $bHasExternals, $aFoundCIDs, + $aContentLocationUrls, $aFoundedContentLocationUrls, $bDoNotReplaceExternalUrl, $bFindLinksInHtml, + $fAdditionalExternalFilter, $fAdditionalDomReader, $bTryToDetectHiddenImages, + $bWrapByFakeHtmlAndBodyDiv); + } + + /** + * @param string $sHtml + * @param bool $bHasExternals = false + * @param array $aFoundCIDs = array() + * @param array $aContentLocationUrls = array() + * @param array $aFoundedContentLocationUrls = array() + * @param bool $bDoNotReplaceExternalUrl = false + * @param bool $bFindLinksInHtml = false + * @param callback|null $fAdditionalExternalFilter = null + * @param callback|null $fAdditionalDomReader = null + * @param bool $bTryToDetectHiddenImages = false + * @param bool $bWrapByFakeHtmlAndBodyDiv = true + * + * @return string + */ + public static function ClearHtml($sHtml, &$bHasExternals = false, &$aFoundCIDs = array(), + $aContentLocationUrls = array(), &$aFoundedContentLocationUrls = array(), + $bDoNotReplaceExternalUrl = false, $bFindLinksInHtml = false, + $fAdditionalExternalFilter = null, $fAdditionalDomReader = false, + $bTryToDetectHiddenImages = false, $bWrapByFakeHtmlAndBodyDiv = true) + { + $sResult = ''; + + $sHtml = null === $sHtml ? '' : (string) $sHtml; + $sHtml = \trim($sHtml); + if (0 === \strlen($sHtml)) + { + return ''; + } + + if ($fAdditionalExternalFilter && !\is_callable($fAdditionalExternalFilter)) + { + $fAdditionalExternalFilter = null; + } + + if ($fAdditionalDomReader && !\is_callable($fAdditionalDomReader)) + { + $fAdditionalDomReader = null; + } + + $bHasExternals = false; + + // Dom Part + $oDom = \MailSo\Base\HtmlUtils::GetDomFromText($sHtml); + unset($sHtml); + + if (!$oDom) + { + return ''; + } + + if ($fAdditionalDomReader) + { + $oResDom = \call_user_func($fAdditionalDomReader, $oDom); + if ($oResDom) + { + $oDom = $oResDom; + } + + unset($oResDom); + } + + if ($bFindLinksInHtml) + { + \MailSo\Base\HtmlUtils::FindLinksInDOM($oDom); + } + + \MailSo\Base\HtmlUtils::ClearComments($oDom); + \MailSo\Base\HtmlUtils::ClearTags($oDom); + + $sLinkColor = ''; + $aNodes = $oDom->getElementsByTagName('*'); + foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement) + { + $aRemovedAttrs = array(); + $sTagNameLower = \strtolower($oElement->tagName); + + // convert body attributes to styles + if ('body' === $sTagNameLower) + { + $aAttrs = array( + 'link' => '', + 'text' => '', + 'topmargin' => '', + 'leftmargin' => '', + 'bottommargin' => '', + 'rightmargin' => '' + ); + + if (isset($oElement->attributes)) + { + foreach ($oElement->attributes as $sAttrName => /* @var $oAttributeNode \DOMNode */ $oAttributeNode) + { + if ($oAttributeNode && isset($oAttributeNode->nodeValue)) + { + $sAttrNameLower = \trim(\strtolower($sAttrName)); + if (isset($aAttrs[$sAttrNameLower]) && '' === $aAttrs[$sAttrNameLower]) + { + $aAttrs[$sAttrNameLower] = array($sAttrName, \trim($oAttributeNode->nodeValue)); + } + } + } + } + + $aStyles = array(); + foreach ($aAttrs as $sIndex => $aItem) + { + if (\is_array($aItem)) + { + $oElement->removeAttribute($aItem[0]); + + switch ($sIndex) + { + case 'link': + $sLinkColor = \trim($aItem[1]); + if (!\preg_match('/^#[abcdef0-9]{3,6}$/i', $sLinkColor)) + { + $sLinkColor = ''; + } + break; + case 'text': + $aStyles[] = 'color: '.$aItem[1]; + break; + case 'topmargin': + $aStyles[] = 'margin-top: '.((int) $aItem[1]).'px'; + break; + case 'leftmargin': + $aStyles[] = 'margin-left: '.((int) $aItem[1]).'px'; + break; + case 'bottommargin': + $aStyles[] = 'margin-bottom: '.((int) $aItem[1]).'px'; + break; + case 'rightmargin': + $aStyles[] = 'margin-right: '.((int) $aItem[1]).'px'; + break; + } + } + } + + if (0 < \count($aStyles)) + { + $sStyles = $oElement->hasAttribute('style') ? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : ''; + $oElement->setAttribute('style', (empty($sStyles) ? '' : $sStyles.'; ').\implode('; ', $aStyles)); + } + } + + if ('iframe' === $sTagNameLower || 'frame' === $sTagNameLower) + { + $oElement->setAttribute('src', 'javascript:false'); + } + + if ('a' === $sTagNameLower && !empty($sLinkColor)) + { + $sStyles = $oElement->hasAttribute('style') + ? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : ''; + + $oElement->setAttribute('style', + 'color: '.$sLinkColor.\trim((empty($sStyles) ? '' : '; '.$sStyles))); + } + + if ($oElement->hasAttributes() && isset($oElement->attributes) && $oElement->attributes) + { + $aHtmlAllowedAttributes = isset(\MailSo\Config::$HtmlStrictAllowedAttributes) && + \is_array(\MailSo\Config::$HtmlStrictAllowedAttributes) && 0 < \count(\MailSo\Config::$HtmlStrictAllowedAttributes) ? + \MailSo\Config::$HtmlStrictAllowedAttributes : null; + + $sAttrsForRemove = array(); + foreach ($oElement->attributes as $sAttrName => $oAttr) + { + if ($sAttrName && $oAttr) + { + $sAttrNameLower = \trim(\strtolower($sAttrName)); + if ($aHtmlAllowedAttributes && !\in_array($sAttrNameLower, $aHtmlAllowedAttributes)) + { + $sAttrsForRemove[] = $sAttrName; + } + else if ('on' === \substr($sAttrNameLower, 0, 2) || in_array($sAttrNameLower, array( + 'id', 'class', 'contenteditable', 'designmode', 'formaction', 'manifest', 'action', + 'data-bind', 'data-reactid', 'xmlns', 'srcset', 'data-x-skip-style', + 'fscommand', 'seeksegmenttime' + ))) + { + $sAttrsForRemove[] = $sAttrName; + } + } + } + + if (0 < \count($sAttrsForRemove)) + { + foreach ($sAttrsForRemove as $sName) + { + @$oElement->removeAttribute($sName); + $aRemovedAttrs[\trim(\strtolower($sName))] = true; + } + } + + unset($sAttrsForRemove); + } + + if ($oElement->hasAttribute('href')) + { + $sHref = \trim($oElement->getAttribute('href')); + if (!\preg_match('/^(http[s]?|ftp|skype|mailto):/i', $sHref) && '//' !== \substr($sHref, 0, 2)) + { + $oElement->setAttribute('data-x-broken-href', $sHref); + $oElement->setAttribute('href', 'javascript:false'); + } + + if ('a' === $sTagNameLower) + { + $oElement->setAttribute('rel', 'external nofollow noopener noreferrer'); + } + } + + $sLinkHref = \trim($oElement->getAttribute('xlink:href')); + if ($sLinkHref && !\preg_match('/^(http[s]?):/i', $sLinkHref) && '//' !== \substr($sLinkHref, 0, 2)) + { + $oElement->setAttribute('data-x-blocked-xlink-href', $sLinkHref); + $oElement->removeAttribute('xlink:href'); + } + + if (\in_array($sTagNameLower, array('a', 'form', 'area'))) + { + $oElement->setAttribute('target', '_blank'); + } + + if (\in_array($sTagNameLower, array('a', 'form', 'area', 'input', 'button', 'textarea'))) + { + $oElement->setAttribute('tabindex', '-1'); + } + + if ($bTryToDetectHiddenImages && 'img' === $sTagNameLower) + { + $sAlt = $oElement->hasAttribute('alt') + ? \trim($oElement->getAttribute('alt')) : ''; + + if ($oElement->hasAttribute('src') && '' === $sAlt) + { + $aH = array( + 'email.microsoftemail.com/open', + 'github.com/notifications/beacon/', + 'mandrillapp.com/track/open', + 'list-manage.com/track/open' + ); + + $sH = $oElement->hasAttribute('height') + ? \trim($oElement->getAttribute('height')) : ''; + +// $sW = $oElement->hasAttribute('width') +// ? \trim($oElement->getAttribute('width')) : ''; + + $sStyles = $oElement->hasAttribute('style') + ? \preg_replace('/[\s]+/', '', \trim(\trim(\trim($oElement->getAttribute('style')), ';'))) : ''; + + $sSrc = \trim($oElement->getAttribute('src')); + + $bC = \in_array($sH, array('1', '0', '1px', '0px')) || + \preg_match('/(display:none|visibility:hidden|height:0|height:[01][a-z][a-z])/i', $sStyles); + + if (!$bC) + { + $sSrcLower = \strtolower($sSrc); + foreach ($aH as $sLine) + { + if (false !== \strpos($sSrcLower, $sLine)) + { + $bC = true; + break; + } + } + } + + if ($bC) + { + $oElement->setAttribute('style', 'display:none'); + $oElement->setAttribute('data-x-skip-style', 'true'); + $oElement->setAttribute('data-x-hidden-src', $sSrc); + + $oElement->removeAttribute('src'); + } + } + } + + if ($oElement->hasAttribute('src')) + { + $sSrc = \trim($oElement->getAttribute('src')); + $oElement->removeAttribute('src'); + + if (\in_array($sSrc, $aContentLocationUrls)) + { + $oElement->setAttribute('data-x-src-location', $sSrc); + $aFoundedContentLocationUrls[] = $sSrc; + } + else if ('cid:' === \strtolower(\substr($sSrc, 0, 4))) + { + $oElement->setAttribute('data-x-src-cid', \substr($sSrc, 4)); + $aFoundCIDs[] = \substr($sSrc, 4); + } + else + { + if (\preg_match('/^http[s]?:\/\//i', $sSrc) || '//' === \substr($sSrc, 0, 2)) + { + if ($bDoNotReplaceExternalUrl) + { + $oElement->setAttribute('src', $sSrc); + } + else + { + $oElement->setAttribute('data-x-src', $sSrc); + if ($fAdditionalExternalFilter) + { + $sCallResult = \call_user_func($fAdditionalExternalFilter, $sSrc); + if (0 < \strlen($sCallResult)) + { + $oElement->setAttribute('data-x-additional-src', $sCallResult); + } + } + } + + $bHasExternals = true; + } + else if ('data:image/' === \strtolower(\substr($sSrc, 0, 11))) + { + $oElement->setAttribute('src', $sSrc); + } + else + { + $oElement->setAttribute('data-x-broken-src', $sSrc); + } + } + } + + $sBackground = $oElement->hasAttribute('background') + ? \trim($oElement->getAttribute('background')) : ''; + $sBackgroundColor = $oElement->hasAttribute('bgcolor') + ? \trim($oElement->getAttribute('bgcolor')) : ''; + + if (!empty($sBackground) || !empty($sBackgroundColor)) + { + $aStyles = array(); + $sStyles = $oElement->hasAttribute('style') + ? \trim(\trim(\trim($oElement->getAttribute('style')), ';')) : ''; + + if (!empty($sBackground)) + { + $aStyles[] = 'background-image: url(\''.$sBackground.'\')'; + $oElement->removeAttribute('background'); + } + + if (!empty($sBackgroundColor)) + { + $aStyles[] = 'background-color: '.$sBackgroundColor; + $oElement->removeAttribute('bgcolor'); + } + + $oElement->setAttribute('style', (empty($sStyles) ? '' : $sStyles.'; ').\implode('; ', $aStyles)); + } + + if ($oElement->hasAttribute('style') && !$oElement->hasAttribute('data-x-skip-style')) + { + $oElement->setAttribute('style', + \MailSo\Base\HtmlUtils::ClearStyle($oElement->getAttribute('style'), $oElement, $bHasExternals, + $aFoundCIDs, $aContentLocationUrls, $aFoundedContentLocationUrls, $bDoNotReplaceExternalUrl, $fAdditionalExternalFilter)); + } + + $oElement->removeAttribute('data-x-skip-style'); + + if (\MailSo\Config::$HtmlStrictDebug && 0 < \count($aRemovedAttrs)) + { + unset($aRemovedAttrs['class'], $aRemovedAttrs['target'], $aRemovedAttrs['id'], $aRemovedAttrs['name'], + $aRemovedAttrs['itemprop'], $aRemovedAttrs['itemscope'], $aRemovedAttrs['itemtype']); + + $aRemovedAttrs = \array_keys($aRemovedAttrs); + if (0 < \count($aRemovedAttrs)) + { + $oElement->setAttribute('data-removed-attrs', \implode(',', $aRemovedAttrs)); + } + } + } + + $sResult = \MailSo\Base\HtmlUtils::GetTextFromDom($oDom, $bWrapByFakeHtmlAndBodyDiv); + unset($oDom); + + return $sResult; + } + + /** + * @param string $sHtml + * @param array $aFoundCids = array() + * @param array|null $mFoundDataURL = null + * @param array $aFoundedContentLocationUrls = array() + * + * @return string + */ + public static function BuildHtml($sHtml, &$aFoundCids = array(), &$mFoundDataURL = null, &$aFoundedContentLocationUrls = array()) + { + $oDom = \MailSo\Base\HtmlUtils::GetDomFromText($sHtml); + + \MailSo\Base\HtmlUtils::ClearTags($oDom); + unset($sHtml); + + $aNodes = $oDom->getElementsByTagName('*'); + foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement) + { + $sTagNameLower = \strtolower($oElement->tagName); + + if ($oElement->hasAttribute('data-x-src-cid')) + { + $sCid = $oElement->getAttribute('data-x-src-cid'); + $oElement->removeAttribute('data-x-src-cid'); + + if (!empty($sCid)) + { + $aFoundCids[] = $sCid; + + @$oElement->removeAttribute('src'); + $oElement->setAttribute('src', 'cid:'.$sCid); + } + } + + if ($oElement->hasAttribute('data-x-src-location')) + { + $sSrc = $oElement->getAttribute('data-x-src-location'); + $oElement->removeAttribute('data-x-src-location'); + + if (!empty($sSrc)) + { + $aFoundedContentLocationUrls[] = $sSrc; + + @$oElement->removeAttribute('src'); + $oElement->setAttribute('src', $sSrc); + } + } + + if ($oElement->hasAttribute('data-x-broken-src')) + { + $oElement->setAttribute('src', $oElement->getAttribute('data-x-broken-src')); + $oElement->removeAttribute('data-x-broken-src'); + } + + if ($oElement->hasAttribute('data-x-src')) + { + $oElement->setAttribute('src', $oElement->getAttribute('data-x-src')); + $oElement->removeAttribute('data-x-src'); + } + + if ($oElement->hasAttribute('data-x-href')) + { + $oElement->setAttribute('href', $oElement->getAttribute('data-x-href')); + $oElement->removeAttribute('data-x-href'); + } + + if ($oElement->hasAttribute('data-x-style-cid-name') && $oElement->hasAttribute('data-x-style-cid')) + { + $sCidName = $oElement->getAttribute('data-x-style-cid-name'); + $sCid = $oElement->getAttribute('data-x-style-cid'); + + $oElement->removeAttribute('data-x-style-cid-name'); + $oElement->removeAttribute('data-x-style-cid'); + if (!empty($sCidName) && !empty($sCid) && \in_array($sCidName, + array('background-image', 'background', 'list-style-image', 'content'))) + { + $sStyles = ''; + if ($oElement->hasAttribute('style')) + { + $sStyles = \trim(\trim($oElement->getAttribute('style')), ';'); + } + + $sBack = $sCidName.': url(cid:'.$sCid.')'; + $sStyles = \preg_replace('/'.\preg_quote($sCidName, '/').':\s?[^;]+/i', $sBack, $sStyles); + if (false === \strpos($sStyles, $sBack)) + { + $sStyles .= empty($sStyles) ? '': '; '; + $sStyles .= $sBack; + } + + $oElement->setAttribute('style', $sStyles); + $aFoundCids[] = $sCid; + } + } + + foreach (array( + 'data-x-additional-src', 'data-x-additional-style-url', 'data-removed-attrs', + 'data-original', 'data-x-div-type', 'data-wrp', 'data-bind' + ) as $sName) + { + if ($oElement->hasAttribute($sName)) + { + $oElement->removeAttribute($sName); + } + } + + if ($oElement->hasAttribute('data-x-style-url')) + { + $sAddStyles = $oElement->getAttribute('data-x-style-url'); + $oElement->removeAttribute('data-x-style-url'); + + if (!empty($sAddStyles)) + { + $sStyles = ''; + if ($oElement->hasAttribute('style')) + { + $sStyles = \trim(\trim($oElement->getAttribute('style')), ';'); + } + + $oElement->setAttribute('style', (empty($sStyles) ? '' : $sStyles.'; ').$sAddStyles); + } + } + + if ('img' === $sTagNameLower && \is_array($mFoundDataURL)) + { + $sSrc = $oElement->getAttribute('src'); + if ('data:image/' === \strtolower(\substr($sSrc, 0, 11))) + { + $sHash = \md5($sSrc); + $mFoundDataURL[$sHash] = $sSrc; + + $oElement->setAttribute('src', 'cid:'.$sHash); + } + } + } + + $sResult = \MailSo\Base\HtmlUtils::GetTextFromDom($oDom, false); + unset($oDom); + + return '
'. + ''.$sResult.''; + } + + /** + * @param string $sText + * @param bool $bLinksWithTargetBlank = true + * + * @return string + */ + public static function ConvertPlainToHtml($sText, $bLinksWithTargetBlank = true) + { + $sText = \trim($sText); + if (0 === \strlen($sText)) + { + return ''; + } + + $sText = \MailSo\Base\LinkFinder::NewInstance() + ->Text($sText) + ->UseDefaultWrappers($bLinksWithTargetBlank) + ->CompileText() + ; + + $sText = \str_replace("\r", '', $sText); + + $aText = \explode("\n", $sText); + unset($sText); + + $bIn = false; + $bDo = true; + do + { + $bDo = false; + $aNextText = array(); + foreach ($aText as $sTextLine) + { + $bStart = 0 === \strpos(\ltrim($sTextLine), '>'); + if ($bStart && !$bIn) + { + $bDo = true; + $bIn = true; + $aNextText[] = ''; + $aNextText[] = \substr(\ltrim($sTextLine), 4); + } + else if (!$bStart && $bIn) + { + $bIn = false; + $aNextText[] = ''; + $aNextText[] = $sTextLine; + } + else if ($bStart && $bIn) + { + $aNextText[] = \substr(\ltrim($sTextLine), 4); + } + else + { + $aNextText[] = $sTextLine; + } + } + + if ($bIn) + { + $bIn = false; + $aNextText[] = ''; + } + + $aText = $aNextText; + } + while ($bDo); + + $sText = \join("\n", $aText); + unset($aText); + + $sText = \preg_replace('/[\n][ ]+/', "\n", $sText); +// $sText = \preg_replace('/[\s]+([\s])/', '\\1', $sText); + + $sText = \preg_replace('/
[\s]+/i', '', $sText); + $sText = \preg_replace('/[\s]+<\/blockquote>/i', '', $sText); + + $sText = \preg_replace('/<\/blockquote>([\n]{0,2})/i', '\\1', $sText); + $sText = \preg_replace('/[\n]{3,}/', "\n\n", $sText); + + $sText = \strtr($sText, array( + "\n" => "
", + "\t" => ' ', + ' ' => ' ' + )); + + return $sText; + } + + /** + * @param string $sText + * + * @return string + */ + public static function ConvertHtmlToPlain($sText) + { + $sText = \trim(\stripslashes($sText)); + $sText = \MailSo\Base\Utils::StripSpaces($sText); + + $sText = \preg_replace(array( + "/\r/", + "/[\n\t]+/", + '/', '<\/script>', + \str_replace($aJsonReplaces[0], $aJsonReplaces[1], $sText)); + } + + /** + * @param array $aInput + */ + public static function ClearArrayUtf8Values(&$aInput) + { + if (\is_array($aInput)) + { + foreach ($aInput as $mKey => $mItem) + { + if (\is_string($mItem)) + { + $aInput[$mKey] = \MailSo\Base\Utils::Utf8Clear($mItem); + } + else if (\is_array($mItem)) + { + \MailSo\Base\Utils::ClearArrayUtf8Values($mItem); + $aInput[$mKey] = $mItem; + } + } + } + } + + /** + * @param mixed $mInput + * @param \MailSo\Log\Logger|null $oLogger = null + * + * @return string + */ + public static function Php2js($mInput, $oLogger = null) + { + static $iOpt = null; + if (null === $iOpt) + { + $iOpt = \defined('JSON_UNESCAPED_UNICODE') ? JSON_UNESCAPED_UNICODE : 0; + } + + $sResult = @\json_encode($mInput, $iOpt); + if (!\is_string($sResult) || '' === $sResult) + { + if (!$oLogger && \MailSo\Log\Logger::IsSystemEnabled()) + { + $oLogger = \MailSo\Config::$SystemLogger; + } + + if (!($oLogger instanceof \MailSo\Log\Logger)) + { + $oLogger = null; + } + + if ($oLogger) + { + $oLogger->Write('json_encode: '.\trim( + (\MailSo\Base\Utils::FunctionExistsAndEnabled('json_last_error') ? ' [Error Code: '.\json_last_error().']' : ''). + (\MailSo\Base\Utils::FunctionExistsAndEnabled('json_last_error_msg') ? ' [Error Message: '.\json_last_error_msg().']' : '') + ), \MailSo\Log\Enumerations\Type::WARNING, 'JSON' + ); + } + + if (\is_array($mInput)) + { + if ($oLogger) + { + $oLogger->WriteDump($mInput, \MailSo\Log\Enumerations\Type::INFO, 'JSON'); + $oLogger->Write('Trying to clear Utf8 before json_encode', \MailSo\Log\Enumerations\Type::INFO, 'JSON'); + } + + \MailSo\Base\Utils::ClearArrayUtf8Values($mInput); + $sResult = @\json_encode($mInput, $iOpt); + } + } + + return $sResult; + } + + /** + * @param string $sFileName + * + * @return string + */ + public static function ClearFileName($sFileName) + { + return \MailSo\Base\Utils::Trim(\MailSo\Base\Utils::ClearNullBite( + \MailSo\Base\Utils::StripSpaces( + \str_replace(array('"', '/', '\\', '*', '?', '<', '>', '|', ':'), ' ', $sFileName)))); + } + + /** + * @param string $sValue + * + * @return string + */ + public static function ClearXss($sValue) + { + return \MailSo\Base\Utils::Trim(\MailSo\Base\Utils::ClearNullBite( + \str_replace(array('"', '/', '\\', '*', '?', '<', '>', '|', ':'), ' ', $sValue))); + } + + /** + * @param string $sValue + * + * @return string + */ + public static function Trim($sValue) + { + return \trim(\preg_replace('/^[\x00-\x1F]+/u', '', + \preg_replace('/[\x00-\x1F]+$/u', '', \trim($sValue)))); + } + + /** + * @param string $sDir + * + * @return bool + */ + public static function RecRmDir($sDir) + { + if (@\is_dir($sDir)) + { + $aObjects = \scandir($sDir); + foreach ($aObjects as $sObject) + { + if ('.' !== $sObject && '..' !== $sObject) + { +// if ('dir' === \filetype($sDir.'/'.$sObject)) + if (\is_dir($sDir.'/'.$sObject)) + { + self::RecRmDir($sDir.'/'.$sObject); + } + else + { + @\unlink($sDir.'/'.$sObject); + } + } + } + + return @\rmdir($sDir); + } + + return false; + } + + /** + * @param string $sSource + * @param string $sDestination + */ + public static function CopyDir($sSource, $sDestination) + { + if (\is_dir($sSource)) + { + if (!\is_dir($sDestination)) + { + \mkdir($sDestination); + } + + $oDirectory = \dir($sSource); + if ($oDirectory) + { + while (false !== ($sRead = $oDirectory->read())) + { + if ('.' === $sRead || '..' === $sRead) + { + continue; + } + + $sPathDir = $sSource.'/'.$sRead; + if (\is_dir($sPathDir)) + { + \MailSo\Base\Utils::CopyDir($sPathDir, $sDestination.'/'.$sRead); + continue; + } + + \copy($sPathDir, $sDestination.'/'.$sRead); + } + + $oDirectory->close(); + } + } + } + + /** + * @param string $sTempPath + * @param int $iTime2Kill + * @param int $iNow + * + * @return bool + */ + public static function RecTimeDirRemove($sTempPath, $iTime2Kill, $iNow) + { + $iFileCount = 0; + + $sTempPath = rtrim($sTempPath, '\\/'); + if (@\is_dir($sTempPath)) + { + $rDirH = @\opendir($sTempPath); + if ($rDirH) + { + $bRemoveAllDirs = true; + while (($sFile = @\readdir($rDirH)) !== false) + { + if ('.' !== $sFile && '..' !== $sFile) + { + if (@\is_dir($sTempPath.'/'.$sFile)) + { + if (!\MailSo\Base\Utils::RecTimeDirRemove($sTempPath.'/'.$sFile, $iTime2Kill, $iNow)) + { + $bRemoveAllDirs = false; + } + } + else + { + $iFileCount++; + } + } + } + + @\closedir($rDirH); + } + + if ($iFileCount > 0) + { + if (\MailSo\Base\Utils::TimeFilesRemove($sTempPath, $iTime2Kill, $iNow)) + { + return @\rmdir($sTempPath); + } + } + else + { + return $bRemoveAllDirs ? @\rmdir($sTempPath) : false; + } + + return false; + } + + return true; + } + + /** + * @param string $sTempPath + * @param int $iTime2Kill + * @param int $iNow + */ + public static function TimeFilesRemove($sTempPath, $iTime2Kill, $iNow) + { + $bResult = true; + + $sTempPath = rtrim($sTempPath, '\\/'); + if (@\is_dir($sTempPath)) + { + $rDirH = @\opendir($sTempPath); + if ($rDirH) + { + while (($sFile = @\readdir($rDirH)) !== false) + { + if ($sFile !== '.' && $sFile !== '..') + { + if ($iNow - \filemtime($sTempPath.'/'.$sFile) > $iTime2Kill) + { + @\unlink($sTempPath.'/'.$sFile); + } + else + { + $bResult = false; + } + } + } + + @\closedir($rDirH); + } + } + + return $bResult; + } + + /** + * @param string $sUtfString + * @param int $iLength + * + * @return string + */ + public static function Utf8Truncate($sUtfString, $iLength) + { + if (\strlen($sUtfString) <= $iLength) + { + return $sUtfString; + } + + while ($iLength >= 0) + { + if ((\ord($sUtfString[$iLength]) < 0x80) || (\ord($sUtfString[$iLength]) >= 0xC0)) + { + return \substr($sUtfString, 0, $iLength); + } + + $iLength--; + } + + return ''; + } + + /** + * @param string $sUtfString + * @param string $sReplaceOn = '' + * + * @return string + */ + public static function Utf8Clear($sUtfString, $sReplaceOn = '') + { + if ('' === $sUtfString) + { + return $sUtfString; + } + + $sUtfString = \preg_replace(\MailSo\Base\Utils::$sValidUtf8Regexp, '$1', $sUtfString); + + $sUtfString = \preg_replace( + '/\xE0[\x80-\x9F][\x80-\xBF]'. + '|\xEF\xBF\xBF'. + '|\xED[\xA0-\xBF][\x80-\xBF]/S', $sReplaceOn, $sUtfString); + + $sUtfString = \preg_replace('/\xEF\xBF\xBD/', '?', $sUtfString); + + $sNewUtfString = false; + if (false === $sNewUtfString && \MailSo\Base\Utils::IsMbStringSupported()) + { + $sNewUtfString = \MailSo\Base\Utils::MbConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); + } + + if (false === $sNewUtfString && \MailSo\Base\Utils::IsIconvSupported()) + { + $sNewUtfString = \MailSo\Base\Utils::IconvConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); + } + + if (false !== $sNewUtfString) + { + $sUtfString = $sNewUtfString; + } + + return $sUtfString; + } + + /** + * @param string $sUtfString + * + * @return bool + */ + public static function IsRTL($sUtfString) + { + // \x{0591}-\x{05F4} - Hebrew + // \x{0600}-\x{068F} - Arabic + // \x{0750}-\x{077F} - Arabic + // \x{08A0}-\x{08FF} - Arabic + // \x{103A0}-\x{103DF} - Old Persian + return 0 < (int) preg_match('/[\x{0591}-\x{05F4}\x{0600}-\x{068F}\x{0750}-\x{077F}\x{08A0}-\x{08FF}\x{103A0}-\x{103DF}]/u', $sUtfString); + } + + /** + * @param string $sString + * + * @return string + */ + public static function Base64Decode($sString) + { + $sResultString = \base64_decode($sString, true); + if (false === $sResultString) + { + $sString = \str_replace(array(' ', "\r", "\n", "\t"), '', $sString); + $sString = \preg_replace('/[^a-zA-Z0-9=+\/](.*)$/', '', $sString); + + if (false !== \strpos(\trim(\trim($sString), '='), '=')) + { + $sString = \preg_replace('/=([^=])/', '= $1', $sString); + $aStrings = \explode(' ', $sString); + foreach ($aStrings as $iIndex => $sParts) + { + $aStrings[$iIndex] = \base64_decode($sParts); + } + + $sResultString = \implode('', $aStrings); + } + else + { + $sResultString = \base64_decode($sString); + } + } + + return $sResultString; + } + + /** + * @param string $sValue + * + * @return string + */ + public static function UrlSafeBase64Encode($sValue) + { + return \rtrim(\strtr(\base64_encode($sValue), '+/', '-_'), '='); + } + + /** + * @param string $sValue + * + * @return string + */ + public static function UrlSafeBase64Decode($sValue) + { + $sValue = \rtrim(\strtr($sValue, '-_.', '+/='), '='); + return \MailSo\Base\Utils::Base64Decode(\str_pad($sValue, \strlen($sValue) + (\strlen($sValue) % 4), '=', STR_PAD_RIGHT)); + } + + /** + * @param string $sSequence + * + * @return array + */ + public static function ParseFetchSequence($sSequence) + { + $aResult = array(); + $sSequence = \trim($sSequence); + if (0 < \strlen($sSequence)) + { + $aSequence = \explode(',', $sSequence); + foreach ($aSequence as $sItem) + { + if (false === \strpos($sItem, ':')) + { + $aResult[] = (int) $sItem; + } + else + { + $aItems = \explode(':', $sItem); + $iMax = \max($aItems[0], $aItems[1]); + + for ($iIndex = $aItems[0]; $iIndex <= $iMax; $iIndex++) + { + $aResult[] = (int) $iIndex; + } + } + } + } + + return $aResult; + } + /** + * @param array $aSequence + * + * @return string + */ + public static function PrepearFetchSequence($aSequence) + { + $aResult = array(); + if (\is_array($aSequence) && 0 < \count($aSequence)) + { + $iStart = null; + $iPrev = null; + + foreach ($aSequence as $sItem) + { + // simple protection + if (false !== \strpos($sItem, ':')) + { + $aResult[] = $sItem; + continue; + } + + $iItem = (int) $sItem; + if (null === $iStart || null === $iPrev) + { + $iStart = $iItem; + $iPrev = $iItem; + continue; + } + + if ($iPrev === $iItem - 1) + { + $iPrev = $iItem; + } + else + { + $aResult[] = $iStart === $iPrev ? $iStart : $iStart.':'.$iPrev; + $iStart = $iItem; + $iPrev = $iItem; + } + } + + if (null !== $iStart && null !== $iPrev) + { + $aResult[] = $iStart === $iPrev ? $iStart : $iStart.':'.$iPrev; + } + } + + return \implode(',', $aResult); + } + + /** + * + * @param resource $fResource + * @param int $iBufferLen = 8192 + * + * @return bool + */ + public static function FpassthruWithTimeLimitReset($fResource, $iBufferLen = 8192) + { + $bResult = false; + if (\is_resource($fResource)) + { + while (!\feof($fResource)) + { + $sBuffer = @\fread($fResource, $iBufferLen); + if (false !== $sBuffer) + { + echo $sBuffer; + \MailSo\Base\Utils::ResetTimeLimit(); + continue; + } + + break; + } + + $bResult = true; + } + + return $bResult; + } + + /** + * @param resource $rRead + * @param array $aWrite + * @param int $iBufferLen = 8192 + * @param bool $bResetTimeLimit = true + * @param bool $bFixCrLf = false + * @param bool $bRewindOnComplete = false + * + * @return int|bool + */ + public static function MultipleStreamWriter($rRead, $aWrite, $iBufferLen = 8192, $bResetTimeLimit = true, $bFixCrLf = false, $bRewindOnComplete = false) + { + $mResult = false; + if ($rRead && \is_array($aWrite) && 0 < \count($aWrite)) + { + $mResult = 0; + while (!\feof($rRead)) + { + $sBuffer = \fread($rRead, $iBufferLen); + if (false === $sBuffer) + { + $mResult = false; + break; + } + + if (0 === $iBufferLen || '' === $sBuffer) + { + break; + } + + if ($bFixCrLf) + { + $sBuffer = \str_replace("\n", "\r\n", \str_replace("\r", '', $sBuffer)); + } + + $mResult += \strlen($sBuffer); + + foreach ($aWrite as $rWriteStream) + { + $mWriteResult = \fwrite($rWriteStream, $sBuffer); + if (false === $mWriteResult) + { + $mResult = false; + break 2; + } + } + + if ($bResetTimeLimit) + { + \MailSo\Base\Utils::ResetTimeLimit(); + } + } + } + + if ($mResult && $bRewindOnComplete) + { + foreach ($aWrite as $rWriteStream) + { + if (\is_resource($rWriteStream)) + { + @\rewind($rWriteStream); + } + } + } + + return $mResult; + } + + /** + * @param string $sUtfModifiedString + * + * @return string + */ + public static function ModifiedToPlainUtf7($sUtfModifiedString) + { + $sUtf = ''; + $bBase = false; + + for ($iIndex = 0, $iLen = \strlen($sUtfModifiedString); $iIndex < $iLen; $iIndex++) + { + if ('&' === $sUtfModifiedString[$iIndex]) + { + if (isset($sUtfModifiedString[$iIndex+1]) && '-' === $sUtfModifiedString[$iIndex + 1]) + { + $sUtf .= '&'; + $iIndex++; + } + else + { + $sUtf .= '+'; + $bBase = true; + } + } + else if ($sUtfModifiedString[$iIndex] == '-' && $bBase) + { + $bBase = false; + } + else + { + if ($bBase && ',' === $sUtfModifiedString[$iIndex]) + { + $sUtf .= '/'; + } + else if (!$bBase && '+' === $sUtfModifiedString[$iIndex]) + { + $sUtf .= '+-'; + } + else + { + $sUtf .= $sUtfModifiedString[$iIndex]; + } + } + } + + return $sUtf; + } + + /** + * @param string $sStr + * + * @return string|bool + */ + public static function Utf7ModifiedToUtf8($sStr) + { + $aArray = array(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,62, 63,-1,-1,-1,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9, + 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1); + + $sResult = ''; + $bError = false; + $iLen = \strlen($sStr); + + for ($iIndex = 0; $iLen > 0; $iIndex++, $iLen--) + { + $sChar = $sStr{$iIndex}; + if ($sChar == '&') + { + $iIndex++; + $iLen--; + + $sChar = isset($sStr{$iIndex}) ? $sStr{$iIndex} : null; + if ($sChar === null) + { + break; + } + + if ($iLen && $sChar == '-') + { + $sResult .= '&'; + continue; + } + + $iCh = 0; + $iK = 10; + for (; $iLen > 0; $iIndex++, $iLen--) + { + $sChar = $sStr{$iIndex}; + + $iB = $aArray[\ord($sChar)]; + if ((\ord($sChar) & 0x80) || $iB == -1) + { + break; + } + + if ($iK > 0) + { + $iCh |= $iB << $iK; + $iK -= 6; + } + else + { + $iCh |= $iB >> (-$iK); + if ($iCh < 0x80) + { + if (0x20 <= $iCh && $iCh < 0x7f) + { + return $bError; + } + + $sResult .= \chr($iCh); + } + else if ($iCh < 0x800) + { + $sResult .= \chr(0xc0 | ($iCh >> 6)); + $sResult .= \chr(0x80 | ($iCh & 0x3f)); + } + else + { + $sResult .= \chr(0xe0 | ($iCh >> 12)); + $sResult .= \chr(0x80 | (($iCh >> 6) & 0x3f)); + $sResult .= \chr(0x80 | ($iCh & 0x3f)); + } + + $iCh = ($iB << (16 + $iK)) & 0xffff; + $iK += 10; + } + } + + if (($iCh || $iK < 6) || + (!$iLen || $sChar != '-') || + ($iLen > 2 && '&' === $sStr{$iIndex+1} && '-' !== $sStr{$iIndex+2})) + { + return $bError; + } + } + else if (\ord($sChar) < 0x20 || \ord($sChar) >= 0x7f) + { + return $bError; + } + else + { + $sResult .= $sChar; + } + } + + return $sResult; + } + + /** + * @param string $sStr + * + * @return string|bool + */ + public static function Utf8ToUtf7Modified($sStr) + { + $sArray = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S', + 'T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', + 'p','q','r','s','t','u','v','w','x','y','z', '0','1','2','3','4','5','6','7','8','9','+',','); + + $sLen = \strlen($sStr); + $bIsB = false; + $iIndex = $iN = 0; + $sReturn = ''; + $bError = false; + $iCh = $iB = $iK = 0; + + while ($sLen) + { + $iC = \ord($sStr{$iIndex}); + if ($iC < 0x80) + { + $iCh = $iC; + $iN = 0; + } + else if ($iC < 0xc2) + { + return $bError; + } + else if ($iC < 0xe0) + { + $iCh = $iC & 0x1f; + $iN = 1; + } + else if ($iC < 0xf0) + { + $iCh = $iC & 0x0f; + $iN = 2; + } + else if ($iC < 0xf8) + { + $iCh = $iC & 0x07; + $iN = 3; + } + else if ($iC < 0xfc) + { + $iCh = $iC & 0x03; + $iN = 4; + } + else if ($iC < 0xfe) + { + $iCh = $iC & 0x01; + $iN = 5; + } + else + { + return $bError; + } + + $iIndex++; + $sLen--; + + if ($iN > $sLen) + { + return $bError; + } + + for ($iJ = 0; $iJ < $iN; $iJ++) + { + $iO = \ord($sStr{$iIndex+$iJ}); + if (($iO & 0xc0) != 0x80) + { + return $bError; + } + + $iCh = ($iCh << 6) | ($iO & 0x3f); + } + + if ($iN > 1 && !($iCh >> ($iN * 5 + 1))) + { + return $bError; + } + + $iIndex += $iN; + $sLen -= $iN; + + if ($iCh < 0x20 || $iCh >= 0x7f) + { + if (!$bIsB) + { + $sReturn .= '&'; + $bIsB = true; + $iB = 0; + $iK = 10; + } + + if ($iCh & ~0xffff) + { + $iCh = 0xfffe; + } + + $sReturn .= $sArray[($iB | $iCh >> $iK)]; + $iK -= 6; + for (; $iK >= 0; $iK -= 6) + { + $sReturn .= $sArray[(($iCh >> $iK) & 0x3f)]; + } + + $iB = ($iCh << (-$iK)) & 0x3f; + $iK += 16; + } + else + { + if ($bIsB) + { + if ($iK > 10) + { + $sReturn .= $sArray[$iB]; + } + $sReturn .= '-'; + $bIsB = false; + } + + $sReturn .= \chr($iCh); + if ('&' === \chr($iCh)) + { + $sReturn .= '-'; + } + } + } + + if ($bIsB) + { + if ($iK > 10) + { + $sReturn .= $sArray[$iB]; + } + + $sReturn .= '-'; + } + + return $sReturn; + } + + /** + * @param string|array $mFunctionNameOrNames + * + * @return bool + */ + public static function FunctionExistsAndEnabled($mFunctionNameOrNames) + { + static $aCache = null; + + if (\is_array($mFunctionNameOrNames)) + { + foreach ($mFunctionNameOrNames as $sFunctionName) + { + if (!\MailSo\Base\Utils::FunctionExistsAndEnabled($sFunctionName)) + { + return false; + } + } + + return true; + } + + if (empty($mFunctionNameOrNames) || !\function_exists($mFunctionNameOrNames) || !\is_callable($mFunctionNameOrNames)) + { + return false; + } + + if (null === $aCache) + { + $sDisableFunctions = @\ini_get('disable_functions'); + $sDisableFunctions = \is_string($sDisableFunctions) && 0 < \strlen($sDisableFunctions) ? $sDisableFunctions : ''; + + $aCache = \explode(',', $sDisableFunctions); + $aCache = \is_array($aCache) && 0 < \count($aCache) ? $aCache : array(); + + if (\extension_loaded('suhosin')) + { + $sSuhosin = @\ini_get('suhosin.executor.func.blacklist'); + $sSuhosin = \is_string($sSuhosin) && 0 < \strlen($sSuhosin) ? $sSuhosin : ''; + + $aSuhosinCache = \explode(',', $sSuhosin); + $aSuhosinCache = \is_array($aSuhosinCache) && 0 < \count($aSuhosinCache) ? $aSuhosinCache : array(); + + if (0 < \count($aSuhosinCache)) + { + $aCache = \array_merge($aCache, $aSuhosinCache); + $aCache = \array_unique($aCache); + } + } + } + + return !\in_array($mFunctionNameOrNames, $aCache); + } + + /** + * @param string $mValue + * + * @return string + */ + public static function ClearNullBite($mValue) + { + return \str_replace('%00', '', $mValue); + } + + /** + * @param mixed $mValue + * @param bool $bClearNullBite = false + * + * @return mixed + */ + public static function StripSlashesValue($mValue, $bClearNullBite = false) + { + static $bIsMagicQuotesOn = null; + if (null === $bIsMagicQuotesOn) + { + $bIsMagicQuotesOn = (bool) @\ini_get('magic_quotes_gpc'); + } + + if (!$bIsMagicQuotesOn) + { + return $bClearNullBite && \is_string($mValue) ? \MailSo\Base\Utils::ClearNullBite($mValue) : $mValue; + } + + $sType = \gettype($mValue); + if ('string' === $sType) + { + return \stripslashes($bClearNullBite ? \MailSo\Base\Utils::ClearNullBite($mValue) : $mValue); + } + else if ('array' === $sType) + { + $aReturnValue = array(); + $mValueKeys = \array_keys($mValue); + foreach ($mValueKeys as $sKey) + { + $aReturnValue[$sKey] = \MailSo\Base\Utils::StripSlashesValue($mValue[$sKey], $bClearNullBite); + } + + return $aReturnValue; + } + + return $mValue; + } + + /** + * @param string $sStr + * + * @return string + */ + public static function CharsetDetect($sStr) + { + $mResult = ''; + if (!\MailSo\Base\Utils::IsAscii($sStr)) + { + $mResult = \MailSo\Base\Utils::IsMbStringSupported() && + \MailSo\Base\Utils::FunctionExistsAndEnabled('mb_detect_encoding') ? + @\mb_detect_encoding($sStr, 'auto', true) : false; + + if (false === $mResult && \MailSo\Base\Utils::IsIconvSupported()) + { + $mResult = \md5(@\iconv('utf-8', 'utf-8//IGNORE', $sStr)) === \md5($sStr) ? 'utf-8' : ''; + } + } + + return \is_string($mResult) && 0 < \strlen($mResult) ? $mResult : ''; + } + + /** + * @param string $sAdditionalSalt = '' + * + * @return string + */ + public static function Md5Rand($sAdditionalSalt = '') + { + return \md5(\microtime(true).\rand(10000, 99999). + \md5($sAdditionalSalt).\rand(10000, 99999).\microtime(true)); + } + + /** + * @param string $sAdditionalSalt = '' + * + * @return string + */ + public static function Sha1Rand($sAdditionalSalt = '') + { + return \sha1(\microtime(true).\rand(10000, 99999). + \sha1($sAdditionalSalt).\rand(10000, 99999).\microtime(true)); + } + + /** + * @param string $sData + * @param string $sKey + * + * @return string + */ + public static function Hmac($sData, $sKey) + { + if (\function_exists('hash_hmac')) + { + return \hash_hmac('md5', $sData, $sKey); + } + + $iLen = 64; + if ($iLen < \strlen($sKey)) + { + $sKey = \pack('H*', \md5($sKey)); + } + + $sKey = \str_pad($sKey, $iLen, \chr(0x00)); + $sIpad = \str_pad('', $iLen, \chr(0x36)); + $sOpad = \str_pad('', $iLen, \chr(0x5c)); + + return \md5(($sKey ^ $sOpad).\pack('H*', \md5(($sKey ^ $sIpad).$sData))); + } + + /** + * @param string $sDomain + * @param bool $bSimple = false + * + * @return bool + */ + public static function ValidateDomain($sDomain, $bSimple = false) + { + $aMatch = array(); + if ($bSimple) + { + return \preg_match('/.+(\.[a-zA-Z]+)$/', $sDomain, $aMatch) && !empty($aMatch[1]); + } + + return \preg_match('/.+(\.[a-zA-Z]+)$/', $sDomain, $aMatch) && !empty($aMatch[1]) && \in_array($aMatch[1], \explode(' ', + '.academy .actor .agency .audio .bar .beer .bike .blue .boutique .cab .camera .camp .capital .cards .careers .cash .catering .center .cheap .city .cleaning .clinic .clothing .club .coffee .community .company .computer .construction .consulting .contractors .cool .credit .dance .dating .democrat .dental .diamonds .digital .direct .directory .discount .domains .education .email .energy .equipment .estate .events .expert .exposed .fail .farm .fish .fitness .florist .fund .futbol .gallery .gift .glass .graphics .guru .help .holdings .holiday .host .hosting .house .institute .international .kitchen .land .life .lighting .limo .link .management .market .marketing .media .menu .moda .partners .parts .photo .photography .photos .pics .pink .press .productions .pub .red .rentals .repair .report .rest .sexy .shoes .social .solar .solutions .space .support .systems .tattoo .tax .technology .tips .today .tools .town .toys .trade .training .university .uno .vacations .vision .vodka .voyage .watch .webcam .wiki .work .works .wtf .zone .aero .asia .biz .cat .com .coop .edu .gov .info .int .jobs .mil .mobi .museum .name .net .org .pro .tel .travel .xxx .xyz '. + '.ac .ad .ae .af .ag .ai .al .am .an .ao .aq .ar .as .at .au .aw .ax .az .ba .bb .bd .be .bf .bg .bh .bi .bj .bm .bn .bo .br .bs .bt .bv .bw .by .bz .ca .cc .cd .cf .cg .ch .ci .ck .cl .cm .cn .co .cr .cs .cu .cv .cx .cy .cz .dd .de .dj .dk .dm .do .dz .ec .ee .eg .er .es .et .eu .fi .fj .fk .fm .fo .fr .ga .gb .gd .ge .gf .gg .gh .gi .gl .gm .gn .gp .gq .gr .gs .gt .gu .gw .gy .hk .hm .hn .hr .ht .hu .id .ie .il .im .in .io .iq .ir .is .it .je .jm .jo .jp .ke .kg .kh .ki .km .kn .kp .kr .kw .ky .kz .la .lb .lc .li .lk .lr .ls .lt .lu .lv .ly .ma .mc .md .me .mg .mh .mk .ml .mm .mn .mo .mp .mq .mr .ms .mt .mu .mv .mw .mx .my .mz .na .nc .ne .nf .ng .ni .nl .no .np .nr .nu .nz .om .pa .pe .pf .pg .ph .pk .pl .pm .pn .pr .ps .pt .pw .py .qa .re .ro .rs .ru . .rw .sa .sb .sc .sd .se .sg .sh .si .sj .sk .sl .sm .sn .so .sr .st .su .sv .sy .sz .tc .td .tf .tg .th .tj .tk .tl .tm .tn .to .tp .tr .tt .tv .tw .tz .ua .ug .uk .us .uy .uz .va .vc .ve .vg .vi .vn .vu .wf .ws .ye .yt .za .zm .zw' + )); + } + + /** + * @param string $sIp + * + * @return bool + */ + public static function ValidateIP($sIp) + { + return !empty($sIp) && $sIp === @\filter_var($sIp, FILTER_VALIDATE_IP); + } + + /** + * @return \Net_IDNA2 + */ + private static function idn() + { + static $oIdn = null; + if (null === $oIdn) + { + include_once MAILSO_LIBRARY_ROOT_PATH.'Vendors/Net/IDNA2.php'; + $oIdn = new \Net_IDNA2(); + $oIdn->setParams('utf8', true); + } + + return $oIdn; + } + + /** + * @param string $sStr + * @param bool $bLowerIfAscii = false + * + * @return string + */ + public static function IdnToUtf8($sStr, $bLowerIfAscii = false) + { + if (0 < \strlen($sStr) && \preg_match('/(^|\.|@)xn--/i', $sStr)) + { + try + { + $sStr = self::idn()->decode($sStr); + } + catch (\Exception $oException) {} + } + + return $bLowerIfAscii ? \MailSo\Base\Utils::StrMailDomainToLowerIfAscii($sStr) : $sStr; + } + + /** + * @param string $sStr + * @param bool $bLowerIfAscii = false + * + * @return string + */ + public static function IdnToAscii($sStr, $bLowerIfAscii = false) + { + $sStr = $bLowerIfAscii ? \MailSo\Base\Utils::StrMailDomainToLowerIfAscii($sStr) : $sStr; + + $sUser = ''; + $sDomain = $sStr; + if (false !== \strpos($sStr, '@')) + { + $sUser = \MailSo\Base\Utils::GetAccountNameFromEmail($sStr); + $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sStr); + } + + if (0 < \strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain)) + { + try + { + $sDomain = self::idn()->encode($sDomain); + } + catch (\Exception $oException) {} + } + + return ('' === $sUser ? '' : $sUser.'@').$sDomain; + } + + /** + * @param string $sHash + * @param string $sSalt + * + * @return int + */ + public static function HashToId($sHash, $sSalt = '') + { + $sData = $sHash ? @\MailSo\Base\Crypt::XxteaDecrypt(\hex2bin($sHash), \md5($sSalt)) : null; + + $aMatch = array(); + if ($sData && preg_match('/^id:(\d+)$/', $sData, $aMatch) && isset($aMatch[1])) + { + return is_numeric($aMatch[1]) ? (int) $aMatch[1] : null; + } + + return null; + } + + /** + * @param int $iID + * @param string $sSalt + * + * @return string + */ + public static function IdToHash($iID, $sSalt = '') + { + return is_int($iID) ? + \bin2hex(\MailSo\Base\Crypt::XxteaEncrypt('id:'.$iID, \md5($sSalt))) : null + ; + } + + /** + * @param string $sPassword + * + * @return bool + */ + public static function PasswordWeaknessCheck($sPassword) + { + $sPassword = \trim($sPassword); + if (6 > \strlen($sPassword)) + { + return false; + } + + $sLine = 'password 123.456 12345678 abc123 qwerty monkey letmein dragon 111.111 baseball iloveyou trustno1 1234567 sunshine master 123.123 welcome shadow ashley football jesus michael ninja mustang password1 123456 123456789 qwerty 111111 1234567 666666 12345678 7777777 123321 654321 1234567890 123123 555555 vkontakte gfhjkm 159753 777777 temppassword qazwsx 1q2w3e 1234 112233 121212 qwertyuiop qq18ww899 987654321 12345 zxcvbn zxcvbnm 999999 samsung ghbdtn 1q2w3e4r 1111111 123654 159357 131313 qazwsxedc 123qwe 222222 asdfgh 333333 9379992 asdfghjkl 4815162342 12344321 88888888 11111111 knopka 789456 qwertyu 1q2w3e4r5t iloveyou vfhbyf marina password qweasdzxc 10203 987654 yfnfif cjkysirj nikita 888888 vfrcbv k.,jdm qwertyuiop[] qwe123 qweasd natasha 123123123 fylhtq q1w2e3 stalker 1111111111 q1w2e3r4 nastya 147258369 147258 fyfcnfcbz 1234554321 1qaz2wsx andrey 111222 147852 genius sergey 7654321 232323 123789 fktrcfylh spartak admin test 123 azerty abc123 lol123 easytocrack1 hello saravn holysh!t test123 tundra_cool2 456 dragon thomas killer root 1111 pass master aaaaaa a monkey daniel asdasd e10adc3949ba59abbe56e057f20f883e changeme computer jessica letmein mirage loulou lol superman shadow admin123 secret administrator sophie kikugalanetroot doudou liverpool hallo sunshine charlie parola 100827092 michael andrew password1 fuckyou matrix cjmasterinf internet hallo123 eminem demo gewinner pokemon abcd1234 guest ngockhoa martin sandra asdf hejsan george qweqwe lollipop lovers q1q1q1 tecktonik naruto 12 password12 password123 password1234 password12345 password123456 password1234567 password12345678 password123456789 000000 maximius 123abc baseball1 football1 soccer princess slipknot 11111 nokia super star 666999 12341234 1234321 135790 159951 212121 zzzzzz 121314 134679 142536 19921992 753951 7007 1111114 124578 19951995 258456 qwaszx zaqwsx 55555 77777 54321 qwert 22222 33333 99999 88888 66666'; + return false === \strpos($sLine, \strtolower($sPassword)); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Base/Validator.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Base/Validator.php new file mode 100644 index 0000000..dbaf2ff --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Base/Validator.php @@ -0,0 +1,100 @@ += $iMin || null === $iMin) && + (null !== $iMax && $iNumber <= $iMax || null === $iMax); + } + + /** + * @param int $iPort + * + * @return bool + */ + public static function PortInt($iPort) + { + return \MailSo\Base\Validator::RangeInt($iPort, 0, 65535); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/CacheClient.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/CacheClient.php new file mode 100644 index 0000000..e72f56e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/CacheClient.php @@ -0,0 +1,215 @@ +oDriver = null; + $this->sCacheIndex = ''; + } + + /** + * @return \MailSo\Cache\CacheClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return $this->oDriver ? $this->oDriver->Set($sKey.$this->sCacheIndex, $sValue) : false; + } + + /** + * @param string $sKey + * + * @return bool + */ + public function SetTimer($sKey) + { + return $this->Set($sKey.'/TIMER', time()); + } + + /** + * @param string $sKey + * + * @return bool + */ + public function SetLock($sKey) + { + return $this->Set($sKey.'/LOCK', '1'); + } + + /** + * @param string $sKey + * + * @return bool + */ + public function RemoveLock($sKey) + { + return $this->Set($sKey.'/LOCK', '0'); + } + + /** + * @param string $sKey + * + * @return bool + */ + public function GetLock($sKey) + { + return '1' === $this->Get($sKey.'/LOCK'); + } + + /** + * @param string $sKey + * @param string $bClearAfterGet = false + * + * @return string + */ + public function Get($sKey, $bClearAfterGet = false) + { + $sValue = ''; + + if ($this->oDriver) + { + $sValue = $this->oDriver->Get($sKey.$this->sCacheIndex); + } + + if ($bClearAfterGet) + { + $this->Delete($sKey); + } + + return $sValue; + } + + /** + * @param string $sKey + * + * @return int + */ + public function GetTimer($sKey) + { + $iTimer = 0; + $sValue = $this->Get($sKey.'/TIMER'); + if (0 < strlen($sValue) && is_numeric($sValue)) + { + $iTimer = (int) $sValue; + } + + return $iTimer; + } + + /** + * @param string $sKey + * + * @return \MailSo\Cache\CacheClient + */ + public function Delete($sKey) + { + if ($this->oDriver) + { + $this->oDriver->Delete($sKey.$this->sCacheIndex); + } + + return $this; + } + + /** + * @param \MailSo\Cache\DriverInterface $oDriver + * + * @return \MailSo\Cache\CacheClient + */ + public function SetDriver(\MailSo\Cache\DriverInterface $oDriver) + { + $this->oDriver = $oDriver; + + return $this; + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + return $this->oDriver ? $this->oDriver->GC($iTimeToClearInHours) : false; + } + + /** + * @return bool + */ + public function IsInited() + { + return $this->oDriver instanceof \MailSo\Cache\DriverInterface; + } + + /** + * @param string $sCacheIndex + * + * @return \MailSo\Cache\CacheClient + */ + public function SetCacheIndex($sCacheIndex) + { + $this->sCacheIndex = 0 < \strlen($sCacheIndex) ? "\x0".$sCacheIndex : ''; + + return $this; + } + + /** + * @param bool $bCache = false + * + * @return bool + */ + public function Verify($bCache = false) + { + if ($this->oDriver) + { + $sCacheData = \gmdate('Y-m-d-H'); + if ($bCache && $sCacheData === $this->Get('__verify_key__')) + { + return true; + } + + return $this->Set('__verify_key__', $sCacheData); + } + + return false; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/DriverInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/DriverInterface.php new file mode 100644 index 0000000..8751ba1 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/DriverInterface.php @@ -0,0 +1,48 @@ +sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) + { + $this->sKeyPrefix = + \preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/'; + } + } + + /** + * @param string $sKeyPrefix = '' + * + * @return \MailSo\Cache\Drivers\APC + */ + public static function NewInstance($sKeyPrefix = '') + { + return new self($sKeyPrefix); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return \apc_store($this->generateCachedKey($sKey), (string) $sValue); + } + + /** + * @param string $sKey + * + * @return string + */ + public function Get($sKey) + { + $sValue = \apc_fetch($this->generateCachedKey($sKey)); + return \is_string($sValue) ? $sValue : ''; + } + + /** + * @param string $sKey + * + * @return void + */ + public function Delete($sKey) + { + \apc_delete($this->generateCachedKey($sKey)); + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + if (0 === $iTimeToClearInHours) + { + return \apc_clear_cache('user'); + } + + return false; + } + + /** + * @param string $sKey + * + * @return string + */ + private function generateCachedKey($sKey) + { + return $this->sKeyPrefix.\sha1($sKey); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/File.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/File.php new file mode 100644 index 0000000..bee1c51 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/File.php @@ -0,0 +1,153 @@ +sCacheFolder = $sCacheFolder; + $this->sCacheFolder = rtrim(trim($this->sCacheFolder), '\\/').'/'; + + $this->sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) + { + $this->sKeyPrefix = \str_pad(\preg_replace('/[^a-zA-Z0-9_]/', '_', + rtrim(trim($this->sKeyPrefix), '\\/')), 5, '_'); + + $this->sKeyPrefix = '__/'. + \substr($this->sKeyPrefix, 0, 2).'/'.\substr($this->sKeyPrefix, 2, 2).'/'. + $this->sKeyPrefix.'/'; + } + } + + /** + * @param string $sCacheFolder + * @param string $sKeyPrefix = '' + * + * @return \MailSo\Cache\Drivers\File + */ + public static function NewInstance($sCacheFolder, $sKeyPrefix = '') + { + return new self($sCacheFolder, $sKeyPrefix); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + $sPath = $this->generateCachedFileName($sKey, true); + return '' === $sPath ? false : false !== \file_put_contents($sPath, $sValue); + } + + /** + * @param string $sKey + * + * @return string + */ + public function Get($sKey) + { + $sValue = ''; + $sPath = $this->generateCachedFileName($sKey); + if ('' !== $sPath && \file_exists($sPath)) + { + $sValue = \file_get_contents($sPath); + } + + return \is_string($sValue) ? $sValue : ''; + } + + /** + * @param string $sKey + * + * @return void + */ + public function Delete($sKey) + { + $sPath = $this->generateCachedFileName($sKey); + if ('' !== $sPath && \file_exists($sPath)) + { + \unlink($sPath); + } + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + if (0 < $iTimeToClearInHours) + { + \MailSo\Base\Utils::RecTimeDirRemove($this->sCacheFolder, 60 * 60 * $iTimeToClearInHours, \time()); + return true; + } + + return false; + } + + /** + * @param string $sKey + * @param bool $bMkDir = false + * + * @return string + */ + private function generateCachedFileName($sKey, $bMkDir = false) + { + $sFilePath = ''; + if (3 < \strlen($sKey)) + { + $sKeyPath = \sha1($sKey); + $sKeyPath = \substr($sKeyPath, 0, 2).'/'.\substr($sKeyPath, 2, 2).'/'.$sKeyPath; + + $sFilePath = $this->sCacheFolder.$this->sKeyPrefix.$sKeyPath; + if ($bMkDir && !\is_dir(\dirname($sFilePath))) + { + if (!@\mkdir(\dirname($sFilePath), 0755, true)) + { + if (!@\mkdir(\dirname($sFilePath), 0755, true)) + { + $sFilePath = ''; + } + } + } + } + + return $sFilePath; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/Memcache.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/Memcache.php new file mode 100644 index 0000000..35094b4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/Memcache.php @@ -0,0 +1,144 @@ +sHost = $sHost; + $this->iPost = $iPost; + $this->iExpire = 0 < $iExpire ? $iExpire : 43200; + + $this->oMem = new \Memcache(); + if (!$this->oMem->connect($this->sHost, $this->iPost)) + { + $this->oMem = null; + } + + $this->sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) + { + $this->sKeyPrefix = + \preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/'; + } + } + + /** + * @param string $sHost = '127.0.0.1' + * @param int $iPost = 11211 + * @param int $iExpire = 43200 + * @param string $sKeyPrefix = '' + * + * @return \MailSo\Cache\Drivers\APC + */ + public static function NewInstance($sHost = '127.0.0.1', $iPost = 11211, $iExpire = 43200, $sKeyPrefix = '') + { + return new self($sHost, $iPost, $iExpire, $sKeyPrefix); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return $this->oMem ? $this->oMem->set($this->generateCachedKey($sKey), $sValue, 0, $this->iExpire) : false; + } + + /** + * @param string $sKey + * + * @return string + */ + public function Get($sKey) + { + $sValue = $this->oMem ? $this->oMem->get($this->generateCachedKey($sKey)) : ''; + return \is_string($sValue) ? $sValue : ''; + } + + /** + * @param string $sKey + * + * @return void + */ + public function Delete($sKey) + { + if ($this->oMem) + { + $this->oMem->delete($this->generateCachedKey($sKey)); + } + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + if (0 === $iTimeToClearInHours && $this->oMem) + { + return $this->oMem->flush(); + } + + return false; + } + + /** + * @param string $sKey + * + * @return string + */ + private function generateCachedKey($sKey) + { + return $this->sKeyPrefix.\sha1($sKey); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/Redis.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/Redis.php new file mode 100644 index 0000000..638f868 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Cache/Drivers/Redis.php @@ -0,0 +1,160 @@ +sHost = $sHost; + $this->iPost = $iPost; + $this->iExpire = 0 < $iExpire ? $iExpire : 43200; + + $this->oRedis = null; + + try + { + $this->oRedis = new \Predis\Client('unix:' === substr($sHost, 0, 5) ? $sHost : array( + 'host' => $sHost, + 'port' => $iPost + )); + + $this->oRedis->connect(); + + if (!$this->oRedis->isConnected()) + { + $this->oRedis = null; + } + } + catch (\Exception $oExc) + { + $this->oRedis = null; + unset($oExc); + } + + $this->sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) + { + $this->sKeyPrefix = + \preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/'; + } + } + + /** + * @param string $sHost = '127.0.0.1' + * @param int $iPost = 11211 + * @param int $iExpire = 43200 + * @param string $sKeyPrefix = '' + * + * @return \MailSo\Cache\Drivers\APC + */ + public static function NewInstance($sHost = '127.0.0.1', $iPost = 6379, $iExpire = 43200, $sKeyPrefix = '') + { + return new self($sHost, $iPost, $iExpire, $sKeyPrefix); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return $this->oRedis ? $this->oRedis->setex($this->generateCachedKey($sKey), $this->iExpire, $sValue) : false; + } + + /** + * @param string $sKey + * + * @return string + */ + public function Get($sKey) + { + $sValue = $this->oRedis ? $this->oRedis->get($this->generateCachedKey($sKey)) : ''; + return \is_string($sValue) ? $sValue : ''; + } + + /** + * @param string $sKey + * + * @return void + */ + public function Delete($sKey) + { + if ($this->oRedis) + { + $this->oRedis->del($this->generateCachedKey($sKey)); + } + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + if (0 === $iTimeToClearInHours && $this->oRedis) + { + return $this->oRedis->flushdb(); + } + + return false; + } + + /** + * @param string $sKey + * + * @return string + */ + private function generateCachedKey($sKey) + { + return $this->sKeyPrefix.\sha1($sKey); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Config.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Config.php new file mode 100644 index 0000000..696def7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Config.php @@ -0,0 +1,113 @@ +sContentType = $sContentType; + $this->sCharset = $sCharset; + $this->aBodyParams = $aBodyParams; + $this->sContentID = $sContentID; + $this->sDescription = $sDescription; + $this->sMailEncodingName = $sMailEncodingName; + $this->sDisposition = $sDisposition; + $this->aDispositionParams = $aDispositionParams; + $this->sFileName = $sFileName; + $this->sLanguage = $sLanguage; + $this->sLocation = $sLocation; + $this->iSize = $iSize; + $this->iTextLineCount = $iTextLineCount; + $this->sPartID = $sPartID; + $this->aSubParts = $aSubParts; + } + + /** + * return string + */ + public function MailEncodingName() + { + return $this->sMailEncodingName; + } + + /** + * return string + */ + public function PartID() + { + return (string) $this->sPartID; + } + + /** + * return string + */ + public function FileName() + { + return $this->sFileName; + } + + /** + * return string + */ + public function ContentType() + { + return $this->sContentType; + } + + /** + * return int + */ + public function Size() + { + return (int) $this->iSize; + } + + /** + * return int + */ + public function EstimatedSize() + { + $fCoefficient = 1; + switch (\strtolower($this->MailEncodingName())) + { + case 'base64': + $fCoefficient = 0.75; + break; + case 'quoted-printable': + $fCoefficient = 0.44; + break; + } + + return (int) ($this->Size() * $fCoefficient); + } + + /** + * return string + */ + public function Charset() + { + return $this->sCharset; + } + + + /** + * return string + */ + public function ContentID() + { + return (null === $this->sContentID) ? '' : $this->sContentID; + } + + /** + * return string + */ + public function ContentLocation() + { + return (null === $this->sLocation) ? '' : $this->sLocation; + } + + /** + * return bool + */ + public function IsInline() + { + return (null === $this->sDisposition) ? + (0 < \strlen($this->ContentID())) : ('inline' === strtolower($this->sDisposition)); + } + + /** + * return bool + */ + public function IsImage() + { + return 'image' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * return bool + */ + public function IsArchive() + { + return 'archive' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsPdf() + { + return 'pdf' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsDoc() + { + return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsPgpSignature() + { + return \in_array(\strtolower($this->ContentType()), + array('application/pgp-signature', 'application/pkcs7-signature')); + } + + /** + * @return bool + */ + public function IsAttachBodyPart() + { + $bResult = ( + (null !== $this->sDisposition && 'attachment' === \strtolower($this->sDisposition)) + ); + + if (!$bResult && null !== $this->sContentType) + { + $sContentType = \strtolower($this->sContentType); + $bResult = false === \strpos($sContentType, 'multipart/') && + 'text/html' !== $sContentType && 'text/plain' !== $sContentType; + } + + return $bResult; + } + + /** + * @return bool + */ + public function IsFlowedFormat() + { + $bResult = !empty($this->aBodyParams['format']) && + 'flowed' === \strtolower(\trim($this->aBodyParams['format'])); + + if ($bResult && \in_array(\strtolower($this->MailEncodingName()), array('base64', 'quoted-printable'))) + { + $bResult = false; + } + + return $bResult; + } + + /** + * @return array|null + */ + public function SearchPlainParts() + { + $aReturn = array(); + $aParts = $this->SearchByContentType('text/plain'); + foreach ($aParts as $oPart) + { + if (!$oPart->IsAttachBodyPart()) + { + $aReturn[] = $oPart; + } + } + return $aReturn; + } + + /** + * @return array|null + */ + public function SearchHtmlParts() + { + $aReturn = array(); + $aParts = $this->SearchByContentType('text/html'); + + foreach ($aParts as $oPart) + { + if (!$oPart->IsAttachBodyPart()) + { + $aReturn[] = $oPart; + } + } + + return $aReturn; + } + + /** + * @return \MailSo\Imap\BodyStructure|null + */ + public function SearchInlineEncryptedPart() + { + if ('multipart/encrypted' === \strtolower($this->ContentType())) + { + $aSearchParts = $this->SearchByCallback(function ($oItem) { + return $oItem->IsInline(); + }); + + if (is_array($aSearchParts) && 1 === \count($aSearchParts) && isset($aSearchParts[0])) + { + return $aSearchParts[0]; + } + } + + return null; + } + + /** + * @return array|null + */ + public function SearchHtmlOrPlainParts() + { + $mResult = $this->SearchHtmlParts(); + if (null === $mResult || (\is_array($mResult) && 0 === count($mResult))) + { + $mResult = $this->SearchPlainParts(); + } + + if (null === $mResult || (\is_array($mResult) && 0 === count($mResult))) + { + $oPart = $this->SearchInlineEncryptedPart(); + if ($oPart instanceof \MailSo\Imap\BodyStructure) + { + $mResult = array($oPart); + } + } + + return $mResult; + } + + /** + * @return string + */ + public function SearchCharset() + { + $sResult = ''; + $mParts = array(); + + $mHtmlParts = $this->SearchHtmlParts(); + $mPlainParts = $this->SearchPlainParts(); + + if (\is_array($mHtmlParts) && 0 < \count($mHtmlParts)) + { + $mParts = \array_merge($mParts, $mHtmlParts); + } + + if (\is_array($mPlainParts) && 0 < \count($mPlainParts)) + { + $mParts = \array_merge($mParts, $mPlainParts); + } + + foreach ($mParts as $oPart) + { + $sResult = $oPart ? $oPart->Charset() : ''; + if (!empty($sResult)) + { + break; + } + } + + if (0 === strlen($sResult)) + { + $aParts = $this->SearchAttachmentsParts(); + foreach ($aParts as $oPart) + { + if (0 === \strlen($sResult)) + { + $sResult = $oPart ? $oPart->Charset() : ''; + } + else + { + break; + } + } + } + + return $sResult; + } + + /** + * @param mixed $fCallback + * + * @return array + */ + public function SearchByCallback($fCallback) + { + $aReturn = array(); + if (\call_user_func($fCallback, $this)) + { + $aReturn[] = $this; + } + + if (\is_array($this->aSubParts) && 0 < \count($this->aSubParts)) + { + foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ &$oSubPart) + { + $aReturn = \array_merge($aReturn, $oSubPart->SearchByCallback($fCallback)); + } + } + + return $aReturn; + } + + /** + * @return array + */ + public function SearchAttachmentsParts() + { + return $this->SearchByCallback(function ($oItem) { + return $oItem->IsAttachBodyPart(); + }); + } + + /** + * @param string $sContentType + * + * @return array + */ + public function SearchByContentType($sContentType) + { + $sContentType = \strtolower($sContentType); + return $this->SearchByCallback(function ($oItem) use ($sContentType) { + return $sContentType === $oItem->ContentType(); + }); + } + + /** + * @param string $sMimeIndex + * + * @return \MailSo\Imap\BodyStructure + */ + public function GetPartByMimeIndex($sMimeIndex) + { + $oPart = null; + if (0 < \strlen($sMimeIndex)) + { + if ($sMimeIndex === $this->sPartID) + { + $oPart = $this; + } + + if (null === $oPart && is_array($this->aSubParts) && 0 < count($this->aSubParts)) + { + foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ &$oSubPart) + { + $oPart = $oSubPart->GetPartByMimeIndex($sMimeIndex); + if (null !== $oPart) + { + break; + } + } + } + } + + return $oPart; + } + + /** + * @param array $aParams + * @param string $sParamName + * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 + * + * @return string + */ + private static function decodeAttrParamenter($aParams, $sParamName, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) + { + $sResult = ''; + if (isset($aParams[$sParamName])) + { + $sResult = \MailSo\Base\Utils::DecodeHeaderValue($aParams[$sParamName], $sCharset); + } + else if (isset($aParams[$sParamName.'*'])) + { + $aValueParts = \explode("''", $aParams[$sParamName.'*'], 2); + if (\is_array($aValueParts) && 2 === \count($aValueParts)) + { + $sCharset = isset($aValueParts[0]) ? $aValueParts[0] : \MailSo\Base\Enumerations\Charset::UTF_8; + + $sResult = \MailSo\Base\Utils::ConvertEncoding( + \urldecode($aValueParts[1]), $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8); + } + else + { + $sResult = \urldecode($aParams[$sParamName.'*']); + } + } + else + { + $sCharset = ''; + $sCharsetIndex = -1; + + $aFileNames = array(); + foreach ($aParams as $sName => $sValue) + { + $aMatches = array(); + if (\preg_match('/^'.\preg_quote($sParamName, '/').'\*([0-9]+)\*$/i', $sName, $aMatches)) + { + $iIndex = (int) $aMatches[1]; + if ($sCharsetIndex < $iIndex && false !== \strpos($sValue, "''")) + { + $aValueParts = \explode("''", $sValue, 2); + if (\is_array($aValueParts) && 2 === \count($aValueParts) && 0 < \strlen($aValueParts[0])) + { + $sCharsetIndex = $iIndex; + $sCharset = $aValueParts[0]; + $sValue = $aValueParts[1]; + } + } + + $aFileNames[$iIndex] = $sValue; + } + } + + if (0 < \count($aFileNames)) + { + \ksort($aFileNames, SORT_NUMERIC); + $sResult = \implode(\array_values($aFileNames)); + $sResult = \urldecode($sResult); + + if (0 < \strlen($sCharset)) + { + $sResult = \MailSo\Base\Utils::ConvertEncoding($sResult, + $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8); + } + } + } + + return $sResult; + } + + /** + * @param array $aBodyStructure + * @param string $sPartID = '' + * + * @return \MailSo\Imap\BodyStructure + */ + public static function NewInstance(array $aBodyStructure, $sPartID = '') + { + if (!\is_array($aBodyStructure) || 2 > \count($aBodyStructure)) + { + return null; + } + else + { + $sBodyMainType = null; + if (\is_string($aBodyStructure[0]) && 'NIL' !== $aBodyStructure[0]) + { + $sBodyMainType = $aBodyStructure[0]; + } + + $sBodySubType = null; + $sContentType = ''; + $aSubParts = null; + $aBodyParams = array(); + $sName = null; + $sCharset = null; + $sContentID = null; + $sDescription = null; + $sMailEncodingName = null; + $iSize = 0; + $iTextLineCount = 0; // valid for rfc822/message and text parts + $iExtraItemPos = 0; // list index of items which have no well-established position (such as 0, 1, 5, etc). + + if (null === $sBodyMainType) + { + // Process multipart body structure + if (!\is_array($aBodyStructure[0])) + { + return null; + } + else + { + $sBodyMainType = 'multipart'; + $sSubPartIDPrefix = ''; + if (0 === \strlen($sPartID) || '.' === $sPartID[\strlen($sPartID) - 1]) + { + // This multi-part is root part of message. + $sSubPartIDPrefix = $sPartID; + $sPartID .= 'TEXT'; + } + else if (0 < \strlen($sPartID)) + { + // This multi-part is a part of another multi-part. + $sSubPartIDPrefix = $sPartID.'.'; + } + + $aSubParts = array(); + $iIndex = 1; + + while ($iExtraItemPos < \count($aBodyStructure) && \is_array($aBodyStructure[$iExtraItemPos])) + { + $oPart = self::NewInstance($aBodyStructure[$iExtraItemPos], $sSubPartIDPrefix.$iIndex); + if (null === $oPart) + { + return null; + } + else + { + // For multipart, we have no charset info in the part itself. Thus, + // obtain charset from nested parts. + if ($sCharset == null) + { + $sCharset = $oPart->Charset(); + } + + $aSubParts[] = $oPart; + $iExtraItemPos++; + $iIndex++; + } + } + } + + if ($iExtraItemPos < \count($aBodyStructure)) + { + if (!\is_string($aBodyStructure[$iExtraItemPos]) || 'NIL' === $aBodyStructure[$iExtraItemPos]) + { + return null; + } + + $sBodySubType = \strtolower($aBodyStructure[$iExtraItemPos]); + $iExtraItemPos++; + } + + if ($iExtraItemPos < \count($aBodyStructure)) + { + $sBodyParamList = $aBodyStructure[$iExtraItemPos]; + if (\is_array($sBodyParamList)) + { + $aBodyParams = self::getKeyValueListFromArrayList($sBodyParamList); + } + } + + $iExtraItemPos++; + } + else + { + // Process simple (singlepart) body structure + if (7 > \count($aBodyStructure)) + { + return null; + } + + $sBodyMainType = \strtolower($sBodyMainType); + if (!\is_string($aBodyStructure[1]) || 'NIL' === $aBodyStructure[1]) + { + return null; + } + + $sBodySubType = \strtolower($aBodyStructure[1]); + + $aBodyParamList = $aBodyStructure[2]; + if (\is_array($aBodyParamList)) + { + $aBodyParams = self::getKeyValueListFromArrayList($aBodyParamList); + if (isset($aBodyParams['charset'])) + { + $sCharset = $aBodyParams['charset']; + } + + if (\is_array($aBodyParams)) + { + $sName = self::decodeAttrParamenter($aBodyParams, 'name', $sContentType); + } + } + + if (null !== $aBodyStructure[3] && 'NIL' !== $aBodyStructure[3]) + { + if (!\is_string($aBodyStructure[3])) + { + return null; + } + + $sContentID = $aBodyStructure[3]; + } + + if (null !== $aBodyStructure[4] && 'NIL' !== $aBodyStructure[4]) + { + if (!\is_string($aBodyStructure[4])) + { + return null; + } + + $sDescription = $aBodyStructure[4]; + } + + if (null !== $aBodyStructure[5] && 'NIL' !== $aBodyStructure[5]) + { + if (!\is_string($aBodyStructure[5])) + { + return null; + } + $sMailEncodingName = $aBodyStructure[5]; + } + + if (\is_numeric($aBodyStructure[6])) + { + $iSize = (int) $aBodyStructure[6]; + } + else + { + $iSize = -1; + } + + if (0 === \strlen($sPartID) || '.' === $sPartID[\strlen($sPartID) - 1]) + { + // This is the only sub-part of the message (otherwise, it would be + // one of sub-parts of a multi-part, and partID would already be fully set up). + $sPartID .= '1'; + } + + $iExtraItemPos = 7; + if ('text' === $sBodyMainType) + { + if ($iExtraItemPos < \count($aBodyStructure)) + { + if (\is_numeric($aBodyStructure[$iExtraItemPos])) + { + $iTextLineCount = (int) $aBodyStructure[$iExtraItemPos]; + } + else + { + $iTextLineCount = -1; + } + } + else + { + $iTextLineCount = -1; + } + + $iExtraItemPos++; + } + else if ('message' === $sBodyMainType && 'rfc822' === $sBodySubType) + { + if ($iExtraItemPos + 2 < \count($aBodyStructure)) + { + if (\is_numeric($aBodyStructure[$iExtraItemPos + 2])) + { + $iTextLineCount = (int) $aBodyStructure[$iExtraItemPos + 2]; + } + else + { + $iTextLineCount = -1; + } + } + else + { + $iTextLineCount = -1; + } + + $iExtraItemPos += 3; + } + + $iExtraItemPos++; // skip MD5 digest of the body because most mail servers leave it NIL anyway + } + + $sContentType = $sBodyMainType.'/'.$sBodySubType; + + $sDisposition = null; + $aDispositionParams = null; + $sFileName = null; + + if ($iExtraItemPos < \count($aBodyStructure)) + { + $aDispList = $aBodyStructure[$iExtraItemPos]; + if (\is_array($aDispList) && 1 < \count($aDispList)) + { + if (null !== $aDispList[0]) + { + if (\is_string($aDispList[0]) && 'NIL' !== $aDispList[0]) + { + $sDisposition = $aDispList[0]; + } + else + { + return null; + } + } + } + + $aDispParamList = $aDispList[1]; + if (\is_array($aDispParamList)) + { + $aDispositionParams = self::getKeyValueListFromArrayList($aDispParamList); + if (\is_array($aDispositionParams)) + { + $sFileName = self::decodeAttrParamenter($aDispositionParams, 'filename', $sCharset); + } + } + } + + $iExtraItemPos++; + + $sLanguage = null; + if ($iExtraItemPos < count($aBodyStructure)) + { + if (null !== $aBodyStructure[$iExtraItemPos] && 'NIL' !== $aBodyStructure[$iExtraItemPos]) + { + if (\is_array($aBodyStructure[$iExtraItemPos])) + { + $sLanguage = \implode(',', $aBodyStructure[$iExtraItemPos]); + } + else if (\is_string($aBodyStructure[$iExtraItemPos])) + { + $sLanguage = $aBodyStructure[$iExtraItemPos]; + } + } + $iExtraItemPos++; + } + + $sLocation = null; + if ($iExtraItemPos < \count($aBodyStructure)) + { + if (null !== $aBodyStructure[$iExtraItemPos] && 'NIL' !== $aBodyStructure[$iExtraItemPos]) + { + if (\is_string($aBodyStructure[$iExtraItemPos])) + { + $sLocation = $aBodyStructure[$iExtraItemPos]; + } + } + $iExtraItemPos++; + } + + return new self( + $sContentType, + $sCharset, + $aBodyParams, + $sContentID, + $sDescription, + $sMailEncodingName, + $sDisposition, + $aDispositionParams, + \MailSo\Base\Utils::Utf8Clear((null === $sFileName || 0 === \strlen($sFileName)) ? $sName : $sFileName), + $sLanguage, + $sLocation, + $iSize, + $iTextLineCount, + $sPartID, + $aSubParts + ); + } + } + + /** + * @param array $aBodyStructure + * @param string $sSubPartID + * + * @return \MailSo\Imap\BodyStructure|null + */ + public static function NewInstanceFromRfc822SubPart(array $aBodyStructure, $sSubPartID) + { + $oBody = null; + $aBodySubStructure = self::findPartByIndexInArray($aBodyStructure, $sSubPartID); + if ($aBodySubStructure && \is_array($aBodySubStructure) && isset($aBodySubStructure[8])) + { + $oBody = self::NewInstance($aBodySubStructure[8], $sSubPartID); + } + + return $oBody; + } + + /** + * @param array $aList + * @param string $sPartID + * + * @return array|null + */ + private static function findPartByIndexInArray(array $aList, $sPartID) + { + $bFind = false; + $aPath = \explode('.', ''.$sPartID); + $aCurrentPart = $aList; + + foreach ($aPath as $iPos => $iNum) + { + $iIndex = \intval($iNum) - 1; + if (0 <= $iIndex && 0 < $iPos ? isset($aCurrentPart[8][$iIndex]) : isset($aCurrentPart[$iIndex])) + { + $aCurrentPart = 0 < $iPos ? $aCurrentPart[8][$iIndex] : $aCurrentPart[$iIndex]; + $bFind = true; + } + } + + return $bFind ? $aCurrentPart : null; + } + + /** + * Returns dict with key="charset" and value="US-ASCII" for array ("CHARSET" "US-ASCII"). + * Keys are lowercased (StringDictionary itself does this), values are not altered. + * + * @param array $aList + * + * @return array + */ + private static function getKeyValueListFromArrayList(array $aList) + { + $aDict = null; + if (0 === \count($aList) % 2) + { + $aDict = array(); + for ($iIndex = 0, $iLen = \count($aList); $iIndex < $iLen; $iIndex += 2) + { + if (\is_string($aList[$iIndex]) && isset($aList[$iIndex + 1]) && \is_string($aList[$iIndex + 1])) + { + $aDict[\strtolower($aList[$iIndex])] = $aList[$iIndex + 1]; + } + } + } + + return $aDict; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php new file mode 100644 index 0000000..fe6a36e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php @@ -0,0 +1,127 @@ +GetLastResponse(); + if ($oResponse && $oResponse->IsStatusResponse && !empty($oResponse->HumanReadable) && + isset($oResponse->OptionalResponse[0]) && 'ALERT' === $oResponse->OptionalResponse[0]) + { + $sResult = $oResponse->HumanReadable; + } + + return $sResult; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php new file mode 100644 index 0000000..24f0d8c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php @@ -0,0 +1,57 @@ +aResponses = $aResponses; + } + } + + /** + * @return array + */ + public function GetResponses() + { + return $this->aResponses; + } + + /** + * @return \MailSo\Imap\Response|null + */ + public function GetLastResponse() + { + return 0 < count($this->aResponses) ? $this->aResponses[count($this->aResponses) - 1] : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Exceptions/ResponseNotFoundException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Exceptions/ResponseNotFoundException.php new file mode 100644 index 0000000..2d4d029 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Exceptions/ResponseNotFoundException.php @@ -0,0 +1,19 @@ +oImapResponse = $oImapResponse; + $this->aEnvelopeCache = null; + } + + /** + * @param \MailSo\Imap\Response $oImapResponse + * @return \MailSo\Imap\FetchResponse + */ + public static function NewInstance($oImapResponse) + { + return new self($oImapResponse); + } + + /** + * @param bool $bForce = false + * + * @return array|null + */ + public function GetEnvelope($bForce = false) + { + if (null === $this->aEnvelopeCache || $bForce) + { + $this->aEnvelopeCache = $this->GetFetchValue(Enumerations\FetchType::ENVELOPE); + } + return $this->aEnvelopeCache; + } + + /** + * @param int $iIndex + * @param mixed $mNullResult = null + * + * @return mixed + */ + public function GetFetchEnvelopeValue($iIndex, $mNullResult) + { + return self::findEnvelopeIndex($this->GetEnvelope(), $iIndex, $mNullResult); + } + + /** + * @param int $iIndex + * @param string $sParentCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1 + * + * @return \MailSo\Mime\EmailCollection|null + */ + public function GetFetchEnvelopeEmailCollection($iIndex, $sParentCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1) + { + $oResult = null; + $aEmails = $this->GetFetchEnvelopeValue($iIndex, null); + if (is_array($aEmails) && 0 < count($aEmails)) + { + $oResult = \MailSo\Mime\EmailCollection::NewInstance(); + foreach ($aEmails as $aEmailItem) + { + if (is_array($aEmailItem) && 4 === count($aEmailItem)) + { + $sDisplayName = \MailSo\Base\Utils::DecodeHeaderValue( + self::findEnvelopeIndex($aEmailItem, 0, ''), $sParentCharset); + +// $sRemark = \MailSo\Base\Utils::DecodeHeaderValue( +// self::findEnvelopeIndex($aEmailItem, 1, ''), $sParentCharset); + + $sLocalPart = self::findEnvelopeIndex($aEmailItem, 2, ''); + $sDomainPart = self::findEnvelopeIndex($aEmailItem, 3, ''); + + if (0 < strlen($sLocalPart) && 0 < strlen($sDomainPart)) + { + $oResult->Add( + \MailSo\Mime\Email::NewInstance($sLocalPart.'@'.$sDomainPart, $sDisplayName) + ); + } + } + } + } + + return $oResult; + } + + /** + * @param string $sRfc822SubMimeIndex = '' + * + * @return \MailSo\Imap\BodyStructure|null + */ + public function GetFetchBodyStructure($sRfc822SubMimeIndex = '') + { + $oBodyStructure = null; + $aBodyStructureArray = $this->GetFetchValue(Enumerations\FetchType::BODYSTRUCTURE); + + if (is_array($aBodyStructureArray)) + { + if (0 < strlen($sRfc822SubMimeIndex)) + { + $oBodyStructure = BodyStructure::NewInstanceFromRfc822SubPart($aBodyStructureArray, $sRfc822SubMimeIndex); + } + else + { + $oBodyStructure = BodyStructure::NewInstance($aBodyStructureArray); + } + } + + return $oBodyStructure; + } + + /** + * @param string $sFetchItemName + * + * @return mixed + */ + public function GetFetchValue($sFetchItemName) + { + $mReturn = null; + $bNextIsValue = false; + + if (Enumerations\FetchType::INDEX === $sFetchItemName) + { + $mReturn = $this->oImapResponse->ResponseList[1]; + } + else if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3])) + { + foreach ($this->oImapResponse->ResponseList[3] as $mItem) + { + if ($bNextIsValue) + { + $mReturn = $mItem; + break; + } + + if ($sFetchItemName === $mItem) + { + $bNextIsValue = true; + } + } + } + + return $mReturn; + } + + /** + * @param string $sRfc822SubMimeIndex = '' + * + * @return string + */ + public function GetHeaderFieldsValue($sRfc822SubMimeIndex = '') + { + $sReturn = ''; + $bNextIsValue = false; + + $sRfc822SubMimeIndex = 0 < \strlen($sRfc822SubMimeIndex) ? ''.$sRfc822SubMimeIndex.'.' : ''; + + if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3])) + { + foreach ($this->oImapResponse->ResponseList[3] as $mItem) + { + if ($bNextIsValue) + { + $sReturn = (string) $mItem; + break; + } + + if (\is_string($mItem) && ( + $mItem === 'BODY['.$sRfc822SubMimeIndex.'HEADER]' || + 0 === \strpos($mItem, 'BODY['.$sRfc822SubMimeIndex.'HEADER.FIELDS') || + $mItem === 'BODY['.$sRfc822SubMimeIndex.'MIME]')) + { + $bNextIsValue = true; + } + } + } + + return $sReturn; + } + + private static function findFetchUidAndSize($aList) + { + $bUid = false; + $bSize = false; + if (is_array($aList)) + { + foreach ($aList as $mItem) + { + if (\MailSo\Imap\Enumerations\FetchType::UID === $mItem) + { + $bUid = true; + } + else if (\MailSo\Imap\Enumerations\FetchType::RFC822_SIZE === $mItem) + { + $bSize = true; + } + } + } + + return $bUid && $bSize; + } + + /** + * @param \MailSo\Imap\Response $oImapResponse + * + * @return bool + */ + public static function IsValidFetchImapResponse($oImapResponse) + { + return ( + $oImapResponse + && true !== $oImapResponse->IsStatusResponse + && \MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && 3 < count($oImapResponse->ResponseList) && 'FETCH' === $oImapResponse->ResponseList[2] + && is_array($oImapResponse->ResponseList[3]) + ); + } + + /** + * @param \MailSo\Imap\Response $oImapResponse + * + * @return bool + */ + public static function IsNotEmptyFetchImapResponse($oImapResponse) + { + return ( + $oImapResponse + && self::IsValidFetchImapResponse($oImapResponse) + && isset($oImapResponse->ResponseList[3]) + && self::findFetchUidAndSize($oImapResponse->ResponseList[3]) + ); + } + + /** + * @param array $aEnvelope + * @param int $iIndex + * @param mixed $mNullResult = null + * + * @return mixed + */ + private static function findEnvelopeIndex($aEnvelope, $iIndex, $mNullResult) + { + return (isset($aEnvelope[$iIndex]) && 'NIL' !== $aEnvelope[$iIndex] && '' !== $aEnvelope[$iIndex]) + ? $aEnvelope[$iIndex] : $mNullResult; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Folder.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Folder.php new file mode 100644 index 0000000..50fd423 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Folder.php @@ -0,0 +1,193 @@ +sNameRaw = ''; + $this->sFullNameRaw = ''; + $this->sDelimiter = ''; + $this->aFlags = array(); + $this->aExtended = array(); + + $sDelimiter = 'NIL' === \strtoupper($sDelimiter) ? '' : $sDelimiter; + if (empty($sDelimiter)) + { + $sDelimiter = '.'; // default delimiter + } + + if (!\is_array($aFlags) || + !\is_string($sDelimiter) || 1 < \strlen($sDelimiter) || + !\is_string($sFullNameRaw) || 0 === \strlen($sFullNameRaw)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->sFullNameRaw = $sFullNameRaw; + $this->sDelimiter = $sDelimiter; + $this->aFlags = $aFlags; + $this->aFlagsLowerCase = \array_map('strtolower', $this->aFlags); + + $this->sFullNameRaw = 'INBOX'.$this->sDelimiter === \substr(\strtoupper($this->sFullNameRaw), 0, 5 + \strlen($this->sDelimiter)) ? + 'INBOX'.\substr($this->sFullNameRaw, 5) : $this->sFullNameRaw; + + if ($this->IsInbox()) + { + $this->sFullNameRaw = 'INBOX'; + } + + $this->sNameRaw = $this->sFullNameRaw; + if (0 < \strlen($this->sDelimiter)) + { + $aNames = \explode($this->sDelimiter, $this->sFullNameRaw); + if (false !== \array_search('', $aNames)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->sNameRaw = \end($aNames); + } + } + + /** + * @param string $sFullNameRaw + * @param string $sDelimiter = '.' + * @param array $aFlags = array() + * + * @return \MailSo\Imap\Folder + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function NewInstance($sFullNameRaw, $sDelimiter = '.', $aFlags = array()) + { + return new self($sFullNameRaw, $sDelimiter, $aFlags); + } + + /** + * @return string + */ + public function NameRaw() + { + return $this->sNameRaw; + } + + /** + * @return string + */ + public function FullNameRaw() + { + return $this->sFullNameRaw; + } + + /** + * @return string | null + */ + public function Delimiter() + { + return $this->sDelimiter; + } + + /** + * @return array + */ + public function Flags() + { + return $this->aFlags; + } + + /** + * @return array + */ + public function FlagsLowerCase() + { + return $this->aFlagsLowerCase; + } + + /** + * @return bool + */ + public function IsSelectable() + { + return !\in_array('\noselect', $this->aFlagsLowerCase); + } + + /** + * @return bool + */ + public function IsInbox() + { + return 'INBOX' === \strtoupper($this->sFullNameRaw) || \in_array('\inbox', $this->aFlagsLowerCase); + } + + /** + * @param string $sName + * @param mixed $mData + */ + public function SetExtended($sName, $mData) + { + $this->aExtended[$sName] = $mData; + } + + /** + * @param string $sName + * @return mixed + */ + public function GetExtended($sName) + { + return isset($this->aExtended[$sName]) ? $this->aExtended[$sName] : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/FolderInformation.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/FolderInformation.php new file mode 100644 index 0000000..d8fe5a0 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/FolderInformation.php @@ -0,0 +1,112 @@ +FolderName = $sFolderName; + $this->IsWritable = $bIsWritable; + $this->Exists = null; + $this->Recent = null; + $this->Flags = array(); + $this->PermanentFlags = array(); + + $this->Unread = null; + $this->Uidnext = null; + $this->HighestModSeq = null; + } + + /** + * @param string $sFolderName + * @param bool $bIsWritable + * + * @return \MailSo\Imap\FolderInformation + */ + public static function NewInstance($sFolderName, $bIsWritable) + { + return new self($sFolderName, $bIsWritable); + } + + /** + * @param string $sFlag + * + * @return bool + */ + public function IsFlagSupported($sFlag) + { + return \in_array('\\*', $this->PermanentFlags) || + \in_array($sFlag, $this->PermanentFlags) || + \in_array($sFlag, $this->Flags); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/ImapClient.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/ImapClient.php new file mode 100644 index 0000000..beef069 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/ImapClient.php @@ -0,0 +1,2669 @@ +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 + * @param string $sClientCert = '' + * + * @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, + $sClientCert = '') + { + $this->aTagTimeouts['*'] = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert); + + $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/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/NamespaceResult.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/NamespaceResult.php new file mode 100644 index 0000000..7d053f2 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/NamespaceResult.php @@ -0,0 +1,132 @@ +sPersonal = ''; + $this->sPersonalDelimiter = ''; + $this->sOtherUser = ''; + $this->sOtherUserDelimiter = ''; + $this->sShared = ''; + $this->sSharedDelimiter = ''; + } + + /** + * @return \MailSo\Imap\NamespaceResult + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param \MailSo\Imap\Response $oImapResponse + * + * @return \MailSo\Imap\NamespaceResult + */ + public function InitByImapResponse($oImapResponse) + { + if ($oImapResponse && $oImapResponse instanceof \MailSo\Imap\Response) + { + if (isset($oImapResponse->ResponseList[2][0]) && + \is_array($oImapResponse->ResponseList[2][0]) && + 2 <= \count($oImapResponse->ResponseList[2][0])) + { + $this->sPersonal = $oImapResponse->ResponseList[2][0][0]; + $this->sPersonalDelimiter = $oImapResponse->ResponseList[2][0][1]; + + $this->sPersonal = 'INBOX'.$this->sPersonalDelimiter === \substr(\strtoupper($this->sPersonal), 0, 6) ? + 'INBOX'.$this->sPersonalDelimiter.\substr($this->sPersonal, 6) : $this->sPersonal; + } + + if (isset($oImapResponse->ResponseList[3][0]) && + \is_array($oImapResponse->ResponseList[3][0]) && + 2 <= \count($oImapResponse->ResponseList[3][0])) + { + $this->sOtherUser = $oImapResponse->ResponseList[3][0][0]; + $this->sOtherUserDelimiter = $oImapResponse->ResponseList[3][0][1]; + + $this->sOtherUser = 'INBOX'.$this->sOtherUserDelimiter === \substr(\strtoupper($this->sOtherUser), 0, 6) ? + 'INBOX'.$this->sOtherUserDelimiter.\substr($this->sOtherUser, 6) : $this->sOtherUser; + } + + if (isset($oImapResponse->ResponseList[4][0]) && + \is_array($oImapResponse->ResponseList[4][0]) && + 2 <= \count($oImapResponse->ResponseList[4][0])) + { + $this->sShared = $oImapResponse->ResponseList[4][0][0]; + $this->sSharedDelimiter = $oImapResponse->ResponseList[4][0][1]; + + $this->sShared = 'INBOX'.$this->sSharedDelimiter === \substr(\strtoupper($this->sShared), 0, 6) ? + 'INBOX'.$this->sSharedDelimiter.\substr($this->sShared, 6) : $this->sShared; + } + } + + return $this; + } + + /** + * @return string + */ + public function GetPersonalNamespace() + { + return $this->sPersonal; + } + + /** + * @return string + */ + public function GetPersonalNamespaceDelimiter() + { + return $this->sPersonalDelimiter; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Response.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Response.php new file mode 100644 index 0000000..fb787ba --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Imap/Response.php @@ -0,0 +1,104 @@ +ResponseList = array(); + $this->OptionalResponse = null; + $this->StatusOrIndex = ''; + $this->HumanReadable = ''; + $this->IsStatusResponse = false; + $this->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNKNOWN; + $this->Tag = ''; + } + + /** + * @return \MailSo\Imap\Response + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $aList + * + * @return string + */ + private function recToLine($aList) + { + $aResult = array(); + if (\is_array($aList)) + { + foreach ($aList as $mItem) + { + $aResult[] = \is_array($mItem) ? '('.$this->recToLine($mItem).')' : (string) $mItem; + } + } + + return \implode(' ', $aResult); + } + + + /** + * @return string + */ + public function ToLine() + { + return $this->recToLine($this->ResponseList); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/LICENSE b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/LICENSE new file mode 100644 index 0000000..6229c40 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Usenko Timur + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Driver.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Driver.php new file mode 100644 index 0000000..fd313fc --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Driver.php @@ -0,0 +1,408 @@ +sDatePattern = 'H:i:s'; + $this->sName = 'INFO'; + $this->sNewLine = "\r\n"; + $this->bTimePrefix = true; + $this->bTypedPrefix = true; + $this->bGuidPrefix = true; + + $this->sTimeOffset = '0'; + + $this->iWriteOnTimeoutOnly = 0; + $this->bWriteOnErrorOnly = false; + $this->bWriteOnPhpErrorOnly = false; + $this->bFlushCache = false; + $this->aCache = array(); + + $this->aPrefixes = array( + \MailSo\Log\Enumerations\Type::INFO => '[DATA]', + \MailSo\Log\Enumerations\Type::SECURE => '[SECURE]', + \MailSo\Log\Enumerations\Type::NOTE => '[NOTE]', + \MailSo\Log\Enumerations\Type::TIME => '[TIME]', + \MailSo\Log\Enumerations\Type::TIME_DELTA => '[TIME]', + \MailSo\Log\Enumerations\Type::MEMORY => '[MEMORY]', + \MailSo\Log\Enumerations\Type::NOTICE => '[NOTICE]', + \MailSo\Log\Enumerations\Type::WARNING => '[WARNING]', + \MailSo\Log\Enumerations\Type::ERROR => '[ERROR]', + + \MailSo\Log\Enumerations\Type::NOTICE_PHP => '[NOTICE]', + \MailSo\Log\Enumerations\Type::WARNING_PHP => '[WARNING]', + \MailSo\Log\Enumerations\Type::ERROR_PHP => '[ERROR]', + ); + } + + /** + * @param string $sTimeOffset + * + * @return \MailSo\Log\Driver + */ + public function SetTimeOffset($sTimeOffset) + { + $this->sTimeOffset = (string) $sTimeOffset; + return $this; + } + + /** + * @return \MailSo\Log\Driver + */ + public function DisableGuidPrefix() + { + $this->bGuidPrefix = false; + return $this; + } + + /** + * @return \MailSo\Log\Driver + */ + public function DisableTimePrefix() + { + $this->bTimePrefix = false; + return $this; + } + + /** + * @param bool $bValue + * + * @return \MailSo\Log\Driver + */ + public function WriteOnErrorOnly($bValue) + { + $this->bWriteOnErrorOnly = !!$bValue; + return $this; + } + + /** + * @param bool $bValue + * + * @return \MailSo\Log\Driver + */ + public function WriteOnPhpErrorOnly($bValue) + { + $this->bWriteOnPhpErrorOnly = !!$bValue; + return $this; + } + + /** + * @param int $iTimeout + * + * @return \MailSo\Log\Driver + */ + public function WriteOnTimeoutOnly($iTimeout) + { + $this->iWriteOnTimeoutOnly = (int) $iTimeout; + if (0 > $this->iWriteOnTimeoutOnly) + { + $this->iWriteOnTimeoutOnly = 0; + } + + return $this; + } + + /** + * @return \MailSo\Log\Driver + */ + public function DisableTypedPrefix() + { + $this->bTypedPrefix = false; + return $this; + } + + /** + * @param string|array $mDesc + * @return bool + */ + abstract protected function writeImplementation($mDesc); + + /** + * @return bool + */ + protected function writeEmptyLineImplementation() + { + return $this->writeImplementation(''); + } + + /** + * @param string $sTimePrefix + * @param string $sDesc + * @param int $iType = \MailSo\Log\Enumerations\Type::INFO + * @param array $sName = '' + * + * @return string + */ + protected function loggerLineImplementation($sTimePrefix, $sDesc, + $iType = \MailSo\Log\Enumerations\Type::INFO, $sName = '') + { + return \ltrim( + ($this->bTimePrefix ? '['.$sTimePrefix.']' : ''). + ($this->bGuidPrefix ? '['.\MailSo\Log\Logger::Guid().']' : ''). + ($this->bTypedPrefix ? ' '.$this->getTypedPrefix($iType, $sName) : '') + ).$sDesc; + } + + /** + * @return bool + */ + protected function clearImplementation() + { + return true; + } + + /** + * @return string + */ + protected function getTimeWithMicroSec() + { + $aMicroTimeItems = \explode(' ', \microtime()); + return \MailSo\Log\Logger::DateHelper($this->sDatePattern, $this->sTimeOffset, $aMicroTimeItems[1]).'.'. + \str_pad((int) ($aMicroTimeItems[0] * 1000), 3, '0', STR_PAD_LEFT); + } + + /** + * @param int $iType + * @param string $sName = '' + * + * @return string + */ + protected function getTypedPrefix($iType, $sName = '') + { + $sName = 0 < \strlen($sName) ? $sName : $this->sName; + return isset($this->aPrefixes[$iType]) ? $sName.$this->aPrefixes[$iType].': ' : ''; + } + + /** + * @param string|array $mDesc + * @param bool $bDiplayCrLf = false + * + * @return string + */ + protected function localWriteImplementation($mDesc, $bDiplayCrLf = false) + { + if ($bDiplayCrLf) + { + if (\is_array($mDesc)) + { + foreach ($mDesc as &$sLine) + { + $sLine = \strtr($sLine, array("\r" => '\r', "\n" => '\n'.$this->sNewLine)); + $sLine = \rtrim($sLine); + } + } + else + { + $mDesc = \strtr($mDesc, array("\r" => '\r', "\n" => '\n'.$this->sNewLine)); + $mDesc = \rtrim($mDesc); + } + } + + return $this->writeImplementation($mDesc); + } + + /** + * @final + * @param string $sDesc + * @param int $iType = \MailSo\Log\Enumerations\Type::INFO + * @param string $sName = '' + * @param bool $bDiplayCrLf = false + * + * @return bool + */ + final public function Write($sDesc, $iType = \MailSo\Log\Enumerations\Type::INFO, $sName = '', $bDiplayCrLf = false) + { + $bResult = true; + if (!$this->bFlushCache && ($this->bWriteOnErrorOnly || $this->bWriteOnPhpErrorOnly || 0 < $this->iWriteOnTimeoutOnly)) + { + $bErrorPhp = false; + + $bError = $this->bWriteOnErrorOnly && \in_array($iType, array( + \MailSo\Log\Enumerations\Type::NOTICE, + \MailSo\Log\Enumerations\Type::NOTICE_PHP, + \MailSo\Log\Enumerations\Type::WARNING, + \MailSo\Log\Enumerations\Type::WARNING_PHP, + \MailSo\Log\Enumerations\Type::ERROR, + \MailSo\Log\Enumerations\Type::ERROR_PHP + )); + + if (!$bError) + { + $bErrorPhp = $this->bWriteOnPhpErrorOnly && \in_array($iType, array( + \MailSo\Log\Enumerations\Type::NOTICE_PHP, + \MailSo\Log\Enumerations\Type::WARNING_PHP, + \MailSo\Log\Enumerations\Type::ERROR_PHP + )); + } + + if ($bError || $bErrorPhp) + { + $sFlush = '--- FlushLogCache: '.($bError ? 'WriteOnErrorOnly' : 'WriteOnPhpErrorOnly'); + if (isset($this->aCache[0]) && empty($this->aCache[0])) + { + $this->aCache[0] = $sFlush; + \array_unshift($this->aCache, ''); + } + else + { + \array_unshift($this->aCache, $sFlush); + } + + $this->aCache[] = '--- FlushLogCache: Trigger'; + $this->aCache[] = $this->loggerLineImplementation($this->getTimeWithMicroSec(), $sDesc, $iType, $sName); + + $this->bFlushCache = true; + $bResult = $this->localWriteImplementation($this->aCache, $bDiplayCrLf); + $this->aCache = array(); + } + else if (0 < $this->iWriteOnTimeoutOnly && \time() - APP_START_TIME > $this->iWriteOnTimeoutOnly) + { + $sFlush = '--- FlushLogCache: WriteOnTimeoutOnly ['.(\time() - APP_START_TIME).'sec]'; + if (isset($this->aCache[0]) && empty($this->aCache[0])) + { + $this->aCache[0] = $sFlush; + \array_unshift($this->aCache, ''); + } + else + { + \array_unshift($this->aCache, $sFlush); + } + + $this->aCache[] = '--- FlushLogCache: Trigger'; + $this->aCache[] = $this->loggerLineImplementation($this->getTimeWithMicroSec(), $sDesc, $iType, $sName); + + $this->bFlushCache = true; + $bResult = $this->localWriteImplementation($this->aCache, $bDiplayCrLf); + $this->aCache = array(); + } + else + { + $this->aCache[] = $this->loggerLineImplementation($this->getTimeWithMicroSec(), $sDesc, $iType, $sName); + } + } + else + { + $bResult = $this->localWriteImplementation( + $this->loggerLineImplementation($this->getTimeWithMicroSec(), $sDesc, $iType, $sName), $bDiplayCrLf); + } + + return $bResult; + } + + /** + * @return string + */ + public function GetNewLine() + { + return $this->sNewLine; + } + + /** + * @final + * @return bool + */ + final public function Clear() + { + return $this->clearImplementation(); + } + + /** + * @final + * @return void + */ + final public function WriteEmptyLine() + { + if (!$this->bFlushCache && ($this->bWriteOnErrorOnly || $this->bWriteOnPhpErrorOnly || 0 < $this->iWriteOnTimeoutOnly)) + { + $this->aCache[] = ''; + } + else + { + $this->writeEmptyLineImplementation(); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Callback.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Callback.php new file mode 100644 index 0000000..46e8789 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Callback.php @@ -0,0 +1,83 @@ +fWriteCallback = \is_callable($fWriteCallback) ? $fWriteCallback : null; + $this->fClearCallback = \is_callable($fClearCallback) ? $fClearCallback : null; + } + + /** + * @param mixed $fWriteCallback + * @param mixed $fClearCallback = null + * + * @return \MailSo\Log\Drivers\Callback + */ + public static function NewInstance($fWriteCallback, $fClearCallback = null) + { + return new self($fWriteCallback, $fClearCallback); + } + + /** + * @param string|array $mDesc + * + * @return bool + */ + protected function writeImplementation($mDesc) + { + if ($this->fWriteCallback) + { + \call_user_func_array($this->fWriteCallback, array($mDesc)); + } + + return true; + } + + /** + * @return bool + */ + protected function clearImplementation() + { + if ($this->fClearCallback) + { + \call_user_func($this->fClearCallback); + } + + return true; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/File.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/File.php new file mode 100644 index 0000000..b3c3678 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/File.php @@ -0,0 +1,91 @@ +sLoggerFileName = $sLoggerFileName; + $this->sNewLine = $sNewLine; + } + + /** + * @param string $sLoggerFileName + */ + public function SetLoggerFileName($sLoggerFileName) + { + $this->sLoggerFileName = $sLoggerFileName; + } + + /** + * @param string $sLoggerFileName + * @param string $sNewLine = "\r\n" + * + * @return \MailSo\Log\Drivers\File + */ + public static function NewInstance($sLoggerFileName, $sNewLine = "\r\n") + { + return new self($sLoggerFileName, $sNewLine); + } + + /** + * @param string|array $mDesc + * + * @return bool + */ + protected function writeImplementation($mDesc) + { + return $this->writeToLogFile($mDesc); + } + + /** + * @return bool + */ + protected function clearImplementation() + { + return \unlink($this->sLoggerFileName); + } + + /** + * @param string|array $mDesc + * + * @return bool + */ + private function writeToLogFile($mDesc) + { + if (\is_array($mDesc)) + { + $mDesc = \implode($this->sNewLine, $mDesc); + } + + return \error_log($mDesc.$this->sNewLine, 3, $this->sLoggerFileName); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Inline.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Inline.php new file mode 100644 index 0000000..817ffbb --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Inline.php @@ -0,0 +1,89 @@ +sNewLine = $sNewLine; + $this->bHtmlEncodeSpecialChars = $bHtmlEncodeSpecialChars; + } + + /** + * @param string $sNewLine = "\r\n" + * @param bool $bHtmlEncodeSpecialChars = false + * + * @return \MailSo\Log\Drivers\Inline + */ + public static function NewInstance($sNewLine = "\r\n", $bHtmlEncodeSpecialChars = false) + { + return new self($sNewLine, $bHtmlEncodeSpecialChars); + } + + /** + * @param string $mDesc + * + * @return bool + */ + protected function writeImplementation($mDesc) + { + if (\is_array($mDesc)) + { + if ($this->bHtmlEncodeSpecialChars) + { + $mDesc = \array_map(function ($sItem) { + return \htmlspecialchars($sItem); + }, $mDesc); + } + + $mDesc = \implode($this->sNewLine, $mDesc); + } + else + { + echo ($this->bHtmlEncodeSpecialChars) ? \htmlspecialchars($mDesc).$this->sNewLine : $mDesc.$this->sNewLine; + } + + return true; + } + + /** + * @return bool + */ + protected function clearImplementation() + { + if (\defined('PHP_SAPI') && 'cli' === PHP_SAPI && \MailSo\Base\Utils::FunctionExistsAndEnabled('system')) + { + \system('clear'); + } + + return true; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Syslog.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Syslog.php new file mode 100644 index 0000000..f189506 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Drivers/Syslog.php @@ -0,0 +1,81 @@ +iLogLevel = \defined('LOG_INFO') ? LOG_INFO : 6; + + if (\function_exists('openlog') && \function_exists('closelog') && \defined('LOG_ODELAY') && \defined('LOG_USER')) + { + \openlog('rainloop', LOG_ODELAY, LOG_USER); + + \register_shutdown_function(function () { + @\closelog(); + }); + } + else + { + $this->iLogLevel = null; + } + } + + /** + * @return \MailSo\Log\Drivers\Syslog + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string|array $mDesc + * + * @return bool + */ + protected function writeImplementation($mDesc) + { + if (null === $this->iLogLevel) + { + return false; + } + + if (\is_array($mDesc)) + { + $mDesc = \implode($this->sNewLine, $mDesc); + } + + return \syslog($this->iLogLevel, $mDesc); + } + + /** + * @return bool + */ + protected function clearImplementation() + { + return true; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Enumerations/Type.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Enumerations/Type.php new file mode 100644 index 0000000..6c3db90 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Log/Enumerations/Type.php @@ -0,0 +1,34 @@ +bUsed = false; + $this->aForbiddenTypes = array(); + $this->aSecretWords = array(); + $this->bShowSecter = false; + $this->bHideErrorNotices = false; + + if ($bRegPhpErrorHandler) + { + \set_error_handler(array(&$this, '__phpErrorHandler')); + } + + \register_shutdown_function(array(&$this, '__loggerShutDown')); + } + + /** + * @param bool $bRegPhpErrorHandler = false + * + * @return \MailSo\Log\Logger + */ + public static function NewInstance($bRegPhpErrorHandler = false) + { + return new self($bRegPhpErrorHandler); + } + + /** + * @staticvar \MailSo\Log\Logger $oInstance; + * + * @return \MailSo\Log\Logger + */ + public static function SingletonInstance() + { + static $oInstance = null; + if (null === $oInstance) + { + $oInstance = self::NewInstance(); + } + + return $oInstance; + } + + /** + * @param string $sFormat + * @param string $sTimeOffset = '0' + * @param int $iTimestamp = 0 + * + * @return string + */ + public static function DateHelper($sFormat, $sTimeOffset = '0', $iTimestamp = null) + { + $iTimestamp = null === $iTimestamp ? \time() : (int) $iTimestamp; + return \gmdate($sFormat, $iTimestamp + \MailSo\Base\DateTimeHelper::TimeToSec((string) $sTimeOffset)); + } + + /** + * @return bool + */ + public static function IsSystemEnabled() + { + return !!(\MailSo\Config::$SystemLogger instanceof \MailSo\Log\Logger); + } + + /** + * @param mixed $mData + * @param int $iType = \MailSo\Log\Enumerations\Type::INFO + */ + public static function SystemLog($mData, $iType = \MailSo\Log\Enumerations\Type::INFO) + { + if (\MailSo\Config::$SystemLogger instanceof \MailSo\Log\Logger) + { + \MailSo\Config::$SystemLogger->WriteMixed($mData, $iType); + } + } + + /** + * @staticvar string $sCache; + * + * @return string + */ + public static function Guid() + { + static $sCache = null; + if (null === $sCache) + { + $sCache = \substr(\MailSo\Base\Utils::Md5Rand(), -8); + } + + return $sCache; + } + + /** + * @return bool + */ + public function Ping() + { + return true; + } + + /** + * @return bool + */ + public function IsEnabled() + { + return 0 < $this->Count(); + } + + /** + * @param string $sWord + * + * @return bool + */ + public function AddSecret($sWord) + { + if (\is_string($sWord) && 0 < \strlen(\trim($sWord))) + { + $this->aSecretWords[] = $sWord; + $this->aSecretWords = \array_unique($this->aSecretWords); + } + } + + /** + * @param bool $bShow + * + * @return \MailSo\Log\Logger + */ + public function SetShowSecter($bShow) + { + $this->bShowSecter = !!$bShow; + return $this; + } + + /** + * @param bool $bValue + * + * @return \MailSo\Log\Logger + */ + public function HideErrorNotices($bValue) + { + $this->bHideErrorNotices = !!$bValue; + return $this; + } + + /** + * @return bool + */ + public function IsShowSecter() + { + return $this->bShowSecter; + } + + /** + * @param int $iType + * + * @return \MailSo\Log\Logger + */ + public function AddForbiddenType($iType) + { + $this->aForbiddenTypes[$iType] = true; + + return $this; + } + + /** + * @param int $iType + * + * @return \MailSo\Log\Logger + */ + public function RemoveForbiddenType($iType) + { + $this->aForbiddenTypes[$iType] = false; + return $this; + } + + /** + * @param int $iErrNo + * @param string $sErrStr + * @param string $sErrFile + * @param int $iErrLine + * + * @return bool + */ + public function __phpErrorHandler($iErrNo, $sErrStr, $sErrFile, $iErrLine) + { + $iType = \MailSo\Log\Enumerations\Type::NOTICE_PHP; + switch ($iErrNo) + { + case E_USER_ERROR: + $iType = \MailSo\Log\Enumerations\Type::ERROR_PHP; + break; + case E_USER_WARNING: + $iType = \MailSo\Log\Enumerations\Type::WARNING_PHP; + break; + } + + $this->Write($sErrFile.' [line:'.$iErrLine.', code:'.$iErrNo.']', $iType, 'PHP'); + $this->Write('Error: '.$sErrStr, $iType, 'PHP'); + + return !!(\MailSo\Log\Enumerations\Type::NOTICE === $iType && $this->bHideErrorNotices); + } + + /** + * @return void + */ + public function __loggerShutDown() + { + if ($this->bUsed) + { + $aStatistic = \MailSo\Base\Loader::Statistic(); + if (\is_array($aStatistic)) + { + if (isset($aStatistic['php']['memory_get_peak_usage'])) + { + $this->Write('Memory peak usage: '.$aStatistic['php']['memory_get_peak_usage'], + \MailSo\Log\Enumerations\Type::MEMORY); + } + + if (isset($aStatistic['time'])) + { + $this->Write('Time delta: '.$aStatistic['time'], \MailSo\Log\Enumerations\Type::TIME_DELTA); + } + } + } + } + + /** + * @return bool + */ + public function WriteEmptyLine() + { + $iResult = 1; + + $aLoggers =& $this->GetAsArray(); + foreach ($aLoggers as /* @var $oLogger \MailSo\Log\Driver */ &$oLogger) + { + $iResult &= $oLogger->WriteEmptyLine(); + } + + return (bool) $iResult; + } + + /** + * @param string $sDesc + * @param int $iType = \MailSo\Log\Enumerations\Type::INFO + * @param string $sName = '' + * @param bool $bSearchSecretWords = true + * @param bool $bDiplayCrLf = false + * + * @return bool + */ + public function Write($sDesc, $iType = \MailSo\Log\Enumerations\Type::INFO, + $sName = '', $bSearchSecretWords = true, $bDiplayCrLf = false) + { + if (isset($this->aForbiddenTypes[$iType]) && true === $this->aForbiddenTypes[$iType]) + { + return true; + } + + $this->bUsed = true; + + $oLogger = null; + $aLoggers = array(); + $iResult = 1; + + if ($bSearchSecretWords && !$this->bShowSecter && 0 < \count($this->aSecretWords)) + { + $sDesc = \str_replace($this->aSecretWords, '*******', $sDesc); + } + + $aLoggers =& $this->GetAsArray(); + foreach ($aLoggers as /* @var $oLogger \MailSo\Log\Driver */ $oLogger) + { + $iResult &= $oLogger->Write($sDesc, $iType, $sName, $bDiplayCrLf); + } + + return (bool) $iResult; + } + + /** + * @param mixed $oValue + * @param int $iType = \MailSo\Log\Enumerations\Type::INFO + * @param string $sName = '' + * @param bool $bSearchSecretWords = false + * @param bool $bDiplayCrLf = false + * + * @return bool + */ + public function WriteDump($oValue, $iType = \MailSo\Log\Enumerations\Type::INFO, $sName = '', + $bSearchSecretWords = false, $bDiplayCrLf = false) + { + return $this->Write(\print_r($oValue, true), $iType, $sName, $bSearchSecretWords, $bDiplayCrLf); + } + + /** + * @param \Exception $oException + * @param int $iType = \MailSo\Log\Enumerations\Type::NOTICE + * @param string $sName = '' + * @param bool $bSearchSecretWords = true + * @param bool $bDiplayCrLf = false + * + * @return bool + */ + public function WriteException($oException, $iType = \MailSo\Log\Enumerations\Type::NOTICE, $sName = '', + $bSearchSecretWords = true, $bDiplayCrLf = false) + { + if ($oException instanceof \Exception) + { + if (isset($oException->__LOGINNED__)) + { + return true; + } + + $oException->__LOGINNED__ = true; + + return $this->Write((string) $oException, $iType, $sName, $bSearchSecretWords, $bDiplayCrLf); + } + + return false; + } + + /** + * @param \Exception $oException + * @param int $iType = \MailSo\Log\Enumerations\Type::NOTICE + * @param string $sName = '' + * @param bool $bSearchSecretWords = true + * @param bool $bDiplayCrLf = false + * + * @return bool + */ + public function WriteExceptionShort($oException, $iType = \MailSo\Log\Enumerations\Type::NOTICE, $sName = '', + $bSearchSecretWords = true, $bDiplayCrLf = false) + { + if ($oException instanceof \Exception) + { + if (isset($oException->__LOGINNED__)) + { + return true; + } + + $oException->__LOGINNED__ = true; + + return $this->Write($oException->getMessage(), $iType, $sName, $bSearchSecretWords, $bDiplayCrLf); + } + + return false; + } + + /** + * @param mixed $mData + * @param int $iType = \MailSo\Log\Enumerations\Type::NOTICE + * @param string $sName = '' + * @param bool $bSearchSecretWords = true + * @param bool $bDiplayCrLf = false + * + * @return bool + */ + public function WriteMixed($mData, $iType = null, $sName = '', + $bSearchSecretWords = true, $bDiplayCrLf = false) + { + $iType = null === $iType ? \MailSo\Log\Enumerations\Type::INFO : $iType; + if (\is_array($mData) || \is_object($mData)) + { + if ($mData instanceof \Exception) + { + $iType = null === $iType ? \MailSo\Log\Enumerations\Type::NOTICE : $iType; + return $this->WriteException($mData, $iType, $sName, $bSearchSecretWords, $bDiplayCrLf); + } + else + { + return $this->WriteDump($mData, $iType, $sName, $bSearchSecretWords, $bDiplayCrLf); + } + } + else + { + return $this->Write($mData, $iType, $sName, $bSearchSecretWords, $bDiplayCrLf); + } + + return false; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Attachment.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Attachment.php new file mode 100644 index 0000000..51def4e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Attachment.php @@ -0,0 +1,239 @@ +Clear(); + } + + /** + * @return \MailSo\Mail\Attachment + */ + public function Clear() + { + $this->sFolder = ''; + $this->iUid = 0; + $this->oBodyStructure = null; + + return $this; + } + + /** + * @return string + */ + public function Folder() + { + return $this->sFolder; + } + + /** + * @return int + */ + public function Uid() + { + return $this->iUid; + } + + /** + * @return string + */ + public function MimeIndex() + { + return $this->oBodyStructure ? $this->oBodyStructure->PartID() : ''; + } + + /** + * @param bool $bCalculateOnEmpty = false + * + * @return string + */ + public function FileName($bCalculateOnEmpty = false) + { + $sFileName = ''; + if ($this->oBodyStructure) + { + $sFileName = $this->oBodyStructure->FileName(); + if ($bCalculateOnEmpty && 0 === \strlen(trim($sFileName))) + { + $sMimeType = \strtolower(\trim($this->MimeType())); + if ('message/rfc822' === $sMimeType) + { + $sFileName = 'message'.$this->MimeIndex().'.eml'; + } + else if ('text/calendar' === $sMimeType) + { + $sFileName = 'calendar'.$this->MimeIndex().'.ics'; + } + else if (0 < \strlen($sMimeType)) + { + $sFileName = \str_replace('/', $this->MimeIndex().'.', $sMimeType); + } + } + } + + return $sFileName; + } + + /** + * @return string + */ + public function MimeType() + { + return $this->oBodyStructure ? $this->oBodyStructure->ContentType() : ''; + } + + /** + * @return string + */ + public function ContentTransferEncoding() + { + return $this->oBodyStructure ? $this->oBodyStructure->MailEncodingName() : ''; + } + + /** + * @return int + */ + public function EncodedSize() + { + return $this->oBodyStructure ? $this->oBodyStructure->Size() : 0; + } + + /** + * @return int + */ + public function EstimatedSize() + { + return $this->oBodyStructure ? $this->oBodyStructure->EstimatedSize() : 0; + } + + /** + * @return string + */ + public function Cid() + { + return $this->oBodyStructure ? $this->oBodyStructure->ContentID() : ''; + } + + /** + * @return string + */ + public function ContentLocation() + { + return $this->oBodyStructure ? $this->oBodyStructure->ContentLocation() : ''; + } + + /** + * @return bool + */ + public function IsInline() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsInline() : false; + } + + /** + * @return bool + */ + public function IsImage() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsImage() : false; + } + + /** + * @return bool + */ + public function IsArchive() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsArchive() : false; + } + + /** + * @return bool + */ + public function IsPdf() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsPdf() : false; + } + + /** + * @return bool + */ + public function IsDoc() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsDoc() : false; + } + + /** + * @return bool + */ + public function IsPgpSignature() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsPgpSignature() : false; + } + + /** + * @return \MailSo\Mail\Attachment + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sFolder + * @param int $iUid + * @param \MailSo\Imap\BodyStructure $oBodyStructure + * @return \MailSo\Mail\Attachment + */ + public static function NewBodyStructureInstance($sFolder, $iUid, $oBodyStructure) + { + return self::NewInstance()->InitByBodyStructure($sFolder, $iUid, $oBodyStructure); + } + + /** + * @param string $sFolder + * @param int $iUid + * @param \MailSo\Imap\BodyStructure $oBodyStructure + * @return \MailSo\Mail\Attachment + */ + public function InitByBodyStructure($sFolder, $iUid, $oBodyStructure) + { + $this->sFolder = $sFolder; + $this->iUid = $iUid; + $this->oBodyStructure = $oBodyStructure; + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/AttachmentCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/AttachmentCollection.php new file mode 100644 index 0000000..5cc64b5 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/AttachmentCollection.php @@ -0,0 +1,74 @@ +FilterList(function ($oAttachment) { + return $oAttachment && $oAttachment->IsInline(); + }); + + return \is_array($aList) ? \count($aList) : 0; + } + + /** + * @return int + */ + public function NonInlineCount() + { + $aList = $this->FilterList(function ($oAttachment) { + return $oAttachment && !$oAttachment->IsInline(); + }); + + return \is_array($aList) ? \count($aList) : 0; + } + + /** + * @return array + */ + public function SpecData() + { + return $this->MapList(function ($oAttachment) { + if ($oAttachment) + { + return array($oAttachment->FileName(true), $oAttachment->MimeType()); + } + + return null; + }); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Exceptions/Exception.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Exceptions/Exception.php new file mode 100644 index 0000000..9bf1c42 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Exceptions/Exception.php @@ -0,0 +1,19 @@ +oImapFolder = $oImapFolder; + $this->oSubFolders = null; + + $aNames = \explode($this->oImapFolder->Delimiter(), $this->oImapFolder->FullNameRaw()); + $this->iNestingLevel = \count($aNames); + + $this->sParentFullNameRaw = ''; + if (1 < $this->iNestingLevel) + { + \array_pop($aNames); + $this->sParentFullNameRaw = \implode($this->oImapFolder->Delimiter(), $aNames); + } + + $this->bSubscribed = $bSubscribed; + $this->bExisten = $bExisten; + } + else + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + } + + /** + * @param \MailSo\Imap\Folder $oImapFolder + * @param bool $bSubscribed = true + * @param bool $bExisten = true + * + * @return \MailSo\Mail\Folder + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function NewInstance($oImapFolder, $bSubscribed = true, $bExisten = true) + { + return new self($oImapFolder, $bSubscribed, $bExisten); + } + + /** + * @param string $sFullNameRaw + * @param string $sDelimiter + * + * @return \MailSo\Mail\Folder + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function NewNonExistenInstance($sFullNameRaw, $sDelimiter) + { + return self::NewInstance( + \MailSo\Imap\Folder::NewInstance($sFullNameRaw, $sDelimiter, array('\NoSelect')), true, false); + } + + /** + * @return string + */ + public function Name() + { + return \MailSo\Base\Utils::ConvertEncoding($this->NameRaw(), + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP, + \MailSo\Base\Enumerations\Charset::UTF_8); + } + + /** + * @return string + */ + public function FullName() + { + return \MailSo\Base\Utils::ConvertEncoding($this->FullNameRaw(), + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP, + \MailSo\Base\Enumerations\Charset::UTF_8); + } + + /** + * @return string + */ + public function NameRaw() + { + return $this->oImapFolder->NameRaw(); + } + + /** + * @return string + */ + public function FullNameRaw() + { + return $this->oImapFolder->FullNameRaw(); + } + + /** + * @return string + */ + public function ParentFullName() + { + return \MailSo\Base\Utils::ConvertEncoding($this->sParentFullNameRaw, + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP, + \MailSo\Base\Enumerations\Charset::UTF_8); + } + + /** + * @return string + */ + public function ParentFullNameRaw() + { + return $this->sParentFullNameRaw; + } + + /** + * @return string + */ + public function Delimiter() + { + return $this->oImapFolder->Delimiter(); + } + + /** + * @return array + */ + public function Flags() + { + return $this->oImapFolder->Flags(); + } + + /** + * @return array + */ + public function FlagsLowerCase() + { + return $this->oImapFolder->FlagsLowerCase(); + } + + /** + * @param bool $bCreateIfNull = false + * @return \MailSo\Mail\FolderCollection + */ + public function SubFolders($bCreateIfNull = false) + { + if ($bCreateIfNull && !$this->oSubFolders) + { + $this->oSubFolders = FolderCollection::NewInstance(); + } + + return $this->oSubFolders; + } + + /** + * @return bool + */ + public function HasSubFolders() + { + return $this->oSubFolders && 0 < $this->oSubFolders->Count(); + } + + /** + * @return bool + */ + public function HasVisibleSubFolders() + { + $sList = array(); + if ($this->oSubFolders) + { + $sList = $this->oSubFolders->FilterList(function (\MailSo\Mail\Folder $oFolder) { + return $oFolder->IsSubscribed(); + }); + } + + return 0 < \count($sList); + } + + /** + * @return bool + */ + public function IsSubscribed() + { + return $this->bSubscribed; + } + + /** + * @return bool + */ + public function IsExists() + { + return $this->bExisten; + } + + /** + * @return bool + */ + public function IsSelectable() + { + return $this->IsExists() && $this->oImapFolder->IsSelectable(); + } + + /** + * @return mixed + */ + public function Status() + { + return $this->oImapFolder->GetExtended('STATUS'); + } + + /** + * @return bool + */ + public function IsInbox() + { + return $this->oImapFolder->IsInbox(); + } + + /** + * @return int + */ + public function GetFolderListType() + { + $aFlags = $this->oImapFolder->FlagsLowerCase(); + $iListType = \MailSo\Imap\Enumerations\FolderType::USER; + + if (\is_array($aFlags)) + { + switch (true) + { + case \in_array('\inbox', $aFlags) || 'INBOX' === \strtoupper($this->FullNameRaw()): + $iListType = \MailSo\Imap\Enumerations\FolderType::INBOX; + break; + case \in_array('\sent', $aFlags): + case \in_array('\sentmail', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::SENT; + break; + case \in_array('\drafts', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::DRAFTS; + break; + case \in_array('\junk', $aFlags): + case \in_array('\spam', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::JUNK; + break; + case \in_array('\trash', $aFlags): + case \in_array('\bin', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::TRASH; + break; + case \in_array('\important', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::IMPORTANT; + break; + case \in_array('\flagged', $aFlags): + case \in_array('\starred', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::FLAGGED; + break; + case \in_array('\all', $aFlags): + case \in_array('\allmail', $aFlags): + case \in_array('\archive', $aFlags): + $iListType = \MailSo\Imap\Enumerations\FolderType::ALL; + break; + } + } + + return $iListType; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/FolderCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/FolderCollection.php new file mode 100644 index 0000000..9f0c00a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/FolderCollection.php @@ -0,0 +1,279 @@ +Namespace = ''; + $this->FoldersHash = ''; + $this->SystemFolders = array(); + $this->IsThreadsSupported = false; + $this->Optimized = false; + } + + /** + * @return \MailSo\Mail\FolderCollection + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sFullNameRaw + * + * @return \MailSo\Mail\Folder|null + */ + public function GetByFullNameRaw($sFullNameRaw) + { + $mResult = null; + foreach ($this->aItems as /* @var $oFolder \MailSo\Mail\Folder */ $oFolder) + { + if ($oFolder->FullNameRaw() === $sFullNameRaw) + { + $mResult = $oFolder; + break; + } + else if ($oFolder->HasSubFolders()) + { + $mResult = $oFolder->SubFolders(true)->GetByFullNameRaw($sFullNameRaw); + if ($mResult) + { + break; + } + else + { + $mResult = null; + } + } + } + + return $mResult; + } + + /** + * @return int + */ + public function CountRec() + { + $iResult = $this->Count(); + foreach ($this->aItems as /* @var $oFolder \MailSo\Mail\Folder */ $oFolder) + { + if ($oFolder) + { + $oSub = $oFolder->SubFolders(); + $iResult += $oSub ? $oSub->CountRec() : 0; + } + } + + return $iResult; + } + + /** + * @return string + */ + public function GetNamespace() + { + return $this->Namespace; + } + + /** + * @return string + */ + public function FindDelimiter() + { + $sDelimiter = '/'; + + $oFolder = $this->GetByFullNameRaw('INBOX'); + if (!$oFolder) + { + $oFolder = $this->GetByIndex(0); + } + + if ($oFolder) + { + $sDelimiter = $oFolder->Delimiter(); + } + + return $sDelimiter; + } + + /** + * @param string $sNamespace + * + * @return \MailSo\Mail\FolderCollection + */ + public function SetNamespace($sNamespace) + { + $this->Namespace = $sNamespace; + + return $this; + } + + /** + * @param array $aUnsortedMailFolders + * + * @return void + */ + public function InitByUnsortedMailFolderArray($aUnsortedMailFolders) + { + $this->Clear(); + + $aSortedByLenImapFolders = array(); + foreach ($aUnsortedMailFolders as /* @var $oMailFolder \MailSo\Mail\Folder */ &$oMailFolder) + { + $aSortedByLenImapFolders[$oMailFolder->FullNameRaw()] =& $oMailFolder; + unset($oMailFolder); + } + unset($aUnsortedMailFolders); + + $aAddedFolders = array(); + foreach ($aSortedByLenImapFolders as /* @var $oMailFolder \MailSo\Mail\Folder */ $oMailFolder) + { + $sDelimiter = $oMailFolder->Delimiter(); + $aFolderExplode = \explode($sDelimiter, $oMailFolder->FullNameRaw()); + + if (1 < \count($aFolderExplode)) + { + \array_pop($aFolderExplode); + + $sNonExistenFolderFullNameRaw = ''; + foreach ($aFolderExplode as $sFolderExplodeItem) + { + $sNonExistenFolderFullNameRaw .= (0 < \strlen($sNonExistenFolderFullNameRaw)) + ? $sDelimiter.$sFolderExplodeItem : $sFolderExplodeItem; + + if (!isset($aSortedByLenImapFolders[$sNonExistenFolderFullNameRaw])) + { + try + { + $aAddedFolders[$sNonExistenFolderFullNameRaw] = + Folder::NewNonExistenInstance($sNonExistenFolderFullNameRaw, $sDelimiter); + } + catch (\Exception $oExc) + { + unset($oExc); + } + } + } + } + } + + $aSortedByLenImapFolders = \array_merge($aSortedByLenImapFolders, $aAddedFolders); + unset($aAddedFolders); + + \uasort($aSortedByLenImapFolders, function ($oFolderA, $oFolderB) { + return \strnatcmp($oFolderA->FullNameRaw(), $oFolderB->FullNameRaw()); + }); + + foreach ($aSortedByLenImapFolders as /* @var $oMailFolder \MailSo\Mail\Folder */ &$oMailFolder) + { + $this->AddWithPositionSearch($oMailFolder); + unset($oMailFolder); + } + + unset($aSortedByLenImapFolders); + } + + /** + * @param \MailSo\Mail\Folder $oMailFolder + * + * @return bool + */ + public function AddWithPositionSearch($oMailFolder) + { + $oItemFolder = null; + $bIsAdded = false; + $aList =& $this->GetAsArray(); + + foreach ($aList as /* @var $oItemFolder \MailSo\Mail\Folder */ $oItemFolder) + { + if ($oMailFolder instanceof \MailSo\Mail\Folder && + 0 === \strpos($oMailFolder->FullNameRaw(), $oItemFolder->FullNameRaw().$oItemFolder->Delimiter())) + { + if ($oItemFolder->SubFolders(true)->AddWithPositionSearch($oMailFolder)) + { + $bIsAdded = true; + } + + break; + } + } + + if (!$bIsAdded && $oMailFolder instanceof \MailSo\Mail\Folder) + { + $bIsAdded = true; + $this->Add($oMailFolder); + } + + return $bIsAdded; + } + + /** + * @param callable $fCallback + * + * @return void + */ + public function SortByCallback($fCallback) + { + if (\is_callable($fCallback)) + { + $aList =& $this->GetAsArray(); + + \usort($aList, $fCallback); + + foreach ($aList as &$oItemFolder) + { + if ($oItemFolder->HasSubFolders()) + { + $oItemFolder->SubFolders()->SortByCallback($fCallback); + } + } + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/MailClient.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/MailClient.php new file mode 100644 index 0000000..b527e3b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/MailClient.php @@ -0,0 +1,2678 @@ +oLogger = null; + + $this->oImapClient = \MailSo\Imap\ImapClient::NewInstance(); + $this->oImapClient->SetTimeOuts(10, \MailSo\Config::$ImapTimeout); + } + + /** + * @return \MailSo\Mail\MailClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return \MailSo\Imap\ImapClient + */ + public function ImapClient() + { + return $this->oImapClient; + } + + /** + * @param string $sServerName + * @param int $iPort = 143 + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param string $sClientCert = "" + * + * @return \MailSo\Mail\MailClient + * + * @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 = false, $sClientCert = '') + { + $this->oImapClient->Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned, $sClientCert); + return $this; + } + + /** + * @param string $sLogin + * @param string $sPassword + * @param string $sProxyAuthUser = '' + * @param bool $bUseAuthPlainIfSupported = true + * @param bool $bUseAuthCramMd5IfSupported = true + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\LoginException + */ + public function Login($sLogin, $sPassword, $sProxyAuthUser = '', + $bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true) + { + $this->oImapClient->Login($sLogin, $sPassword, $sProxyAuthUser, $bUseAuthPlainIfSupported, $bUseAuthCramMd5IfSupported); + return $this; + } + + /** + * @param string $sXOAuth2Token + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\LoginException + */ + public function LoginWithXOauth2($sXOAuth2Token) + { + $this->oImapClient->LoginWithXOauth2($sXOAuth2Token); + return $this; + } + + /** + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function Logout() + { + $this->oImapClient->Logout(); + return $this; + } + + /** + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function Disconnect() + { + $this->oImapClient->Disconnect(); + return $this; + } + + /** + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function LogoutAndDisconnect() + { + return $this->Logout()->Disconnect(); + } + + /** + * @return bool + */ + public function IsConnected() + { + return $this->oImapClient->IsConnected(); + } + + /** + * @return bool + */ + public function IsLoggined() + { + return $this->oImapClient->IsLoggined(); + } + + /** + * @return string + */ + private function getEnvelopeOrHeadersRequestStringForSimpleList() + { + return \MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array( + \MailSo\Mime\Enumerations\Header::RETURN_PATH, + \MailSo\Mime\Enumerations\Header::RECEIVED, + \MailSo\Mime\Enumerations\Header::MIME_VERSION, + \MailSo\Mime\Enumerations\Header::FROM_, + \MailSo\Mime\Enumerations\Header::TO_, + \MailSo\Mime\Enumerations\Header::CC, + \MailSo\Mime\Enumerations\Header::SENDER, + \MailSo\Mime\Enumerations\Header::REPLY_TO, + \MailSo\Mime\Enumerations\Header::DATE, + \MailSo\Mime\Enumerations\Header::SUBJECT, + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE, + ), true); + } + + /** + * @return string + */ + private function getEnvelopeOrHeadersRequestString() + { + if (\MailSo\Config::$MessageAllHeaders) + { + return \MailSo\Imap\Enumerations\FetchType::BODY_HEADER_PEEK; + } + + return \MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array( + \MailSo\Mime\Enumerations\Header::RETURN_PATH, + \MailSo\Mime\Enumerations\Header::RECEIVED, + \MailSo\Mime\Enumerations\Header::MIME_VERSION, + \MailSo\Mime\Enumerations\Header::MESSAGE_ID, + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Header::FROM_, + \MailSo\Mime\Enumerations\Header::TO_, + \MailSo\Mime\Enumerations\Header::CC, + \MailSo\Mime\Enumerations\Header::BCC, + \MailSo\Mime\Enumerations\Header::SENDER, + \MailSo\Mime\Enumerations\Header::REPLY_TO, + \MailSo\Mime\Enumerations\Header::DELIVERED_TO, + \MailSo\Mime\Enumerations\Header::IN_REPLY_TO, + \MailSo\Mime\Enumerations\Header::REFERENCES, + \MailSo\Mime\Enumerations\Header::DATE, + \MailSo\Mime\Enumerations\Header::SUBJECT, + \MailSo\Mime\Enumerations\Header::SENSITIVITY, + \MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY, + \MailSo\Mime\Enumerations\Header::IMPORTANCE, + \MailSo\Mime\Enumerations\Header::X_PRIORITY, + \MailSo\Mime\Enumerations\Header::X_DRAFT_INFO, + \MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO, + \MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO, + \MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO, + \MailSo\Mime\Enumerations\Header::AUTHENTICATION_RESULTS, + \MailSo\Mime\Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS, + \MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE, + ), true); +// +// return \MailSo\Imap\Enumerations\FetchType::ENVELOPE; + } + + /** + * @param string $sFolderName + * @param string $sMessageFlag + * @param bool $bSetAction = true + * @param bool $sSkipUnsupportedFlag = false + * @param array $aCustomUids = null + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + * @throws \MailSo\Mail\Exceptions\Exception + */ + public function MessageSetFlagToAll($sFolderName, $sMessageFlag, $bSetAction = true, $sSkipUnsupportedFlag = false, $aCustomUids = null) + { + $this->oImapClient->FolderSelect($sFolderName); + + $oFolderInfo = $this->oImapClient->FolderCurrentInformation(); + if (!$oFolderInfo || !$oFolderInfo->IsFlagSupported($sMessageFlag)) + { + if (!$sSkipUnsupportedFlag) + { + throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag "'.$sMessageFlag.'" is not supported.'); + } + } + + if ($oFolderInfo && 0 < $oFolderInfo->Exists) + { + $sStoreAction = $bSetAction + ? \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT + : \MailSo\Imap\Enumerations\StoreAction::REMOVE_FLAGS_SILENT + ; + + if (is_array($aCustomUids)) + { + if (0 < count($aCustomUids)) + { + $this->oImapClient->MessageStoreFlag(implode(',', $aCustomUids), true, array($sMessageFlag), $sStoreAction); + } + } + else + { + $this->oImapClient->MessageStoreFlag('1:*', false, array($sMessageFlag), $sStoreAction); + } + } + } + + /** + * @param string $sFolderName + * @param array $aIndexRange + * @param bool $bIndexIsUid + * @param string $sMessageFlag + * @param bool $bSetAction = true + * @param bool $sSkipUnsupportedFlag = false + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + * @throws \MailSo\Mail\Exceptions\Exception + */ + public function MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid, $sMessageFlag, $bSetAction = true, $sSkipUnsupportedFlag = false) + { + $this->oImapClient->FolderSelect($sFolderName); + + $oFolderInfo = $this->oImapClient->FolderCurrentInformation(); + if (!$oFolderInfo || !$oFolderInfo->IsFlagSupported($sMessageFlag)) + { + if (!$sSkipUnsupportedFlag) + { + throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag "'.$sMessageFlag.'" is not supported.'); + } + } + else + { + $sStoreAction = $bSetAction + ? \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT + : \MailSo\Imap\Enumerations\StoreAction::REMOVE_FLAGS_SILENT + ; + + $this->oImapClient->MessageStoreFlag(\MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), + $bIndexIsUid, array($sMessageFlag), $sStoreAction); + } + } + + /** + * @param string $sFolderName + * @param array $aIndexRange + * @param bool $bIndexIsUid + * @param bool $bSetAction = true + * @param bool $sSkipUnsupportedFlag = false + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSetFlagged($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true, $sSkipUnsupportedFlag = false) + { + $this->MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid, + \MailSo\Imap\Enumerations\MessageFlag::FLAGGED, $bSetAction, $sSkipUnsupportedFlag); + } + + /** + * @param string $sFolderName + * @param bool $bSetAction = true + * @param array $aCustomUids = null + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSetSeenToAll($sFolderName, $bSetAction = true, $aCustomUids = null) + { + $this->MessageSetFlagToAll($sFolderName, \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction, true, $aCustomUids); + } + + /** + * @param string $sFolderName + * @param array $aIndexRange + * @param bool $bIndexIsUid + * @param bool $bSetAction = true + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSetSeen($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true) + { + $this->MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid, + \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction, true); + } + + /** + * @param string $sFolderName + * @param int $iIndex + * @param bool $bIndexIsUid = true + * @param \MailSo\Cache\CacheClient $oCacher = null + * @param int $iBodyTextLimit = null + * + * @return \MailSo\Mail\Message|false + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Message($sFolderName, $iIndex, $bIndexIsUid = true, $oCacher = null, $iBodyTextLimit = null) + { + if (!\MailSo\Base\Validator::RangeInt($iIndex, 1)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderSelect($sFolderName); + + $oBodyStructure = null; + $oMessage = false; + + $aBodyPeekMimeIndexes = array(); + $aSignatureMimeIndexes = array(); + + $aFetchResponse = $this->oImapClient->Fetch(array(\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE), $iIndex, $bIndexIsUid); + if (0 < \count($aFetchResponse) && isset($aFetchResponse[0])) + { + $oBodyStructure = $aFetchResponse[0]->GetFetchBodyStructure(); + if ($oBodyStructure) + { + $aTextParts = $oBodyStructure->SearchHtmlOrPlainParts(); + if (is_array($aTextParts) && 0 < \count($aTextParts)) + { + foreach ($aTextParts as $oPart) + { + $aBodyPeekMimeIndexes[] = array($oPart->PartID(), $oPart->Size()); + } + } + + $aSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature'); + if (is_array($aSignatureParts) && 0 < \count($aSignatureParts)) + { + foreach ($aSignatureParts as $oPart) + { + $aSignatureMimeIndexes[] = $oPart->PartID(); + } + } + } + } + + $aFetchItems = array( + \MailSo\Imap\Enumerations\FetchType::INDEX, + \MailSo\Imap\Enumerations\FetchType::UID, + \MailSo\Imap\Enumerations\FetchType::RFC822_SIZE, + \MailSo\Imap\Enumerations\FetchType::INTERNALDATE, + \MailSo\Imap\Enumerations\FetchType::FLAGS, + $this->getEnvelopeOrHeadersRequestString() + ); + + if (0 < \count($aBodyPeekMimeIndexes)) + { + foreach ($aBodyPeekMimeIndexes as $aTextMimeData) + { + $sLine = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$aTextMimeData[0].']'; + if (\is_numeric($iBodyTextLimit) && 0 < $iBodyTextLimit && $iBodyTextLimit < $aTextMimeData[1]) + { + $sLine .= '<0.'.((int) $iBodyTextLimit).'>'; + } + + $aFetchItems[] = $sLine; + } + } + + if (0 < \count($aSignatureMimeIndexes)) + { + foreach ($aSignatureMimeIndexes as $sTextMimeIndex) + { + $aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sTextMimeIndex.']'; + } + } + + if (!$oBodyStructure) + { + $aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE; + } + + $aFetchResponse = $this->oImapClient->Fetch($aFetchItems, $iIndex, $bIndexIsUid); + if (0 < \count($aFetchResponse)) + { + $oMessage = \MailSo\Mail\Message::NewFetchResponseInstance( + $sFolderName, $aFetchResponse[0], $oBodyStructure); + } + + return $oMessage; + } + + /** + * @param mixed $mCallback + * @param string $sFolderName + * @param int $iIndex + * @param bool $bIndexIsUid = true, + * @param string $sMimeIndex = '' + * + * @return bool + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageMimeStream($mCallback, $sFolderName, $iIndex, $bIndexIsUid = true, $sMimeIndex = '') + { + if (!is_callable($mCallback)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderSelect($sFolderName); + + $sFileName = ''; + $sContentType = ''; + $sMailEncodingName = ''; + + $sMimeIndex = trim($sMimeIndex); + $aFetchResponse = $this->oImapClient->Fetch(array( + 0 === \strlen($sMimeIndex) + ? \MailSo\Imap\Enumerations\FetchType::BODY_HEADER_PEEK + : \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sMimeIndex.'.MIME]' + ), $iIndex, $bIndexIsUid); + + if (0 < \count($aFetchResponse)) + { + $sMime = $aFetchResponse[0]->GetFetchValue( + 0 === \strlen($sMimeIndex) + ? \MailSo\Imap\Enumerations\FetchType::BODY_HEADER + : \MailSo\Imap\Enumerations\FetchType::BODY.'['.$sMimeIndex.'.MIME]' + ); + + if (0 < \strlen($sMime)) + { + $oHeaders = \MailSo\Mime\HeaderCollection::NewInstance()->Parse($sMime); + + if (0 < \strlen($sMimeIndex)) + { + $sFileName = $oHeaders->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, + \MailSo\Mime\Enumerations\Parameter::FILENAME); + + if (0 === \strlen($sFileName)) + { + $sFileName = $oHeaders->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::NAME); + } + + $sMailEncodingName = $oHeaders->ValueByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING); + + $sContentType = $oHeaders->ValueByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE); + } + else + { + $sSubject = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT); + + $sFileName = 0 === \strlen($sSubject) ? (string) $iIndex : $sSubject; + $sFileName .= '.eml'; + + $sContentType = 'message/rfc822'; + } + } + } + + $aFetchResponse = $this->oImapClient->Fetch(array( + array(\MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sMimeIndex.']', + function ($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream) use ($mCallback, $sMimeIndex, $sMailEncodingName, $sContentType, $sFileName) + { + if (0 < \strlen($sLiteralAtomUpperCase)) + { + if (is_resource($rImapLiteralStream) && 'FETCH' === $sParent) + { + $rMessageMimeIndexStream = (0 === \strlen($sMailEncodingName)) + ? $rImapLiteralStream + : \MailSo\Base\StreamWrappers\Binary::CreateStream($rImapLiteralStream, + \MailSo\Base\StreamWrappers\Binary::GetInlineDecodeOrEncodeFunctionName( + $sMailEncodingName, true)); + + \call_user_func($mCallback, $rMessageMimeIndexStream, $sContentType, $sFileName, $sMimeIndex); + } + } + } + )), $iIndex, $bIndexIsUid); + + return ($aFetchResponse && 1 === \count($aFetchResponse)); + } + + /** + * @param string $sFolder + * @param array $aIndexRange + * @param bool $bIndexIsUid + * @param bool $bUseExpunge = true + * @param bool $bExpungeAll = false + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageDelete($sFolder, $aIndexRange, $bIndexIsUid, $bUseExpunge = true, $bExpungeAll = false) + { + if (0 === \strlen($sFolder) || !\is_array($aIndexRange) || 0 === \count($aIndexRange)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderSelect($sFolder); + + $sIndexRange = \MailSo\Base\Utils::PrepearFetchSequence($aIndexRange); + + $this->oImapClient->MessageStoreFlag($sIndexRange, $bIndexIsUid, + array(\MailSo\Imap\Enumerations\MessageFlag::DELETED), + \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT + ); + + if ($bUseExpunge) + { + $this->oImapClient->MessageExpunge($bIndexIsUid ? $sIndexRange : '', $bIndexIsUid, $bExpungeAll); + } + + return $this; + } + + /** + * @param string $sFromFolder + * @param string $sToFolder + * @param array $aIndexRange + * @param bool $bIndexIsUid + * @param bool $bUseMoveSupported = false + * @param bool $bExpungeAll = false + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageMove($sFromFolder, $sToFolder, $aIndexRange, $bIndexIsUid, $bUseMoveSupported = false, $bExpungeAll = false) + { + if (0 === \strlen($sFromFolder) || 0 === \strlen($sToFolder) || + !\is_array($aIndexRange) || 0 === \count($aIndexRange)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderSelect($sFromFolder); + + if ($bUseMoveSupported && $this->oImapClient->IsSupported('MOVE')) + { + $this->oImapClient->MessageMove($sToFolder, + \MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid); + } + else + { + $this->oImapClient->MessageCopy($sToFolder, + \MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid); + + $this->MessageDelete($sFromFolder, $aIndexRange, $bIndexIsUid, true, $bExpungeAll); + } + + return $this; + } + + /** + * @param string $sFromFolder + * @param string $sToFolder + * @param array $aIndexRange + * @param bool $bIndexIsUid + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageCopy($sFromFolder, $sToFolder, $aIndexRange, $bIndexIsUid) + { + if (0 === \strlen($sFromFolder) || 0 === \strlen($sToFolder) || + !\is_array($aIndexRange) || 0 === \count($aIndexRange)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderSelect($sFromFolder); + $this->oImapClient->MessageCopy($sToFolder, + \MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid); + + return $this; + } + + /** + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderUnSelect() + { + if ($this->oImapClient->IsSelected()) + { + $this->oImapClient->FolderUnSelect(); + } + + return $this; + } + + /** + * @param resource $rMessageStream + * @param int $iMessageStreamSize + * @param string $sFolderToSave + * @param array $aAppendFlags = null + * @param int $iUid = null + * + * @return \MailSo\Mail\MailClient + */ + public function MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderToSave, $aAppendFlags = null, &$iUid = null) + { + if (!\is_resource($rMessageStream) || 0 === \strlen($sFolderToSave)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->MessageAppendStream( + $sFolderToSave, $rMessageStream, $iMessageStreamSize, $aAppendFlags, $iUid); + + return $this; + } + + /** + * @param string $sMessageFileName + * @param string $sFolderToSave + * @param array $aAppendFlags = null + * @param int &$iUid = null + * + * @return \MailSo\Mail\MailClient + */ + public function MessageAppendFile($sMessageFileName, $sFolderToSave, $aAppendFlags = null, &$iUid = null) + { + if (!@\is_file($sMessageFileName) || !@\is_readable($sMessageFileName)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $iMessageStreamSize = \filesize($sMessageFileName); + $rMessageStream = \fopen($sMessageFileName, 'rb'); + + $this->MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderToSave, $aAppendFlags, $iUid); + + if (\is_resource($rMessageStream)) + { + @fclose($rMessageStream); + } + + return $this; + } + + /** + * @param string $sFolderName + * @param int $iCount + * @param int $iUnseenCount + * @param string $sUidNext + * @param string $sHighestModSeq + * + * @return void + */ + protected function initFolderValues($sFolderName, &$iCount, &$iUnseenCount, + &$sUidNext, &$sHighestModSeq = '') + { + $aTypes = array( + \MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES, + \MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN, + \MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT + ); + + if ($this->oImapClient->IsSupported('CONDSTORE')) + { + $aTypes[] = \MailSo\Imap\Enumerations\FolderResponseStatus::HIGHESTMODSEQ; + } + + $aFolderStatus = $this->oImapClient->FolderStatus($sFolderName, $aTypes); + + $iCount = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES]) + ? (int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES] : 0; + + $iUnseenCount = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN]) + ? (int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN] : 0; + + $sUidNext = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT]) + ? (string) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT] : '0'; + + $sHighestModSeq = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::HIGHESTMODSEQ]) + ? (string) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::HIGHESTMODSEQ] : ''; + + if ($this->IsGmail() && + ('INBOX' === $sFolderName || '[gmail]' === \strtolower(\substr($sFolderName, 0, 7)))) + { + $oFolder = $this->oImapClient->FolderCurrentInformation(); + if ($oFolder && null !== $oFolder->Exists && $oFolder->FolderName === $sFolderName) + { + $iSubCount = (int) $oFolder->Exists; + if (0 < $iSubCount && $iSubCount < $iCount) + { + $iCount = $iSubCount; + } + } + } + } + + /** + * @return string + */ + public function GenerateImapClientHash() + { + return \md5('ImapClientHash/'. + $this->oImapClient->GetLogginedUser().'@'. + $this->oImapClient->GetConnectedHost().':'. + $this->oImapClient->GetConnectedPort() + ); + } + + /** + * @param string $sFolder + * @param int $iCount + * @param int $iUnseenCount + * @param string $sUidNext + * @param string $sHighestModSeq = '' + * + * @return string + */ + public function GenerateFolderHash($sFolder, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq = '') + { + $iUnseenCount = 0; // unneccessery + return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext.'-'. + $sHighestModSeq.'-'.$this->GenerateImapClientHash().'-'. + \MailSo\Config::$MessageListPermanentFilter + ); + } + + /** + * @param string $sFolderName + * @param string $sPrevUidNext + * @param string $sCurrentUidNext + * + * @return array + */ + private function getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sCurrentUidNext) + { + $aNewMessages = array(); + + if (0 < \strlen($sPrevUidNext) && (string) $sPrevUidNext !== (string) $sCurrentUidNext) + { + $this->oImapClient->FolderSelect($sFolderName); + + $aFetchResponse = $this->oImapClient->Fetch(array( + \MailSo\Imap\Enumerations\FetchType::INDEX, + \MailSo\Imap\Enumerations\FetchType::UID, + \MailSo\Imap\Enumerations\FetchType::FLAGS, + \MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array( + \MailSo\Mime\Enumerations\Header::FROM_, + \MailSo\Mime\Enumerations\Header::SUBJECT, + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE + )) + ), $sPrevUidNext.':*', true); + + if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse)) + { + foreach ($aFetchResponse as /* @var $oFetchResponse \MailSo\Imap\FetchResponse */ $oFetchResponse) + { + $aFlags = \array_map('strtolower', $oFetchResponse->GetFetchValue( + \MailSo\Imap\Enumerations\FetchType::FLAGS)); + + if (!\in_array(\strtolower(\MailSo\Imap\Enumerations\MessageFlag::SEEN), $aFlags)) + { + $sUid = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID); + $sHeaders = $oFetchResponse->GetHeaderFieldsValue(); + + $oHeaders = \MailSo\Mime\HeaderCollection::NewInstance()->Parse($sHeaders); + + $sContentTypeCharset = $oHeaders->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::CHARSET + ); + + $sCharset = ''; + if (0 < \strlen($sContentTypeCharset)) + { + $sCharset = $sContentTypeCharset; + } + + if (0 < \strlen($sCharset)) + { + $oHeaders->SetParentCharset($sCharset); + } + + $aNewMessages[] = array( + 'Folder' => $sFolderName, + 'Uid' => $sUid, + 'Subject' => $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT, 0 === \strlen($sCharset)), + 'From' => $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, 0 === \strlen($sCharset)) + ); + } + } + } + } + + return $aNewMessages; + } + + /** + * @param string $sFolderName + * @param string $sPrevUidNext = '' + * @param array $aUids = '' + * + * @return string + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderInformation($sFolderName, $sPrevUidNext = '', $aUids = array()) + { + $aFlags = array(); + + $bSelect = false; + if ($this->IsGmail() && + ('INBOX' === $sFolderName || '[gmail]' === \strtolower(\substr($sFolderName, 0, 7)))) + { + $this->oImapClient->FolderSelect($sFolderName); + $bSelect = true; + } + + if (\is_array($aUids) && 0 < \count($aUids)) + { + if (!$bSelect) + { + $this->oImapClient->FolderSelect($sFolderName); + } + + $aFetchResponse = $this->oImapClient->Fetch(array( + \MailSo\Imap\Enumerations\FetchType::INDEX, + \MailSo\Imap\Enumerations\FetchType::UID, + \MailSo\Imap\Enumerations\FetchType::FLAGS + ), \MailSo\Base\Utils::PrepearFetchSequence($aUids), true); + + if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse)) + { + foreach ($aFetchResponse as $oFetchResponse) + { + $sUid = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID); + $aFlags[(\is_numeric($sUid) ? (int) $sUid : 0)] = + $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::FLAGS); + } + } + } + + $iCount = 0; + $iUnseenCount = 0; + $sUidNext = '0'; + $sHighestModSeq = ''; + + $this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq); + + $aResult = array( + 'Folder' => $sFolderName, + 'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq), + 'MessageCount' => $iCount, + 'MessageUnseenCount' => $iUnseenCount, + 'UidNext' => $sUidNext, + 'Flags' => $aFlags, + 'HighestModSeq' => $sHighestModSeq, + 'NewMessages' => 'INBOX' === $sFolderName && \MailSo\Config::$CheckNewMessages ? + $this->getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sUidNext) : array() + ); + + return $aResult; + } + + /** + * @param string $sFolderName + * + * @return string + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderHash($sFolderName) + { + $iCount = 0; + $iUnseenCount = 0; + $sUidNext = '0'; + $sHighestModSeq = ''; + + $this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq); + + return $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext, $sHighestModSeq); + } + + /** + * @return int + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function InboxUnreadCount() + { + $aFolderStatus = $this->oImapClient->FolderStatus('INBOX', array( + \MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN + )); + + $iResult = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN]) ? + (int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN] : 0; + + return 0 < $iResult ? $iResult : 0; + } + + /** + * @return bool + */ + public function IsGmail() + { + return 'ssl://imap.gmail.com' === \strtolower($this->oImapClient->GetConnectedHost()); + } + + /** + * @param string $sSearch + * @param bool $bDetectGmail = true + * + * @return string + */ + private function escapeSearchString($sSearch, $bDetectGmail = true) + { + return !\MailSo\Base\Utils::IsAscii($sSearch) + ? '{'.\strlen($sSearch).'}'."\r\n".$sSearch : $this->oImapClient->EscapeString($sSearch); +// return ($bDetectGmail && !\MailSo\Base\Utils::IsAscii($sSearch) && $this->IsGmail()) +// ? '{'.\strlen($sSearch).'+}'."\r\n".$sSearch : $this->oImapClient->EscapeString($sSearch); + } + + /** + * @param string $sDate + * @param int $iTimeZoneOffset + * + * @return int + */ + private function parseSearchDate($sDate, $iTimeZoneOffset) + { + $iResult = 0; + if (0 < \strlen($sDate)) + { + $oDateTime = \DateTime::createFromFormat('Y.m.d', $sDate, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); + return $oDateTime ? $oDateTime->getTimestamp() - $iTimeZoneOffset : 0; + } + + return $iResult; + } + + /** + * @param string $sSize + * + * @return int + */ + private function parseFriendlySize($sSize) + { + $sSize = preg_replace('/[^0-9bBkKmM]/', '', $sSize); + + $iResult = 0; + $aMatch = array(); + + if (\preg_match('/([\d]+)(B|KB|M|MB|G|GB)$/i', $sSize, $aMatch) && isset($aMatch[1], $aMatch[2])) + { + $iResult = (int) $aMatch[1]; + switch (\strtoupper($aMatch[2])) + { + case 'K': + case 'KB': + $iResult *= 1024; + case 'M': + case 'MB': + $iResult *= 1024; + case 'G': + case 'GB': + $iResult *= 1024; + } + } + else + { + $iResult = (int) $sSize; + } + + return $iResult; + } + + /** + * @param string $sSearch + * + * @return array + */ + private function parseSearchString($sSearch) + { + $aResult = array( + 'OTHER' => '' + ); + + $aCache = array(); + + $sReg = 'e?mail|from|to|subject|has|is|date|text|body|size|larger|bigger|smaller|maxsize|minsize'; + + $sSearch = \MailSo\Base\Utils::StripSpaces($sSearch); + $sSearch = \trim(\preg_replace('/('.$sReg.'): /i', '\\1:', $sSearch)); + + $mMatch = array(); + \preg_match_all('/".*?(? $sName) + { + if (isset($mMatch[2][$iIndex]) && 0 < \strlen($mMatch[2][$iIndex])) + { + $sName = \strtoupper($sName); + $sValue = $mMatch[2][$iIndex]; + switch ($sName) + { + case 'TEXT': + case 'BODY': + case 'EMAIL': + case 'MAIL': + case 'FROM': + case 'TO': + case 'SUBJECT': + case 'IS': + case 'HAS': + case 'SIZE': + case 'SMALLER': + case 'LARGER': + case 'BIGGER': + case 'MAXSIZE': + case 'MINSIZE': + case 'DATE': + if ('MAIL' === $sName) + { + $sName = 'EMAIL'; + } + if ('BODY' === $sName) + { + $sName = 'TEXT'; + } + if ('SIZE' === $sName || 'BIGGER' === $sName || 'MINSIZE' === $sName) + { + $sName = 'LARGER'; + } + if ('MAXSIZE' === $sName) + { + $sName = 'SMALLER'; + } + $aResult[$sName] = $sValue; + break; + } + } + } + } + + $aResult['OTHER'] = $sSearch; + foreach ($aResult as $sName => $sValue) + { + if (isset($aCache[$sValue])) + { + $aResult[$sName] = \trim($aCache[$sValue], '"\' '); + } + } + + return $aResult; + } + + /** + * @param string $sSearch + * @param string $sFilter + * @param int $iTimeZoneOffset = 0 + * @param bool $bUseCache = true + * + * @return string + */ + private function getImapSearchCriterias($sSearch, $sFilter, $iTimeZoneOffset = 0, &$bUseCache = true) + { + $bUseCache = true; + $iTimeFilter = 0; + $aCriteriasResult = array(); + + if (0 < \MailSo\Config::$MessageListDateFilter) + { + $iD = \time() - 3600 * 24 * 30 * \MailSo\Config::$MessageListDateFilter; + $iTimeFilter = \gmmktime(1, 1, 1, \gmdate('n', $iD), 1, \gmdate('Y', $iD)); + } + + if (0 < \strlen(\trim($sSearch))) + { + $sGmailRawSearch = ''; + $sResultBodyTextSearch = ''; + + $aLines = $this->parseSearchString($sSearch); + $bIsGmail = $this->oImapClient->IsSupported('X-GM-EXT-1'); + + if (1 === \count($aLines) && isset($aLines['OTHER'])) + { + $sValue = $this->escapeSearchString($aLines['OTHER']); + + if (\MailSo\Config::$MessageListFastSimpleSearch) + { + $aCriteriasResult[] = 'OR OR OR'; + $aCriteriasResult[] = 'FROM'; + $aCriteriasResult[] = $sValue; + $aCriteriasResult[] = 'TO'; + $aCriteriasResult[] = $sValue; + $aCriteriasResult[] = 'CC'; + $aCriteriasResult[] = $sValue; + $aCriteriasResult[] = 'SUBJECT'; + $aCriteriasResult[] = $sValue; + } + else + { + $aCriteriasResult[] = 'TEXT'; + $aCriteriasResult[] = $sValue; + } + } + else + { + if (isset($aLines['EMAIL'])) + { + $sValue = $this->escapeSearchString($aLines['EMAIL']); + + $aCriteriasResult[] = 'OR OR'; + $aCriteriasResult[] = 'FROM'; + $aCriteriasResult[] = $sValue; + $aCriteriasResult[] = 'TO'; + $aCriteriasResult[] = $sValue; + $aCriteriasResult[] = 'CC'; + $aCriteriasResult[] = $sValue; + + unset($aLines['EMAIL']); + } + + if (isset($aLines['TO'])) + { + $sValue = $this->escapeSearchString($aLines['TO']); + + $aCriteriasResult[] = 'OR'; + $aCriteriasResult[] = 'TO'; + $aCriteriasResult[] = $sValue; + $aCriteriasResult[] = 'CC'; + $aCriteriasResult[] = $sValue; + + unset($aLines['TO']); + } + + $sMainText = ''; + foreach ($aLines as $sName => $sRawValue) + { + if ('' === \trim($sRawValue)) + { + continue; + } + + $sValue = $this->escapeSearchString($sRawValue); + switch ($sName) + { + case 'FROM': + $aCriteriasResult[] = 'FROM'; + $aCriteriasResult[] = $sValue; + break; + case 'SUBJECT': + $aCriteriasResult[] = 'SUBJECT'; + $aCriteriasResult[] = $sValue; + break; + case 'OTHER': + case 'TEXT': + $sMainText .= ' '.$sRawValue; + break; + case 'HAS': + $aValue = \explode(',', \strtolower($sRawValue)); + $aValue = \array_map('trim', $aValue); + + $aCompareArray = array('file', 'files', 'attach', 'attachs', 'attachment', 'attachments'); + if (\count($aCompareArray) > \count(\array_diff($aCompareArray, $aValue))) + { + if ($bIsGmail) + { + $sGmailRawSearch .= ' has:attachment'; + } + else + { + // Simple, is not detailed search (Sometimes doesn't work) + $aCriteriasResult[] = 'OR OR OR'; + $aCriteriasResult[] = 'HEADER Content-Type application/'; + $aCriteriasResult[] = 'HEADER Content-Type multipart/m'; + $aCriteriasResult[] = 'HEADER Content-Type multipart/signed'; + $aCriteriasResult[] = 'HEADER Content-Type multipart/report'; + } + } + + case 'IS': + $aValue = \explode(',', \strtolower($sRawValue)); + $aValue = \array_map('trim', $aValue); + + $aCompareArray = array('flag', 'flagged', 'star', 'starred', 'pinned'); + $aCompareArray2 = array('unflag', 'unflagged', 'unstar', 'unstarred', 'unpinned'); + if (\count($aCompareArray) > \count(\array_diff($aCompareArray, $aValue))) + { + $aCriteriasResult[] = 'FLAGGED'; + $bUseCache = false; + } + else if (\count($aCompareArray2) > \count(\array_diff($aCompareArray2, $aValue))) + { + $aCriteriasResult[] = 'UNFLAGGED'; + $bUseCache = false; + } + + $aCompareArray = array('unread', 'unseen'); + $aCompareArray2 = array('read', 'seen'); + if (\count($aCompareArray) > \count(\array_diff($aCompareArray, $aValue))) + { + $aCriteriasResult[] = 'UNSEEN'; + $bUseCache = false; + } + else if (\count($aCompareArray2) > \count(\array_diff($aCompareArray2, $aValue))) + { + $aCriteriasResult[] = 'SEEN'; + $bUseCache = false; + } + break; + + case 'LARGER': + $aCriteriasResult[] = 'LARGER'; + $aCriteriasResult[] = $this->parseFriendlySize($sRawValue); + break; + case 'SMALLER': + $aCriteriasResult[] = 'SMALLER'; + $aCriteriasResult[] = $this->parseFriendlySize($sRawValue); + break; + case 'DATE': + $iDateStampFrom = $iDateStampTo = 0; + + $sDate = $sRawValue; + $aDate = \explode('/', $sDate); + + if (\is_array($aDate) && 2 === \count($aDate)) + { + if (0 < \strlen($aDate[0])) + { + $iDateStampFrom = $this->parseSearchDate($aDate[0], $iTimeZoneOffset); + } + + if (0 < \strlen($aDate[1])) + { + $iDateStampTo = $this->parseSearchDate($aDate[1], $iTimeZoneOffset); + $iDateStampTo += 60 * 60 * 24; + } + } + else + { + if (0 < \strlen($sDate)) + { + $iDateStampFrom = $this->parseSearchDate($sDate, $iTimeZoneOffset); + $iDateStampTo = $iDateStampFrom + 60 * 60 * 24; + } + } + + if (0 < $iDateStampFrom) + { + $aCriteriasResult[] = 'SINCE'; + $aCriteriasResult[] = \gmdate('j-M-Y', $iTimeFilter > $iDateStampFrom ? + $iTimeFilter : $iDateStampFrom); + + $iTimeFilter = 0; + } + + if (0 < $iDateStampTo) + { + $aCriteriasResult[] = 'BEFORE'; + $aCriteriasResult[] = \gmdate('j-M-Y', $iDateStampTo); + } + break; + } + } + + if ('' !== \trim($sMainText)) + { + $sMainText = \trim(\MailSo\Base\Utils::StripSpaces($sMainText), '"'); + if ($bIsGmail) + { + $sGmailRawSearch .= ' '.$sMainText; + } + else + { + $sResultBodyTextSearch .= ' '.$sMainText; + } + } + } + + $sGmailRawSearch = \trim($sGmailRawSearch); + if ($bIsGmail && 0 < \strlen($sGmailRawSearch)) + { + $aCriteriasResult[] = 'X-GM-RAW'; + $aCriteriasResult[] = $this->escapeSearchString($sGmailRawSearch, false); + } + + $sResultBodyTextSearch = \trim($sResultBodyTextSearch); + if (0 < \strlen($sResultBodyTextSearch)) + { + $aCriteriasResult[] = 'BODY'; + $aCriteriasResult[] = $this->escapeSearchString($sResultBodyTextSearch); + } + } + + $sCriteriasResult = \trim(\implode(' ', $aCriteriasResult)); + + if (0 < $iTimeFilter) + { + $sCriteriasResult .= ' SINCE '.\gmdate('j-M-Y', $iTimeFilter); + } + + $sCriteriasResult = \trim($sCriteriasResult); + if (\MailSo\Config::$MessageListUndeletedOnly) + { + $sCriteriasResult = \trim($sCriteriasResult.' UNDELETED'); + } + + $sFilter = \trim($sFilter); + if ('' !== $sFilter) + { + $sCriteriasResult .= ' '.$sFilter; + } + + $sCriteriasResult = \trim($sCriteriasResult); + if ('' !== \MailSo\Config::$MessageListPermanentFilter) + { + $sCriteriasResult = \trim($sCriteriasResult.' '.\MailSo\Config::$MessageListPermanentFilter); + } + + $sCriteriasResult = \trim($sCriteriasResult); + if ('' === $sCriteriasResult) + { + $sCriteriasResult = 'ALL'; + } + + return $sCriteriasResult; + } + + /** + * @param array $aThreads + * @return array + */ + private function threadArrayMap($aThreads) + { + $aNew = array(); + foreach ($aThreads as $mItem) + { + if (!\is_array($mItem)) + { + $aNew[] = $mItem; + } + else + { + $mMap = $this->threadArrayMap($mItem); + if (\is_array($mMap) && 0 < \count($mMap)) + { + $aNew = \array_merge($aNew, $mMap); + } + } + } + + return $aNew; + } + + /** + * @param array $aThreads + * + * @return array + */ + private function compileThreadArray($aThreads) + { + $aResult = array(); + foreach ($aThreads as $mItem) + { + if (\is_array($mItem)) + { + $aMap = $this->threadArrayMap($mItem); + if (\is_array($aMap)) + { + if (1 < \count($aMap)) + { + $aResult[] = $aMap; + } + else if (0 < \count($aMap)) + { + $aResult[] = $aMap[0]; + } + } + } + else + { + $aResult[] = $mItem; + } + } + + return $aResult; + } + + /** + * @param string $sFolderName + * @param string $sFolderHash + * @param array $aIndexOrUids + * @param \MailSo\Cache\CacheClient $oCacher + * @param bool $bCacheOnly = false + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageListThreadsMap($sFolderName, $sFolderHash, $aIndexOrUids, $oCacher, $bCacheOnly = false) + { + $iThreadLimit = \MailSo\Config::$LargeThreadLimit; + + $sSearchHash = ''; + if (0 < \MailSo\Config::$MessageListDateFilter) + { + $iD = \time() - 3600 * 24 * 30 * \MailSo\Config::$MessageListDateFilter; + $iTimeFilter = \gmmktime(1, 1, 1, \gmdate('n', $iD), 1, \gmdate('Y', $iD)); + + $sSearchHash .= ' SINCE '.\gmdate('j-M-Y', $iTimeFilter); + } + + if ('' === \trim($sSearchHash)) + { + $sSearchHash = 'ALL'; + } + + if ($oCacher && $oCacher->IsInited()) + { + $sSerializedHashKey = + 'ThreadsMapSorted/'.$sSearchHash.'/'. + 'Limit='.$iThreadLimit.'/'.$sFolderName.'/'.$sFolderHash; + + if ($this->oLogger) + { + $this->oLogger->Write($sSerializedHashKey); + } + + $sSerializedUids = $oCacher->Get($sSerializedHashKey); + if (!empty($sSerializedUids)) + { + $aSerializedUids = @\json_decode($sSerializedUids, true); + if (isset($aSerializedUids['ThreadsUids']) && \is_array($aSerializedUids['ThreadsUids'])) + { + if ($this->oLogger) + { + $this->oLogger->Write('Get Serialized Thread UIDS from cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aSerializedUids['ThreadsUids']).']'); + } + + return $aSerializedUids['ThreadsUids']; + } + } + } + + if ($bCacheOnly) + { + return null; + } + + $this->oImapClient->FolderExamine($sFolderName); + + $aThreadUids = array(); + try + { + $aThreadUids = $this->oImapClient->MessageSimpleThread($sSearchHash); + } + catch (\MailSo\Imap\Exceptions\RuntimeException $oException) + { + unset($oException); + $aThreadUids = array(); + } + + $aResult = array(); + $aCompiledThreads = $this->compileThreadArray($aThreadUids); + + foreach ($aCompiledThreads as $mData) + { + if (\is_array($mData)) + { + foreach ($mData as $mSubData) + { + $aResult[(int) $mSubData] = + \array_diff($mData, array((int) $mSubData)); + } + } + else if (\is_int($mData) || \is_string($mData)) + { + $aResult[(int) $mData] = (int) $mData; + } + } + + $aParentsMap = array(); + foreach ($aIndexOrUids as $iUid) + { + if (isset($aResult[$iUid]) && \is_array($aResult[$iUid])) + { + foreach ($aResult[$iUid] as $iTempUid) + { + $aParentsMap[$iTempUid] = $iUid; + if (isset($aResult[$iTempUid])) + { + unset($aResult[$iTempUid]); + } + } + } + } + + $aSortedThreads = array(); + foreach ($aIndexOrUids as $iUid) + { + if (isset($aResult[$iUid])) + { + $aSortedThreads[$iUid] = $iUid; + } + } + + foreach ($aIndexOrUids as $iUid) + { + if (!isset($aSortedThreads[$iUid]) && + isset($aParentsMap[$iUid]) && + isset($aSortedThreads[$aParentsMap[$iUid]])) + { + if (!\is_array($aSortedThreads[$aParentsMap[$iUid]])) + { + $aSortedThreads[$aParentsMap[$iUid]] = array(); + } + + $aSortedThreads[$aParentsMap[$iUid]][] = $iUid; + } + } + + $aResult = $aSortedThreads; + unset($aParentsMap, $aSortedThreads); + + $aTemp = array(); + foreach ($aResult as $iUid => $mValue) + { + if (0 < $iThreadLimit && \is_array($mValue) && $iThreadLimit < \count($mValue)) + { + $aParts = \array_chunk($mValue, $iThreadLimit); + if (0 < count($aParts)) + { + foreach ($aParts as $iIndex => $aItem) + { + if (0 === $iIndex) + { + $aResult[$iUid] = $aItem; + } + else if (0 < $iIndex && \is_array($aItem)) + { + $mFirst = \array_shift($aItem); + if (!empty($mFirst)) + { + $aTemp[$mFirst] = 0 < \count($aItem) ? $aItem : $mFirst; + } + } + } + } + } + } + + foreach ($aTemp as $iUid => $mValue) + { + $aResult[$iUid] = $mValue; + } + + unset($aTemp); + + $aLastResult = array(); + foreach ($aIndexOrUids as $iUid) + { + if (isset($aResult[$iUid])) + { + $aLastResult[$iUid] = $aResult[$iUid]; + } + } + + $aResult = $aLastResult; + unset($aLastResult); + + if ($oCacher && $oCacher->IsInited() && !empty($sSerializedHashKey)) + { + $oCacher->Set($sSerializedHashKey, @\json_encode(array( + 'ThreadsUids' => $aResult + ))); + + if ($this->oLogger) + { + $this->oLogger->Write('Save Serialized Thread UIDS to cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aResult).']'); + } + } + + return $aResult; + } + + /** + * @param \MailSo\Mail\MessageCollection &$oMessageCollection + * @param array $aRequestIndexOrUids + * @param bool $bIndexAsUid + * @param bool $bSimple = false + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageListByRequestIndexOrUids(&$oMessageCollection, $aRequestIndexOrUids, $bIndexAsUid, $bSimple = false) + { + if (\is_array($aRequestIndexOrUids) && 0 < \count($aRequestIndexOrUids)) + { + $aFetchResponse = $this->oImapClient->Fetch(array( + \MailSo\Imap\Enumerations\FetchType::INDEX, + \MailSo\Imap\Enumerations\FetchType::UID, + \MailSo\Imap\Enumerations\FetchType::RFC822_SIZE, + \MailSo\Imap\Enumerations\FetchType::INTERNALDATE, + \MailSo\Imap\Enumerations\FetchType::FLAGS, + \MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE, + $bSimple ? + $this->getEnvelopeOrHeadersRequestStringForSimpleList() : + $this->getEnvelopeOrHeadersRequestString() + ), \MailSo\Base\Utils::PrepearFetchSequence($aRequestIndexOrUids), $bIndexAsUid); + + if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse)) + { + $aFetchIndexArray = array(); + $oFetchResponseItem = null; + foreach ($aFetchResponse as /* @var $oFetchResponseItem \MailSo\Imap\FetchResponse */ &$oFetchResponseItem) + { + $aFetchIndexArray[($bIndexAsUid) + ? $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID) + : $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INDEX)] =& $oFetchResponseItem; + + unset($oFetchResponseItem); + } + + foreach ($aRequestIndexOrUids as $iFUid) + { + if (isset($aFetchIndexArray[$iFUid])) + { + $oMessageCollection->Add( + Message::NewFetchResponseInstance( + $oMessageCollection->FolderName, $aFetchIndexArray[$iFUid])); + } + } + } + } + } + + /** + * @return bool + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function IsThreadsSupported() + { + return $this->oImapClient->IsSupported('THREAD=REFS') || + $this->oImapClient->IsSupported('THREAD=REFERENCES') || + $this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT'); + } + + /** + * @param string $sFolderName + * @param array $aUids + * + * @return \MailSo\Mail\MessageCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageListSimple($sFolderName, $aUids) + { + if (0 === \strlen($sFolderName) || !\MailSo\Base\Validator::NotEmptyArray($aUids)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderExamine($sFolderName); + + $oMessageCollection = \MailSo\Mail\MessageCollection::NewInstance(); + $oMessageCollection->FolderName = $sFolderName; + + $this->MessageListByRequestIndexOrUids($oMessageCollection, $aUids, true, true); + + return $oMessageCollection->GetAsArray(); + } + + /** + * @param \MailSo\Cache\CacheClient|null $oCacher + * @param string $sSearch + * @param string $sFilter + * @param string $sFolderName + * @param string $sFolderHash + * @param bool $bUseSortIfSupported = false + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function GetUids($oCacher, $sSearch, $sFilter, $sFolderName, $sFolderHash, $bUseSortIfSupported = false) + { + $aResultUids = false; + $bUidsFromCacher = false; + $bUseCacheAfterSearch = true; + + $sSerializedHash = ''; + $sSerializedLog = ''; + + $bUseSortIfSupported = $bUseSortIfSupported ? !!$this->oImapClient->IsSupported('SORT') : false; + + if (0 < \strlen($sSearch)) + { + $bUseSortIfSupported = false; + } + + $sSearchCriterias = $this->getImapSearchCriterias($sSearch, $sFilter, 0, $bUseCacheAfterSearch); + if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited()) + { + $sSerializedHash = 'GetUids/'. + ($bUseSortIfSupported ? 'S': 'N').'/'. + $this->GenerateImapClientHash().'/'. + $sFolderName.'/'.$sSearchCriterias; + + $sSerializedLog = '"'.$sFolderName.'" / '.$sSearchCriterias.''; + + $sSerialized = $oCacher->Get($sSerializedHash); + if (!empty($sSerialized)) + { + $aSerialized = @\json_decode($sSerialized, true); + if (\is_array($aSerialized) && isset($aSerialized['FolderHash'], $aSerialized['Uids']) && + $sFolderHash === $aSerialized['FolderHash'] && + \is_array($aSerialized['Uids']) + ) + { + if ($this->oLogger) + { + $this->oLogger->Write('Get Serialized UIDS from cache ('.$sSerializedLog.') [count:'.\count($aSerialized['Uids']).']'); + } + + $aResultUids = $aSerialized['Uids']; + $bUidsFromCacher = true; + } + } + } + + if (!\is_array($aResultUids)) + { + $aResultUids = $bUseSortIfSupported ? + $this->oImapClient->MessageSimpleSort(array('REVERSE ARRIVAL'), $sSearchCriterias, true) : + $this->oImapClient->MessageSimpleSearch($sSearchCriterias, true, \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? '' : 'UTF-8') + ; + + if (!$bUidsFromCacher && $bUseCacheAfterSearch && \is_array($aResultUids) && $oCacher && $oCacher->IsInited() && 0 < \strlen($sSerializedHash)) + { + $oCacher->Set($sSerializedHash, @\json_encode(array( + 'FolderHash' => $sFolderHash, + 'Uids' => $aResultUids + ))); + + if ($this->oLogger) + { + $this->oLogger->Write('Save Serialized UIDS to cache ('.$sSerializedLog.') [count:'.\count($aResultUids).']'); + } + } + } + + return \is_array($aResultUids) ? $aResultUids : array(); + } + + /** + * @param string $sFolderName + * @param int $iOffset = 0 + * @param int $iLimit = 10 + * @param string $sSearch = '' + * @param string $sPrevUidNext = '' + * @param \MailSo\Cache\CacheClient|null $oCacher = null + * @param bool $bUseSortIfSupported = false + * @param bool $bUseThreadSortIfSupported = false + * @param bool $bUseESearchOrESortRequest = false + * @param string $sThreadUid = '' + * @param string $sFilter = '' + * + * @return \MailSo\Mail\MessageCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null, + $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $sThreadUid = '', $sFilter = '') + { + $sFilter = \trim($sFilter); + $sSearch = \trim($sSearch); + if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) || + !\MailSo\Base\Validator::RangeInt($iLimit, 0, 999)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $bUseFilter = '' !== $sFilter; + + $this->oImapClient->FolderSelect($sFolderName); + + $oMessageCollection = MessageCollection::NewInstance(); + $oMessageCollection->FolderName = $sFolderName; + $oMessageCollection->Offset = $iOffset; + $oMessageCollection->Limit = $iLimit; + $oMessageCollection->Search = $sSearch; + $oMessageCollection->ThreadUid = $sThreadUid; + $oMessageCollection->Filtered = '' !== \MailSo\Config::$MessageListPermanentFilter; + + $aUids = array(); + $mAllSortedUids = null; + $mAllThreads = null; + + $iThreadUid = empty($sThreadUid) ? 0 : (int) $sThreadUid; + + $iMessageRealCount = 0; + $iMessageUnseenCount = 0; + $sUidNext = '0'; + $sHighestModSeq = ''; + + $bUseSortIfSupported = $bUseSortIfSupported ? $this->oImapClient->IsSupported('SORT') : false; + + $bUseThreadSortIfSupported = $bUseThreadSortIfSupported ? + ($this->oImapClient->IsSupported('THREAD=REFS') || $this->oImapClient->IsSupported('THREAD=REFERENCES') || $this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT')) : false; + + if (!empty($sThreadUid) && !$bUseThreadSortIfSupported) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + if (!$oCacher || !($oCacher instanceof \MailSo\Cache\CacheClient)) + { + $oCacher = null; + } + + $this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext, $sHighestModSeq); + + if ($bUseFilter) + { + $iMessageUnseenCount = 0; + } + + $oMessageCollection->FolderHash = $this->GenerateFolderHash( + $sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext, $sHighestModSeq); + + $oMessageCollection->UidNext = $sUidNext; + + if (empty($sThreadUid) && 0 < \strlen($sPrevUidNext) && 'INBOX' === $sFolderName) + { + $oMessageCollection->NewMessages = $this->getFolderNextMessageInformation( + $sFolderName, $sPrevUidNext, $sUidNext); + } + + $bSearch = false; + $bMessageListOptimization = 0 < \MailSo\Config::$MessageListCountLimitTrigger && + \MailSo\Config::$MessageListCountLimitTrigger < $iMessageRealCount; + + if ($bMessageListOptimization) + { + $bUseSortIfSupported = false; + $bUseThreadSortIfSupported = false; + } + + if (0 < $iMessageRealCount && !$bMessageListOptimization) + { + $mAllSortedUids = $this->GetUids($oCacher, '', $sFilter, + $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported); + + $mAllThreads = $bUseThreadSortIfSupported ? $this->MessageListThreadsMap( + $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $mAllSortedUids, $oCacher) : null; + + if ($bUseThreadSortIfSupported && 0 < $iThreadUid && \is_array($mAllThreads)) + { + $aUids = array(); + $iResultRootUid = 0; + + if (isset($mAllThreads[$iThreadUid])) + { + $iResultRootUid = $iThreadUid; + if (\is_array($mAllThreads[$iThreadUid])) + { + $aUids = $mAllThreads[$iThreadUid]; + } + } + else + { + foreach ($mAllThreads as $iRootUid => $mSubUids) + { + if (\is_array($mSubUids) && \in_array($iThreadUid, $mSubUids)) + { + $iResultRootUid = $iRootUid; + $aUids = $mSubUids; + continue; + } + } + } + + if (0 < $iResultRootUid && \in_array($iResultRootUid, $mAllSortedUids)) + { + \array_unshift($aUids, $iResultRootUid); + } + } + else if ($bUseThreadSortIfSupported && \is_array($mAllThreads)) + { + $aUids = \array_keys($mAllThreads); + } + else + { + $bUseThreadSortIfSupported = false; + $aUids = $mAllSortedUids; + } + + if (0 < \strlen($sSearch) && \is_array($aUids)) + { + $aSearchedUids = $this->GetUids($oCacher, $sSearch, $sFilter, + $oMessageCollection->FolderName, $oMessageCollection->FolderHash); + + if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids)) + { + $aFlippedSearchedUids = \array_flip($aSearchedUids); + + $bSearch = true; + $aNewUids = array(); + + foreach ($aUids as $iUid) + { + if (isset($aFlippedSearchedUids[$iUid])) + { + $aNewUids[] = $iUid; + } + else if ($bUseThreadSortIfSupported && 0 === $iThreadUid && isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid])) + { + foreach ($mAllThreads[$iUid] as $iSubUid) + { + if (isset($aFlippedSearchedUids[$iSubUid])) + { + $aNewUids[] = $iUid; + continue; + } + } + } + } + + $aUids = \array_unique($aNewUids); + unset($aNewUids); + } + else + { + $aUids = array(); + } + } + + if (\is_array($aUids)) + { + $oMessageCollection->MessageCount = $iMessageRealCount; + $oMessageCollection->MessageUnseenCount = $iMessageUnseenCount; + $oMessageCollection->MessageResultCount = \count($aUids); + + if (0 < \count($aUids)) + { + $aRequestUids = \array_slice($aUids, $iOffset, $iLimit); + $this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestUids, true); + } + } + } + else if (0 < $iMessageRealCount) + { + if ($this->oLogger) + { + $this->oLogger->Write('List optimization (count: '.$iMessageRealCount. + ', limit:'.\MailSo\Config::$MessageListCountLimitTrigger.')'); + } + + $oMessageCollection->MessageCount = $iMessageRealCount; + $oMessageCollection->MessageUnseenCount = $iMessageUnseenCount; + + if (0 < \strlen($sSearch) || $bUseFilter) + { + $aUids = $this->GetUids($oCacher, $sSearch, $sFilter, + $oMessageCollection->FolderName, $oMessageCollection->FolderHash); + + if (0 < \count($aUids)) + { + $oMessageCollection->MessageResultCount = \count($aUids); + + $aRequestUids = \array_slice($aUids, $iOffset, $iLimit); + $this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestUids, true); + } + else + { + $oMessageCollection->MessageResultCount = 0; + } + } + else + { + $oMessageCollection->MessageResultCount = $iMessageRealCount; + + if (1 < $iMessageRealCount) + { + $aRequestIndexes = \array_slice(array_reverse(range(1, $iMessageRealCount)), $iOffset, $iLimit); + } + else + { + $aRequestIndexes = \array_slice(array(1), $iOffset, $iLimit); + } + + $this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestIndexes, false); + } + } + + if ($bUseThreadSortIfSupported && 0 === $iThreadUid && \is_array($mAllThreads) && 0 < \count($mAllThreads)) + { + $oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($mAllThreads) { + + $iUid = $oMessage->Uid(); + if (isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid]) && 0 < \count($mAllThreads[$iUid])) + { + $aSubThreads = $mAllThreads[$iUid]; + \array_unshift($aSubThreads, $iUid); + + $oMessage->SetThreads(\array_map('trim', $aSubThreads)); + unset($aSubThreads); + } + }); + } + + return $oMessageCollection; + } + + /** + * @return array|false + */ + public function Quota() + { + return $this->oImapClient->Quota(); + } + + /** + * @param string $sFolderName + * @param string $sMessageId + * + * @return int|null + */ + public function FindMessageUidByMessageId($sFolderName, $sMessageId) + { + if (0 === \strlen($sMessageId)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderExamine($sFolderName); + + $aUids = $this->oImapClient->MessageSimpleSearch( + 'HEADER Message-ID '.$sMessageId, true); + + return \is_array($aUids) && 1 === \count($aUids) && \is_numeric($aUids[0]) ? (int) $aUids[0] : null; + } + + /** + * @param array $aMailFoldersHelper + * @param int $iOptimizationLimit = 0 + * + * @return array + */ + public function folderListOptimization($aMailFoldersHelper, $iOptimizationLimit = 0) + { + // optimization + if (10 < $iOptimizationLimit && \is_array($aMailFoldersHelper) && $iOptimizationLimit < \count($aMailFoldersHelper)) + { + if ($this->oLogger) + { + $this->oLogger->Write('Start optimization (limit:'.$iOptimizationLimit.') for '.\count($aMailFoldersHelper).' folders'); + } + + $iForeachLimit = 1; + + $aFilteredNames = array( + 'inbox', + 'sent', 'send', 'outbox', 'sentmail', 'sendmail', + 'drafts', 'draft', + 'junk', 'spam', 'spambucket', + 'trash', 'bin', 'deleted', + 'archives', 'archive', 'allmail', 'all', + 'starred', 'flagged', 'important', + 'contacts', 'chats' + ); + + $aNewMailFoldersHelper = array(); + + $iCountLimit = $iForeachLimit; + + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + // mandatory folders + if ($oFolder && \in_array(\str_replace(' ', '', \strtolower($oFolder->NameRaw())), $aFilteredNames)) + { + $aNewMailFoldersHelper[] = $oFolder; + $aMailFoldersHelper[$iIndex] = null; + } + } + + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + // subscribed folders + if ($oFolder && $oFolder->IsSubscribed()) + { + $aNewMailFoldersHelper[] = $oFolder; + + $aMailFoldersHelper[$iIndex] = null; + $iCountLimit--; + } + + if (0 > $iCountLimit) + { + if ($iOptimizationLimit < \count($aNewMailFoldersHelper)) + { + break; + } + else + { + $iCountLimit = $iForeachLimit; + } + } + } + + $iCountLimit = $iForeachLimit; + if ($iOptimizationLimit >= \count($aNewMailFoldersHelper)) + { + // name filter + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + if ($oFolder && !\preg_match('/[{}\[\]]/', $oFolder->NameRaw())) + { + $aNewMailFoldersHelper[] = $oFolder; + + $aMailFoldersHelper[$iIndex] = null; + $iCountLimit--; + } + + if (0 > $iCountLimit) + { + if ($iOptimizationLimit < \count($aNewMailFoldersHelper)) + { + break; + } + else + { + $iCountLimit = $iForeachLimit; + } + } + } + } + + $iCountLimit = $iForeachLimit; + if ($iOptimizationLimit >= \count($aNewMailFoldersHelper)) + { + // other + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + if ($oFolder) + { + $aNewMailFoldersHelper[] = $oFolder; + + $aMailFoldersHelper[$iIndex] = null; + $iCountLimit--; + } + + if (0 > $iCountLimit) + { + if ($iOptimizationLimit < \count($aNewMailFoldersHelper)) + { + break; + } + else + { + $iCountLimit = $iForeachLimit; + } + } + } + } + + $aMailFoldersHelper = $aNewMailFoldersHelper; + + if ($this->oLogger) + { + $this->oLogger->Write('Result optimization: '.\count($aMailFoldersHelper).' folders'); + } + } + + return $aMailFoldersHelper; + } + + /** + * @param string $sParent = '' + * @param string $sListPattern = '*' + * @param bool $bUseListSubscribeStatus = false + * @param int $iOptimizationLimit = 0 + * + * @return \MailSo\Mail\FolderCollection|false + */ + public function Folders($sParent = '', $sListPattern = '*', $bUseListSubscribeStatus = true, $iOptimizationLimit = 0) + { + $oFolderCollection = false; + + $aSubscribedFolders = null; + if ($bUseListSubscribeStatus) + { + try + { + $aSubscribedFolders = $this->oImapClient->FolderSubscribeList($sParent, $sListPattern); + } + catch (\Exception $oException) + { + unset($oException); + } + } + + $aImapSubscribedFoldersHelper = null; + if (\is_array($aSubscribedFolders)) + { + $aImapSubscribedFoldersHelper = array(); + foreach ($aSubscribedFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder) + { + $aImapSubscribedFoldersHelper[] = $oImapFolder->FullNameRaw(); + } + } + + $aFolders = $this->oImapClient->FolderList($sParent, $sListPattern); + + $bOptimized = false; + $aMailFoldersHelper = null; + + if (\is_array($aFolders)) + { + $aMailFoldersHelper = array(); + + foreach ($aFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder) + { + $aMailFoldersHelper[] = Folder::NewInstance($oImapFolder, + (null === $aImapSubscribedFoldersHelper || \in_array($oImapFolder->FullNameRaw(), $aImapSubscribedFoldersHelper)) || + $oImapFolder->IsInbox() + ); + } + + $iCount = \count($aMailFoldersHelper); + $aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit); + + $bOptimized = $iCount !== \count($aMailFoldersHelper); + } + + if (\is_array($aMailFoldersHelper)) + { + $oFolderCollection = FolderCollection::NewInstance(); + $oFolderCollection->InitByUnsortedMailFolderArray($aMailFoldersHelper); + + $oFolderCollection->Optimized = $bOptimized; + } + + if ($oFolderCollection) + { + $oFolderCollection->SortByCallback(function ($oFolderA, $oFolderB) { + $sA = \strtoupper($oFolderA->FullNameRaw()); + $sB = \strtoupper($oFolderB->FullNameRaw()); + switch (true) + { + case 'INBOX' === $sA: + return -1; + case 'INBOX' === $sB: + return 1; + case '[GMAIL]' === $sA: + return -1; + case '[GMAIL]' === $sB: + return 1; + } + + return \strnatcasecmp($oFolderA->FullName(), $oFolderB->FullName()); + }); + + $oNamespace = $this->oImapClient->GetNamespace(); + if ($oNamespace) + { + $oFolderCollection->SetNamespace($oNamespace->GetPersonalNamespace()); + } + + $oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported(); + } + + return $oFolderCollection; + } + + /** + * @param string $sFolderNameInUtf8 + * @param string $sFolderParentFullNameRaw = '' + * @param bool $bSubscribeOnCreation = true + * @param string $sDelimiter = '' + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function FolderCreate($sFolderNameInUtf8, $sFolderParentFullNameRaw = '', $bSubscribeOnCreation = true, $sDelimiter = '') + { + if (!\MailSo\Base\Validator::NotEmptyString($sFolderNameInUtf8, true) || + !\is_string($sFolderParentFullNameRaw)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $sFolderNameInUtf8 = \trim($sFolderNameInUtf8); + + if (0 === \strlen($sDelimiter) || 0 < \strlen(\trim($sFolderParentFullNameRaw))) + { + $aFolders = $this->oImapClient->FolderList('', 0 === \strlen(\trim($sFolderParentFullNameRaw)) ? 'INBOX' : $sFolderParentFullNameRaw); + if (!\is_array($aFolders) || !isset($aFolders[0])) + { + // TODO + throw new \MailSo\Mail\Exceptions\RuntimeException( + 0 === \strlen(trim($sFolderParentFullNameRaw)) + ? 'Cannot get folder delimiter' + : 'Cannot create folder in non-existen parent folder'); + } + + $sDelimiter = $aFolders[0]->Delimiter(); + if (0 < \strlen($sDelimiter) && 0 < \strlen(\trim($sFolderParentFullNameRaw))) + { + $sFolderParentFullNameRaw .= $sDelimiter; + } + } + + $sFullNameRawToCreate = \MailSo\Base\Utils::ConvertEncoding($sFolderNameInUtf8, + \MailSo\Base\Enumerations\Charset::UTF_8, + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP); + + if (0 < \strlen($sDelimiter) && false !== \strpos($sFullNameRawToCreate, $sDelimiter)) + { + // TODO + throw new \MailSo\Mail\Exceptions\RuntimeException( + 'New folder name contains delimiter'); + } + + $sFullNameRawToCreate = $sFolderParentFullNameRaw.$sFullNameRawToCreate; + + $this->oImapClient->FolderCreate($sFullNameRawToCreate); + + if ($bSubscribeOnCreation) + { + $this->oImapClient->FolderSubscribe($sFullNameRawToCreate); + } + + return $this; + } + + /** + * @param string $sPrevFolderFullNameRaw + * @param string $sNextFolderFullNameInUtf + * @param bool $bSubscribeOnMove = true + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function FolderMove($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, $bSubscribeOnMove = true) + { + return $this->folderModify($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, false, $bSubscribeOnMove); + } + + /** + * @param string $sPrevFolderFullNameRaw + * @param string $sNewTopFolderNameInUtf + * @param bool $bSubscribeOnRename = true + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function FolderRename($sPrevFolderFullNameRaw, $sNewTopFolderNameInUtf, $bSubscribeOnRename = true) + { + return $this->folderModify($sPrevFolderFullNameRaw, $sNewTopFolderNameInUtf, true, $bSubscribeOnRename); + } + + /** + * @param string $sPrevFolderFullNameRaw + * @param string $sNextFolderNameInUtf + * @param bool $bRenameOrMove + * @param bool $bSubscribeOnModify + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function folderModify($sPrevFolderFullNameRaw, $sNextFolderNameInUtf, $bRenameOrMove, $bSubscribeOnModify) + { + if (0 === \strlen($sPrevFolderFullNameRaw) || 0 === \strlen($sNextFolderNameInUtf)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $aFolders = $this->oImapClient->FolderList('', $sPrevFolderFullNameRaw); + if (!\is_array($aFolders) || !isset($aFolders[0])) + { + // TODO + throw new \MailSo\Mail\Exceptions\RuntimeException('Cannot rename non-existen folder'); + } + + $sDelimiter = $aFolders[0]->Delimiter(); + $iLast = \strrpos($sPrevFolderFullNameRaw, $sDelimiter); + + $mSubscribeFolders = null; + if ($bSubscribeOnModify) + { + $mSubscribeFolders = $this->oImapClient->FolderSubscribeList($sPrevFolderFullNameRaw, '*'); + if (\is_array($mSubscribeFolders) && 0 < count($mSubscribeFolders)) + { + foreach ($mSubscribeFolders as /* @var $oFolder \MailSo\Imap\Folder */ $oFolder) + { + $this->oImapClient->FolderUnSubscribe($oFolder->FullNameRaw()); + } + } + } + + $sNewFolderFullNameRaw = \MailSo\Base\Utils::ConvertEncoding($sNextFolderNameInUtf, + \MailSo\Base\Enumerations\Charset::UTF_8, + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP); + + if($bRenameOrMove) + { + if (0 < \strlen($sDelimiter) && false !== \strpos($sNewFolderFullNameRaw, $sDelimiter)) + { + // TODO + throw new \MailSo\Mail\Exceptions\RuntimeException('New folder name contains delimiter'); + } + + $sFolderParentFullNameRaw = false === $iLast ? '' : \substr($sPrevFolderFullNameRaw, 0, $iLast + 1); + $sNewFolderFullNameRaw = $sFolderParentFullNameRaw.$sNewFolderFullNameRaw; + } + + $this->oImapClient->FolderRename($sPrevFolderFullNameRaw, $sNewFolderFullNameRaw); + + if (\is_array($mSubscribeFolders) && 0 < count($mSubscribeFolders)) + { + foreach ($mSubscribeFolders as /* @var $oFolder \MailSo\Imap\Folder */ $oFolder) + { + $sFolderFullNameRawForResubscrine = $oFolder->FullNameRaw(); + if (0 === \strpos($sFolderFullNameRawForResubscrine, $sPrevFolderFullNameRaw)) + { + $sNewFolderFullNameRawForResubscrine = $sNewFolderFullNameRaw. + \substr($sFolderFullNameRawForResubscrine, \strlen($sPrevFolderFullNameRaw)); + + $this->oImapClient->FolderSubscribe($sNewFolderFullNameRawForResubscrine); + } + } + } + + return $this; + } + + /** + * @param string $sFolderFullNameRaw + * @param bool $bUnsubscribeOnDeletion = true + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Mail\Exceptions\RuntimeException + */ + public function FolderDelete($sFolderFullNameRaw, $bUnsubscribeOnDeletion = true) + { + if (0 === \strlen($sFolderFullNameRaw) || 'INBOX' === $sFolderFullNameRaw) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderExamine($sFolderFullNameRaw); + + $aIndexOrUids = $this->oImapClient->MessageSimpleSearch('ALL'); + if (0 < \count($aIndexOrUids)) + { + throw new \MailSo\Mail\Exceptions\NonEmptyFolder(); + } + + $this->oImapClient->FolderExamine('INBOX'); + + if ($bUnsubscribeOnDeletion) + { + $this->oImapClient->FolderUnSubscribe($sFolderFullNameRaw); + } + + $this->oImapClient->FolderDelete($sFolderFullNameRaw); + + return $this; + } + + /** + * @param string $sFolderFullNameRaw + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function FolderClear($sFolderFullNameRaw) + { + $this->oImapClient->FolderSelect($sFolderFullNameRaw); + + $oFolderInformation = $this->oImapClient->FolderCurrentInformation(); + if ($oFolderInformation && $oFolderInformation->Exists && 0 < $oFolderInformation->Exists) // STATUS? + { + $this->oImapClient->MessageStoreFlag('1:*', false, + array(\MailSo\Imap\Enumerations\MessageFlag::DELETED), + \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT + ); + + $this->oImapClient->MessageExpunge(); + } + + return $this; + } + + /** + * @param string $sFolderFullNameRaw + * @param bool $bSubscribe + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function FolderSubscribe($sFolderFullNameRaw, $bSubscribe) + { + if (0 === \strlen($sFolderFullNameRaw)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->{($bSubscribe) ? 'FolderSubscribe' : 'FolderUnSubscribe'}($sFolderFullNameRaw); + + return $this; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Mail\MailClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + if (!($oLogger instanceof \MailSo\Log\Logger)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oLogger = $oLogger; + $this->oImapClient->SetLogger($this->oLogger); + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Message.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Message.php new file mode 100644 index 0000000..9c1c525 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/Message.php @@ -0,0 +1,890 @@ +Clear(); + } + + /** + * @return \MailSo\Mail\Message + */ + public function Clear() + { + $this->sFolder = ''; + $this->iUid = 0; + $this->sSubject = ''; + $this->sMessageId = ''; + $this->sContentType = ''; + $this->iSize = 0; + $this->iInternalTimeStampInUTC = 0; + $this->iHeaderTimeStampInUTC = 0; + $this->sHeaderDate = ''; + $this->aFlags = array(); + $this->aFlagsLowerCase = array(); + + $this->oFrom = null; + $this->oSender = null; + $this->oReplyTo = null; + $this->oDeliveredTo = null; + $this->oTo = null; + $this->oCc = null; + $this->oBcc = null; + + $this->sPlain = ''; + $this->sHtml = ''; + + $this->oAttachments = null; + $this->aDraftInfo = null; + + $this->sInReplyTo = ''; + $this->sReferences = ''; + $this->aUnsubsribeLinks = array(); + + $this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::NOTHING; + $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL; + $this->sDeliveryReceipt = ''; + $this->sReadReceipt = ''; + + $this->aThreads = array(); + + $this->bTextPartIsTrimmed = false; + + $this->sPgpSignature = ''; + $this->bPgpSigned = false; + $this->bPgpEncrypted = false; + + return $this; + } + + /** + * @return \MailSo\Mail\Message + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return string + */ + public function Plain() + { + return $this->sPlain; + } + + /** + * @return string + */ + public function Html() + { + return $this->sHtml; + } + + /** + * @return string + */ + public function PgpSignature() + { + return $this->sPgpSignature; + } + + /** + * @return bool + */ + public function PgpSigned() + { + return $this->bPgpSigned; + } + + /** + * @return bool + */ + public function PgpEncrypted() + { + return $this->bPgpEncrypted; + } + + /** + * @param string $sHtml + * + * @retun void + */ + public function SetHtml($sHtml) + { + $this->sHtml = $sHtml; + } + + /** + * @return string + */ + public function Folder() + { + return $this->sFolder; + } + + /** + * @return int + */ + public function Uid() + { + return $this->iUid; + } + + /** + * @return string + */ + public function MessageId() + { + return $this->sMessageId; + } + + /** + * @return string + */ + public function Subject() + { + return $this->sSubject; + } + + /** + * @return string + */ + public function ContentType() + { + return $this->sContentType; + } + + /** + * @return int + */ + public function Size() + { + return $this->iSize; + } + + /** + * @return int + */ + public function InternalTimeStampInUTC() + { + return $this->iInternalTimeStampInUTC; + } + + /** + * @return int + */ + public function HeaderTimeStampInUTC() + { + return $this->iHeaderTimeStampInUTC; + } + + /** + * @return string + */ + public function HeaderDate() + { + return $this->sHeaderDate; + } + + /** + * @return array + */ + public function Flags() + { + return $this->aFlags; + } + + /** + * @return array + */ + public function FlagsLowerCase() + { + return $this->aFlagsLowerCase; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function From() + { + return $this->oFrom; + } + + /** + * @return int + */ + public function Sensitivity() + { + return $this->iSensitivity; + } + + /** + * @return int + */ + public function Priority() + { + return $this->iPriority; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function Sender() + { + return $this->oSender; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function ReplyTo() + { + return $this->oReplyTo; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function DeliveredTo() + { + return $this->oDeliveredTo; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function To() + { + return $this->oTo; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function Cc() + { + return $this->oCc; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function Bcc() + { + return $this->oBcc; + } + + /** + * @return \MailSo\Mail\AttachmentCollection + */ + public function Attachments() + { + return $this->oAttachments; + } + + /** + * @return string + */ + public function InReplyTo() + { + return $this->sInReplyTo; + } + + /** + * @return string + */ + public function References() + { + return $this->sReferences; + } + + /** + * @return string + */ + public function DeliveryReceipt() + { + return $this->sDeliveryReceipt; + } + + /** + * @return string + */ + public function ReadReceipt() + { + return $this->sReadReceipt; + } + + /** + * @return array + */ + public function UnsubsribeLinks() + { + return $this->aUnsubsribeLinks; + } + + /** + * @return string + */ + public function ReadingConfirmation() + { + return $this->ReadReceipt(); + } + + /** + * @return array | null + */ + public function DraftInfo() + { + return $this->aDraftInfo; + } + + /** + * @return array + */ + public function Threads() + { + return $this->aThreads; + } + + /** + * @param array $aThreads + */ + public function SetThreads($aThreads) + { + $this->aThreads = \is_array($aThreads) ? $aThreads : array(); + } + + /** + * @return boole + */ + public function TextPartIsTrimmed() + { + return $this->bTextPartIsTrimmed; + } + + /** + * @param string $sFolder + * @param \MailSo\Imap\FetchResponse $oFetchResponse + * @param \MailSo\Imap\BodyStructure $oBodyStructure = null + * + * @return \MailSo\Mail\Message + */ + public static function NewFetchResponseInstance($sFolder, $oFetchResponse, $oBodyStructure = null) + { + return self::NewInstance()->InitByFetchResponse($sFolder, $oFetchResponse, $oBodyStructure); + } + + /** + * @param string $sFolder + * @param \MailSo\Imap\FetchResponse $oFetchResponse + * @param \MailSo\Imap\BodyStructure $oBodyStructure = null + * + * @return \MailSo\Mail\Message + */ + public function InitByFetchResponse($sFolder, $oFetchResponse, $oBodyStructure = null) + { + if (!$oBodyStructure) + { + $oBodyStructure = $oFetchResponse->GetFetchBodyStructure(); + } + + $sUid = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID); + $sSize = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::RFC822_SIZE); + $sInternalDate = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INTERNALDATE); + $aFlags = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::FLAGS); + + $this->sFolder = $sFolder; + $this->iUid = \is_numeric($sUid) ? (int) $sUid : 0; + $this->iSize = \is_numeric($sSize) ? (int) $sSize : 0; + $this->aFlags = \is_array($aFlags) ? $aFlags : array(); + $this->aFlagsLowerCase = \array_map('strtolower', $this->aFlags); + + $this->iInternalTimeStampInUTC = + \MailSo\Base\DateTimeHelper::ParseInternalDateString($sInternalDate); + + $sCharset = $oBodyStructure ? $oBodyStructure->SearchCharset() : ''; + $sCharset = \MailSo\Base\Utils::NormalizeCharset($sCharset); + + $sHeaders = $oFetchResponse->GetHeaderFieldsValue(); + if (0 < \strlen($sHeaders)) + { + $oHeaders = \MailSo\Mime\HeaderCollection::NewInstance()->Parse($sHeaders, false, $sCharset); + + $sContentTypeCharset = $oHeaders->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::CHARSET + ); + + if (0 < \strlen($sContentTypeCharset)) + { + $sCharset = $sContentTypeCharset; + $sCharset = \MailSo\Base\Utils::NormalizeCharset($sCharset); + } + + if (0 < \strlen($sCharset)) + { + $oHeaders->SetParentCharset($sCharset); + } + + $bCharsetAutoDetect = 0 === \strlen($sCharset); + + $this->sSubject = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT, $bCharsetAutoDetect); + $this->sMessageId = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::MESSAGE_ID); + $this->sContentType = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE); + + $this->oFrom = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, $bCharsetAutoDetect); + $this->oTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::TO_, $bCharsetAutoDetect); + $this->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect); + $this->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect); + + $oHeaders->PopulateEmailColectionByDkim($this->oFrom); + + $this->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect); + $this->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect); + $this->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect); + + $this->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO); + $this->sReferences = \MailSo\Base\Utils::StripSpaces( + $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES)); + + $sHeaderDate = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE); + $this->sHeaderDate = $sHeaderDate; + $this->iHeaderTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sHeaderDate); + + // Sensitivity + $this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::NOTHING; + $sSensitivity = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SENSITIVITY); + switch (\strtolower($sSensitivity)) + { + case 'personal': + $this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::PERSONAL; + break; + case 'private': + $this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::PRIVATE_; + break; + case 'company-confidential': + $this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::CONFIDENTIAL; + break; + } + + // Priority + $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL; + $sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY); + if (0 === \strlen($sPriority)) + { + $sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IMPORTANCE); + } + if (0 === \strlen($sPriority)) + { + $sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_PRIORITY); + } + if (0 < \strlen($sPriority)) + { + switch (\str_replace(' ', '', \strtolower($sPriority))) + { + case 'high': + case '1(highest)': + case '2(high)': + case '1': + case '2': + $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::HIGH; + break; + + case 'low': + case '4(low)': + case '5(lowest)': + case '4': + case '5': + $this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::LOW; + break; + } + } + + // Delivery Receipt + $this->sDeliveryReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO)); + + // Read Receipt + $this->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO)); + if (empty($this->sReadReceipt)) + { + $this->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO)); + } + + //Unsubscribe links + $this->aUnsubsribeLinks = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE); + if (empty($this->aUnsubsribeLinks)) + { + $this->aUnsubsribeLinks = array(); + } + else + { + $this->aUnsubsribeLinks = explode(',', $this->aUnsubsribeLinks); + $this->aUnsubsribeLinks = array_map( + function ($link) { + return trim($link, ' <>'); + }, + $this->aUnsubsribeLinks + ); + } + + $sDraftInfo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_DRAFT_INFO); + if (0 < \strlen($sDraftInfo)) + { + $sType = ''; + $sFolder = ''; + $sUid = ''; + + \MailSo\Mime\ParameterCollection::NewInstance($sDraftInfo) + ->ForeachList(function ($oParameter) use (&$sType, &$sFolder, &$sUid) { + + switch (\strtolower($oParameter->Name())) + { + case 'type': + $sType = $oParameter->Value(); + break; + case 'uid': + $sUid = $oParameter->Value(); + break; + case 'folder': + $sFolder = \base64_decode($oParameter->Value()); + break; + } + }) + ; + + if (0 < \strlen($sType) && 0 < \strlen($sFolder) && 0 < \strlen($sUid)) + { + $this->aDraftInfo = array($sType, $sUid, $sFolder); + } + } + } + else if ($oFetchResponse->GetEnvelope()) + { + if (0 === \strlen($sCharset) && $oBodyStructure) + { + $sCharset = $oBodyStructure->SearchCharset(); + $sCharset = \MailSo\Base\Utils::NormalizeCharset($sCharset); + } + + if (0 === \strlen($sCharset)) + { + $sCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1; + } + + // date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to, message-id + $this->sMessageId = $oFetchResponse->GetFetchEnvelopeValue(9, ''); + $this->sSubject = \MailSo\Base\Utils::DecodeHeaderValue($oFetchResponse->GetFetchEnvelopeValue(1, ''), $sCharset); + + $this->oFrom = $oFetchResponse->GetFetchEnvelopeEmailCollection(2, $sCharset); + $this->oSender = $oFetchResponse->GetFetchEnvelopeEmailCollection(3, $sCharset); + $this->oReplyTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(4, $sCharset); + $this->oTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(5, $sCharset); + $this->oCc = $oFetchResponse->GetFetchEnvelopeEmailCollection(6, $sCharset); + $this->oBcc = $oFetchResponse->GetFetchEnvelopeEmailCollection(7, $sCharset); + $this->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, ''); + } + + $aTextParts = $oBodyStructure ? $oBodyStructure->SearchHtmlOrPlainParts() : null; + if (\is_array($aTextParts) && 0 < \count($aTextParts)) + { + if (0 === \strlen($sCharset)) + { + $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8; + } + + $aHtmlParts = array(); + $aPlainParts = array(); + + foreach ($aTextParts as $oPart) + { + $sText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$oPart->PartID().']'); + if (null === $sText) + { + $sText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$oPart->PartID().']<0>'); + if (\is_string($sText) && 0 < \strlen($sText)) + { + $this->bTextPartIsTrimmed = true; + } + } + + if (\is_string($sText) && 0 < \strlen($sText)) + { + $sTextCharset = $oPart->Charset(); + if (empty($sTextCharset)) + { + $sTextCharset = $sCharset; + } + + $sTextCharset = \MailSo\Base\Utils::NormalizeCharset($sTextCharset, true); + + $sText = \MailSo\Base\Utils::DecodeEncodingValue($sText, $oPart->MailEncodingName()); + $sText = \MailSo\Base\Utils::ConvertEncoding($sText, $sTextCharset, \MailSo\Base\Enumerations\Charset::UTF_8); + $sText = \MailSo\Base\Utils::Utf8Clear($sText); + + if ('text/html' === $oPart->ContentType()) + { + $aHtmlParts[] = $sText; + } + else + { + if ($oPart->IsFlowedFormat()) + { + $sText = \MailSo\Base\Utils::DecodeFlowedFormat($sText); + } + + $aPlainParts[] = $sText; + } + } + } + + if (0 < \count($aHtmlParts)) + { + $this->sHtml = \implode('
', $aHtmlParts); + } + else + { + $this->sPlain = \trim(\implode("\n", $aPlainParts)); + } + + $aMatch = array(); + if (\preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0])) + { + $this->sPgpSignature = \trim($aMatch[0]); + $this->bPgpSigned = true; + } + + $aMatch = array(); + if (\preg_match('/-----BEGIN PGP MESSAGE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0])) + { + $this->bPgpEncrypted = true; + } + + unset($aHtmlParts, $aPlainParts, $aMatch); + } + +// if (empty($this->sPgpSignature) && 'multipart/signed' === \strtolower($this->sContentType) && +// 'application/pgp-signature' === \strtolower($oHeaders->ParameterValue( +// \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, +// \MailSo\Mime\Enumerations\Parameter::PROTOCOL +// ))) +// { +// $aPgpSignatureParts = $oBodyStructure ? $oBodyStructure->SearchByContentType('application/pgp-signature') : null; +// if (\is_array($aPgpSignatureParts) && 0 < \count($aPgpSignatureParts) && isset($aPgpSignatureParts[0])) +// { +// $sPgpSignatureText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$aPgpSignatureParts[0]->PartID().']'); +// if (\is_string($sPgpSignatureText) && 0 < \strlen($sPgpSignatureText) && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) +// { +// $this->sPgpSignature = \trim($sPgpSignatureText); +// $this->bPgpSigned = true; +// } +// } +// } + + if ($oBodyStructure) + { + $aAttachmentsParts = $oBodyStructure->SearchAttachmentsParts(); + if ($aAttachmentsParts && 0 < count($aAttachmentsParts)) + { + $this->oAttachments = AttachmentCollection::NewInstance(); + foreach ($aAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem) + { + $this->oAttachments->Add( + \MailSo\Mail\Attachment::NewBodyStructureInstance($this->sFolder, $this->iUid, $oAttachmentItem) + ); + } + } + } + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/MessageCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/MessageCollection.php new file mode 100644 index 0000000..427a18c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mail/MessageCollection.php @@ -0,0 +1,123 @@ +Clear(); + } + + /** + * @return \MailSo\Mail\MessageCollection + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return \MailSo\Mail\MessageCollection + */ + public function Clear() + { + parent::Clear(); + + $this->FolderHash = ''; + + $this->MessageCount = 0; + $this->MessageUnseenCount = 0; + $this->MessageResultCount = 0; + + $this->FolderName = ''; + $this->Offset = 0; + $this->Limit = 0; + $this->Search = ''; + $this->UidNext = ''; + $this->ThreadUid = ''; + $this->NewMessages = array(); + + $this->Filtered = false; + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/MailSo.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/MailSo.php new file mode 100644 index 0000000..aa294dc --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/MailSo.php @@ -0,0 +1,40 @@ +rResource = $rResource; + $this->sFileName = $sFileName; + $this->iFileSize = $iFileSize; + $this->bIsInline = $bIsInline; + $this->bIsLinked = $bIsLinked; + $this->sCID = $sCID; + $this->aCustomContentTypeParams = $aCustomContentTypeParams; + $this->sContentLocation = $sContentLocation; + } + + /** + * @param resource $rResource + * @param string $sFileName = '' + * @param int $iFileSize = 0 + * @param bool $bIsInline = false + * @param bool $bIsLinked = false + * @param string $sCID = '' + * @param array $aCustomContentTypeParams = array() + * @param string $sContentLocation = '' + * + * @return \MailSo\Mime\Attachment + */ + public static function NewInstance($rResource, $sFileName = '', $iFileSize = 0, $bIsInline = false, + $bIsLinked = false, $sCID = '', $aCustomContentTypeParams = array(), $sContentLocation = '') + { + return new self($rResource, $sFileName, $iFileSize, $bIsInline, $bIsLinked, $sCID, $aCustomContentTypeParams, $sContentLocation); + } + + /** + * @return resource + */ + public function Resource() + { + return $this->rResource; + } + + /** + * @return string + */ + public function ContentType() + { + return \MailSo\Base\Utils::MimeContentType($this->sFileName); + } + + /** + * @return array + */ + public function CustomContentTypeParams() + { + return $this->aCustomContentTypeParams; + } + + /** + * @return string + */ + public function CID() + { + return $this->sCID; + } + + /** + * @return string + */ + public function ContentLocation() + { + return $this->sContentLocation; + } + + /** + * @return string + */ + public function FileName() + { + return $this->sFileName; + } + + /** + * @return int + */ + public function FileSize() + { + return $this->iFileSize; + } + + /** + * @return bool + */ + public function IsInline() + { + return $this->bIsInline; + } + + /** + * @return bool + */ + public function IsImage() + { + return 'image' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsArchive() + { + return 'archive' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsPdf() + { + return 'pdf' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsDoc() + { + return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName()); + } + + /** + * @return bool + */ + public function IsLinked() + { + return $this->bIsLinked && 0 < \strlen($this->sCID); + } +} \ No newline at end of file diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/AttachmentCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/AttachmentCollection.php new file mode 100644 index 0000000..68376fd --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/AttachmentCollection.php @@ -0,0 +1,71 @@ +FilterList(function ($oItem) { + return $oItem && $oItem->IsLinked(); + }); + } + + /** + * @return array + */ + public function UnlinkedAttachments() + { + return $this->FilterList(function ($oItem) { + return $oItem && !$oItem->IsLinked(); + }); + } + + /** + * @return int + */ + public function SizeOfAttachments() + { + $iResult = 0; + $this->ForeachList(function ($oItem) use (&$iResult) { + if ($oItem) + { + $iResult += $oItem->FileSize(); + } + }); + + return $iResult; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Email.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Email.php new file mode 100644 index 0000000..6347f0f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Email.php @@ -0,0 +1,315 @@ +sEmail = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sEmail), true); + + $this->sDisplayName = \MailSo\Base\Utils::Trim($sDisplayName); + + $this->sDkimStatus = \MailSo\Mime\Enumerations\DkimStatus::NONE; + $this->sDkimValue = ''; + } + + /** + * @param string $sEmail + * @param string $sDisplayName = '' + * + * @return \MailSo\Mime\Email + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function NewInstance($sEmail, $sDisplayName = '') + { + return new self($sEmail, $sDisplayName); + } + + /** + * @param string $sEmailAddress + * @return \MailSo\Mime\Email + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function Parse($sEmailAddress) + { + $sEmailAddress = \MailSo\Base\Utils::Trim($sEmailAddress); + if (!\MailSo\Base\Validator::NotEmptyString($sEmailAddress, true)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $sName = ''; + $sEmail = ''; + $sComment = ''; + + $bInName = false; + $bInAddress = false; + $bInComment = false; + + $iStartIndex = 0; + $iEndIndex = 0; + $iCurrentIndex = 0; + + while ($iCurrentIndex < \strlen($sEmailAddress)) + { + switch ($sEmailAddress{$iCurrentIndex}) + { +// case '\'': + case '"': +// $sQuoteChar = $sEmailAddress{$iCurrentIndex}; + if ((!$bInName) && (!$bInAddress) && (!$bInComment)) + { + $bInName = true; + $iStartIndex = $iCurrentIndex; + } + else if ((!$bInAddress) && (!$bInComment)) + { + $iEndIndex = $iCurrentIndex; + $sName = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); + $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1); + $iEndIndex = 0; + $iCurrentIndex = 0; + $iStartIndex = 0; + $bInName = false; + } + break; + case '<': + if ((!$bInName) && (!$bInAddress) && (!$bInComment)) + { + if ($iCurrentIndex > 0 && \strlen($sName) === 0) + { + $sName = \substr($sEmailAddress, 0, $iCurrentIndex); + } + + $bInAddress = true; + $iStartIndex = $iCurrentIndex; + } + break; + case '>': + if ($bInAddress) + { + $iEndIndex = $iCurrentIndex; + $sEmail = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); + $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1); + $iEndIndex = 0; + $iCurrentIndex = 0; + $iStartIndex = 0; + $bInAddress = false; + } + break; + case '(': + if ((!$bInName) && (!$bInAddress) && (!$bInComment)) + { + $bInComment = true; + $iStartIndex = $iCurrentIndex; + } + break; + case ')': + if ($bInComment) + { + $iEndIndex = $iCurrentIndex; + $sComment = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); + $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1); + $iEndIndex = 0; + $iCurrentIndex = 0; + $iStartIndex = 0; + $bInComment = false; + } + break; + case '\\': + $iCurrentIndex++; + break; + } + + $iCurrentIndex++; + } + + if (\strlen($sEmail) === 0) + { + $aRegs = array(''); + if (\preg_match('/[^@\s]+@\S+/i', $sEmailAddress, $aRegs) && isset($aRegs[0])) + { + $sEmail = $aRegs[0]; + } + else + { + $sName = $sEmailAddress; + } + } + + if ((\strlen($sEmail) > 0) && (\strlen($sName) == 0) && (\strlen($sComment) == 0)) + { + $sName = \str_replace($sEmail, '', $sEmailAddress); + } + + $sEmail = \trim(\trim($sEmail), '<>'); + $sEmail = \rtrim(\trim($sEmail), '.'); + $sEmail = \trim($sEmail); + + $sName = \trim(\trim($sName), '"'); + $sName = \trim($sName, '\''); + $sComment = \trim(\trim($sComment), '()'); + + // Remove backslash + $sName = \preg_replace('/\\\\(.)/s', '$1', $sName); + $sComment = \preg_replace('/\\\\(.)/s', '$1', $sComment); + + return Email::NewInstance($sEmail, $sName); + } + + /** + * @param bool $bIdn = false + * + * @return string + */ + public function GetEmail($bIdn = false) + { + return $bIdn ? \MailSo\Base\Utils::IdnToUtf8($this->sEmail) : $this->sEmail; + } + + /** + * @return string + */ + public function GetDisplayName() + { + return $this->sDisplayName; + } + + /** + * @return string + */ + public function GetDkimStatus() + { + return $this->sDkimStatus; + } + + /** + * @return string + */ + public function GetDkimValue() + { + return $this->sDkimValue; + } + + /** + * @return string + */ + public function GetAccountName() + { + return \MailSo\Base\Utils::GetAccountNameFromEmail($this->GetEmail(false)); + } + + /** + * @param bool $bIdn = false + * + * @return string + */ + public function GetDomain($bIdn = false) + { + return \MailSo\Base\Utils::GetDomainFromEmail($this->GetEmail($bIdn)); + } + + /** + * @param string $sDkimStatus + * @param string $sDkimValue = '' + */ + public function SetDkimStatusAndValue($sDkimStatus, $sDkimValue = '') + { + $this->sDkimStatus = \MailSo\Mime\Enumerations\DkimStatus::normalizeValue($sDkimStatus); + $this->sDkimValue = $sDkimValue; + } + + /** + * @param bool $bIdn = false + * @param bool $bDkim = true + * + * @return array + */ + public function ToArray($bIdn = false, $bDkim = true) + { + return $bDkim ? + array($this->sDisplayName, $this->GetEmail($bIdn), $this->sDkimStatus, $this->sDkimValue) : + array($this->sDisplayName, $this->GetEmail($bIdn)); + } + + /** + * @param bool $bConvertSpecialsName = false + * @param bool $bIdn = false + * + * @return string + */ + public function ToString($bConvertSpecialsName = false, $bIdn = false) + { + $sReturn = ''; + + $sDisplayName = \str_replace('"', '\"', $this->sDisplayName); + if ($bConvertSpecialsName) + { + $sDisplayName = 0 === \strlen($sDisplayName) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( + \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, + $sDisplayName); + } + + $sDisplayName = 0 === \strlen($sDisplayName) ? '' : '"'.$sDisplayName.'"'; + if (0 < \strlen($this->sEmail)) + { + $sReturn = $this->GetEmail($bIdn); + if (0 < \strlen($sDisplayName)) + { + $sReturn = $sDisplayName.' <'.$sReturn.'>'; + } + } + + return \trim($sReturn); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/EmailCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/EmailCollection.php new file mode 100644 index 0000000..53e5db3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/EmailCollection.php @@ -0,0 +1,243 @@ +parseEmailAddresses($sEmailAddresses); + } + } + + /** + * @param string $sEmailAddresses = '' + * + * @return \MailSo\Mime\EmailCollection + */ + public static function NewInstance($sEmailAddresses = '') + { + return new self($sEmailAddresses); + } + + /** + * @param string $sEmailAddresses + * + * @return \MailSo\Mime\EmailCollection + */ + public static function Parse($sEmailAddresses) + { + return self::NewInstance($sEmailAddresses); + } + + /** + * @return array + */ + public function ToArray() + { + $aReturn = $aEmails = array(); + $aEmails =& $this->GetAsArray(); + foreach ($aEmails as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) + { + $aReturn[] = $oEmail->ToArray(); + } + + return $aReturn; + } + + /** + * @param \MailSo\Mime\EmailCollection $oEmails + * + * @return \MailSo\Mime\EmailCollection + */ + public function MergeWithOtherCollection(\MailSo\Mime\EmailCollection $oEmails) + { + $aEmails =& $oEmails->GetAsArray(); + foreach ($aEmails as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) + { + $this->Add($oEmail); + } + + return $this; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function Unique() + { + $aCache = array(); + $aReturn = array(); + + $aEmails =& $this->GetAsArray(); + foreach ($aEmails as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) + { + $sEmail = $oEmail->GetEmail(); + if (!isset($aCache[$sEmail])) + { + $aCache[$sEmail] = true; + $aReturn[] = $oEmail; + } + } + + $this->SetAsArray($aReturn); + + return $this; + } + + /** + * @param bool $bConvertSpecialsName = false + * @param bool $bIdn = false + * + * @return string + */ + public function ToString($bConvertSpecialsName = false, $bIdn = false) + { + $aReturn = $aEmails = array(); + $aEmails =& $this->GetAsArray(); + foreach ($aEmails as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) + { + $aReturn[] = $oEmail->ToString($bConvertSpecialsName, $bIdn); + } + + return \implode(', ', $aReturn); + } + + /** + * @param string $sRawEmails + * + * @return \MailSo\Mime\EmailCollection + */ + private function parseEmailAddresses($sRawEmails) + { + $this->Clear(); + + $sWorkingRecipients = \trim($sRawEmails); + + if (0 === \strlen($sWorkingRecipients)) + { + return $this; + } + + $iEmailStartPos = 0; + $iEmailEndPos = 0; + + $bIsInQuotes = false; + $sChQuote = '"'; + $bIsInAngleBrackets = false; + $bIsInBrackets = false; + + $iCurrentPos = 0; + + $sWorkingRecipientsLen = \strlen($sWorkingRecipients); + + while ($iCurrentPos < $sWorkingRecipientsLen) + { + switch ($sWorkingRecipients{$iCurrentPos}) + { + case '\'': + case '"': + if (!$bIsInQuotes) + { + $sChQuote = $sWorkingRecipients{$iCurrentPos}; + $bIsInQuotes = true; + } + else if ($sChQuote == $sWorkingRecipients{$iCurrentPos}) + { + $bIsInQuotes = false; + } + break; + + case '<': + if (!$bIsInAngleBrackets) + { + $bIsInAngleBrackets = true; + if ($bIsInQuotes) + { + $bIsInQuotes = false; + } + } + break; + + case '>': + if ($bIsInAngleBrackets) + { + $bIsInAngleBrackets = false; + } + break; + + case '(': + if (!$bIsInBrackets) + { + $bIsInBrackets = true; + } + break; + + case ')': + if ($bIsInBrackets) + { + $bIsInBrackets = false; + } + break; + + case ',': + case ';': + if (!$bIsInAngleBrackets && !$bIsInBrackets && !$bIsInQuotes) + { + $iEmailEndPos = $iCurrentPos; + + try + { + $this->Add( + \MailSo\Mime\Email::Parse(\substr($sWorkingRecipients, $iEmailStartPos, $iEmailEndPos - $iEmailStartPos)) + ); + + $iEmailStartPos = $iCurrentPos + 1; + } + catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) + { + } + } + break; + } + + $iCurrentPos++; + } + + if ($iEmailStartPos < $iCurrentPos) + { + try + { + $this->Add( + \MailSo\Mime\Email::Parse(\substr($sWorkingRecipients, $iEmailStartPos, $iCurrentPos - $iEmailStartPos)) + ); + } + catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) {} + } + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/EmailDep.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/EmailDep.php new file mode 100644 index 0000000..7a923fa --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/EmailDep.php @@ -0,0 +1,339 @@ +sEmail = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sEmail), true); + + $this->sDisplayName = \MailSo\Base\Utils::Trim($sDisplayName); + $this->sRemark = \MailSo\Base\Utils::Trim($sRemark); + + $this->sDkimStatus = \MailSo\Mime\Enumerations\DkimStatus::NONE; + $this->sDkimValue = ''; + } + + /** + * @param string $sEmail + * @param string $sDisplayName = '' + * @param string $sRemark = '' + * + * @return \MailSo\Mime\Email + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function NewInstance($sEmail, $sDisplayName = '', $sRemark = '') + { + return new self($sEmail, $sDisplayName, $sRemark); + } + + /** + * @param string $sEmailAddress + * @return \MailSo\Mime\Email + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public static function Parse($sEmailAddress) + { + $sEmailAddress = \MailSo\Base\Utils::Trim($sEmailAddress); + if (!\MailSo\Base\Validator::NotEmptyString($sEmailAddress, true)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $sName = ''; + $sEmail = ''; + $sComment = ''; + + $bInName = false; + $bInAddress = false; + $bInComment = false; + + $iStartIndex = 0; + $iEndIndex = 0; + $iCurrentIndex = 0; + + while ($iCurrentIndex < \strlen($sEmailAddress)) + { + switch ($sEmailAddress{$iCurrentIndex}) + { +// case '\'': + case '"': +// $sQuoteChar = $sEmailAddress{$iCurrentIndex}; + if ((!$bInName) && (!$bInAddress) && (!$bInComment)) + { + $bInName = true; + $iStartIndex = $iCurrentIndex; + } + else if ((!$bInAddress) && (!$bInComment)) + { + $iEndIndex = $iCurrentIndex; + $sName = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); + $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1); + $iEndIndex = 0; + $iCurrentIndex = 0; + $iStartIndex = 0; + $bInName = false; + } + break; + case '<': + if ((!$bInName) && (!$bInAddress) && (!$bInComment)) + { + if ($iCurrentIndex > 0 && \strlen($sName) === 0) + { + $sName = \substr($sEmailAddress, 0, $iCurrentIndex); + } + + $bInAddress = true; + $iStartIndex = $iCurrentIndex; + } + break; + case '>': + if ($bInAddress) + { + $iEndIndex = $iCurrentIndex; + $sEmail = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); + $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1); + $iEndIndex = 0; + $iCurrentIndex = 0; + $iStartIndex = 0; + $bInAddress = false; + } + break; + case '(': + if ((!$bInName) && (!$bInAddress) && (!$bInComment)) + { + $bInComment = true; + $iStartIndex = $iCurrentIndex; + } + break; + case ')': + if ($bInComment) + { + $iEndIndex = $iCurrentIndex; + $sComment = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1); + $sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1); + $iEndIndex = 0; + $iCurrentIndex = 0; + $iStartIndex = 0; + $bInComment = false; + } + break; + case '\\': + $iCurrentIndex++; + break; + } + + $iCurrentIndex++; + } + + if (\strlen($sEmail) === 0) + { + $aRegs = array(''); + if (\preg_match('/[^@\s]+@\S+/i', $sEmailAddress, $aRegs) && isset($aRegs[0])) + { + $sEmail = $aRegs[0]; + } + else + { + $sName = $sEmailAddress; + } + } + + if ((\strlen($sEmail) > 0) && (\strlen($sName) == 0) && (\strlen($sComment) == 0)) + { + $sName = \str_replace($sEmail, '', $sEmailAddress); + } + + $sEmail = \trim(\trim($sEmail), '<>'); + $sEmail = \rtrim(\trim($sEmail), '.'); + $sEmail = \trim($sEmail); + + $sName = \trim(\trim($sName), '"'); + $sName = \trim($sName, '\''); + $sComment = \trim(\trim($sComment), '()'); + + // Remove backslash + $sName = \preg_replace('/\\\\(.)/s', '$1', $sName); + $sComment = \preg_replace('/\\\\(.)/s', '$1', $sComment); + + return Email::NewInstance($sEmail, $sName, $sComment); + } + + /** + * @param bool $bIdn = false + * + * @return string + */ + public function GetEmail($bIdn = false) + { + return $bIdn ? \MailSo\Base\Utils::IdnToUtf8($this->sEmail) : $this->sEmail; + } + + /** + * @return string + */ + public function GetDisplayName() + { + return $this->sDisplayName; + } + + /** + * @return string + */ + public function GetRemark() + { + return $this->sRemark; + } + + /** + * @return string + */ + public function GetDkimStatus() + { + return $this->sDkimStatus; + } + + /** + * @return string + */ + public function GetDkimValue() + { + return $this->sDkimValue; + } + + /** + * @return string + */ + public function GetAccountName() + { + return \MailSo\Base\Utils::GetAccountNameFromEmail($this->GetEmail(false)); + } + + /** + * @param bool $bIdn = false + * + * @return string + */ + public function GetDomain($bIdn = false) + { + return \MailSo\Base\Utils::GetDomainFromEmail($this->GetEmail($bIdn)); + } + + /** + * @param string $sDkimStatus + * @param string $sDkimValue = '' + */ + public function SetDkimStatusAndValue($sDkimStatus, $sDkimValue = '') + { + $this->sDkimStatus = \MailSo\Mime\Enumerations\DkimStatus::normalizeValue($sDkimStatus); + $this->sDkimValue = $sDkimValue; + } + + /** + * @param bool $bIdn = false + * @param bool $bDkim = true + * + * @return array + */ + public function ToArray($bIdn = false, $bDkim = true) + { + return $bDkim ? array($this->sDisplayName, $this->GetEmail($bIdn), $this->sRemark, $this->sDkimStatus, $this->sDkimValue) : + array($this->sDisplayName, $this->GetEmail($bIdn), $this->sRemark); + } + + /** + * @param bool $bConvertSpecialsName = false + * @param bool $bIdn = false + * + * @return string + */ + public function ToString($bConvertSpecialsName = false, $bIdn = false) + { + $sReturn = ''; + + $sRemark = \str_replace(')', '\)', $this->sRemark); + $sDisplayName = \str_replace('"', '\"', $this->sDisplayName); + + if ($bConvertSpecialsName) + { + $sDisplayName = 0 === \strlen($sDisplayName) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( + \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, + $sDisplayName); + + $sRemark = 0 === \strlen($sRemark) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( + \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, + $sRemark); + } + + $sDisplayName = 0 === \strlen($sDisplayName) ? '' : '"'.$sDisplayName.'"'; + $sRemark = 0 === \strlen($sRemark) ? '' : '('.$sRemark.')'; + + if (0 < \strlen($this->sEmail)) + { + $sReturn = $this->GetEmail($bIdn); + if (0 < \strlen($sDisplayName.$sRemark)) + { + $sReturn = $sDisplayName.' <'.$sReturn.'> '.$sRemark; + } + } + + return \trim($sReturn); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Enumerations/Constants.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Enumerations/Constants.php new file mode 100644 index 0000000..41da581 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Enumerations/Constants.php @@ -0,0 +1,25 @@ +sParentCharset = $sParentCharset; + + $this->initInputData($sName, $sValue, $sEncodedValueForReparse); + } + + /** + * @param string $sName + * @param string $sValue + * @param string $sEncodedValueForReparse + * + * @return void + */ + private function initInputData($sName, $sValue, $sEncodedValueForReparse) + { + $this->sName = trim($sName); + $this->sFullValue = trim($sValue); + $this->sEncodedValueForReparse = ''; + + $this->oParameters = null; + if (0 < \strlen($sEncodedValueForReparse) && $this->IsReparsed()) + { + $this->sEncodedValueForReparse = \trim($sEncodedValueForReparse); + } + + if (0 < \strlen($this->sFullValue) && $this->IsParameterized()) + { + $aRawExplode = \explode(';', $this->sFullValue, 2); + if (2 === \count($aRawExplode)) + { + $this->sValue = $aRawExplode[0]; + $this->oParameters = + \MailSo\Mime\ParameterCollection::NewInstance($aRawExplode[1]); + } + else + { + $this->sValue = $this->sFullValue; + } + } + else + { + $this->sValue = $this->sFullValue; + } + } + + /** + * @param string $sName + * @param string $sValue = '' + * @param string $sEncodedValueForReparse = '' + * @param string $sParentCharset = '' + * + * @return \MailSo\Mime\Header + */ + public static function NewInstance($sName, $sValue = '', $sEncodedValueForReparse = '', $sParentCharset = '') + { + return new self($sName, $sValue, $sEncodedValueForReparse, $sParentCharset); + } + + /** + * @param string $sEncodedLines + * @param string $sIncomingCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1 + * + * @return \MailSo\Mime\Header | false + */ + public static function NewInstanceFromEncodedString($sEncodedLines, $sIncomingCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1) + { + if (empty($sIncomingCharset)) + { + $sIncomingCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1; + } + + $aParts = \explode(':', \str_replace("\r", '', $sEncodedLines), 2); + if (isset($aParts[0]) && isset($aParts[1]) && 0 < \strlen($aParts[0]) && 0 < \strlen($aParts[1])) + { + return self::NewInstance( + \trim($aParts[0]), + \trim(\MailSo\Base\Utils::DecodeHeaderValue(\trim($aParts[1]), $sIncomingCharset)), + \trim($aParts[1]), + $sIncomingCharset + ); + } + + return false; + } + + /** + * @return string + */ + public function Name() + { + return $this->sName; + } + + /** + * @return string + */ + public function NameWithDelimitrom() + { + return $this->Name().': '; + } + + /** + * @return string + */ + public function Value() + { + return $this->sValue; + } + + /** + * @return string + */ + public function FullValue() + { + return $this->sFullValue; + } + + /** + * @param string $sParentCharset + * @return \MailSo\Mime\Header + */ + public function SetParentCharset($sParentCharset) + { + if ($this->sParentCharset !== $sParentCharset && $this->IsReparsed() && 0 < \strlen($this->sEncodedValueForReparse)) + { + $this->initInputData( + $this->sName, + \trim(\MailSo\Base\Utils::DecodeHeaderValue($this->sEncodedValueForReparse, $sParentCharset)), + $this->sEncodedValueForReparse + ); + } + + $this->sParentCharset = $sParentCharset; + + return $this; + } + + /** + * @return \MailSo\Mime\ParameterCollection | null + */ + public function Parameters() + { + return $this->oParameters; + } + + /** + * @param string $sValue + * @return string + */ + private function wordWrapHelper($sValue, $sGlue = "\r\n ") + { + return \trim(substr(wordwrap($this->NameWithDelimitrom().$sValue, + \MailSo\Mime\Enumerations\Constants::LINE_LENGTH, $sGlue + ), \strlen($this->NameWithDelimitrom()))); + } + + /** + * @return string + */ + public function EncodedValue() + { + $sResult = $this->sFullValue; + + if ($this->IsSubject()) + { + if (!\MailSo\Base\Utils::IsAscii($sResult) && + \MailSo\Base\Utils::IsIconvSupported() && + \function_exists('iconv_mime_encode')) + { + $aPreferences = array( +// 'scheme' => \MailSo\Base\Enumerations\Encoding::QUOTED_PRINTABLE_SHORT, + 'scheme' => \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, + 'input-charset' => \MailSo\Base\Enumerations\Charset::UTF_8, + 'output-charset' => \MailSo\Base\Enumerations\Charset::UTF_8, + 'line-length' => \MailSo\Mime\Enumerations\Constants::LINE_LENGTH, + 'line-break-chars' => \MailSo\Mime\Enumerations\Constants::CRLF + ); + + return \iconv_mime_encode($this->Name(), $sResult, $aPreferences); + } + } + else if ($this->IsParameterized() && $this->oParameters && 0 < $this->oParameters->Count()) + { + $sResult = $this->sValue.'; '.$this->oParameters->ToString(true); + } + else if ($this->IsEmail()) + { + $oEmailCollection = \MailSo\Mime\EmailCollection::NewInstance($this->sFullValue); + if ($oEmailCollection && 0 < $oEmailCollection->Count()) + { + $sResult = $oEmailCollection->ToString(true, false); + } + } + + return $this->NameWithDelimitrom().$this->wordWrapHelper($sResult); + } + + /** + * @return bool + */ + public function IsSubject() + { + return \strtolower(\MailSo\Mime\Enumerations\Header::SUBJECT) === \strtolower($this->Name()); + } + + /** + * @return bool + */ + public function IsParameterized() + { + return \in_array(\strtolower($this->sName), array( + \strtolower(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE), + \strtolower(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION) + )); + } + + /** + * @return bool + */ + public function IsEmail() + { + return \in_array(\strtolower($this->sName), array( + \strtolower(\MailSo\Mime\Enumerations\Header::FROM_), + \strtolower(\MailSo\Mime\Enumerations\Header::TO_), + \strtolower(\MailSo\Mime\Enumerations\Header::CC), + \strtolower(\MailSo\Mime\Enumerations\Header::BCC), + \strtolower(\MailSo\Mime\Enumerations\Header::REPLY_TO), + \strtolower(\MailSo\Mime\Enumerations\Header::RETURN_PATH), + \strtolower(\MailSo\Mime\Enumerations\Header::SENDER) + )); + } + + /** + * @return string + */ + public function ValueWithCharsetAutoDetect() + { + $sValue = $this->Value(); + if (!\MailSo\Base\Utils::IsAscii($sValue) && + 0 < \strlen($this->sEncodedValueForReparse) && + !\MailSo\Base\Utils::IsAscii($this->sEncodedValueForReparse)) + { + $sValueCharset = \MailSo\Base\Utils::CharsetDetect($this->sEncodedValueForReparse); + if (0 < \strlen($sValueCharset)) + { + $this->SetParentCharset($sValueCharset); + $sValue = $this->Value(); + } + } + + return $sValue; + } + + /** + * @return bool + */ + public function IsReparsed() + { + return $this->IsEmail() || $this->IsSubject() || $this->IsParameterized(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/HeaderCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/HeaderCollection.php new file mode 100644 index 0000000..f5e36d4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/HeaderCollection.php @@ -0,0 +1,480 @@ +sRawHeaders = ''; + $this->sParentCharset = ''; + + if (0 < \strlen($sRawHeaders)) + { + $this->Parse($sRawHeaders, $bStoreRawHeaders); + } + } + + /** + * @param string $sRawHeaders = '' + * @param bool $bStoreRawHeaders = true + * + * @return \MailSo\Mime\HeaderCollection + */ + public static function NewInstance($sRawHeaders = '', $bStoreRawHeaders = true) + { + return new self($sRawHeaders, $bStoreRawHeaders); + } + + /** + * @param string $sName + * @param string $sValue + * @param bool $bToTop = false + * + * @return \MailSo\Mime\HeaderCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function AddByName($sName, $sValue, $bToTop = false) + { + return $this->Add(Header::NewInstance($sName, $sValue), $bToTop); + } + + /** + * @param string $sName + * @param string $sValue + * @param bool $bToTop = false + * + * @return \MailSo\Mime\HeaderCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetByName($sName, $sValue, $bToTop = false) + { + return $this->RemoveByName($sName)->Add(Header::NewInstance($sName, $sValue), $bToTop); + } + + /** + * @return \MailSo\Mime\Header | null + */ + public function &GetByIndex($iIndex) + { + $mResult = null; + $mResult =& parent::GetByIndex($iIndex); + return $mResult; + } + + /** + * @param string $sHeaderName + * @param bool $bCharsetAutoDetect = false + * @return string + */ + public function ValueByName($sHeaderName, $bCharsetAutoDetect = false) + { + $oHeader = null; + $oHeader =& $this->GetByName($sHeaderName); + return (null !== $oHeader) ? ($bCharsetAutoDetect ? $oHeader->ValueWithCharsetAutoDetect() : $oHeader->Value()) : ''; + } + + /** + * @param string $sHeaderName + * @param bool $bCharsetAutoDetect = false + * @return array + */ + public function ValuesByName($sHeaderName, $bCharsetAutoDetect = false) + { + $aResult = array(); + $oHeader = null; + + $sHeaderNameLower = \strtolower($sHeaderName); + $aHeaders =& $this->GetAsArray(); + foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) + { + if ($sHeaderNameLower === \strtolower($oHeader->Name())) + { + $aResult[] = $bCharsetAutoDetect ? $oHeader->ValueWithCharsetAutoDetect() : $oHeader->Value(); + } + } + + return $aResult; + } + + /** + * @param string $sHeaderName + * + * @return \MailSo\Mime\HeaderCollection + */ + public function RemoveByName($sHeaderName) + { + $aResult = $this->FilterList(function ($oHeader) use ($sHeaderName) { + return $oHeader && \strtolower($oHeader->Name()) !== \strtolower($sHeaderName); + }); + + return $this->SetAsArray($aResult); + } + + /** + * @param string $sHeaderName + * @param bool $bCharsetAutoDetect = false + * + * @return \MailSo\Mime\EmailCollection|null + */ + public function GetAsEmailCollection($sHeaderName, $bCharsetAutoDetect = false) + { + $oResult = null; + $sValue = $this->ValueByName($sHeaderName, $bCharsetAutoDetect); + if (0 < \strlen($sValue)) + { + $oResult = \MailSo\Mime\EmailCollection::NewInstance($sValue); + } + + return $oResult && 0 < $oResult->Count() ? $oResult : null; + } + + /** + * @param string $sHeaderName + * @return \MailSo\Mime\ParameterCollection|null + */ + public function ParametersByName($sHeaderName) + { + $oParameters = $oHeader = null; + $oHeader =& $this->GetByName($sHeaderName); + if ($oHeader) + { + $oParameters = $oHeader->Parameters(); + } + + return $oParameters; + } + + /** + * @param string $sHeaderName + * @param string $sParamName + * @return string + */ + public function ParameterValue($sHeaderName, $sParamName) + { + $oParameters = $this->ParametersByName($sHeaderName); + return (null !== $oParameters) ? $oParameters->ParameterValueByName($sParamName) : ''; + } + + /** + * @param string $sHeaderName + * @return \MailSo\Mime\Header | false + */ + public function &GetByName($sHeaderName) + { + $oResult = $oHeader = null; + + $sHeaderNameLower = \strtolower($sHeaderName); + $aHeaders =& $this->GetAsArray(); + foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) + { + if ($sHeaderNameLower === \strtolower($oHeader->Name())) + { + $oResult =& $oHeader; + break; + } + } + + return $oResult; + } + + /** + * @param array $aList + * @return \MailSo\Mime\HeaderCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetAsArray($aList) + { + parent::SetAsArray($aList); + + return $this; + } + + /** + * @param string $sParentCharset + * @return \MailSo\Mime\HeaderCollection + */ + public function SetParentCharset($sParentCharset) + { + if (0 < \strlen($sParentCharset)) + { + if ($this->sParentCharset !== $sParentCharset) + { + $oHeader = null; + $aHeaders =& $this->GetAsArray(); + + foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) + { + $oHeader->SetParentCharset($sParentCharset); + } + + $this->sParentCharset = $sParentCharset; + } + } + + return $this; + } + + /** + * @return void + */ + public function Clear() + { + parent::Clear(); + + $this->sRawHeaders = ''; + } + + /** + * @param string $sRawHeaders + * @param bool $bStoreRawHeaders = false + * @param string $sParentCharset = '' + * + * @return \MailSo\Mime\HeaderCollection + */ + public function Parse($sRawHeaders, $bStoreRawHeaders = false, $sParentCharset = '') + { + $this->Clear(); + + if ($bStoreRawHeaders) + { + $this->sRawHeaders = $sRawHeaders; + } + + if (0 === \strlen($this->sParentCharset)) + { + $this->sParentCharset = $sParentCharset; + } + + $aHeaders = \explode("\n", \str_replace("\r", '', $sRawHeaders)); + + $sName = null; + $sValue = null; + foreach ($aHeaders as $sHeadersValue) + { + if (0 === strlen($sHeadersValue)) + { + continue; + } + + $sFirstChar = \substr($sHeadersValue, 0, 1); + if ($sFirstChar !== ' ' && $sFirstChar !== "\t" && false === \strpos($sHeadersValue, ':')) + { + continue; + } + else if (null !== $sName && ($sFirstChar === ' ' || $sFirstChar === "\t")) + { + $sValue = \is_null($sValue) ? '' : $sValue; + + if ('?=' === \substr(\rtrim($sHeadersValue), -2)) + { + $sHeadersValue = \rtrim($sHeadersValue); + } + + if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2)) + { + $sHeadersValue = \ltrim($sHeadersValue); + } + + if ('=?' === \substr($sHeadersValue, 0, 2)) + { + $sValue .= $sHeadersValue; + } + else + { + $sValue .= "\n".$sHeadersValue; + } + } + else + { + if (null !== $sName) + { + $oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset); + if ($oHeader) + { + $this->Add($oHeader); + } + + $sName = null; + $sValue = null; + } + + $aHeaderParts = \explode(':', $sHeadersValue, 2); + $sName = $aHeaderParts[0]; + $sValue = isset($aHeaderParts[1]) ? $aHeaderParts[1] : ''; + + if ('?=' === \substr(\rtrim($sValue), -2)) + { + $sValue = \rtrim($sValue); + } + } + } + + if (null !== $sName) + { + $oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset); + if ($oHeader) + { + $this->Add($oHeader); + } + } + + return $this; + } + + /** + * @return int + */ + public function DkimStatuses() + { + $aResult = array(); + + $aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::AUTHENTICATION_RESULTS); + if (\is_array($aHeaders) && 0 < \count($aHeaders)) + { + foreach ($aHeaders as $sHeaderValue) + { + $sStatus = ''; + $sHeader = ''; + $sDkimLine = ''; + + $aMatch = array(); + + $sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue); + + if (\preg_match('/dkim=.+/i', $sHeaderValue, $aMatch) && !empty($aMatch[0])) + { + $sDkimLine = $aMatch[0]; + + $aMatch = array(); + if (\preg_match('/dkim=([a-zA-Z0-9]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[1])) + { + $sStatus = $aMatch[1]; + } + + $aMatch = array(); + if (\preg_match('/header\.(d|i|from)=([^\s;]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[2])) + { + $sHeader = \trim($aMatch[2]); + } + + if (!empty($sStatus) && !empty($sHeader)) + { + $aResult[] = array($sStatus, $sHeader, $sDkimLine); + } + } + } + } + else + { + // X-DKIM-Authentication-Results: signer="hostinger.com" status="pass" + $aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS); + if (\is_array($aHeaders) && 0 < \count($aHeaders)) + { + foreach ($aHeaders as $sHeaderValue) + { + $sStatus = ''; + $sHeader = ''; + + $aMatch = array(); + + $sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue); + + if (\preg_match('/status[\s]?=[\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) + { + $sStatus = $aMatch[1]; + } + + if (\preg_match('/signer[\s]?=[\s]?"([^";]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) + { + $sHeader = \trim($aMatch[1]); + } + + if (!empty($sStatus) && !empty($sHeader)) + { + $aResult[] = array($sStatus, $sHeader, $sHeaderValue); + } + } + } + } + + return $aResult; + } + + /** + * @return int + */ + public function PopulateEmailColectionByDkim($oEmails) + { + if ($oEmails && $oEmails instanceof \MailSo\Mime\EmailCollection) + { + $aDkimStatuses = $this->DkimStatuses(); + if (\is_array($aDkimStatuses) && 0 < \count($aDkimStatuses)) + { + $oEmails->ForeachList(function (/* @var $oItem \MailSo\Mime\Email */ $oItem) use ($aDkimStatuses) { + if ($oItem && $oItem instanceof \MailSo\Mime\Email) + { + $sEmail = $oItem->GetEmail(); + foreach ($aDkimStatuses as $aDkimData) + { + if (isset($aDkimData[0], $aDkimData[1]) && + $aDkimData[1] === \strstr($sEmail, $aDkimData[1])) + { + $oItem->SetDkimStatusAndValue($aDkimData[0], empty($aDkimData[2]) ? '' : $aDkimData[2]); + } + } + } + }); + } + } + } + + /** + * @return string + */ + public function ToEncodedString() + { + $aResult = array(); + $aHeaders =& $this->GetAsArray(); + foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) + { + $aResult[] = $oHeader->EncodedValue(); + } + + return \implode(\MailSo\Mime\Enumerations\Constants::CRLF, $aResult); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Message.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Message.php new file mode 100644 index 0000000..44dfa80 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Message.php @@ -0,0 +1,937 @@ +aHeadersValue = array(); + $this->aAlternativeParts = array(); + $this->oAttachmentCollection = AttachmentCollection::NewInstance(); + $this->bAddEmptyTextPart = true; + $this->bAddDefaultXMailer = true; + } + + /** + * @return \MailSo\Mime\Message + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return \MailSo\Mime\Message + */ + public function DoesNotCreateEmptyTextPart() + { + $this->bAddEmptyTextPart = false; + + return $this; + } + + /** + * @return \MailSo\Mime\Message + */ + public function DoesNotAddDefaultXMailer() + { + $this->bAddDefaultXMailer = false; + + return $this; + } + + /** + * @return string + */ + public function MessageId() + { + $sResult = ''; + if (!empty($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::MESSAGE_ID])) + { + $sResult = $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::MESSAGE_ID]; + } + return $sResult; + } + + /** + * @param string $sMessageId + * + * @return void + */ + public function SetMessageId($sMessageId) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::MESSAGE_ID] = $sMessageId; + } + + /** + * @param string $sHostName = '' + * + * @return void + */ + public function RegenerateMessageId($sHostName = '') + { + $this->SetMessageId($this->generateNewMessageId($sHostName)); + } + + /** + * @return \MailSo\Mime\AttachmentCollection + */ + public function Attachments() + { + return $this->oAttachmentCollection; + } + + /** + * @return string + */ + public function GetSubject() + { + return isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SUBJECT]) ? + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SUBJECT] : ''; + } + + /** + * @return \MailSo\Mime\Email|null + */ + public function GetFrom() + { + $oResult = null; + + if (isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::FROM_]) && + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::FROM_] instanceof \MailSo\Mime\Email) + { + $oResult = $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::FROM_]; + } + + return $oResult; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function GetTo() + { + $oResult = \MailSo\Mime\EmailCollection::NewInstance(); + + if (isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_]) && + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_] instanceof \MailSo\Mime\EmailCollection) + { + $oResult->MergeWithOtherCollection($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_]); + } + + return $oResult->Unique(); + } + + /** + * @return \MailSo\Mime\EmailCollection|null + */ + public function GetBcc() + { + $oResult = null; + + if (isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC]) && + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC] instanceof \MailSo\Mime\EmailCollection) + { + $oResult = $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC]; + } + + return $oResult ? $oResult->Unique() : null; + } + + /** + * @return \MailSo\Mime\EmailCollection + */ + public function GetRcpt() + { + $oResult = \MailSo\Mime\EmailCollection::NewInstance(); + + if (isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_]) && + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_] instanceof \MailSo\Mime\EmailCollection) + { + $oResult->MergeWithOtherCollection($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_]); + } + + if (isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::CC]) && + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::CC] instanceof \MailSo\Mime\EmailCollection) + { + $oResult->MergeWithOtherCollection($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::CC]); + } + + if (isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC]) && + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC] instanceof \MailSo\Mime\EmailCollection) + { + $oResult->MergeWithOtherCollection($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC]); + } + + return $oResult->Unique(); + } + + /** + * @param string $sHeaderName + * @param string $sValue + * + * @return \MailSo\Mime\Message + */ + public function SetCustomHeader($sHeaderName, $sValue) + { + $sHeaderName = \trim($sHeaderName); + if (0 < \strlen($sHeaderName)) + { + $this->aHeadersValue[$sHeaderName] = $sValue; + } + + return $this; + } + + /** + * @param string $sSubject + * + * @return \MailSo\Mime\Message + */ + public function SetSubject($sSubject) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SUBJECT] = $sSubject; + + return $this; + } + + /** + * @param string $sInReplyTo + * + * @return \MailSo\Mime\Message + */ + public function SetInReplyTo($sInReplyTo) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::IN_REPLY_TO] = $sInReplyTo; + + return $this; + } + + /** + * @param string $sReferences + * + * @return \MailSo\Mime\Message + */ + public function SetReferences($sReferences) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::REFERENCES] = + \MailSo\Base\Utils::StripSpaces($sReferences); + + return $this; + } + + /** + * @param string $sEmail + * + * @return \MailSo\Mime\Message + */ + public function SetReadReceipt($sEmail) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO] = $sEmail; + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO] = $sEmail; + + return $this; + } + + /** + * @param string $sEmail + * + * @return \MailSo\Mime\Message + */ + public function SetReadConfirmation($sEmail) + { + return $this->SetReadReceipt($sEmail); + } + + /** + * @param int $iValue + * + * @return \MailSo\Mime\Message + */ + public function SetPriority($iValue) + { + $sResult = ''; + switch ($iValue) + { + case \MailSo\Mime\Enumerations\MessagePriority::HIGH: + $sResult = \MailSo\Mime\Enumerations\MessagePriority::HIGH.' (Highest)'; + break; + case \MailSo\Mime\Enumerations\MessagePriority::NORMAL: + $sResult = \MailSo\Mime\Enumerations\MessagePriority::NORMAL.' (Normal)'; + break; + case \MailSo\Mime\Enumerations\MessagePriority::LOW: + $sResult = \MailSo\Mime\Enumerations\MessagePriority::LOW.' (Lowest)'; + break; + } + + if (0 < \strlen($sResult)) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::X_PRIORITY] = $sResult; + } + + return $this; + } + + /** + * @param int $iValue + * + * @return \MailSo\Mime\Message + */ + public function SetSensitivity($iValue) + { + $sResult = ''; + switch ($iValue) + { + case \MailSo\Mime\Enumerations\Sensitivity::CONFIDENTIAL: + $sResult = 'Company-Confidential'; + break; + case \MailSo\Mime\Enumerations\Sensitivity::PERSONAL: + $sResult = 'Personal'; + break; + case \MailSo\Mime\Enumerations\Sensitivity::PRIVATE_: + $sResult = 'Private'; + break; + } + + if (0 < \strlen($sResult)) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SENSITIVITY] = $sResult; + } + + return $this; + } + + /** + * @param string $sXMailer + * + * @return \MailSo\Mime\Message + */ + public function SetXMailer($sXMailer) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::X_MAILER] = $sXMailer; + + return $this; + } + + /** + * @param \MailSo\Mime\Email $oEmail + * + * @return \MailSo\Mime\Message + */ + public function SetFrom(\MailSo\Mime\Email $oEmail) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::FROM_] = $oEmail; + + return $this; + } + + /** + * @param \MailSo\Mime\EmailCollection $oEmails + * + * @return \MailSo\Mime\Message + */ + public function SetTo(\MailSo\Mime\EmailCollection $oEmails) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::TO_] = $oEmails; + + return $this; + } + + /** + * @param int $iDateTime + * + * @return \MailSo\Mime\Message + */ + public function SetDate($iDateTime) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::DATE] = gmdate('r', $iDateTime); + + return $this; + } + + /** + * @param \MailSo\Mime\EmailCollection $oEmails + * + * @return \MailSo\Mime\Message + */ + public function SetReplyTo(\MailSo\Mime\EmailCollection $oEmails) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::REPLY_TO] = $oEmails; + + return $this; + } + + /** + * @param \MailSo\Mime\EmailCollection $oEmails + * + * @return \MailSo\Mime\Message + */ + public function SetCc(\MailSo\Mime\EmailCollection $oEmails) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::CC] = $oEmails; + + return $this; + } + + /** + * @param \MailSo\Mime\EmailCollection $oEmails + * + * @return \MailSo\Mime\Message + */ + public function SetBcc(\MailSo\Mime\EmailCollection $oEmails) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::BCC] = $oEmails; + + return $this; + } + + /** + * @param \MailSo\Mime\EmailCollection $oEmails + * + * @return \MailSo\Mime\Message + */ + public function SetSender(\MailSo\Mime\EmailCollection $oEmails) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SENDER] = $oEmails; + + return $this; + } + + /** + * @param string $sType + * @param string $sUid + * @param string $sFolder + * + * @return \MailSo\Mime\Message + */ + public function SetDraftInfo($sType, $sUid, $sFolder) + { + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::X_DRAFT_INFO] = \MailSo\Mime\ParameterCollection::NewInstance() + ->Add(\MailSo\Mime\Parameter::NewInstance('type', $sType)) + ->Add(\MailSo\Mime\Parameter::NewInstance('uid', $sUid)) + ->Add(\MailSo\Mime\Parameter::NewInstance('folder', base64_encode($sFolder))) + ; + + return $this; + } + + /** + * @param string $sPlain + * + * @return \MailSo\Mime\Message + */ + public function AddPlain($sPlain) + { + return $this->AddAlternative( + \MailSo\Mime\Enumerations\MimeType::TEXT_PLAIN, trim($sPlain), + \MailSo\Base\Enumerations\Encoding::QUOTED_PRINTABLE_LOWER); + } + /** + * @param string $sHtml + * + * @return \MailSo\Mime\Message + */ + public function AddHtml($sHtml) + { + return $this->AddAlternative( + \MailSo\Mime\Enumerations\MimeType::TEXT_HTML, trim($sHtml), + \MailSo\Base\Enumerations\Encoding::QUOTED_PRINTABLE_LOWER); + } + + /** + * @param string $sHtmlOrPlainText + * @param bool $bIsHtml = false + * + * @return \MailSo\Mime\Message + */ + public function AddText($sHtmlOrPlainText, $bIsHtml = false) + { + return $bIsHtml ? $this->AddHtml($sHtmlOrPlainText) : $this->AddPlain($sHtmlOrPlainText); + } + + /** + * @param string $sContentType + * @param string|resource $mData + * @param string $sContentTransferEncoding = '' + * @param array $aCustomContentTypeParams = array() + * + * @return \MailSo\Mime\Message + */ + public function AddAlternative($sContentType, $mData, $sContentTransferEncoding = '', $aCustomContentTypeParams = array()) + { + $this->aAlternativeParts[] = array($sContentType, $mData, $sContentTransferEncoding, $aCustomContentTypeParams); + + return $this; + } + + /** + * @return string + */ + private function generateNewBoundary() + { + return '--='.\MailSo\Config::$BoundaryPrefix. + \rand(100, 999).'_'.rand(100000000, 999999999).'.'.\time(); + } + + /** + * @param string $sHostName = '' + * + * @return string + */ + private function generateNewMessageId($sHostName = '') + { + if (0 === \strlen($sHostName)) + { + $sHostName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; + } + + if (empty($sHostName) && \MailSo\Base\Utils::FunctionExistsAndEnabled('php_uname')) + { + $sHostName = \php_uname('n'); + } + + if (empty($sHostName)) + { + $sHostName = 'localhost'; + } + + return '<'. + \MailSo\Base\Utils::Md5Rand($sHostName. + (\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? @\getmypid() : '')).'@'.$sHostName.'>'; + } + + /** + * @param \MailSo\Mime\Attachment $oAttachment + * + * @return \MailSo\Mime\Part + */ + private function createNewMessageAttachmentBody($oAttachment) + { + $oAttachmentPart = Part::NewInstance(); + + $sFileName = $oAttachment->FileName(); + $sCID = $oAttachment->CID(); + $sContentLocation = $oAttachment->ContentLocation(); + + $oContentTypeParameters = null; + $oContentDispositionParameters = null; + + if (0 < strlen(trim($sFileName))) + { + $oContentTypeParameters = + ParameterCollection::NewInstance()->Add(Parameter::NewInstance( + \MailSo\Mime\Enumerations\Parameter::NAME, $sFileName)); + + $oContentDispositionParameters = + ParameterCollection::NewInstance()->Add(Parameter::NewInstance( + \MailSo\Mime\Enumerations\Parameter::FILENAME, $sFileName)); + } + + $oAttachmentPart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + $oAttachment->ContentType().';'. + (($oContentTypeParameters) ? ' '.$oContentTypeParameters->ToString() : '') + ) + ); + + $oAttachmentPart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, + ($oAttachment->IsInline() ? 'inline' : 'attachment').';'. + (($oContentDispositionParameters) ? ' '.$oContentDispositionParameters->ToString() : '') + ) + ); + + if (0 < strlen($sCID)) + { + $oAttachmentPart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_ID, $sCID) + ); + } + + if (0 < strlen($sContentLocation)) + { + $oAttachmentPart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_LOCATION, $sContentLocation) + ); + } + + $oAttachmentPart->Body = $oAttachment->Resource(); + + if ('message/rfc822' !== strtolower($oAttachment->ContentType())) + { + $oAttachmentPart->Headers->Add( + Header::NewInstance( + \MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, + \MailSo\Base\Enumerations\Encoding::BASE64_LOWER + ) + ); + + if (is_resource($oAttachmentPart->Body)) + { + if (!\MailSo\Base\StreamWrappers\Binary::IsStreamRemembed($oAttachmentPart->Body)) + { + $oAttachmentPart->Body = + \MailSo\Base\StreamWrappers\Binary::CreateStream($oAttachmentPart->Body, + \MailSo\Base\StreamWrappers\Binary::GetInlineDecodeOrEncodeFunctionName( + \MailSo\Base\Enumerations\Encoding::BASE64, false)); + + \MailSo\Base\StreamWrappers\Binary::RememberStream($oAttachmentPart->Body); + } + } + } + + return $oAttachmentPart; + } + + /** + * @param array $aAlternativeData + * + * @return \MailSo\Mime\Part + */ + private function createNewMessageAlternativePartBody($aAlternativeData) + { + $oAlternativePart = null; + + if (is_array($aAlternativeData) && isset($aAlternativeData[0])) + { + $oAlternativePart = Part::NewInstance(); + $oParameters = ParameterCollection::NewInstance(); + $oParameters->Add( + Parameter::NewInstance( + \MailSo\Mime\Enumerations\Parameter::CHARSET, + \MailSo\Base\Enumerations\Charset::UTF_8) + ); + + if (isset($aAlternativeData[3]) && \is_array($aAlternativeData[3]) && 0 < \count($aAlternativeData[3])) + { + foreach ($aAlternativeData[3] as $sName => $sValue) + { + $oParameters->Add(Parameter::NewInstance($sName, $sValue)); + } + } + + $oAlternativePart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + $aAlternativeData[0].'; '.$oParameters->ToString()) + ); + + $oAlternativePart->Body = null; + if (isset($aAlternativeData[1])) + { + if (is_resource($aAlternativeData[1])) + { + $oAlternativePart->Body = $aAlternativeData[1]; + } + else if (is_string($aAlternativeData[1]) && 0 < strlen($aAlternativeData[1])) + { + $oAlternativePart->Body = + \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($aAlternativeData[1]); + } + } + + if (isset($aAlternativeData[2]) && 0 < strlen($aAlternativeData[2])) + { + $oAlternativePart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, + $aAlternativeData[2] + ) + ); + + if (is_resource($oAlternativePart->Body)) + { + if (!\MailSo\Base\StreamWrappers\Binary::IsStreamRemembed($oAlternativePart->Body)) + { + $oAlternativePart->Body = + \MailSo\Base\StreamWrappers\Binary::CreateStream($oAlternativePart->Body, + \MailSo\Base\StreamWrappers\Binary::GetInlineDecodeOrEncodeFunctionName( + $aAlternativeData[2], false)); + + \MailSo\Base\StreamWrappers\Binary::RememberStream($oAlternativePart->Body); + } + } + } + + if (!is_resource($oAlternativePart->Body)) + { + $oAlternativePart->Body = + \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(''); + } + } + + return $oAlternativePart; + } + + /** + * @param \MailSo\Mime\Part $oPlainPart + * @param \MailSo\Mime\Part $oHtmlPart + * + * @return \MailSo\Mime\Part + */ + private function createNewMessageSimpleOrAlternativeBody() + { + $oResultPart = null; + if (1 < count($this->aAlternativeParts)) + { + $oResultPart = Part::NewInstance(); + + $oResultPart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\MimeType::MULTIPART_ALTERNATIVE.'; '. + ParameterCollection::NewInstance()->Add( + Parameter::NewInstance( + \MailSo\Mime\Enumerations\Parameter::BOUNDARY, + $this->generateNewBoundary()) + )->ToString() + ) + ); + + foreach ($this->aAlternativeParts as $aAlternativeData) + { + $oAlternativePart = $this->createNewMessageAlternativePartBody($aAlternativeData); + if ($oAlternativePart) + { + $oResultPart->SubParts->Add($oAlternativePart); + } + + unset($oAlternativePart); + } + + } + else if (1 === count($this->aAlternativeParts)) + { + $oAlternativePart = $this->createNewMessageAlternativePartBody($this->aAlternativeParts[0]); + if ($oAlternativePart) + { + $oResultPart = $oAlternativePart; + } + } + + if (!$oResultPart) + { + if ($this->bAddEmptyTextPart) + { + $oResultPart = $this->createNewMessageAlternativePartBody(array( + \MailSo\Mime\Enumerations\MimeType::TEXT_PLAIN, null + )); + } + else + { + $aAttachments = $this->oAttachmentCollection->CloneAsArray(); + if (\is_array($aAttachments) && 1 === count($aAttachments) && isset($aAttachments[0])) + { + $this->oAttachmentCollection->Clear(); + + $oResultPart = $this->createNewMessageAlternativePartBody(array( + $aAttachments[0]->ContentType(), $aAttachments[0]->Resource(), + '', $aAttachments[0]->CustomContentTypeParams() + )); + } + } + } + + return $oResultPart; + } + + /** + * @param \MailSo\Mime\Part $oIncPart + * + * @return \MailSo\Mime\Part + */ + private function createNewMessageRelatedBody($oIncPart) + { + $oResultPart = null; + + $aAttachments = $this->oAttachmentCollection->LinkedAttachments(); + if (0 < count($aAttachments)) + { + $oResultPart = Part::NewInstance(); + + $oResultPart->Headers->Add( + Header::NewInstance(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\MimeType::MULTIPART_RELATED.'; '. + ParameterCollection::NewInstance()->Add( + Parameter::NewInstance( + \MailSo\Mime\Enumerations\Parameter::BOUNDARY, + $this->generateNewBoundary()) + )->ToString() + ) + ); + + $oResultPart->SubParts->Add($oIncPart); + + foreach ($aAttachments as $oAttachment) + { + $oResultPart->SubParts->Add($this->createNewMessageAttachmentBody($oAttachment)); + } + } + else + { + $oResultPart = $oIncPart; + } + + return $oResultPart; + } + + /** + * @param \MailSo\Mime\Part $oIncPart + * + * @return \MailSo\Mime\Part + */ + private function createNewMessageMixedBody($oIncPart) + { + $oResultPart = null; + + $aAttachments = $this->oAttachmentCollection->UnlinkedAttachments(); + if (0 < count($aAttachments)) + { + $oResultPart = Part::NewInstance(); + + $oResultPart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\MimeType::MULTIPART_MIXED.'; '. + ParameterCollection::NewInstance()->Add( + Parameter::NewInstance( + \MailSo\Mime\Enumerations\Parameter::BOUNDARY, + $this->generateNewBoundary()) + )->ToString() + ); + + $oResultPart->SubParts->Add($oIncPart); + + foreach ($aAttachments as $oAttachment) + { + $oResultPart->SubParts->Add($this->createNewMessageAttachmentBody($oAttachment)); + } + } + else + { + $oResultPart = $oIncPart; + } + + return $oResultPart; + } + + /** + * @param \MailSo\Mime\Part $oIncPart + * @param bool $bWithoutBcc = false + * + * @return \MailSo\Mime\Part + */ + private function setDefaultHeaders($oIncPart, $bWithoutBcc = false) + { + if (!isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::DATE])) + { + $oIncPart->Headers->SetByName(\MailSo\Mime\Enumerations\Header::DATE, \gmdate('r'), true); + } + + if (!isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::MESSAGE_ID])) + { + $oIncPart->Headers->SetByName(\MailSo\Mime\Enumerations\Header::MESSAGE_ID, $this->generateNewMessageId(), true); + } + + if (!isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::X_MAILER]) && $this->bAddDefaultXMailer) + { + $oIncPart->Headers->SetByName(\MailSo\Mime\Enumerations\Header::X_MAILER, \MailSo\Version::XMailer(), true); + } + + if (!isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::MIME_VERSION])) + { + $oIncPart->Headers->SetByName(\MailSo\Mime\Enumerations\Header::MIME_VERSION, '1.0', true); + } + + foreach ($this->aHeadersValue as $sName => $mValue) + { + if (\is_object($mValue)) + { + if ($mValue instanceof \MailSo\Mime\EmailCollection || $mValue instanceof \MailSo\Mime\Email || + $mValue instanceof \MailSo\Mime\ParameterCollection) + { + $mValue = $mValue->ToString(); + } + } + + if (!($bWithoutBcc && \strtolower(\MailSo\Mime\Enumerations\Header::BCC) === \strtolower($sName))) + { + $oIncPart->Headers->SetByName($sName, (string) $mValue); + } + } + + return $oIncPart; + } + + /** + * @param bool $bWithoutBcc = false + * + * @return \MailSo\Mime\Part + */ + public function ToPart($bWithoutBcc = false) + { + $oPart = $this->createNewMessageSimpleOrAlternativeBody(); + $oPart = $this->createNewMessageRelatedBody($oPart); + $oPart = $this->createNewMessageMixedBody($oPart); + $oPart = $this->setDefaultHeaders($oPart, $bWithoutBcc); + + return $oPart; + } + + /** + * @param bool $bWithoutBcc = false + * + * @return resource + */ + public function ToStream($bWithoutBcc = false) + { + return $this->ToPart($bWithoutBcc)->ToStream(); + } + + /** + * @param bool $bWithoutBcc = false + * + * @return string + */ + public function ToString($bWithoutBcc = false) + { + return \stream_get_contents($this->ToStream($bWithoutBcc)); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Parameter.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Parameter.php new file mode 100644 index 0000000..487a256 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Parameter.php @@ -0,0 +1,141 @@ +sName = $sName; + $this->sValue = $sValue; + } + + /** + * @param string $sName + * @param string $sValue = '' + * + * @return \MailSo\Mime\Parameter + */ + public static function NewInstance($sName, $sValue = '') + { + return new self($sName, $sValue); + } + + /** + * @param string $sName + * @param string $sValue = '' + * + * @return \MailSo\Mime\Parameter + */ + public static function CreateFromParameterLine($sRawParam) + { + $oParameter = self::NewInstance(''); + return $oParameter->Parse($sRawParam); + } + + /** + * @return \MailSo\Mime\Parameter + */ + public function Reset() + { + $this->sName = ''; + $this->sValue = ''; + + return $this; + } + + /** + * @return string + */ + public function Name() + { + return $this->sName; + } + + /** + * @return string + */ + public function Value() + { + return $this->sValue; + } + + /** + * @param string $sRawParam + * @param string $sSeparator = '=' + * + * @return \MailSo\Mime\Parameter + */ + public function Parse($sRawParam, $sSeparator = '=') + { + $this->Reset(); + + $aParts = explode($sSeparator, $sRawParam, 2); + + $this->sName = trim(trim($aParts[0]), '"\''); + if (2 === count($aParts)) + { + $this->sValue = trim(trim($aParts[1]), '"\''); + } + + return $this; + } + + /** + * @param bool $bConvertSpecialsName = false + * + * @return string + */ + public function ToString($bConvertSpecialsName = false) + { + $sResult = ''; + if (0 < strlen($this->sName)) + { + $sResult = $this->sName.'='; + if ($bConvertSpecialsName && in_array(strtolower($this->sName), array( + strtolower(\MailSo\Mime\Enumerations\Parameter::NAME), + strtolower(\MailSo\Mime\Enumerations\Parameter::FILENAME) + ))) + { + $sResult .= '"'.\MailSo\Base\Utils::EncodeUnencodedValue( + \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, + $this->sValue).'"'; + } + else + { + $sResult .= '"'.$this->sValue.'"'; + } + } + + return $sResult; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/ParameterCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/ParameterCollection.php new file mode 100644 index 0000000..52b7dd3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/ParameterCollection.php @@ -0,0 +1,213 @@ +Parse($sRawParams); + } + } + + /** + * @param string $sRawParams = '' + * + * @return \MailSo\Mime\ParameterCollection + */ + public static function NewInstance($sRawParams = '') + { + return new self($sRawParams); + } + + /** + * @return \MailSo\Mime\Parameter|null + */ + public function &GetByIndex($iIndex) + { + $mResult = null; + $mResult =& parent::GetByIndex($iIndex); + return $mResult; + } + + /** + * @param array $aList + * + * @return \MailSo\Mime\ParameterCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetAsArray($aList) + { + parent::SetAsArray($aList); + + return $this; + } + + /** + * @param string $sName + * + * @return string + */ + public function ParameterValueByName($sName) + { + $sResult = ''; + $sName = \trim($sName); + + $aParams =& $this->GetAsArray(); + foreach ($aParams as /* @var $oParam \MailSo\Mime\ParameterCollection */ $oParam) + { + if (\strtolower($sName) === \strtolower($oParam->Name())) + { + $sResult = $oParam->Value(); + break; + } + } + + return $sResult; + } + + /** + * @param string $sRawParams + * + * @return \MailSo\Mime\ParameterCollection + */ + public function Parse($sRawParams) + { + $this->Clear(); + + $aDataToParse = \explode(';', $sRawParams); + + foreach ($aDataToParse as $sParam) + { + $this->Add(Parameter::CreateFromParameterLine($sParam)); + } + + $this->reParseParameters(); + + return $this; + } + + /** + * @param bool $bConvertSpecialsName = false + * + * @return string + */ + public function ToString($bConvertSpecialsName = false) + { + $aResult = array(); + $aParams =& $this->GetAsArray(); + foreach ($aParams as /* @var $oParam \MailSo\Mime\Parameter */ $oParam) + { + $sLine = $oParam->ToString($bConvertSpecialsName); + if (0 < \strlen($sLine)) + { + $aResult[] = $sLine; + } + } + + return 0 < \count($aResult) ? \implode('; ', $aResult) : ''; + } + + /** + * @return void + */ + private function reParseParameters() + { + $aDataToReParse = $this->CloneAsArray(); + $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8; + + $this->Clear(); + + $aPreParams = array(); + foreach ($aDataToReParse as /* @var $oParam \MailSo\Mime\Parameter */ $oParam) + { + $aMatch = array(); + $sParamName = $oParam->Name(); + + if (\preg_match('/([^\*]+)\*([\d]{1,2})\*/', $sParamName, $aMatch) && isset($aMatch[1], $aMatch[2]) + && 0 < \strlen($aMatch[1]) && \is_numeric($aMatch[2])) + { + if (!isset($aPreParams[$aMatch[1]])) + { + $aPreParams[$aMatch[1]] = array(); + } + + $sValue = $oParam->Value(); + + if (false !== \strpos($sValue, "''")) + { + $aValueParts = \explode("''", $sValue, 2); + if (\is_array($aValueParts) && 2 === \count($aValueParts) && 0 < \strlen($aValueParts[1])) + { + $sCharset = $aValueParts[0]; + $sValue = $aValueParts[1]; + } + } + + $aPreParams[$aMatch[1]][(int) $aMatch[2]] = $sValue; + } + else if (\preg_match('/([^\*]+)\*/', $sParamName, $aMatch) && isset($aMatch[1])) + { + if (!isset($aPreParams[$aMatch[1]])) + { + $aPreParams[$aMatch[1]] = array(); + } + + $sValue = $oParam->Value(); + if (false !== \strpos($sValue, "''")) + { + $aValueParts = \explode("''", $sValue, 2); + if (\is_array($aValueParts) && 2 === \count($aValueParts) && 0 < \strlen($aValueParts[1])) + { + $sCharset = $aValueParts[0]; + $sValue = $aValueParts[1]; + } + } + + $aPreParams[$aMatch[1]][0] = $sValue; + } + else + { + $this->Add($oParam); + } + } + + foreach ($aPreParams as $sName => $aValues) + { + ksort($aValues); + $sResult = \implode(\array_values($aValues)); + $sResult = \urldecode($sResult); + + if (0 < \strlen($sCharset)) + { + $sResult = \MailSo\Base\Utils::ConvertEncoding($sResult, + $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8); + } + + $this->Add(Parameter::NewInstance($sName, $sResult)); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php new file mode 100644 index 0000000..2349ade --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php @@ -0,0 +1,81 @@ +oCurrentMime = $oPart; + } + + /** + * @param string $sBuffer + * + * @return void + */ + public function WriteBody($sBuffer) + { + if (null === $this->oCurrentMime->Body) + { + $this->oCurrentMime->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResource(); + } + + if (\is_resource($this->oCurrentMime->Body)) + { + \fwrite($this->oCurrentMime->Body, $sBuffer); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Part.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Part.php new file mode 100644 index 0000000..2e19af0 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/Part.php @@ -0,0 +1,665 @@ +iParseBuffer = \MailSo\Mime\Part::DEFAUL_BUFFER; + $this->Reset(); + } + + /** + * @return \MailSo\Mime\Part + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return \MailSo\Mime\Part + */ + public function Reset() + { + \MailSo\Base\ResourceRegistry::CloseMemoryResource($this->Body); + $this->Body = null; + + $this->Headers = HeaderCollection::NewInstance(); + $this->SubParts = PartCollection::NewInstance(); + $this->LineParts = array(); + $this->sBoundary = ''; + $this->sParentCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1; + + return $this; + } + + /** + * @return string + */ + public function Boundary() + { + return $this->sBoundary; + } + + /** + * @return string + */ + public function ParentCharset() + { + return (0 < \strlen($this->sCharset)) ? $this->sParentCharset : self::$DefaultCharset; + } + + /** + * @param string $sParentCharset + * @return \MailSo\Mime\Part + */ + public function SetParentCharset($sParentCharset) + { + $this->sParentCharset = $sParentCharset; + + return $this; + } + + /** + * @param string $sBoundary + * @return \MailSo\Mime\Part + */ + public function SetBoundary($sBoundary) + { + $this->sBoundary = $sBoundary; + + return $this; + } + + /** + * @param int $iParseBuffer + * @return \MailSo\Mime\Part + */ + public function SetParseBuffer($iParseBuffer) + { + $this->iParseBuffer = $iParseBuffer; + + return $this; + } + + /** + * @return string + */ + public function HeaderCharset() + { + return ($this->Headers) ? trim(strtolower($this->Headers->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::CHARSET))) : ''; + } + + /** + * @return string + */ + public function HeaderBoundary() + { + return ($this->Headers) ? trim($this->Headers->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::BOUNDARY)) : ''; + } + + /** + * @return string + */ + public function ContentType() + { + return ($this->Headers) ? + trim(strtolower($this->Headers->ValueByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE))) : ''; + } + + /** + * @return string + */ + public function ContentTransferEncoding() + { + return ($this->Headers) ? + trim(strtolower($this->Headers->ValueByName( + \MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING))) : ''; + } + + /** + * @return string + */ + public function ContentID() + { + return ($this->Headers) ? trim($this->Headers->ValueByName( + \MailSo\Mime\Enumerations\Header::CONTENT_ID)) : ''; + } + + /** + * @return string + */ + public function ContentLocation() + { + return ($this->Headers) ? trim($this->Headers->ValueByName( + \MailSo\Mime\Enumerations\Header::CONTENT_LOCATION)) : ''; + } + + /** + * @return bool + */ + public function IsFlowedFormat() + { + $bResult = false; + if ($this->Headers) + { + $bResult = 'flowed' === \trim(\strtolower($this->Headers->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::FORMAT))); + + if ($bResult && \in_array(\strtolower($this->MailEncodingName()), array('base64', 'quoted-printable'))) + { + $bResult = false; + } + } + + return $bResult; + } + + /** + * @return string + */ + public function FileName() + { + $sResult = ''; + if ($this->Headers) + { + $sResult = trim($this->Headers->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, + \MailSo\Mime\Enumerations\Parameter::FILENAME)); + + if (0 === strlen($sResult)) + { + $sResult = trim($this->Headers->ParameterValue( + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, + \MailSo\Mime\Enumerations\Parameter::NAME)); + } + } + + return $sResult; + } + + /** + * @param string $sFileName + * @return \MailSo\Mime\Part + */ + public function ParseFromFile($sFileName) + { + $rStreamHandle = (@file_exists($sFileName)) ? @fopen($sFileName, 'rb') : false; + if (is_resource($rStreamHandle)) + { + $this->ParseFromStream($rStreamHandle); + + if (is_resource($rStreamHandle)) + { + fclose($rStreamHandle); + } + } + + return $this; + } + + /** + * @param string $sRawMessage + * @return \MailSo\Mime\Part + */ + public function ParseFromString($sRawMessage) + { + $rStreamHandle = (0 < strlen($sRawMessage)) ? + \MailSo\Base\ResourceRegistry::CreateMemoryResource() : false; + + if (is_resource($rStreamHandle)) + { + fwrite($rStreamHandle, $sRawMessage); + unset($sRawMessage); + fseek($rStreamHandle, 0); + + $this->ParseFromStream($rStreamHandle); + + \MailSo\Base\ResourceRegistry::CloseMemoryResource($rStreamHandle); + } + + return $this; + } + + /** + * @param resource $rStreamHandle + * @return \MailSo\Mime\Part + */ + public function ParseFromStream($rStreamHandle) + { + $this->Reset(); + + $oParserClass = new \MailSo\Mime\Parser\ParserMemory(); + + $oMimePart = null; + $bIsOef = false; + $iOffset = 0; + $sBuffer = ''; + $sPrevBuffer = ''; + $aBoundaryStack = array(); + + + $oParserClass->StartParse($this); + + $this->LineParts[] =& $this; + $this->ParseFromStreamRecursion($rStreamHandle, $oParserClass, $iOffset, + $sPrevBuffer, $sBuffer, $aBoundaryStack, $bIsOef); + + $sFirstNotNullCharset = null; + foreach ($this->LineParts as /* @var $oMimePart \MailSo\Mime\Part */ &$oMimePart) + { + $sCharset = $oMimePart->HeaderCharset(); + if (0 < strlen($sCharset)) + { + $sFirstNotNullCharset = $sCharset; + break; + } + } + + $sForceCharset = self::$ForceCharset; + if (0 < strlen($sForceCharset)) + { + foreach ($this->LineParts as /* @var $oMimePart \MailSo\Mime\Part */ &$oMimePart) + { + $oMimePart->SetParentCharset($sForceCharset); + $oMimePart->Headers->SetParentCharset($sForceCharset); + } + } + else + { + $sFirstNotNullCharset = (null !== $sFirstNotNullCharset) + ? $sFirstNotNullCharset : self::$DefaultCharset; + + foreach ($this->LineParts as /* @var $oMimePart \MailSo\Mime\Part */ &$oMimePart) + { + $sHeaderCharset = $oMimePart->HeaderCharset(); + $oMimePart->SetParentCharset((0 < strlen($sHeaderCharset)) ? $sHeaderCharset : $sFirstNotNullCharset); + $oMimePart->Headers->SetParentCharset($sHeaderCharset); + } + } + + $oParserClass->EndParse($this); + + return $this; + } + + /** + * @param resource $rStreamHandle + * @return \MailSo\Mime\Part + */ + public function ParseFromStreamRecursion($rStreamHandle, &$oCallbackClass, &$iOffset, + &$sPrevBuffer, &$sBuffer, &$aBoundaryStack, &$bIsOef, $bNotFirstRead = false) + { + $oCallbackClass->StartParseMimePart($this); + + $iPos = 0; + $iParsePosition = self::POS_HEADERS; + $sCurrentBoundary = ''; + $bIsBoundaryCheck = false; + $aHeadersLines = array(); + while (true) + { + if (!$bNotFirstRead) + { + $sPrevBuffer = $sBuffer; + $sBuffer = ''; + } + + if (!$bIsOef && !feof($rStreamHandle)) + { + if (!$bNotFirstRead) + { + $sBuffer = @fread($rStreamHandle, $this->iParseBuffer); + if (false === $sBuffer) + { + break; + } + + $oCallbackClass->ReadBuffer($sBuffer); + } + else + { + $bNotFirstRead = false; + } + } + else if ($bIsOef && 0 === strlen($sBuffer)) + { + break; + } + else + { + $bIsOef = true; + } + + while (true) + { + $sCurrentLine = $sPrevBuffer.$sBuffer; + if (self::POS_HEADERS === $iParsePosition) + { + $iEndLen = 4; + $iPos = strpos($sCurrentLine, "\r\n\r\n", $iOffset); + if (false === $iPos) + { + $iEndLen = 2; + $iPos = strpos($sCurrentLine, "\n\n", $iOffset); + } + + if (false !== $iPos) + { + $aHeadersLines[] = substr($sCurrentLine, $iOffset, $iPos + $iEndLen - $iOffset); + + $this->Headers->Parse(implode($aHeadersLines))->SetParentCharset($this->HeaderCharset()); + $aHeadersLines = array(); + + $oCallbackClass->InitMimePartHeader(); + + $sBoundary = $this->HeaderBoundary(); + if (0 < strlen($sBoundary)) + { + $sBoundary = '--'.$sBoundary; + $sCurrentBoundary = $sBoundary; + array_unshift($aBoundaryStack, $sBoundary); + } + + $iOffset = $iPos + $iEndLen; + $iParsePosition = self::POS_BODY; + continue; + } + else + { + $iBufferLen = strlen($sPrevBuffer); + if ($iBufferLen > $iOffset) + { + $aHeadersLines[] = substr($sPrevBuffer, $iOffset); + $iOffset = 0; + } + else + { + $iOffset -= $iBufferLen; + } + break; + } + } + else if (self::POS_BODY === $iParsePosition) + { + $iPos = false; + $sBoundaryLen = 0; + $bIsBoundaryEnd = false; + $bCurrentPartBody = false; + $bIsBoundaryCheck = 0 < count($aBoundaryStack); + + foreach ($aBoundaryStack as $sKey => $sBoundary) + { + if (false !== ($iPos = strpos($sCurrentLine, $sBoundary, $iOffset))) + { + if ($sCurrentBoundary === $sBoundary) + { + $bCurrentPartBody = true; + } + + $sBoundaryLen = strlen($sBoundary); + if ('--' === substr($sCurrentLine, $iPos + $sBoundaryLen, 2)) + { + $sBoundaryLen += 2; + $bIsBoundaryEnd = true; + unset($aBoundaryStack[$sKey]); + $sCurrentBoundary = (isset($aBoundaryStack[$sKey + 1])) + ? $aBoundaryStack[$sKey + 1] : ''; + } + + break; + } + } + + if (false !== $iPos) + { + $oCallbackClass->WriteBody(substr($sCurrentLine, $iOffset, $iPos - $iOffset)); + $iOffset = $iPos; + + if ($bCurrentPartBody) + { + $iParsePosition = self::POS_SUBPARTS; + continue; + } + + $oCallbackClass->EndParseMimePart($this); + return true; + } + else + { + $iBufferLen = strlen($sPrevBuffer); + if ($iBufferLen > $iOffset) + { + $oCallbackClass->WriteBody(substr($sPrevBuffer, $iOffset)); + $iOffset = 0; + } + else + { + $iOffset -= $iBufferLen; + } + break; + } + } + else if (self::POS_SUBPARTS === $iParsePosition) + { + $iPos = false; + $sBoundaryLen = 0; + $bIsBoundaryEnd = false; + $bCurrentPartBody = false; + $bIsBoundaryCheck = 0 < count($aBoundaryStack); + + foreach ($aBoundaryStack as $sKey => $sBoundary) + { + if (false !== ($iPos = strpos($sCurrentLine, $sBoundary, $iOffset))) + { + if ($sCurrentBoundary === $sBoundary) + { + $bCurrentPartBody = true; + } + + $sBoundaryLen = strlen($sBoundary); + if ('--' === substr($sCurrentLine, $iPos + $sBoundaryLen, 2)) + { + $sBoundaryLen += 2; + $bIsBoundaryEnd = true; + unset($aBoundaryStack[$sKey]); + $sCurrentBoundary = (isset($aBoundaryStack[$sKey + 1])) + ? $aBoundaryStack[$sKey + 1] : ''; + } + break; + } + } + + if (false !== $iPos && $bCurrentPartBody) + { + $iOffset = $iPos + $sBoundaryLen; + + $oSubPart = self::NewInstance(); + + $oSubPart + ->SetParseBuffer($this->iParseBuffer) + ->ParseFromStreamRecursion($rStreamHandle, $oCallbackClass, + $iOffset, $sPrevBuffer, $sBuffer, $aBoundaryStack, $bIsOef, true); + + $this->SubParts->Add($oSubPart); + $this->LineParts[] =& $oSubPart; + //$iParsePosition = self::POS_HEADERS; + unset($oSubPart); + } + else + { + $oCallbackClass->EndParseMimePart($this); + return true; + } + } + } + } + + if (0 < strlen($sPrevBuffer)) + { + if (self::POS_HEADERS === $iParsePosition) + { + $aHeadersLines[] = ($iOffset < strlen($sPrevBuffer)) + ? substr($sPrevBuffer, $iOffset) + : $sPrevBuffer; + + $this->Headers->Parse(implode($aHeadersLines))->SetParentCharset($this->HeaderCharset()); + $aHeadersLines = array(); + + $oCallbackClass->InitMimePartHeader(); + } + else if (self::POS_BODY === $iParsePosition) + { + if (!$bIsBoundaryCheck) + { + $oCallbackClass->WriteBody(($iOffset < strlen($sPrevBuffer)) + ? substr($sPrevBuffer, $iOffset) : $sPrevBuffer); + } + } + } + else + { + if (self::POS_HEADERS === $iParsePosition && 0 < count($aHeadersLines)) + { + $this->Headers->Parse(implode($aHeadersLines))->SetParentCharset($this->HeaderCharset()); + $aHeadersLines = array(); + + $oCallbackClass->InitMimePartHeader(); + } + } + + $oCallbackClass->EndParseMimePart($this); + + return $this; + } + + /** + * @return resorce + */ + public function Rewind() + { + if ($this->Body && \is_resource($this->Body)) + { + $aMeta = \stream_get_meta_data($this->Body); + if (isset($aMeta['seekable']) && $aMeta['seekable']) + { + \rewind($this->Body); + } + } + } + + /** + * @return resorce + */ + public function ToStream() + { + $this->Rewind(); + + $aSubStreams = array( + + $this->Headers->ToEncodedString(). + \MailSo\Mime\Enumerations\Constants::CRLF. + \MailSo\Mime\Enumerations\Constants::CRLF, + + null === $this->Body ? '' : $this->Body, + + \MailSo\Mime\Enumerations\Constants::CRLF + ); + + if (0 < $this->SubParts->Count()) + { + $sBoundary = $this->HeaderBoundary(); + if (0 < strlen($sBoundary)) + { + $aSubStreams[] = '--'.$sBoundary.\MailSo\Mime\Enumerations\Constants::CRLF; + + $rSubPartsStream = $this->SubParts->ToStream($sBoundary); + if (is_resource($rSubPartsStream)) + { + $aSubStreams[] = $rSubPartsStream; + } + + $aSubStreams[] = \MailSo\Mime\Enumerations\Constants::CRLF. + '--'.$sBoundary.'--'.\MailSo\Mime\Enumerations\Constants::CRLF; + } + } + + return \MailSo\Base\StreamWrappers\SubStreams::CreateStream($aSubStreams); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/PartCollection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/PartCollection.php new file mode 100644 index 0000000..d3c53ca --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Mime/PartCollection.php @@ -0,0 +1,65 @@ +GetAsArray(); + foreach ($aParts as /* @var $oPart \MailSo\Mime\Part */ &$oPart) + { + if (0 < count($aResult)) + { + $aResult[] = \MailSo\Mime\Enumerations\Constants::CRLF. + '--'.$sBoundary.\MailSo\Mime\Enumerations\Constants::CRLF; + } + + $aResult[] = $oPart->ToStream(); + } + + return \MailSo\Base\StreamWrappers\SubStreams::CreateStream($aResult); + } + + return $rResult; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Net/Enumerations/ConnectionSecurityType.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Net/Enumerations/ConnectionSecurityType.php new file mode 100644 index 0000000..fed485d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Net/Enumerations/ConnectionSecurityType.php @@ -0,0 +1,70 @@ +sSocketMessage = $sSocketMessage; + $this->iSocketCode = $iSocketCode; + } + + /** + * @return string + */ + public function getSocketMessage() + { + return $this->sSocketMessage; + } + + /** + * @return int + */ + public function getSocketCode() + { + return $this->iSocketCode; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php new file mode 100644 index 0000000..b406b3d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php @@ -0,0 +1,19 @@ +rConnect = null; + $this->bUnreadBuffer = false; + $this->bRunningCallback = false; + $this->oLogger = null; + + $this->__AUTOLOGOUT__ = true; + + $this->sResponseBuffer = ''; + + $this->iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::NONE; + $this->sConnectedHost = ''; + $this->iConnectedPort = 0; + + $this->bSecure = false; + + $this->iConnectTimeOut = 10; + $this->iSocketTimeOut = 10; + + $this->Clear(); + } + + /** + * @return void + */ + public function __destruct() + { + try + { + if ($this->__AUTOLOGOUT__) + { + $this->LogoutAndDisconnect(); + } + else + { + $this->Disconnect(); + } + } + catch (\Exception $oException) {} + } + + /** + * @return void + */ + public function Clear() + { + $this->sResponseBuffer = ''; + + $this->sConnectedHost = ''; + $this->iConnectedPort = 0; + + $this->iStartConnectTime = 0; + $this->bSecure = false; + } + + /** + * @return string + */ + public function GetConnectedHost() + { + return $this->sConnectedHost; + } + + /** + * @return int + */ + public function GetConnectedPort() + { + return $this->iConnectedPort; + } + + /** + * @param int $iConnectTimeOut = 10 + * @param int $iSocketTimeOut = 10 + * + * @return void + */ + public function SetTimeOuts($iConnectTimeOut = 10, $iSocketTimeOut = 10) + { + $this->iConnectTimeOut = 5 < $iConnectTimeOut ? $iConnectTimeOut : 5; + $this->iSocketTimeOut = 5 < $iSocketTimeOut ? $iSocketTimeOut : 5; + } + + /** + * @return resource|null + */ + public function ConnectionResource() + { + return $this->rConnect; + } + + /** + * @param int $iErrNo + * @param string $sErrStr + * @param string $sErrFile + * @param int $iErrLine + * + * @return bool + */ + public function capturePhpErrorWithException($iErrNo, $sErrStr, $sErrFile, $iErrLine) + { + throw new \MailSo\Base\Exceptions\Exception($sErrStr, $iErrNo); + } + + /** + * @param string $sServerName + * @param int $iPort + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * @param string $sClientCert = '' + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\SocketAlreadyConnectedException + * @throws \MailSo\Net\Exceptions\SocketCanNotConnectToHostException + */ + public function Connect($sServerName, $iPort, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true, + $sClientCert = '') + { + if (!\MailSo\Base\Validator::NotEmptyString($sServerName, true) || !\MailSo\Base\Validator::PortInt($iPort)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if ($this->IsConnected()) + { + $this->writeLogException( + new Exceptions\SocketAlreadyConnectedException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sServerName = \trim($sServerName); + + $sErrorStr = ''; + $iErrorNo = 0; + + $this->sConnectedHost = $sServerName; + $this->iConnectedPort = $iPort; + $this->iSecurityType = $iSecurityType; + $this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL( + $this->iConnectedPort, $this->iSecurityType); + + if (!\preg_match('/^[a-z0-9._]{2,8}:\/\//i', $this->sConnectedHost)) + { + $this->sConnectedHost = ($this->bSecure ? 'ssl://' : 'tcp://').$this->sConnectedHost; +// $this->sConnectedHost = ($this->bSecure ? 'ssl://' : '').$this->sConnectedHost; + } + + if (!$this->bSecure && \MailSo\Net\Enumerations\ConnectionSecurityType::SSL === $this->iSecurityType) + { + $this->writeLogException( + new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('SSL isn\'t supported: ('.\implode(', ', \stream_get_transports()).')'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->iStartConnectTime = \microtime(true); + $this->writeLog('Start connection to "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"', + \MailSo\Log\Enumerations\Type::NOTE); + +// $this->rConnect = @\fsockopen($this->sConnectedHost, $this->iConnectedPort, +// $iErrorNo, $sErrorStr, $this->iConnectTimeOut); + + $bVerifySsl = !!$bVerifySsl; + $bAllowSelfSigned = $bVerifySsl ? !!$bAllowSelfSigned : true; + $aStreamContextSettings = array( + 'ssl' => array( + 'verify_host' => $bVerifySsl, + 'verify_peer' => $bVerifySsl, + 'verify_peer_name' => $bVerifySsl, + 'allow_self_signed' => $bAllowSelfSigned + ) + ); + + if (!empty($sClientCert)) + { + $aStreamContextSettings['ssl']['local_cert'] = $sClientCert; + } + + \MailSo\Hooks::Run('Net.NetClient.StreamContextSettings/Filter', array(&$aStreamContextSettings)); + + $rStreamContext = \stream_context_create($aStreamContextSettings); + + \set_error_handler(array(&$this, 'capturePhpErrorWithException')); + + try + { + $this->rConnect = \stream_socket_client($this->sConnectedHost.':'.$this->iConnectedPort, + $iErrorNo, $sErrorStr, $this->iConnectTimeOut, STREAM_CLIENT_CONNECT, $rStreamContext); + } + catch (\Exception $oExc) + { + $sErrorStr = $oExc->getMessage(); + $iErrorNo = $oExc->getCode(); + } + + \restore_error_handler(); + + $this->writeLog('Connected ('.(\is_resource($this->rConnect) ? 'success' : 'unsuccess').')', + \MailSo\Log\Enumerations\Type::NOTE); + + if (!\is_resource($this->rConnect)) + { + $this->writeLogException( + new Exceptions\SocketCanNotConnectToHostException( + \MailSo\Base\Utils::ConvertSystemString($sErrorStr), (int) $iErrorNo, + 'Can\'t connect to host "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"' + ), \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->writeLog((\microtime(true) - $this->iStartConnectTime).' (raw connection)', + \MailSo\Log\Enumerations\Type::TIME); + + if ($this->rConnect) + { + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('stream_set_timeout')) + { + @\stream_set_timeout($this->rConnect, $this->iSocketTimeOut); + } + } + } + + public function EnableCrypto() + { + $bError = true; + if (\is_resource($this->rConnect) && + \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_socket_enable_crypto')) + { + switch (true) + { + case defined('STREAM_CRYPTO_METHOD_ANY_CLIENT') && + @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_ANY_CLIENT): + case defined('STREAM_CRYPTO_METHOD_TLS_CLIENT') && + @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT): + case defined('STREAM_CRYPTO_METHOD_SSLv23_CLIENT') && + @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT): + $bError = false; + break; + } + } + + if ($bError) + { + $this->writeLogException( + new \MailSo\Net\Exceptions\Exception('Cannot enable STARTTLS.'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + } + + /** + * @return void + */ + public function Disconnect() + { + if (\is_resource($this->rConnect)) + { + $bResult = \fclose($this->rConnect); + + $this->writeLog('Disconnected from "'.$this->sConnectedHost.':'.$this->iConnectedPort.'" ('. + (($bResult) ? 'success' : 'unsuccess').')', \MailSo\Log\Enumerations\Type::NOTE); + + if (0 !== $this->iStartConnectTime) + { + $this->writeLog((\microtime(true) - $this->iStartConnectTime).' (net session)', + \MailSo\Log\Enumerations\Type::TIME); + + $this->iStartConnectTime = 0; + } + + $this->rConnect = null; + } + } + + /** + * @retun void + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function LogoutAndDisconnect() + { + if (\method_exists($this, 'Logout') && !$this->bUnreadBuffer && !$this->bRunningCallback) + { + $this->Logout(); + } + + $this->Disconnect(); + } + + /** + * @param bool $bThrowExceptionOnFalse = false + * + * @return bool + */ + public function IsConnected($bThrowExceptionOnFalse = false) + { + $bResult = \is_resource($this->rConnect); + if (!$bResult && $bThrowExceptionOnFalse) + { + $this->writeLogException( + new Exceptions\SocketConnectionDoesNotAvailableException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $bResult; + } + + /** + * @return void + * + * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException + */ + public function IsConnectedWithException() + { + $this->IsConnected(true); + } + + /** + * @return array|bool + */ + public function StreamContextParams() + { + return \is_resource($this->rConnect) && \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_context_get_options') + ? \stream_context_get_params($this->rConnect) : false; + } + + /** + * @param string $sRaw + * @param bool $bWriteToLog = true + * @param string $sFakeRaw = '' + * + * @return void + * + * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException + * @throws \MailSo\Net\Exceptions\SocketWriteException + */ + protected function sendRaw($sRaw, $bWriteToLog = true, $sFakeRaw = '') + { + if ($this->bUnreadBuffer) + { + $this->writeLogException( + new Exceptions\SocketUnreadBufferException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $bFake = 0 < \strlen($sFakeRaw); + $sRaw .= "\r\n"; + + if ($this->oLogger && $this->oLogger->IsShowSecter()) + { + $bFake = false; + } + + if ($bFake) + { + $sFakeRaw .= "\r\n"; + } + + $mResult = @\fwrite($this->rConnect, $sRaw); + if (false === $mResult) + { + $this->IsConnected(true); + + $this->writeLogException( + new Exceptions\SocketWriteException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + else + { + \MailSo\Base\Loader::IncStatistic('NetWrite', $mResult); + + if ($bWriteToLog) + { + $this->writeLogWithCrlf('> '.($bFake ? $sFakeRaw : $sRaw), //.' ['.$iWriteSize.']', + $bFake ? \MailSo\Log\Enumerations\Type::SECURE : \MailSo\Log\Enumerations\Type::INFO); + } + } + } + + /** + * @param mixed $mReadLen = null + * @param bool $bForceLogin = false + * + * @return void + * + * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException + * @throws \MailSo\Net\Exceptions\SocketReadException + */ + protected function getNextBuffer($mReadLen = null, $bForceLogin = false) + { + if (null === $mReadLen) + { + $this->sResponseBuffer = @\fgets($this->rConnect); + } + else + { + $this->sResponseBuffer = ''; + $iRead = $mReadLen; + while (0 < $iRead) + { + $sAddRead = @\fread($this->rConnect, $iRead); + if (false === $sAddRead) + { + $this->sResponseBuffer = false; + break; + } + + $this->sResponseBuffer .= $sAddRead; + $iRead -= \strlen($sAddRead); + } + } + + if (false === $this->sResponseBuffer) + { + $this->IsConnected(true); + $this->bUnreadBuffer = true; + + $aSocketStatus = @\stream_get_meta_data($this->rConnect); + if (isset($aSocketStatus['timed_out']) && $aSocketStatus['timed_out']) + { + $this->writeLogException( + new Exceptions\SocketReadTimeoutException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + else + { + $this->writeLog('Stream Meta: '. + \print_r($aSocketStatus, true), \MailSo\Log\Enumerations\Type::ERROR); + + $this->writeLogException( + new Exceptions\SocketReadException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + } + else + { + $iReadedLen = \strlen($this->sResponseBuffer); + if (null === $mReadLen || $bForceLogin) + { + $iLimit = 5000; // 5KB + if ($iLimit < $iReadedLen) + { + $this->writeLogWithCrlf('[cutted:'.$iReadedLen.'] < '.\substr($this->sResponseBuffer, 0, $iLimit).'...', + \MailSo\Log\Enumerations\Type::INFO); + } + else + { + $this->writeLogWithCrlf('< '.$this->sResponseBuffer, //.' ['.$iReadedLen.']', + \MailSo\Log\Enumerations\Type::INFO); + } + } + else + { + $this->writeLog('Received '.$iReadedLen.'/'.$mReadLen.' bytes.', + \MailSo\Log\Enumerations\Type::INFO); + } + + \MailSo\Base\Loader::IncStatistic('NetRead', $iReadedLen); + } + } + + /** + * @return string + */ + protected function getLogName() + { + return 'NET'; + } + + /** + * @param string $sDesc + * @param int $iDescType = \MailSo\Log\Enumerations\Type::INFO + * + * @return void + */ + protected function writeLog($sDesc, $iDescType = \MailSo\Log\Enumerations\Type::INFO, $bDiplayCrLf = false) + { + if ($this->oLogger) + { + $this->oLogger->Write($sDesc, $iDescType, $this->getLogName(), true, $bDiplayCrLf); + } + } + + /** + * @param string $sDesc + * @param int $iDescType = \MailSo\Log\Enumerations\Type::INFO + * + * @return void + */ + protected function writeLogWithCrlf($sDesc, $iDescType = \MailSo\Log\Enumerations\Type::INFO) + { + $this->writeLog($sDesc, $iDescType, true); + } + + /** + * @param \Exception $oException + * @param int $iDescType = \MailSo\Log\Enumerations\Type::NOTICE + * @param bool $bThrowException = false + * + * @return void + */ + protected function writeLogException($oException, + $iDescType = \MailSo\Log\Enumerations\Type::NOTICE, $bThrowException = false) + { + if ($this->oLogger) + { + if ($oException instanceof Exceptions\SocketCanNotConnectToHostException) + { + $this->oLogger->Write('Socket: ['.$oException->getSocketCode().'] '.$oException->getSocketMessage(), $iDescType, $this->getLogName()); + } + + $this->oLogger->WriteException($oException, $iDescType, $this->getLogName()); + } + + if ($bThrowException) + { + throw $oException; + } + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + if (!($oLogger instanceof \MailSo\Log\Logger)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oLogger = $oLogger; + } + + /** + * @return \MailSo\Log\Logger|null + */ + public function Logger() + { + return $this->oLogger; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php new file mode 100644 index 0000000..0cc582d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php @@ -0,0 +1,19 @@ +aResponses = $aResponses; + } + } + + /** + * @return array + */ + public function GetResponses() + { + return $this->aResponses; + } + + /** + * @return \MailSo\Pop3\Response | null + */ + public function GetLastResponse() + { + return 0 < count($this->aResponses) ? $this->aResponses[count($this->aResponses) - 1] : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Pop3/Exceptions/RuntimeException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Pop3/Exceptions/RuntimeException.php new file mode 100644 index 0000000..047c32a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Pop3/Exceptions/RuntimeException.php @@ -0,0 +1,19 @@ +bIsLoggined = false; + $this->iRequestTime = 0; + $this->aCapa = null; + $this->sLastMessage = ''; + } + + /** + * @return \MailSo\Pop3\Pop3Client + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sServerName + * @param int $iPort = 110 + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = null + * + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\ResponseException + */ + public function Connect($sServerName, $iPort = 110, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = null) + { + $this->iRequestTime = microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $this->validateResponse(); + + if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( + in_array('STLS', $this->Capa()), $this->iSecurityType)) + { + $this->sendRequestWithCheck('STLS'); + $this->EnableCrypto(); + + $this->aCapa = 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; + } + + /** + * @param string $sLogin = '' + * @param string $sPassword = '' + * + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\ResponseException + */ + public function Login($sLogin, $sPassword) + { + if ($this->bIsLoggined) + { + $this->writeLogException( + new Exceptions\RuntimeException('Already authenticated for this session'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sLogin = trim($sLogin); + $sPassword = $sPassword; + + try + { + $this->sendRequestWithCheck('USER', $sLogin); + $this->sendRequestWithCheck('PASS', $sPassword); + } + catch (\MailSo\Pop3\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Pop3\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + $this->aCapa = null; + + return $this; + } + + /** + * @return bool + */ + public function IsLoggined() + { + return $this->IsConnected() && $this->bIsLoggined; + } + + /** + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\Exception + */ + public function Noop() + { + $this->sendRequestWithCheck('NOOP'); + return $this; + } + + /** + * @return array [MessagesCount, Size] + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\Exception + */ + public function Status() + { + $this->sendRequestWithCheck('STAT'); + + $iMessageCount = $iSize = 0; + sscanf($this->sLastMessage, '%d %d', $iMessageCount, $iSize); + + return array((int) $iMessageCount, (int) $iSize); + } + + /** + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\Exception + */ + public function Capa() + { + if (null === $this->aCapa) + { + $this->sendRequestWithCheck('CAPA'); + + $this->aCapa = array_filter(explode("\n", $this->readMultilineResponse()), function (&$sCapa) { + return 0 < strlen(trim($sCapa)); + }); + + $this->aCapa = array_map('trim', $this->aCapa); + } + + return $this->aCapa; + } + + /** + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\Exception + */ + public function Logout() + { + if ($this->bIsLoggined) + { + $this->sendRequestWithCheck('QUIT'); + } + + $this->bIsLoggined = false; + return $this; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand = '' + * + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + protected function sendRequest($sCommand, $sAddToCommand = '') + { + if (0 === strlen(trim($sCommand))) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $sCommand = trim($sCommand); + $sRealCommand = $sCommand.(0 === strlen($sAddToCommand) ? '' : ' '.$sAddToCommand); + + $sFakeCommand = ''; + $sFakeAddToCommand = $this->secureRequestParams($sCommand, $sAddToCommand); + if (0 < strlen($sFakeAddToCommand)) + { + $sFakeCommand = $sCommand.' '.$sFakeAddToCommand; + } + + $this->iRequestTime = microtime(true); + $this->sendRaw($sRealCommand, true, $sFakeCommand); + return $this; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand + * + * @return string + */ + private function secureRequestParams($sCommand, $sAddToCommand) + { + $sResult = null; + if (0 < strlen($sAddToCommand)) + { + switch ($sCommand) + { + case 'PASS': + $sResult = '********'; + break; + } + } + + return $sResult; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand = '' + * + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\Exception + */ + private function sendRequestWithCheck($sCommand, $sAddToCommand = '') + { + $this->sendRequest($sCommand, $sAddToCommand); + return $this->validateResponse(); + } + + /** + * @return string + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Pop3\Exceptions\ResponseException + */ + private function validateResponse() + { + $this->getNextBuffer(); + + $this->sLastMessage = ''; + $sStatus = $sMessage = ''; + $sBuffer = trim($this->sResponseBuffer); + $sStatus = $sBuffer; + if (false !== strpos($sBuffer, ' ')) + { + list($sStatus, $sMessage) = explode(' ', $sBuffer, 2); + } + + $this->sLastMessage = $sMessage; + + if ($sStatus != '+OK') + { + $this->writeLogException( + new Exceptions\NegativeResponseException(), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + $this->writeLog((microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + } + + /** + * @return string + * + * @throws \MailSo\Net\Exceptions\Exception + */ + private function readMultilineResponse() + { + $this->iRequestTime = microtime(true); + + $sResult = ''; + do + { + $this->getNextBuffer(); + if (0 === strpos($this->sResponseBuffer, '.')) + { + $sResult .= substr($this->sResponseBuffer, 1); + } + else + { + $sResult .= $this->sResponseBuffer; + } + } + while ('.' !== rtrim($this->sResponseBuffer, "\r\n")); + + $this->writeLog((microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + + return $sResult; + } + + /** + * @return string + */ + protected function getLogName() + { + return 'POP3'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * @return \MailSo\Pop3\Pop3Client + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php new file mode 100644 index 0000000..76097ff --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php @@ -0,0 +1,19 @@ +aResponses = $aResponses; + } + } + + /** + * @return array + */ + public function GetResponses() + { + return $this->aResponses; + } + + /** + * @return \MailSo\Poppassd\Response | null + */ + public function GetLastResponse() + { + return 0 < \count($this->aResponses) ? $this->aResponses[count($this->aResponses) - 1] : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Poppassd/Exceptions/RuntimeException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Poppassd/Exceptions/RuntimeException.php new file mode 100644 index 0000000..5b94cb3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Poppassd/Exceptions/RuntimeException.php @@ -0,0 +1,19 @@ +bIsLoggined = false; + $this->iRequestTime = 0; + + parent::__construct(); + } + + /** + * @return \MailSo\Poppassd\PoppassdClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sServerName + * @param int $iPort = 106 + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Poppassd\Exceptions\ResponseException + */ + public function Connect($sServerName, $iPort = 106, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) + { + $this->iRequestTime = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $this->validateResponse(); + + return $this; + } + + /** + * @param string $sLogin = '' + * @param string $sPassword = '' + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Poppassd\Exceptions\ResponseException + */ + public function Login($sLogin, $sPassword) + { + if ($this->bIsLoggined) + { + $this->writeLogException( + new Exceptions\RuntimeException('Already authenticated for this session'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sLogin = \trim($sLogin); + $sPassword = $sPassword; + + try + { + $this->sendRequestWithCheck('user', $sLogin, true); + $this->sendRequestWithCheck('pass', $sPassword, true); + } + catch (\MailSo\Poppassd\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Poppassd\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + + return $this; + } + + /** + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Poppassd\Exceptions\Exception + */ + public function Logout() + { + if ($this->bIsLoggined) + { + $this->sendRequestWithCheck('quit'); + } + + $this->bIsLoggined = false; + return $this; + } + + /** + * @param string $sNewPassword + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Poppassd\Exceptions\Exception + */ + public function NewPass($sNewPassword) + { + if ($this->bIsLoggined) + { + $this->sendRequestWithCheck('newpass', $sNewPassword); + } + else + { + $this->writeLogException( + new \MailSo\Poppassd\Exceptions\RuntimeException('Required login'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $this; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand + * + * @return string + */ + private function secureRequestParams($sCommand, $sAddToCommand) + { + $sResult = null; + if (0 < \strlen($sAddToCommand)) + { + switch (\strtolower($sCommand)) + { + case 'pass': + case 'newpass': + $sResult = '********'; + break; + } + } + + return $sResult; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand = '' + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + private function sendRequest($sCommand, $sAddToCommand = '') + { + if (0 === \strlen(\trim($sCommand))) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $sCommand = \trim($sCommand); + $sRealCommand = $sCommand.(0 === \strlen($sAddToCommand) ? '' : ' '.$sAddToCommand); + + $sFakeCommand = ''; + $sFakeAddToCommand = $this->secureRequestParams($sCommand, $sAddToCommand); + if (0 < \strlen($sFakeAddToCommand)) + { + $sFakeCommand = $sCommand.' '.$sFakeAddToCommand; + } + + $this->iRequestTime = \microtime(true); + $this->sendRaw($sRealCommand, true, $sFakeCommand); + + return $this; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand = '' + * @param bool $bAuthRequestValidate = false + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Poppassd\Exceptions\Exception + */ + private function sendRequestWithCheck($sCommand, $sAddToCommand = '', $bAuthRequestValidate = false) + { + $this->sendRequest($sCommand, $sAddToCommand); + $this->validateResponse($bAuthRequestValidate); + + return $this; + } + + /** + * @param bool $bAuthRequestValidate = false + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Poppassd\Exceptions\ResponseException + */ + private function validateResponse($bAuthRequestValidate = false) + { + $this->getNextBuffer(); + + $bResult = false; + if ($bAuthRequestValidate) + { + $bResult = (bool) \preg_match('/^[23]\d\d/', trim($this->sResponseBuffer)); + } + else + { + $bResult = (bool) \preg_match('/^2\d\d/', \trim($this->sResponseBuffer)); + } + + if (!$bResult) + { + // POP3 validation hack + $bResult = '+OK ' === \substr(\trim($this->sResponseBuffer), 0, 4); + } + + if (!$bResult) + { + $this->writeLogException( + new Exceptions\NegativeResponseException(), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + $this->writeLog((\microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + + return $this; + } + + /** + * @return string + */ + protected function getLogName() + { + return 'POPPASSD'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Poppassd\PoppassdClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php new file mode 100644 index 0000000..0740485 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php @@ -0,0 +1,19 @@ +aResponses = $aResponses; + } + } + + /** + * @return array + */ + public function GetResponses() + { + return $this->aResponses; + } + + /** + * @return \MailSo\Sieve\Response | null + */ + public function GetLastResponse() + { + return 0 < count($this->aResponses) ? $this->aResponses[count($this->aResponses) - 1] : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Sieve/Exceptions/RuntimeException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Sieve/Exceptions/RuntimeException.php new file mode 100644 index 0000000..8704e46 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Sieve/Exceptions/RuntimeException.php @@ -0,0 +1,19 @@ +bIsLoggined = false; + $this->iRequestTime = 0; + $this->aCapa = array(); + $this->aModules = array(); + + $this->__USE_INITIAL_AUTH_PLAIN_COMMAND = true; + } + + /** + * @return \MailSo\Sieve\ManageSieveClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sCapa + * + * @return bool + */ + public function IsSupported($sCapa) + { + return isset($this->aCapa[\strtoupper($sCapa)]); + } + + /** + * @param string $sModule + * + * @return bool + */ + public function IsModuleSupported($sModule) + { + return $this->IsSupported('SIEVE') && \in_array(\strtolower(\trim($sModule)), $this->aModules); + } + + /** + * @return array + */ + public function Modules() + { + return $this->aModules; + } + + /** + * @param string $sAuth + * + * @return bool + */ + public function IsAuthSupported($sAuth) + { + return $this->IsSupported('SASL') && \in_array(\strtoupper($sAuth), $this->aAuth); + } + + /** + * @param string $sServerName + * @param int $iPort + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Sieve\Exceptions\ResponseException + */ + public function Connect($sServerName, $iPort, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) + { + $this->iRequestTime = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + + if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( + $this->IsSupported('STARTTLS'), $this->iSecurityType)) + { + $this->sendRequestWithCheck('STARTTLS'); + $this->EnableCrypto(); + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + } + 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; + } + + /** + * @param string $sLogin + * @param string $sPassword + * @param string $sLoginAuthKey = '' + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Sieve\Exceptions\LoginException + */ + public function Login($sLogin, $sPassword, $sLoginAuthKey = '') + { + 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); + } + + if ($this->IsSupported('SASL')) + { + $bAuth = false; + try + { + if ($this->IsAuthSupported('PLAIN')) + { + $sAuth = \base64_encode($sLoginAuthKey."\0".$sLogin."\0".$sPassword); + + if ($this->__USE_INITIAL_AUTH_PLAIN_COMMAND) + { + $this->sendRequest('AUTHENTICATE "PLAIN" "'.$sAuth.'"'); + } + else + { + $this->sendRequest('AUTHENTICATE "PLAIN" {'.\strlen($sAuth).'+}'); + $this->sendRequest($sAuth); + } + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + $bAuth = true; + } + else if ($this->IsAuthSupported('LOGIN')) + { + $sLogin = \base64_encode($sLogin); + $sPassword = \base64_encode($sPassword); + + $this->sendRequest('AUTHENTICATE "LOGIN"'); + $this->sendRequest('{'.\strlen($sLogin).'+}'); + $this->sendRequest($sLogin); + $this->sendRequest('{'.\strlen($sPassword).'+}'); + $this->sendRequest($sPassword); + + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + $bAuth = true; + } + } + catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$bAuth) + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\LoginBadMethodException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + } + else + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\LoginException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->bIsLoggined = true; + + return $this; + } + + /** + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function Logout() + { + if ($this->bIsLoggined) + { + $this->sendRequestWithCheck('LOGOUT'); + $this->bIsLoggined = false; + } + + return $this; + } + + /** + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function ListScripts() + { + $this->sendRequest('LISTSCRIPTS'); + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + + $aResult = array(); + if (\is_array($mResponse)) + { + foreach ($mResponse as $sLine) + { + $aTokens = $this->parseLine($sLine); + if (false === $aTokens) + { + continue; + } + + $aResult[$aTokens[0]] = 'ACTIVE' === substr($sLine, -6); + } + } + + return $aResult; + } + + /** + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function Capability() + { + $this->sendRequest('CAPABILITY'); + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + $this->parseStartupResponse($mResponse); + + return $this->aCapa; + } + + /** + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function Noop() + { + $this->sendRequestWithCheck('NOOP'); + + return $this; + } + + /** + * @param string $sScriptName + * + * @return string + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function GetScript($sScriptName) + { + $this->sendRequest('GETSCRIPT "'.$sScriptName.'"'); + $mResponse = $this->parseResponse(); + $this->validateResponse($mResponse); + + $sScript = ''; + if (\is_array($mResponse) && 0 < \count($mResponse)) + { + if ('{' === $mResponse[0]{0}) + { + \array_shift($mResponse); + } + + if (\in_array(\substr($mResponse[\count($mResponse) - 1], 0, 2), array('OK', 'NO'))) + { + \array_pop($mResponse); + } + + $sScript = \implode("\n", $mResponse); + } + + return $sScript; + } + + /** + * @param string $sScriptName + * @param string $sScriptSource + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function PutScript($sScriptName, $sScriptSource) + { + $this->sendRequest('PUTSCRIPT "'.$sScriptName.'" {'.\strlen($sScriptSource).'+}'); + $this->sendRequestWithCheck($sScriptSource); + + return $this; + } + + /** + * @param string $sScriptSource + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function CheckScript($sScriptSource) + { + $this->sendRequest('CHECKSCRIPT {'.\strlen($sScriptSource).'+}'); + $this->sendRequestWithCheck($sScriptSource); + + return $this; + } + + /** + * @param string $sScriptName + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function SetActiveScript($sScriptName) + { + $this->sendRequestWithCheck('SETACTIVE "'.$sScriptName.'"'); + + return $this; + } + + /** + * @param string $sScriptName + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function DeleteScript($sScriptName) + { + $this->sendRequestWithCheck('DELETESCRIPT "'.$sScriptName.'"'); + + return $this; + } + + /** + * @return string + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function GetActiveScriptName() + { + $aList = $this->ListScripts(); + if (\is_array($aList) && 0 < \count($aList)) + { + foreach ($aList as $sName => $bIsActive) + { + if ($bIsActive) + { + return $sName; + } + } + } + + return ''; + } + + /** + * @param string $sScriptName + * + * @return bool + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function IsActiveScript($sScriptName) + { + return $sScriptName === $this->GetActiveScriptName(); + } + + /** + * @param string $sLine + * @return array|false + */ + private function parseLine($sLine) + { + if (false === $sLine || null === $sLine || \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) + { + return false; + } + + $iStart = -1; + $iIndex = 0; + $aResult = false; + + for ($iPos = 0; $iPos < \strlen($sLine); $iPos++) + { + if ('"' === $sLine[$iPos] && '\\' !== $sLine[$iPos]) + { + if (-1 === $iStart) + { + $iStart = $iPos; + } + else + { + $aResult = \is_array($aResult) ? $aResult : array(); + $aResult[$iIndex++] = \substr($sLine, $iStart + 1, $iPos - $iStart - 1); + $iStart = -1; + } + } + } + + return \is_array($aResult) && isset($aResult[0]) ? $aResult : false; + } + + /** + * @param string $mResponse + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + private function parseStartupResponse($mResponse) + { + foreach ($mResponse as $sLine) + { + $aTokens = $this->parseLine($sLine); + + if (false === $aTokens || !isset($aTokens[0]) || + \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) + { + continue; + } + + $sToken = \strtoupper($aTokens[0]); + $this->aCapa[$sToken] = isset($aTokens[1]) ? $aTokens[1] : ''; + + if (isset($aTokens[1])) + { + switch ($sToken) { + case 'SASL': + $this->aAuth = \explode(' ', \strtoupper($aTokens[1])); + break; + case 'SIEVE': + $this->aModules = \explode(' ', \strtolower($aTokens[1])); + break; + } + } + } + } + + /** + * @param string $sRequest + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + private function sendRequest($sRequest) + { + if (!\MailSo\Base\Validator::NotEmptyString($sRequest, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $this->sendRaw($sRequest); + } + + /** + * @param string $sRequest + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + private function sendRequestWithCheck($sRequest) + { + $this->sendRequest($sRequest); + $this->validateResponse($this->parseResponse()); + } + + /** + * @param string $sLine + * + * @return string + */ + private function convertEndOfLine($sLine) + { + $sLine = \trim($sLine); + if ('}' === \substr($sLine, -1)) + { + $iPos = \strrpos($sLine, '{'); + if (false !== $iPos) + { + $sSunLine = \substr($sLine, $iPos + 1, -1); + if (\is_numeric($sSunLine) && 0 < (int) $sSunLine) + { + $iLen = (int) $sSunLine; + + $this->getNextBuffer($iLen, true); + + if (\strlen($this->sResponseBuffer) === $iLen) + { + $sLine = \trim(\substr_replace($sLine, $this->sResponseBuffer, $iPos)); + } + } + } + } + + return $sLine; + } + + /** + * @return array|bool + */ + private function parseResponse() + { + $this->iRequestTime = \microtime(true); + + $aResult = array(); + do + { + $this->getNextBuffer(); + + $sLine = $this->sResponseBuffer; + if (false === $sLine) + { + break; + } + else if (\in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) + { + $aResult[] = $this->convertEndOfLine($sLine); + break; + } + else + { + $aResult[] = $this->convertEndOfLine($sLine); + } + } + while (true); + + $this->writeLog((\microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + + return $aResult; + } + + /** + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + private function validateResponse($aResponse) + { + if (!\is_array($aResponse) || 0 === \count($aResponse) || + 'OK' !== \substr($aResponse[\count($aResponse) - 1], 0, 2)) + { + $this->writeLogException( + new \MailSo\Sieve\Exceptions\NegativeResponseException($aResponse), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + } + + /** + * @return string + */ + protected function getLogName() + { + return 'SIEVE'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Sieve\ManageSieveClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php new file mode 100644 index 0000000..7c18f18 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php @@ -0,0 +1,19 @@ +aResponses = $aResponses; + } + } + + /** + * @return array + */ + public function GetResponses() + { + return $this->aResponses; + } + + /** + * @return \MailSo\Smtp\Response | null + */ + public function GetLastResponse() + { + return 0 < count($this->aResponses) ? $this->aResponses[count($this->aResponses) - 1] : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Smtp/Exceptions/RuntimeException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Smtp/Exceptions/RuntimeException.php new file mode 100644 index 0000000..294b93b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Smtp/Exceptions/RuntimeException.php @@ -0,0 +1,19 @@ +aAuthTypes = array(); + + $this->iRequestTime = 0; + $this->iSizeCapaValue = 0; + $this->aResults = array(); + $this->aCapa = array(); + + $this->bHelo = false; + $this->bRcpt = false; + $this->bMail = false; + $this->bData = false; + } + + /** + * @return \MailSo\Smtp\SmtpClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return bool + */ + public function IsSupported($sCapa) + { + return in_array(strtoupper($sCapa), $this->aCapa); + } + + /** + * @return bool + */ + public function IsAuthSupported($sAuth) + { + return in_array(strtoupper($sAuth), $this->aAuthTypes); + } + + /** + * @return bool + */ + public function HasSupportedAuth() + { + return $this->IsAuthSupported('PLAIN') || $this->IsAuthSupported('LOGIN'); + } + + /** + * @return string + */ + public static function EhloHelper() + { + $sEhloHost = empty($_SERVER['SERVER_NAME']) ? '' : \trim($_SERVER['SERVER_NAME']); + if (empty($sEhloHost)) + { + $sEhloHost = empty($_SERVER['HTTP_HOST']) ? '' : \trim($_SERVER['HTTP_HOST']); + } + + if (empty($sEhloHost)) + { + $sEhloHost = \function_exists('gethostname') ? \gethostname() : 'localhost'; + } + + $sEhloHost = \trim(\preg_replace('/:\d+$/', '', \trim($sEhloHost))); + + if (\preg_match('/^\d+\.\d+\.\d+\.\d+$/', $sEhloHost)) + { + $sEhloHost = '['.$sEhloHost.']'; + } + + return empty($sEhloHost) ? 'localhost' : $sEhloHost; + } + + /** + * @param string $sServerName + * @param int $iPort = 25 + * @param string $sEhloHost = '[127.0.0.1]' + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\ResponseException + */ + public function Connect($sServerName, $iPort = 25, $sEhloHost = '[127.0.0.1]', + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) + { + $this->iRequestTime = microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $this->validateResponse(220); + + $this->preLoginStartTLSAndEhloProcess($sEhloHost); + + return $this; + } + + /** + * @param string $sLogin + * @param string $sPassword + * @param boolean $bUseAuthPlainIfSupported = true + * @param boolean $bUseAuthCramMd5IfSupported = true + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Login($sLogin, $sPassword, $bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true) + { + $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); + + if ($bUseAuthCramMd5IfSupported && $this->IsAuthSupported('CRAM-MD5')) + { + try + { + $this->sendRequestWithCheck('AUTH', 334, 'CRAM-MD5'); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadMethodException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $sTicket = ''; + + $sContinuationResponse = !empty($this->aResults[0]) ? \trim($this->aResults[0]) : ''; + if ($sContinuationResponse && '334 ' === \substr($sContinuationResponse, 0, 4) && 0 < \strlen(\substr($sContinuationResponse, 4))) + { + $sTicket = @\base64_decode(\substr($sContinuationResponse, 4)); + $this->writeLogWithCrlf('ticket: '.$sTicket); + } + + if (empty($sTicket)) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\NegativeResponseException(), + \MailSo\Log\Enumerations\Type::NOTICE, true + ); + } + + try + { + $this->sendRequestWithCheck(\base64_encode($sLogin.' '.\MailSo\Base\Utils::Hmac($sTicket, $sPassword)), 235, '', true); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else if ($bUseAuthPlainIfSupported && $this->IsAuthSupported('PLAIN')) + { + if ($this->__USE_SINGLE_LINE_AUTH_PLAIN_COMMAND) + { + try + { + $this->sendRequestWithCheck('AUTH', 235, 'PLAIN '.\base64_encode("\0".$sLogin."\0".$sPassword), true); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else + { + try + { + $this->sendRequestWithCheck('AUTH', 334, 'PLAIN'); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadMethodException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + try + { + $this->sendRequestWithCheck(\base64_encode("\0".$sLogin."\0".$sPassword), 235, '', true); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + } + else if ($this->IsAuthSupported('LOGIN')) + { + try + { + $this->sendRequestWithCheck('AUTH', 334, 'LOGIN'); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadMethodException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + try + { + $this->sendRequestWithCheck(\base64_encode($sLogin), 334, ''); + $this->sendRequestWithCheck(\base64_encode($sPassword), 235, '', true); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadMethodException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + return $this; + } + + /** + * @param string $sXOAuth2Token + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function LoginWithXOauth2($sXOAuth2Token) + { + if ($this->IsAuthSupported('XOAUTH2')) + { + try + { + $this->sendRequestWithCheck('AUTH', 235, 'XOAUTH2 '.\trim($sXOAuth2Token)); + } + catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), $oException->getMessage(), 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else + { + $this->writeLogException( + new \MailSo\Smtp\Exceptions\LoginBadMethodException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + return $this; + } + + /** + * @param string $sFrom + * @param string $sSizeIfSupported = '' + * @param bool $bDsn = false + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function MailFrom($sFrom, $sSizeIfSupported = '', $bDsn = false) + { + $sFrom = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sFrom), true); + + $sCmd = 'FROM:<'.$sFrom.'>'; + + $sSizeIfSupported = (string) $sSizeIfSupported; + if (0 < \strlen($sSizeIfSupported) && \is_numeric($sSizeIfSupported) && $this->IsSupported('SIZE')) + { + $sCmd .= ' SIZE='.$sSizeIfSupported; + } + + if ($bDsn && $this->IsSupported('DSN')) + { + $sCmd .= ' RET=HDRS'; + } + + $this->sendRequestWithCheck('MAIL', 250, $sCmd); + + $this->bMail = true; + $this->bRcpt = false; + $this->bData = false; + + return $this; + } + + /** + * @param string $sTo + * @param bool $bDsn = false + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Rcpt($sTo, $bDsn = false) + { + if (!$this->bMail) + { + $this->writeLogException( + new Exceptions\RuntimeException('No sender reverse path has been supplied'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sTo = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sTo), true); + + $sCmd = 'TO:<'.$sTo.'>'; + + if ($bDsn && $this->IsSupported('DSN')) + { + $sCmd .= ' NOTIFY=SUCCESS,FAILURE'; + } + + $this->sendRequestWithCheck( + 'RCPT', array(250, 251), $sCmd, false, + 'Failed to add recipient "'.$sTo.'"' + ); + + $this->bRcpt = true; + + return $this; + } + + /** + * @param string $sTo + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function MailTo($sTo) + { + return $this->Rcpt($sTo); + } + + /** + * @param string $sData + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Data($sData) + { + if (!\MailSo\Base\Validator::NotEmptyString($sData, true)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $rDataStream = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($sData); + unset($sData); + $this->DataWithStream($rDataStream); + \MailSo\Base\ResourceRegistry::CloseMemoryResource($rDataStream); + + return $this; + } + + /** + * @param resource $rDataStream + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function DataWithStream($rDataStream) + { + if (!\is_resource($rDataStream)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + if (!$this->bRcpt) + { + $this->writeLogException( + new Exceptions\RuntimeException('No recipient forward path has been supplied'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->sendRequestWithCheck('DATA', 354); + + $this->writeLog('Message data.', \MailSo\Log\Enumerations\Type::NOTE); + + $this->bRunningCallback = true; + + while (!\feof($rDataStream)) + { + $sBuffer = \fgets($rDataStream); + if (false !== $sBuffer) + { + if (0 === \strpos($sBuffer, '.')) + { + $sBuffer = '.'.$sBuffer; + } + + $this->sendRaw(\rtrim($sBuffer, "\r\n"), false); + + \MailSo\Base\Utils::ResetTimeLimit(); + continue; + } + else if (!\feof($rDataStream)) + { + $this->writeLogException( + new Exceptions\RuntimeException('Cannot read input resource'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + break; + } + + $this->sendRequestWithCheck('.', 250); + + $this->bRunningCallback = false; + + $this->bData = true; + + return $this; + } + + /** + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Rset() + { + $this->sendRequestWithCheck('RSET', array(250, 220)); + + $this->bMail = false; + $this->bRcpt = false; + $this->bData = false; + + return $this; + } + + /** + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Vrfy($sUser) + { + $sUser = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sUser)); + + $this->sendRequestWithCheck('VRFY', array(250, 251, 252), $sUser); + + return $this; + } + + /** + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Noop() + { + $this->sendRequestWithCheck('NOOP', 250); + + return $this; + } + + /** + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + public function Logout() + { + if ($this->IsConnected()) + { + $this->sendRequestWithCheck('QUIT', 221); + } + + $this->bHelo = false; + $this->bMail = false; + $this->bRcpt = false; + $this->bData = false; + + return $this; + } + + /** + * @param string $sEhloHost + * + * @return void + */ + private function preLoginStartTLSAndEhloProcess($sEhloHost) + { + if ($this->bHelo) + { + $this->writeLogException( + new Exceptions\RuntimeException('Cannot issue EHLO/HELO to existing session'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->ehloOrHelo($sEhloHost); + + if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( + $this->IsSupported('STARTTLS'), $this->iSecurityType, $this->HasSupportedAuth())) + { + $this->sendRequestWithCheck('STARTTLS', 220); + $this->EnableCrypto(); + + $this->ehloOrHelo($sEhloHost); + } + 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); + } + + $this->bHelo = true; + } + + /** + * @param string $sCommand + * @param string $sAddToCommand = '' + * @param bool $bSecureLog = false + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + private function sendRequest($sCommand, $sAddToCommand = '', $bSecureLog = false) + { + if (!\MailSo\Base\Validator::NotEmptyString($sCommand, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $sCommand = \trim($sCommand); + $sRealCommand = $sCommand.(0 === \strlen($sAddToCommand) ? '' : ' '.$sAddToCommand); + + $sFakeCommand = ($bSecureLog) ? '********' : ''; + + $this->iRequestTime = \microtime(true); + $this->sendRaw($sRealCommand, true, $sFakeCommand); + + return $this; + } + + /** + * @param string $sCommand + * @param int|array $mExpectCode + * @param string $sAddToCommand = '' + * @param bool $bSecureLog = false + * @param string $sErrorPrefix = '' + * + * @return void + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + private function sendRequestWithCheck($sCommand, $mExpectCode, $sAddToCommand = '', $bSecureLog = false, $sErrorPrefix = '') + { + $this->sendRequest($sCommand, $sAddToCommand, $bSecureLog); + $this->validateResponse($mExpectCode, $sErrorPrefix); + } + + /** + * @param string $sHost + * + * @return void + */ + private function ehloOrHelo($sHost) + { + try + { + $this->ehlo($sHost); + } + catch (\Exception $oException) + { + try + { + $this->helo($sHost); + } + catch (\Exception $oException) + { + throw $oException; + } + } + + return $this; + } + + /** + * @param string $sHost + * + * @return void + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + private function ehlo($sHost) + { + $this->sendRequestWithCheck('EHLO', 250, $sHost); + + foreach ($this->aResults as $sLine) + { + $aMatch = array(); + if (\preg_match('/[\d]+[ \-](.+)$/', $sLine, $aMatch) && isset($aMatch[1]) && 0 < \strlen($aMatch[1])) + { + $sLine = \trim($aMatch[1]); + $aLine = \preg_split('/[ =]/', $sLine, 2); + if (\is_array($aLine) && 0 < \count($aLine) && !empty($aLine[0])) + { + $sCapa = \strtoupper($aLine[0]); + if (('AUTH' === $sCapa || 'SIZE' === $sCapa) && !empty($aLine[1])) + { + $sSubLine = \trim(\strtoupper($aLine[1])); + if (0 < \strlen($sSubLine)) + { + if ('AUTH' === $sCapa) + { + $this->aAuthTypes = \explode(' ', $sSubLine); + } + else if ('SIZE' === $sCapa && \is_numeric($sSubLine)) + { + $this->iSizeCapaValue = (int) $sSubLine; + } + } + } + + $this->aCapa[] = $sCapa; + } + } + } + } + + /** + * @param string $sHost + * + * @return void + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Smtp\Exceptions\Exception + */ + private function helo($sHost) + { + $this->sendRequestWithCheck('HELO', 250, $sHost); + $this->aAuthTypes = array(); + $this->iSizeCapaValue = 0; + $this->aCapa = array(); + } + + /** + * @param int|array $mExpectCode + * @param string $sErrorPrefix = '' + * + * @return void + * + * @throws \MailSo\Smtp\Exceptions\ResponseException + */ + private function validateResponse($mExpectCode, $sErrorPrefix = '') + { + if (!\is_array($mExpectCode)) + { + $mExpectCode = array((int) $mExpectCode); + } + else + { + $mExpectCode = \array_map('intval', $mExpectCode); + } + + $aParts = array('', '', ''); + $this->aResults = array(); + do + { + $this->getNextBuffer(); + $aParts = \preg_split('/([\s\-]+)/', $this->sResponseBuffer, 2, PREG_SPLIT_DELIM_CAPTURE); + + if (\is_array($aParts) && 3 === \count($aParts) && \is_numeric($aParts[0])) + { + if ('-' !== \substr($aParts[1], 0, 1) && !\in_array((int) $aParts[0], $mExpectCode)) + { + $this->writeLogException( + new Exceptions\NegativeResponseException($this->aResults, + ('' === $sErrorPrefix ? '' : $sErrorPrefix.': ').\trim( + (0 < \count($this->aResults) ? \implode("\r\n", $this->aResults)."\r\n" : ''). + $this->sResponseBuffer)), \MailSo\Log\Enumerations\Type::ERROR, true); + } + } + else + { + $this->writeLogException( + new Exceptions\ResponseException($this->aResults, + ('' === $sErrorPrefix ? '' : $sErrorPrefix.': ').\trim( + (0 < \count($this->aResults) ? \implode("\r\n", $this->aResults)."\r\n" : ''). + $this->sResponseBuffer)), \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->aResults[] = $this->sResponseBuffer; + } + while ('-' === \substr($aParts[1], 0, 1)); + + $this->writeLog((microtime(true) - $this->iRequestTime), + \MailSo\Log\Enumerations\Type::TIME); + } + + /** + * @return string + */ + protected function getLogName() + { + return 'SMTP'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Smtp\SmtpClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Vendors/Net/IDNA2.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Vendors/Net/IDNA2.php new file mode 100644 index 0000000..8206834 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Vendors/Net/IDNA2.php @@ -0,0 +1,3404 @@ + + * @author Matthias Sommerfeld+ * @author Stefan Neufeind + * @version $Id: IDNA2.php 305344 2010-11-14 23:52:42Z neufeind $ + */ +class Net_IDNA2 +{ + // {{{ npdata + /** + * These Unicode codepoints are + * mapped to nothing, See RFC3454 for details + * + * @static + * @var array + * @access private + */ + private static $_np_map_nothing = array( + 0xAD, + 0x34F, + 0x1806, + 0x180B, + 0x180C, + 0x180D, + 0x200B, + 0x200C, + 0x200D, + 0x2060, + 0xFE00, + 0xFE01, + 0xFE02, + 0xFE03, + 0xFE04, + 0xFE05, + 0xFE06, + 0xFE07, + 0xFE08, + 0xFE09, + 0xFE0A, + 0xFE0B, + 0xFE0C, + 0xFE0D, + 0xFE0E, + 0xFE0F, + 0xFEFF + ); + + /** + * Prohibited codepints + * + * @static + * @var array + * @access private + */ + private static $_general_prohibited = array( + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0xA, + 0xB, + 0xC, + 0xD, + 0xE, + 0xF, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + 0x1B, + 0x1C, + 0x1D, + 0x1E, + 0x1F, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2A, + 0x2B, + 0x2C, + 0x2F, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + 0x40, + 0x5B, + 0x5C, + 0x5D, + 0x5E, + 0x5F, + 0x60, + 0x7B, + 0x7C, + 0x7D, + 0x7E, + 0x7F, + 0x3002 + ); + + /** + * Codepints prohibited by Nameprep + * @static + * @var array + * @access private + */ + private static $_np_prohibit = array( + 0xA0, + 0x1680, + 0x2000, + 0x2001, + 0x2002, + 0x2003, + 0x2004, + 0x2005, + 0x2006, + 0x2007, + 0x2008, + 0x2009, + 0x200A, + 0x200B, + 0x202F, + 0x205F, + 0x3000, + 0x6DD, + 0x70F, + 0x180E, + 0x200C, + 0x200D, + 0x2028, + 0x2029, + 0xFEFF, + 0xFFF9, + 0xFFFA, + 0xFFFB, + 0xFFFC, + 0xFFFE, + 0xFFFF, + 0x1FFFE, + 0x1FFFF, + 0x2FFFE, + 0x2FFFF, + 0x3FFFE, + 0x3FFFF, + 0x4FFFE, + 0x4FFFF, + 0x5FFFE, + 0x5FFFF, + 0x6FFFE, + 0x6FFFF, + 0x7FFFE, + 0x7FFFF, + 0x8FFFE, + 0x8FFFF, + 0x9FFFE, + 0x9FFFF, + 0xAFFFE, + 0xAFFFF, + 0xBFFFE, + 0xBFFFF, + 0xCFFFE, + 0xCFFFF, + 0xDFFFE, + 0xDFFFF, + 0xEFFFE, + 0xEFFFF, + 0xFFFFE, + 0xFFFFF, + 0x10FFFE, + 0x10FFFF, + 0xFFF9, + 0xFFFA, + 0xFFFB, + 0xFFFC, + 0xFFFD, + 0x340, + 0x341, + 0x200E, + 0x200F, + 0x202A, + 0x202B, + 0x202C, + 0x202D, + 0x202E, + 0x206A, + 0x206B, + 0x206C, + 0x206D, + 0x206E, + 0x206F, + 0xE0001 + ); + + /** + * Codepoint ranges prohibited by nameprep + * + * @static + * @var array + * @access private + */ + private static $_np_prohibit_ranges = array( + array(0x80, 0x9F ), + array(0x2060, 0x206F ), + array(0x1D173, 0x1D17A ), + array(0xE000, 0xF8FF ), + array(0xF0000, 0xFFFFD ), + array(0x100000, 0x10FFFD), + array(0xFDD0, 0xFDEF ), + array(0xD800, 0xDFFF ), + array(0x2FF0, 0x2FFB ), + array(0xE0020, 0xE007F ) + ); + + /** + * Replacement mappings (casemapping, replacement sequences, ...) + * + * @static + * @var array + * @access private + */ + private static $_np_replacemaps = array( + 0x41 => array(0x61), + 0x42 => array(0x62), + 0x43 => array(0x63), + 0x44 => array(0x64), + 0x45 => array(0x65), + 0x46 => array(0x66), + 0x47 => array(0x67), + 0x48 => array(0x68), + 0x49 => array(0x69), + 0x4A => array(0x6A), + 0x4B => array(0x6B), + 0x4C => array(0x6C), + 0x4D => array(0x6D), + 0x4E => array(0x6E), + 0x4F => array(0x6F), + 0x50 => array(0x70), + 0x51 => array(0x71), + 0x52 => array(0x72), + 0x53 => array(0x73), + 0x54 => array(0x74), + 0x55 => array(0x75), + 0x56 => array(0x76), + 0x57 => array(0x77), + 0x58 => array(0x78), + 0x59 => array(0x79), + 0x5A => array(0x7A), + 0xB5 => array(0x3BC), + 0xC0 => array(0xE0), + 0xC1 => array(0xE1), + 0xC2 => array(0xE2), + 0xC3 => array(0xE3), + 0xC4 => array(0xE4), + 0xC5 => array(0xE5), + 0xC6 => array(0xE6), + 0xC7 => array(0xE7), + 0xC8 => array(0xE8), + 0xC9 => array(0xE9), + 0xCA => array(0xEA), + 0xCB => array(0xEB), + 0xCC => array(0xEC), + 0xCD => array(0xED), + 0xCE => array(0xEE), + 0xCF => array(0xEF), + 0xD0 => array(0xF0), + 0xD1 => array(0xF1), + 0xD2 => array(0xF2), + 0xD3 => array(0xF3), + 0xD4 => array(0xF4), + 0xD5 => array(0xF5), + 0xD6 => array(0xF6), + 0xD8 => array(0xF8), + 0xD9 => array(0xF9), + 0xDA => array(0xFA), + 0xDB => array(0xFB), + 0xDC => array(0xFC), + 0xDD => array(0xFD), + 0xDE => array(0xFE), + 0xDF => array(0x73, 0x73), + 0x100 => array(0x101), + 0x102 => array(0x103), + 0x104 => array(0x105), + 0x106 => array(0x107), + 0x108 => array(0x109), + 0x10A => array(0x10B), + 0x10C => array(0x10D), + 0x10E => array(0x10F), + 0x110 => array(0x111), + 0x112 => array(0x113), + 0x114 => array(0x115), + 0x116 => array(0x117), + 0x118 => array(0x119), + 0x11A => array(0x11B), + 0x11C => array(0x11D), + 0x11E => array(0x11F), + 0x120 => array(0x121), + 0x122 => array(0x123), + 0x124 => array(0x125), + 0x126 => array(0x127), + 0x128 => array(0x129), + 0x12A => array(0x12B), + 0x12C => array(0x12D), + 0x12E => array(0x12F), + 0x130 => array(0x69, 0x307), + 0x132 => array(0x133), + 0x134 => array(0x135), + 0x136 => array(0x137), + 0x139 => array(0x13A), + 0x13B => array(0x13C), + 0x13D => array(0x13E), + 0x13F => array(0x140), + 0x141 => array(0x142), + 0x143 => array(0x144), + 0x145 => array(0x146), + 0x147 => array(0x148), + 0x149 => array(0x2BC, 0x6E), + 0x14A => array(0x14B), + 0x14C => array(0x14D), + 0x14E => array(0x14F), + 0x150 => array(0x151), + 0x152 => array(0x153), + 0x154 => array(0x155), + 0x156 => array(0x157), + 0x158 => array(0x159), + 0x15A => array(0x15B), + 0x15C => array(0x15D), + 0x15E => array(0x15F), + 0x160 => array(0x161), + 0x162 => array(0x163), + 0x164 => array(0x165), + 0x166 => array(0x167), + 0x168 => array(0x169), + 0x16A => array(0x16B), + 0x16C => array(0x16D), + 0x16E => array(0x16F), + 0x170 => array(0x171), + 0x172 => array(0x173), + 0x174 => array(0x175), + 0x176 => array(0x177), + 0x178 => array(0xFF), + 0x179 => array(0x17A), + 0x17B => array(0x17C), + 0x17D => array(0x17E), + 0x17F => array(0x73), + 0x181 => array(0x253), + 0x182 => array(0x183), + 0x184 => array(0x185), + 0x186 => array(0x254), + 0x187 => array(0x188), + 0x189 => array(0x256), + 0x18A => array(0x257), + 0x18B => array(0x18C), + 0x18E => array(0x1DD), + 0x18F => array(0x259), + 0x190 => array(0x25B), + 0x191 => array(0x192), + 0x193 => array(0x260), + 0x194 => array(0x263), + 0x196 => array(0x269), + 0x197 => array(0x268), + 0x198 => array(0x199), + 0x19C => array(0x26F), + 0x19D => array(0x272), + 0x19F => array(0x275), + 0x1A0 => array(0x1A1), + 0x1A2 => array(0x1A3), + 0x1A4 => array(0x1A5), + 0x1A6 => array(0x280), + 0x1A7 => array(0x1A8), + 0x1A9 => array(0x283), + 0x1AC => array(0x1AD), + 0x1AE => array(0x288), + 0x1AF => array(0x1B0), + 0x1B1 => array(0x28A), + 0x1B2 => array(0x28B), + 0x1B3 => array(0x1B4), + 0x1B5 => array(0x1B6), + 0x1B7 => array(0x292), + 0x1B8 => array(0x1B9), + 0x1BC => array(0x1BD), + 0x1C4 => array(0x1C6), + 0x1C5 => array(0x1C6), + 0x1C7 => array(0x1C9), + 0x1C8 => array(0x1C9), + 0x1CA => array(0x1CC), + 0x1CB => array(0x1CC), + 0x1CD => array(0x1CE), + 0x1CF => array(0x1D0), + 0x1D1 => array(0x1D2), + 0x1D3 => array(0x1D4), + 0x1D5 => array(0x1D6), + 0x1D7 => array(0x1D8), + 0x1D9 => array(0x1DA), + 0x1DB => array(0x1DC), + 0x1DE => array(0x1DF), + 0x1E0 => array(0x1E1), + 0x1E2 => array(0x1E3), + 0x1E4 => array(0x1E5), + 0x1E6 => array(0x1E7), + 0x1E8 => array(0x1E9), + 0x1EA => array(0x1EB), + 0x1EC => array(0x1ED), + 0x1EE => array(0x1EF), + 0x1F0 => array(0x6A, 0x30C), + 0x1F1 => array(0x1F3), + 0x1F2 => array(0x1F3), + 0x1F4 => array(0x1F5), + 0x1F6 => array(0x195), + 0x1F7 => array(0x1BF), + 0x1F8 => array(0x1F9), + 0x1FA => array(0x1FB), + 0x1FC => array(0x1FD), + 0x1FE => array(0x1FF), + 0x200 => array(0x201), + 0x202 => array(0x203), + 0x204 => array(0x205), + 0x206 => array(0x207), + 0x208 => array(0x209), + 0x20A => array(0x20B), + 0x20C => array(0x20D), + 0x20E => array(0x20F), + 0x210 => array(0x211), + 0x212 => array(0x213), + 0x214 => array(0x215), + 0x216 => array(0x217), + 0x218 => array(0x219), + 0x21A => array(0x21B), + 0x21C => array(0x21D), + 0x21E => array(0x21F), + 0x220 => array(0x19E), + 0x222 => array(0x223), + 0x224 => array(0x225), + 0x226 => array(0x227), + 0x228 => array(0x229), + 0x22A => array(0x22B), + 0x22C => array(0x22D), + 0x22E => array(0x22F), + 0x230 => array(0x231), + 0x232 => array(0x233), + 0x345 => array(0x3B9), + 0x37A => array(0x20, 0x3B9), + 0x386 => array(0x3AC), + 0x388 => array(0x3AD), + 0x389 => array(0x3AE), + 0x38A => array(0x3AF), + 0x38C => array(0x3CC), + 0x38E => array(0x3CD), + 0x38F => array(0x3CE), + 0x390 => array(0x3B9, 0x308, 0x301), + 0x391 => array(0x3B1), + 0x392 => array(0x3B2), + 0x393 => array(0x3B3), + 0x394 => array(0x3B4), + 0x395 => array(0x3B5), + 0x396 => array(0x3B6), + 0x397 => array(0x3B7), + 0x398 => array(0x3B8), + 0x399 => array(0x3B9), + 0x39A => array(0x3BA), + 0x39B => array(0x3BB), + 0x39C => array(0x3BC), + 0x39D => array(0x3BD), + 0x39E => array(0x3BE), + 0x39F => array(0x3BF), + 0x3A0 => array(0x3C0), + 0x3A1 => array(0x3C1), + 0x3A3 => array(0x3C3), + 0x3A4 => array(0x3C4), + 0x3A5 => array(0x3C5), + 0x3A6 => array(0x3C6), + 0x3A7 => array(0x3C7), + 0x3A8 => array(0x3C8), + 0x3A9 => array(0x3C9), + 0x3AA => array(0x3CA), + 0x3AB => array(0x3CB), + 0x3B0 => array(0x3C5, 0x308, 0x301), + 0x3C2 => array(0x3C3), + 0x3D0 => array(0x3B2), + 0x3D1 => array(0x3B8), + 0x3D2 => array(0x3C5), + 0x3D3 => array(0x3CD), + 0x3D4 => array(0x3CB), + 0x3D5 => array(0x3C6), + 0x3D6 => array(0x3C0), + 0x3D8 => array(0x3D9), + 0x3DA => array(0x3DB), + 0x3DC => array(0x3DD), + 0x3DE => array(0x3DF), + 0x3E0 => array(0x3E1), + 0x3E2 => array(0x3E3), + 0x3E4 => array(0x3E5), + 0x3E6 => array(0x3E7), + 0x3E8 => array(0x3E9), + 0x3EA => array(0x3EB), + 0x3EC => array(0x3ED), + 0x3EE => array(0x3EF), + 0x3F0 => array(0x3BA), + 0x3F1 => array(0x3C1), + 0x3F2 => array(0x3C3), + 0x3F4 => array(0x3B8), + 0x3F5 => array(0x3B5), + 0x400 => array(0x450), + 0x401 => array(0x451), + 0x402 => array(0x452), + 0x403 => array(0x453), + 0x404 => array(0x454), + 0x405 => array(0x455), + 0x406 => array(0x456), + 0x407 => array(0x457), + 0x408 => array(0x458), + 0x409 => array(0x459), + 0x40A => array(0x45A), + 0x40B => array(0x45B), + 0x40C => array(0x45C), + 0x40D => array(0x45D), + 0x40E => array(0x45E), + 0x40F => array(0x45F), + 0x410 => array(0x430), + 0x411 => array(0x431), + 0x412 => array(0x432), + 0x413 => array(0x433), + 0x414 => array(0x434), + 0x415 => array(0x435), + 0x416 => array(0x436), + 0x417 => array(0x437), + 0x418 => array(0x438), + 0x419 => array(0x439), + 0x41A => array(0x43A), + 0x41B => array(0x43B), + 0x41C => array(0x43C), + 0x41D => array(0x43D), + 0x41E => array(0x43E), + 0x41F => array(0x43F), + 0x420 => array(0x440), + 0x421 => array(0x441), + 0x422 => array(0x442), + 0x423 => array(0x443), + 0x424 => array(0x444), + 0x425 => array(0x445), + 0x426 => array(0x446), + 0x427 => array(0x447), + 0x428 => array(0x448), + 0x429 => array(0x449), + 0x42A => array(0x44A), + 0x42B => array(0x44B), + 0x42C => array(0x44C), + 0x42D => array(0x44D), + 0x42E => array(0x44E), + 0x42F => array(0x44F), + 0x460 => array(0x461), + 0x462 => array(0x463), + 0x464 => array(0x465), + 0x466 => array(0x467), + 0x468 => array(0x469), + 0x46A => array(0x46B), + 0x46C => array(0x46D), + 0x46E => array(0x46F), + 0x470 => array(0x471), + 0x472 => array(0x473), + 0x474 => array(0x475), + 0x476 => array(0x477), + 0x478 => array(0x479), + 0x47A => array(0x47B), + 0x47C => array(0x47D), + 0x47E => array(0x47F), + 0x480 => array(0x481), + 0x48A => array(0x48B), + 0x48C => array(0x48D), + 0x48E => array(0x48F), + 0x490 => array(0x491), + 0x492 => array(0x493), + 0x494 => array(0x495), + 0x496 => array(0x497), + 0x498 => array(0x499), + 0x49A => array(0x49B), + 0x49C => array(0x49D), + 0x49E => array(0x49F), + 0x4A0 => array(0x4A1), + 0x4A2 => array(0x4A3), + 0x4A4 => array(0x4A5), + 0x4A6 => array(0x4A7), + 0x4A8 => array(0x4A9), + 0x4AA => array(0x4AB), + 0x4AC => array(0x4AD), + 0x4AE => array(0x4AF), + 0x4B0 => array(0x4B1), + 0x4B2 => array(0x4B3), + 0x4B4 => array(0x4B5), + 0x4B6 => array(0x4B7), + 0x4B8 => array(0x4B9), + 0x4BA => array(0x4BB), + 0x4BC => array(0x4BD), + 0x4BE => array(0x4BF), + 0x4C1 => array(0x4C2), + 0x4C3 => array(0x4C4), + 0x4C5 => array(0x4C6), + 0x4C7 => array(0x4C8), + 0x4C9 => array(0x4CA), + 0x4CB => array(0x4CC), + 0x4CD => array(0x4CE), + 0x4D0 => array(0x4D1), + 0x4D2 => array(0x4D3), + 0x4D4 => array(0x4D5), + 0x4D6 => array(0x4D7), + 0x4D8 => array(0x4D9), + 0x4DA => array(0x4DB), + 0x4DC => array(0x4DD), + 0x4DE => array(0x4DF), + 0x4E0 => array(0x4E1), + 0x4E2 => array(0x4E3), + 0x4E4 => array(0x4E5), + 0x4E6 => array(0x4E7), + 0x4E8 => array(0x4E9), + 0x4EA => array(0x4EB), + 0x4EC => array(0x4ED), + 0x4EE => array(0x4EF), + 0x4F0 => array(0x4F1), + 0x4F2 => array(0x4F3), + 0x4F4 => array(0x4F5), + 0x4F8 => array(0x4F9), + 0x500 => array(0x501), + 0x502 => array(0x503), + 0x504 => array(0x505), + 0x506 => array(0x507), + 0x508 => array(0x509), + 0x50A => array(0x50B), + 0x50C => array(0x50D), + 0x50E => array(0x50F), + 0x531 => array(0x561), + 0x532 => array(0x562), + 0x533 => array(0x563), + 0x534 => array(0x564), + 0x535 => array(0x565), + 0x536 => array(0x566), + 0x537 => array(0x567), + 0x538 => array(0x568), + 0x539 => array(0x569), + 0x53A => array(0x56A), + 0x53B => array(0x56B), + 0x53C => array(0x56C), + 0x53D => array(0x56D), + 0x53E => array(0x56E), + 0x53F => array(0x56F), + 0x540 => array(0x570), + 0x541 => array(0x571), + 0x542 => array(0x572), + 0x543 => array(0x573), + 0x544 => array(0x574), + 0x545 => array(0x575), + 0x546 => array(0x576), + 0x547 => array(0x577), + 0x548 => array(0x578), + 0x549 => array(0x579), + 0x54A => array(0x57A), + 0x54B => array(0x57B), + 0x54C => array(0x57C), + 0x54D => array(0x57D), + 0x54E => array(0x57E), + 0x54F => array(0x57F), + 0x550 => array(0x580), + 0x551 => array(0x581), + 0x552 => array(0x582), + 0x553 => array(0x583), + 0x554 => array(0x584), + 0x555 => array(0x585), + 0x556 => array(0x586), + 0x587 => array(0x565, 0x582), + 0x1E00 => array(0x1E01), + 0x1E02 => array(0x1E03), + 0x1E04 => array(0x1E05), + 0x1E06 => array(0x1E07), + 0x1E08 => array(0x1E09), + 0x1E0A => array(0x1E0B), + 0x1E0C => array(0x1E0D), + 0x1E0E => array(0x1E0F), + 0x1E10 => array(0x1E11), + 0x1E12 => array(0x1E13), + 0x1E14 => array(0x1E15), + 0x1E16 => array(0x1E17), + 0x1E18 => array(0x1E19), + 0x1E1A => array(0x1E1B), + 0x1E1C => array(0x1E1D), + 0x1E1E => array(0x1E1F), + 0x1E20 => array(0x1E21), + 0x1E22 => array(0x1E23), + 0x1E24 => array(0x1E25), + 0x1E26 => array(0x1E27), + 0x1E28 => array(0x1E29), + 0x1E2A => array(0x1E2B), + 0x1E2C => array(0x1E2D), + 0x1E2E => array(0x1E2F), + 0x1E30 => array(0x1E31), + 0x1E32 => array(0x1E33), + 0x1E34 => array(0x1E35), + 0x1E36 => array(0x1E37), + 0x1E38 => array(0x1E39), + 0x1E3A => array(0x1E3B), + 0x1E3C => array(0x1E3D), + 0x1E3E => array(0x1E3F), + 0x1E40 => array(0x1E41), + 0x1E42 => array(0x1E43), + 0x1E44 => array(0x1E45), + 0x1E46 => array(0x1E47), + 0x1E48 => array(0x1E49), + 0x1E4A => array(0x1E4B), + 0x1E4C => array(0x1E4D), + 0x1E4E => array(0x1E4F), + 0x1E50 => array(0x1E51), + 0x1E52 => array(0x1E53), + 0x1E54 => array(0x1E55), + 0x1E56 => array(0x1E57), + 0x1E58 => array(0x1E59), + 0x1E5A => array(0x1E5B), + 0x1E5C => array(0x1E5D), + 0x1E5E => array(0x1E5F), + 0x1E60 => array(0x1E61), + 0x1E62 => array(0x1E63), + 0x1E64 => array(0x1E65), + 0x1E66 => array(0x1E67), + 0x1E68 => array(0x1E69), + 0x1E6A => array(0x1E6B), + 0x1E6C => array(0x1E6D), + 0x1E6E => array(0x1E6F), + 0x1E70 => array(0x1E71), + 0x1E72 => array(0x1E73), + 0x1E74 => array(0x1E75), + 0x1E76 => array(0x1E77), + 0x1E78 => array(0x1E79), + 0x1E7A => array(0x1E7B), + 0x1E7C => array(0x1E7D), + 0x1E7E => array(0x1E7F), + 0x1E80 => array(0x1E81), + 0x1E82 => array(0x1E83), + 0x1E84 => array(0x1E85), + 0x1E86 => array(0x1E87), + 0x1E88 => array(0x1E89), + 0x1E8A => array(0x1E8B), + 0x1E8C => array(0x1E8D), + 0x1E8E => array(0x1E8F), + 0x1E90 => array(0x1E91), + 0x1E92 => array(0x1E93), + 0x1E94 => array(0x1E95), + 0x1E96 => array(0x68, 0x331), + 0x1E97 => array(0x74, 0x308), + 0x1E98 => array(0x77, 0x30A), + 0x1E99 => array(0x79, 0x30A), + 0x1E9A => array(0x61, 0x2BE), + 0x1E9B => array(0x1E61), + 0x1EA0 => array(0x1EA1), + 0x1EA2 => array(0x1EA3), + 0x1EA4 => array(0x1EA5), + 0x1EA6 => array(0x1EA7), + 0x1EA8 => array(0x1EA9), + 0x1EAA => array(0x1EAB), + 0x1EAC => array(0x1EAD), + 0x1EAE => array(0x1EAF), + 0x1EB0 => array(0x1EB1), + 0x1EB2 => array(0x1EB3), + 0x1EB4 => array(0x1EB5), + 0x1EB6 => array(0x1EB7), + 0x1EB8 => array(0x1EB9), + 0x1EBA => array(0x1EBB), + 0x1EBC => array(0x1EBD), + 0x1EBE => array(0x1EBF), + 0x1EC0 => array(0x1EC1), + 0x1EC2 => array(0x1EC3), + 0x1EC4 => array(0x1EC5), + 0x1EC6 => array(0x1EC7), + 0x1EC8 => array(0x1EC9), + 0x1ECA => array(0x1ECB), + 0x1ECC => array(0x1ECD), + 0x1ECE => array(0x1ECF), + 0x1ED0 => array(0x1ED1), + 0x1ED2 => array(0x1ED3), + 0x1ED4 => array(0x1ED5), + 0x1ED6 => array(0x1ED7), + 0x1ED8 => array(0x1ED9), + 0x1EDA => array(0x1EDB), + 0x1EDC => array(0x1EDD), + 0x1EDE => array(0x1EDF), + 0x1EE0 => array(0x1EE1), + 0x1EE2 => array(0x1EE3), + 0x1EE4 => array(0x1EE5), + 0x1EE6 => array(0x1EE7), + 0x1EE8 => array(0x1EE9), + 0x1EEA => array(0x1EEB), + 0x1EEC => array(0x1EED), + 0x1EEE => array(0x1EEF), + 0x1EF0 => array(0x1EF1), + 0x1EF2 => array(0x1EF3), + 0x1EF4 => array(0x1EF5), + 0x1EF6 => array(0x1EF7), + 0x1EF8 => array(0x1EF9), + 0x1F08 => array(0x1F00), + 0x1F09 => array(0x1F01), + 0x1F0A => array(0x1F02), + 0x1F0B => array(0x1F03), + 0x1F0C => array(0x1F04), + 0x1F0D => array(0x1F05), + 0x1F0E => array(0x1F06), + 0x1F0F => array(0x1F07), + 0x1F18 => array(0x1F10), + 0x1F19 => array(0x1F11), + 0x1F1A => array(0x1F12), + 0x1F1B => array(0x1F13), + 0x1F1C => array(0x1F14), + 0x1F1D => array(0x1F15), + 0x1F28 => array(0x1F20), + 0x1F29 => array(0x1F21), + 0x1F2A => array(0x1F22), + 0x1F2B => array(0x1F23), + 0x1F2C => array(0x1F24), + 0x1F2D => array(0x1F25), + 0x1F2E => array(0x1F26), + 0x1F2F => array(0x1F27), + 0x1F38 => array(0x1F30), + 0x1F39 => array(0x1F31), + 0x1F3A => array(0x1F32), + 0x1F3B => array(0x1F33), + 0x1F3C => array(0x1F34), + 0x1F3D => array(0x1F35), + 0x1F3E => array(0x1F36), + 0x1F3F => array(0x1F37), + 0x1F48 => array(0x1F40), + 0x1F49 => array(0x1F41), + 0x1F4A => array(0x1F42), + 0x1F4B => array(0x1F43), + 0x1F4C => array(0x1F44), + 0x1F4D => array(0x1F45), + 0x1F50 => array(0x3C5, 0x313), + 0x1F52 => array(0x3C5, 0x313, 0x300), + 0x1F54 => array(0x3C5, 0x313, 0x301), + 0x1F56 => array(0x3C5, 0x313, 0x342), + 0x1F59 => array(0x1F51), + 0x1F5B => array(0x1F53), + 0x1F5D => array(0x1F55), + 0x1F5F => array(0x1F57), + 0x1F68 => array(0x1F60), + 0x1F69 => array(0x1F61), + 0x1F6A => array(0x1F62), + 0x1F6B => array(0x1F63), + 0x1F6C => array(0x1F64), + 0x1F6D => array(0x1F65), + 0x1F6E => array(0x1F66), + 0x1F6F => array(0x1F67), + 0x1F80 => array(0x1F00, 0x3B9), + 0x1F81 => array(0x1F01, 0x3B9), + 0x1F82 => array(0x1F02, 0x3B9), + 0x1F83 => array(0x1F03, 0x3B9), + 0x1F84 => array(0x1F04, 0x3B9), + 0x1F85 => array(0x1F05, 0x3B9), + 0x1F86 => array(0x1F06, 0x3B9), + 0x1F87 => array(0x1F07, 0x3B9), + 0x1F88 => array(0x1F00, 0x3B9), + 0x1F89 => array(0x1F01, 0x3B9), + 0x1F8A => array(0x1F02, 0x3B9), + 0x1F8B => array(0x1F03, 0x3B9), + 0x1F8C => array(0x1F04, 0x3B9), + 0x1F8D => array(0x1F05, 0x3B9), + 0x1F8E => array(0x1F06, 0x3B9), + 0x1F8F => array(0x1F07, 0x3B9), + 0x1F90 => array(0x1F20, 0x3B9), + 0x1F91 => array(0x1F21, 0x3B9), + 0x1F92 => array(0x1F22, 0x3B9), + 0x1F93 => array(0x1F23, 0x3B9), + 0x1F94 => array(0x1F24, 0x3B9), + 0x1F95 => array(0x1F25, 0x3B9), + 0x1F96 => array(0x1F26, 0x3B9), + 0x1F97 => array(0x1F27, 0x3B9), + 0x1F98 => array(0x1F20, 0x3B9), + 0x1F99 => array(0x1F21, 0x3B9), + 0x1F9A => array(0x1F22, 0x3B9), + 0x1F9B => array(0x1F23, 0x3B9), + 0x1F9C => array(0x1F24, 0x3B9), + 0x1F9D => array(0x1F25, 0x3B9), + 0x1F9E => array(0x1F26, 0x3B9), + 0x1F9F => array(0x1F27, 0x3B9), + 0x1FA0 => array(0x1F60, 0x3B9), + 0x1FA1 => array(0x1F61, 0x3B9), + 0x1FA2 => array(0x1F62, 0x3B9), + 0x1FA3 => array(0x1F63, 0x3B9), + 0x1FA4 => array(0x1F64, 0x3B9), + 0x1FA5 => array(0x1F65, 0x3B9), + 0x1FA6 => array(0x1F66, 0x3B9), + 0x1FA7 => array(0x1F67, 0x3B9), + 0x1FA8 => array(0x1F60, 0x3B9), + 0x1FA9 => array(0x1F61, 0x3B9), + 0x1FAA => array(0x1F62, 0x3B9), + 0x1FAB => array(0x1F63, 0x3B9), + 0x1FAC => array(0x1F64, 0x3B9), + 0x1FAD => array(0x1F65, 0x3B9), + 0x1FAE => array(0x1F66, 0x3B9), + 0x1FAF => array(0x1F67, 0x3B9), + 0x1FB2 => array(0x1F70, 0x3B9), + 0x1FB3 => array(0x3B1, 0x3B9), + 0x1FB4 => array(0x3AC, 0x3B9), + 0x1FB6 => array(0x3B1, 0x342), + 0x1FB7 => array(0x3B1, 0x342, 0x3B9), + 0x1FB8 => array(0x1FB0), + 0x1FB9 => array(0x1FB1), + 0x1FBA => array(0x1F70), + 0x1FBB => array(0x1F71), + 0x1FBC => array(0x3B1, 0x3B9), + 0x1FBE => array(0x3B9), + 0x1FC2 => array(0x1F74, 0x3B9), + 0x1FC3 => array(0x3B7, 0x3B9), + 0x1FC4 => array(0x3AE, 0x3B9), + 0x1FC6 => array(0x3B7, 0x342), + 0x1FC7 => array(0x3B7, 0x342, 0x3B9), + 0x1FC8 => array(0x1F72), + 0x1FC9 => array(0x1F73), + 0x1FCA => array(0x1F74), + 0x1FCB => array(0x1F75), + 0x1FCC => array(0x3B7, 0x3B9), + 0x1FD2 => array(0x3B9, 0x308, 0x300), + 0x1FD3 => array(0x3B9, 0x308, 0x301), + 0x1FD6 => array(0x3B9, 0x342), + 0x1FD7 => array(0x3B9, 0x308, 0x342), + 0x1FD8 => array(0x1FD0), + 0x1FD9 => array(0x1FD1), + 0x1FDA => array(0x1F76), + 0x1FDB => array(0x1F77), + 0x1FE2 => array(0x3C5, 0x308, 0x300), + 0x1FE3 => array(0x3C5, 0x308, 0x301), + 0x1FE4 => array(0x3C1, 0x313), + 0x1FE6 => array(0x3C5, 0x342), + 0x1FE7 => array(0x3C5, 0x308, 0x342), + 0x1FE8 => array(0x1FE0), + 0x1FE9 => array(0x1FE1), + 0x1FEA => array(0x1F7A), + 0x1FEB => array(0x1F7B), + 0x1FEC => array(0x1FE5), + 0x1FF2 => array(0x1F7C, 0x3B9), + 0x1FF3 => array(0x3C9, 0x3B9), + 0x1FF4 => array(0x3CE, 0x3B9), + 0x1FF6 => array(0x3C9, 0x342), + 0x1FF7 => array(0x3C9, 0x342, 0x3B9), + 0x1FF8 => array(0x1F78), + 0x1FF9 => array(0x1F79), + 0x1FFA => array(0x1F7C), + 0x1FFB => array(0x1F7D), + 0x1FFC => array(0x3C9, 0x3B9), + 0x20A8 => array(0x72, 0x73), + 0x2102 => array(0x63), + 0x2103 => array(0xB0, 0x63), + 0x2107 => array(0x25B), + 0x2109 => array(0xB0, 0x66), + 0x210B => array(0x68), + 0x210C => array(0x68), + 0x210D => array(0x68), + 0x2110 => array(0x69), + 0x2111 => array(0x69), + 0x2112 => array(0x6C), + 0x2115 => array(0x6E), + 0x2116 => array(0x6E, 0x6F), + 0x2119 => array(0x70), + 0x211A => array(0x71), + 0x211B => array(0x72), + 0x211C => array(0x72), + 0x211D => array(0x72), + 0x2120 => array(0x73, 0x6D), + 0x2121 => array(0x74, 0x65, 0x6C), + 0x2122 => array(0x74, 0x6D), + 0x2124 => array(0x7A), + 0x2126 => array(0x3C9), + 0x2128 => array(0x7A), + 0x212A => array(0x6B), + 0x212B => array(0xE5), + 0x212C => array(0x62), + 0x212D => array(0x63), + 0x2130 => array(0x65), + 0x2131 => array(0x66), + 0x2133 => array(0x6D), + 0x213E => array(0x3B3), + 0x213F => array(0x3C0), + 0x2145 => array(0x64), + 0x2160 => array(0x2170), + 0x2161 => array(0x2171), + 0x2162 => array(0x2172), + 0x2163 => array(0x2173), + 0x2164 => array(0x2174), + 0x2165 => array(0x2175), + 0x2166 => array(0x2176), + 0x2167 => array(0x2177), + 0x2168 => array(0x2178), + 0x2169 => array(0x2179), + 0x216A => array(0x217A), + 0x216B => array(0x217B), + 0x216C => array(0x217C), + 0x216D => array(0x217D), + 0x216E => array(0x217E), + 0x216F => array(0x217F), + 0x24B6 => array(0x24D0), + 0x24B7 => array(0x24D1), + 0x24B8 => array(0x24D2), + 0x24B9 => array(0x24D3), + 0x24BA => array(0x24D4), + 0x24BB => array(0x24D5), + 0x24BC => array(0x24D6), + 0x24BD => array(0x24D7), + 0x24BE => array(0x24D8), + 0x24BF => array(0x24D9), + 0x24C0 => array(0x24DA), + 0x24C1 => array(0x24DB), + 0x24C2 => array(0x24DC), + 0x24C3 => array(0x24DD), + 0x24C4 => array(0x24DE), + 0x24C5 => array(0x24DF), + 0x24C6 => array(0x24E0), + 0x24C7 => array(0x24E1), + 0x24C8 => array(0x24E2), + 0x24C9 => array(0x24E3), + 0x24CA => array(0x24E4), + 0x24CB => array(0x24E5), + 0x24CC => array(0x24E6), + 0x24CD => array(0x24E7), + 0x24CE => array(0x24E8), + 0x24CF => array(0x24E9), + 0x3371 => array(0x68, 0x70, 0x61), + 0x3373 => array(0x61, 0x75), + 0x3375 => array(0x6F, 0x76), + 0x3380 => array(0x70, 0x61), + 0x3381 => array(0x6E, 0x61), + 0x3382 => array(0x3BC, 0x61), + 0x3383 => array(0x6D, 0x61), + 0x3384 => array(0x6B, 0x61), + 0x3385 => array(0x6B, 0x62), + 0x3386 => array(0x6D, 0x62), + 0x3387 => array(0x67, 0x62), + 0x338A => array(0x70, 0x66), + 0x338B => array(0x6E, 0x66), + 0x338C => array(0x3BC, 0x66), + 0x3390 => array(0x68, 0x7A), + 0x3391 => array(0x6B, 0x68, 0x7A), + 0x3392 => array(0x6D, 0x68, 0x7A), + 0x3393 => array(0x67, 0x68, 0x7A), + 0x3394 => array(0x74, 0x68, 0x7A), + 0x33A9 => array(0x70, 0x61), + 0x33AA => array(0x6B, 0x70, 0x61), + 0x33AB => array(0x6D, 0x70, 0x61), + 0x33AC => array(0x67, 0x70, 0x61), + 0x33B4 => array(0x70, 0x76), + 0x33B5 => array(0x6E, 0x76), + 0x33B6 => array(0x3BC, 0x76), + 0x33B7 => array(0x6D, 0x76), + 0x33B8 => array(0x6B, 0x76), + 0x33B9 => array(0x6D, 0x76), + 0x33BA => array(0x70, 0x77), + 0x33BB => array(0x6E, 0x77), + 0x33BC => array(0x3BC, 0x77), + 0x33BD => array(0x6D, 0x77), + 0x33BE => array(0x6B, 0x77), + 0x33BF => array(0x6D, 0x77), + 0x33C0 => array(0x6B, 0x3C9), + 0x33C1 => array(0x6D, 0x3C9), + /* 0x33C2 => array(0x61, 0x2E, 0x6D, 0x2E), */ + 0x33C3 => array(0x62, 0x71), + 0x33C6 => array(0x63, 0x2215, 0x6B, 0x67), + 0x33C7 => array(0x63, 0x6F, 0x2E), + 0x33C8 => array(0x64, 0x62), + 0x33C9 => array(0x67, 0x79), + 0x33CB => array(0x68, 0x70), + 0x33CD => array(0x6B, 0x6B), + 0x33CE => array(0x6B, 0x6D), + 0x33D7 => array(0x70, 0x68), + 0x33D9 => array(0x70, 0x70, 0x6D), + 0x33DA => array(0x70, 0x72), + 0x33DC => array(0x73, 0x76), + 0x33DD => array(0x77, 0x62), + 0xFB00 => array(0x66, 0x66), + 0xFB01 => array(0x66, 0x69), + 0xFB02 => array(0x66, 0x6C), + 0xFB03 => array(0x66, 0x66, 0x69), + 0xFB04 => array(0x66, 0x66, 0x6C), + 0xFB05 => array(0x73, 0x74), + 0xFB06 => array(0x73, 0x74), + 0xFB13 => array(0x574, 0x576), + 0xFB14 => array(0x574, 0x565), + 0xFB15 => array(0x574, 0x56B), + 0xFB16 => array(0x57E, 0x576), + 0xFB17 => array(0x574, 0x56D), + 0xFF21 => array(0xFF41), + 0xFF22 => array(0xFF42), + 0xFF23 => array(0xFF43), + 0xFF24 => array(0xFF44), + 0xFF25 => array(0xFF45), + 0xFF26 => array(0xFF46), + 0xFF27 => array(0xFF47), + 0xFF28 => array(0xFF48), + 0xFF29 => array(0xFF49), + 0xFF2A => array(0xFF4A), + 0xFF2B => array(0xFF4B), + 0xFF2C => array(0xFF4C), + 0xFF2D => array(0xFF4D), + 0xFF2E => array(0xFF4E), + 0xFF2F => array(0xFF4F), + 0xFF30 => array(0xFF50), + 0xFF31 => array(0xFF51), + 0xFF32 => array(0xFF52), + 0xFF33 => array(0xFF53), + 0xFF34 => array(0xFF54), + 0xFF35 => array(0xFF55), + 0xFF36 => array(0xFF56), + 0xFF37 => array(0xFF57), + 0xFF38 => array(0xFF58), + 0xFF39 => array(0xFF59), + 0xFF3A => array(0xFF5A), + 0x10400 => array(0x10428), + 0x10401 => array(0x10429), + 0x10402 => array(0x1042A), + 0x10403 => array(0x1042B), + 0x10404 => array(0x1042C), + 0x10405 => array(0x1042D), + 0x10406 => array(0x1042E), + 0x10407 => array(0x1042F), + 0x10408 => array(0x10430), + 0x10409 => array(0x10431), + 0x1040A => array(0x10432), + 0x1040B => array(0x10433), + 0x1040C => array(0x10434), + 0x1040D => array(0x10435), + 0x1040E => array(0x10436), + 0x1040F => array(0x10437), + 0x10410 => array(0x10438), + 0x10411 => array(0x10439), + 0x10412 => array(0x1043A), + 0x10413 => array(0x1043B), + 0x10414 => array(0x1043C), + 0x10415 => array(0x1043D), + 0x10416 => array(0x1043E), + 0x10417 => array(0x1043F), + 0x10418 => array(0x10440), + 0x10419 => array(0x10441), + 0x1041A => array(0x10442), + 0x1041B => array(0x10443), + 0x1041C => array(0x10444), + 0x1041D => array(0x10445), + 0x1041E => array(0x10446), + 0x1041F => array(0x10447), + 0x10420 => array(0x10448), + 0x10421 => array(0x10449), + 0x10422 => array(0x1044A), + 0x10423 => array(0x1044B), + 0x10424 => array(0x1044C), + 0x10425 => array(0x1044D), + 0x1D400 => array(0x61), + 0x1D401 => array(0x62), + 0x1D402 => array(0x63), + 0x1D403 => array(0x64), + 0x1D404 => array(0x65), + 0x1D405 => array(0x66), + 0x1D406 => array(0x67), + 0x1D407 => array(0x68), + 0x1D408 => array(0x69), + 0x1D409 => array(0x6A), + 0x1D40A => array(0x6B), + 0x1D40B => array(0x6C), + 0x1D40C => array(0x6D), + 0x1D40D => array(0x6E), + 0x1D40E => array(0x6F), + 0x1D40F => array(0x70), + 0x1D410 => array(0x71), + 0x1D411 => array(0x72), + 0x1D412 => array(0x73), + 0x1D413 => array(0x74), + 0x1D414 => array(0x75), + 0x1D415 => array(0x76), + 0x1D416 => array(0x77), + 0x1D417 => array(0x78), + 0x1D418 => array(0x79), + 0x1D419 => array(0x7A), + 0x1D434 => array(0x61), + 0x1D435 => array(0x62), + 0x1D436 => array(0x63), + 0x1D437 => array(0x64), + 0x1D438 => array(0x65), + 0x1D439 => array(0x66), + 0x1D43A => array(0x67), + 0x1D43B => array(0x68), + 0x1D43C => array(0x69), + 0x1D43D => array(0x6A), + 0x1D43E => array(0x6B), + 0x1D43F => array(0x6C), + 0x1D440 => array(0x6D), + 0x1D441 => array(0x6E), + 0x1D442 => array(0x6F), + 0x1D443 => array(0x70), + 0x1D444 => array(0x71), + 0x1D445 => array(0x72), + 0x1D446 => array(0x73), + 0x1D447 => array(0x74), + 0x1D448 => array(0x75), + 0x1D449 => array(0x76), + 0x1D44A => array(0x77), + 0x1D44B => array(0x78), + 0x1D44C => array(0x79), + 0x1D44D => array(0x7A), + 0x1D468 => array(0x61), + 0x1D469 => array(0x62), + 0x1D46A => array(0x63), + 0x1D46B => array(0x64), + 0x1D46C => array(0x65), + 0x1D46D => array(0x66), + 0x1D46E => array(0x67), + 0x1D46F => array(0x68), + 0x1D470 => array(0x69), + 0x1D471 => array(0x6A), + 0x1D472 => array(0x6B), + 0x1D473 => array(0x6C), + 0x1D474 => array(0x6D), + 0x1D475 => array(0x6E), + 0x1D476 => array(0x6F), + 0x1D477 => array(0x70), + 0x1D478 => array(0x71), + 0x1D479 => array(0x72), + 0x1D47A => array(0x73), + 0x1D47B => array(0x74), + 0x1D47C => array(0x75), + 0x1D47D => array(0x76), + 0x1D47E => array(0x77), + 0x1D47F => array(0x78), + 0x1D480 => array(0x79), + 0x1D481 => array(0x7A), + 0x1D49C => array(0x61), + 0x1D49E => array(0x63), + 0x1D49F => array(0x64), + 0x1D4A2 => array(0x67), + 0x1D4A5 => array(0x6A), + 0x1D4A6 => array(0x6B), + 0x1D4A9 => array(0x6E), + 0x1D4AA => array(0x6F), + 0x1D4AB => array(0x70), + 0x1D4AC => array(0x71), + 0x1D4AE => array(0x73), + 0x1D4AF => array(0x74), + 0x1D4B0 => array(0x75), + 0x1D4B1 => array(0x76), + 0x1D4B2 => array(0x77), + 0x1D4B3 => array(0x78), + 0x1D4B4 => array(0x79), + 0x1D4B5 => array(0x7A), + 0x1D4D0 => array(0x61), + 0x1D4D1 => array(0x62), + 0x1D4D2 => array(0x63), + 0x1D4D3 => array(0x64), + 0x1D4D4 => array(0x65), + 0x1D4D5 => array(0x66), + 0x1D4D6 => array(0x67), + 0x1D4D7 => array(0x68), + 0x1D4D8 => array(0x69), + 0x1D4D9 => array(0x6A), + 0x1D4DA => array(0x6B), + 0x1D4DB => array(0x6C), + 0x1D4DC => array(0x6D), + 0x1D4DD => array(0x6E), + 0x1D4DE => array(0x6F), + 0x1D4DF => array(0x70), + 0x1D4E0 => array(0x71), + 0x1D4E1 => array(0x72), + 0x1D4E2 => array(0x73), + 0x1D4E3 => array(0x74), + 0x1D4E4 => array(0x75), + 0x1D4E5 => array(0x76), + 0x1D4E6 => array(0x77), + 0x1D4E7 => array(0x78), + 0x1D4E8 => array(0x79), + 0x1D4E9 => array(0x7A), + 0x1D504 => array(0x61), + 0x1D505 => array(0x62), + 0x1D507 => array(0x64), + 0x1D508 => array(0x65), + 0x1D509 => array(0x66), + 0x1D50A => array(0x67), + 0x1D50D => array(0x6A), + 0x1D50E => array(0x6B), + 0x1D50F => array(0x6C), + 0x1D510 => array(0x6D), + 0x1D511 => array(0x6E), + 0x1D512 => array(0x6F), + 0x1D513 => array(0x70), + 0x1D514 => array(0x71), + 0x1D516 => array(0x73), + 0x1D517 => array(0x74), + 0x1D518 => array(0x75), + 0x1D519 => array(0x76), + 0x1D51A => array(0x77), + 0x1D51B => array(0x78), + 0x1D51C => array(0x79), + 0x1D538 => array(0x61), + 0x1D539 => array(0x62), + 0x1D53B => array(0x64), + 0x1D53C => array(0x65), + 0x1D53D => array(0x66), + 0x1D53E => array(0x67), + 0x1D540 => array(0x69), + 0x1D541 => array(0x6A), + 0x1D542 => array(0x6B), + 0x1D543 => array(0x6C), + 0x1D544 => array(0x6D), + 0x1D546 => array(0x6F), + 0x1D54A => array(0x73), + 0x1D54B => array(0x74), + 0x1D54C => array(0x75), + 0x1D54D => array(0x76), + 0x1D54E => array(0x77), + 0x1D54F => array(0x78), + 0x1D550 => array(0x79), + 0x1D56C => array(0x61), + 0x1D56D => array(0x62), + 0x1D56E => array(0x63), + 0x1D56F => array(0x64), + 0x1D570 => array(0x65), + 0x1D571 => array(0x66), + 0x1D572 => array(0x67), + 0x1D573 => array(0x68), + 0x1D574 => array(0x69), + 0x1D575 => array(0x6A), + 0x1D576 => array(0x6B), + 0x1D577 => array(0x6C), + 0x1D578 => array(0x6D), + 0x1D579 => array(0x6E), + 0x1D57A => array(0x6F), + 0x1D57B => array(0x70), + 0x1D57C => array(0x71), + 0x1D57D => array(0x72), + 0x1D57E => array(0x73), + 0x1D57F => array(0x74), + 0x1D580 => array(0x75), + 0x1D581 => array(0x76), + 0x1D582 => array(0x77), + 0x1D583 => array(0x78), + 0x1D584 => array(0x79), + 0x1D585 => array(0x7A), + 0x1D5A0 => array(0x61), + 0x1D5A1 => array(0x62), + 0x1D5A2 => array(0x63), + 0x1D5A3 => array(0x64), + 0x1D5A4 => array(0x65), + 0x1D5A5 => array(0x66), + 0x1D5A6 => array(0x67), + 0x1D5A7 => array(0x68), + 0x1D5A8 => array(0x69), + 0x1D5A9 => array(0x6A), + 0x1D5AA => array(0x6B), + 0x1D5AB => array(0x6C), + 0x1D5AC => array(0x6D), + 0x1D5AD => array(0x6E), + 0x1D5AE => array(0x6F), + 0x1D5AF => array(0x70), + 0x1D5B0 => array(0x71), + 0x1D5B1 => array(0x72), + 0x1D5B2 => array(0x73), + 0x1D5B3 => array(0x74), + 0x1D5B4 => array(0x75), + 0x1D5B5 => array(0x76), + 0x1D5B6 => array(0x77), + 0x1D5B7 => array(0x78), + 0x1D5B8 => array(0x79), + 0x1D5B9 => array(0x7A), + 0x1D5D4 => array(0x61), + 0x1D5D5 => array(0x62), + 0x1D5D6 => array(0x63), + 0x1D5D7 => array(0x64), + 0x1D5D8 => array(0x65), + 0x1D5D9 => array(0x66), + 0x1D5DA => array(0x67), + 0x1D5DB => array(0x68), + 0x1D5DC => array(0x69), + 0x1D5DD => array(0x6A), + 0x1D5DE => array(0x6B), + 0x1D5DF => array(0x6C), + 0x1D5E0 => array(0x6D), + 0x1D5E1 => array(0x6E), + 0x1D5E2 => array(0x6F), + 0x1D5E3 => array(0x70), + 0x1D5E4 => array(0x71), + 0x1D5E5 => array(0x72), + 0x1D5E6 => array(0x73), + 0x1D5E7 => array(0x74), + 0x1D5E8 => array(0x75), + 0x1D5E9 => array(0x76), + 0x1D5EA => array(0x77), + 0x1D5EB => array(0x78), + 0x1D5EC => array(0x79), + 0x1D5ED => array(0x7A), + 0x1D608 => array(0x61), + 0x1D609 => array(0x62), + 0x1D60A => array(0x63), + 0x1D60B => array(0x64), + 0x1D60C => array(0x65), + 0x1D60D => array(0x66), + 0x1D60E => array(0x67), + 0x1D60F => array(0x68), + 0x1D610 => array(0x69), + 0x1D611 => array(0x6A), + 0x1D612 => array(0x6B), + 0x1D613 => array(0x6C), + 0x1D614 => array(0x6D), + 0x1D615 => array(0x6E), + 0x1D616 => array(0x6F), + 0x1D617 => array(0x70), + 0x1D618 => array(0x71), + 0x1D619 => array(0x72), + 0x1D61A => array(0x73), + 0x1D61B => array(0x74), + 0x1D61C => array(0x75), + 0x1D61D => array(0x76), + 0x1D61E => array(0x77), + 0x1D61F => array(0x78), + 0x1D620 => array(0x79), + 0x1D621 => array(0x7A), + 0x1D63C => array(0x61), + 0x1D63D => array(0x62), + 0x1D63E => array(0x63), + 0x1D63F => array(0x64), + 0x1D640 => array(0x65), + 0x1D641 => array(0x66), + 0x1D642 => array(0x67), + 0x1D643 => array(0x68), + 0x1D644 => array(0x69), + 0x1D645 => array(0x6A), + 0x1D646 => array(0x6B), + 0x1D647 => array(0x6C), + 0x1D648 => array(0x6D), + 0x1D649 => array(0x6E), + 0x1D64A => array(0x6F), + 0x1D64B => array(0x70), + 0x1D64C => array(0x71), + 0x1D64D => array(0x72), + 0x1D64E => array(0x73), + 0x1D64F => array(0x74), + 0x1D650 => array(0x75), + 0x1D651 => array(0x76), + 0x1D652 => array(0x77), + 0x1D653 => array(0x78), + 0x1D654 => array(0x79), + 0x1D655 => array(0x7A), + 0x1D670 => array(0x61), + 0x1D671 => array(0x62), + 0x1D672 => array(0x63), + 0x1D673 => array(0x64), + 0x1D674 => array(0x65), + 0x1D675 => array(0x66), + 0x1D676 => array(0x67), + 0x1D677 => array(0x68), + 0x1D678 => array(0x69), + 0x1D679 => array(0x6A), + 0x1D67A => array(0x6B), + 0x1D67B => array(0x6C), + 0x1D67C => array(0x6D), + 0x1D67D => array(0x6E), + 0x1D67E => array(0x6F), + 0x1D67F => array(0x70), + 0x1D680 => array(0x71), + 0x1D681 => array(0x72), + 0x1D682 => array(0x73), + 0x1D683 => array(0x74), + 0x1D684 => array(0x75), + 0x1D685 => array(0x76), + 0x1D686 => array(0x77), + 0x1D687 => array(0x78), + 0x1D688 => array(0x79), + 0x1D689 => array(0x7A), + 0x1D6A8 => array(0x3B1), + 0x1D6A9 => array(0x3B2), + 0x1D6AA => array(0x3B3), + 0x1D6AB => array(0x3B4), + 0x1D6AC => array(0x3B5), + 0x1D6AD => array(0x3B6), + 0x1D6AE => array(0x3B7), + 0x1D6AF => array(0x3B8), + 0x1D6B0 => array(0x3B9), + 0x1D6B1 => array(0x3BA), + 0x1D6B2 => array(0x3BB), + 0x1D6B3 => array(0x3BC), + 0x1D6B4 => array(0x3BD), + 0x1D6B5 => array(0x3BE), + 0x1D6B6 => array(0x3BF), + 0x1D6B7 => array(0x3C0), + 0x1D6B8 => array(0x3C1), + 0x1D6B9 => array(0x3B8), + 0x1D6BA => array(0x3C3), + 0x1D6BB => array(0x3C4), + 0x1D6BC => array(0x3C5), + 0x1D6BD => array(0x3C6), + 0x1D6BE => array(0x3C7), + 0x1D6BF => array(0x3C8), + 0x1D6C0 => array(0x3C9), + 0x1D6D3 => array(0x3C3), + 0x1D6E2 => array(0x3B1), + 0x1D6E3 => array(0x3B2), + 0x1D6E4 => array(0x3B3), + 0x1D6E5 => array(0x3B4), + 0x1D6E6 => array(0x3B5), + 0x1D6E7 => array(0x3B6), + 0x1D6E8 => array(0x3B7), + 0x1D6E9 => array(0x3B8), + 0x1D6EA => array(0x3B9), + 0x1D6EB => array(0x3BA), + 0x1D6EC => array(0x3BB), + 0x1D6ED => array(0x3BC), + 0x1D6EE => array(0x3BD), + 0x1D6EF => array(0x3BE), + 0x1D6F0 => array(0x3BF), + 0x1D6F1 => array(0x3C0), + 0x1D6F2 => array(0x3C1), + 0x1D6F3 => array(0x3B8), + 0x1D6F4 => array(0x3C3), + 0x1D6F5 => array(0x3C4), + 0x1D6F6 => array(0x3C5), + 0x1D6F7 => array(0x3C6), + 0x1D6F8 => array(0x3C7), + 0x1D6F9 => array(0x3C8), + 0x1D6FA => array(0x3C9), + 0x1D70D => array(0x3C3), + 0x1D71C => array(0x3B1), + 0x1D71D => array(0x3B2), + 0x1D71E => array(0x3B3), + 0x1D71F => array(0x3B4), + 0x1D720 => array(0x3B5), + 0x1D721 => array(0x3B6), + 0x1D722 => array(0x3B7), + 0x1D723 => array(0x3B8), + 0x1D724 => array(0x3B9), + 0x1D725 => array(0x3BA), + 0x1D726 => array(0x3BB), + 0x1D727 => array(0x3BC), + 0x1D728 => array(0x3BD), + 0x1D729 => array(0x3BE), + 0x1D72A => array(0x3BF), + 0x1D72B => array(0x3C0), + 0x1D72C => array(0x3C1), + 0x1D72D => array(0x3B8), + 0x1D72E => array(0x3C3), + 0x1D72F => array(0x3C4), + 0x1D730 => array(0x3C5), + 0x1D731 => array(0x3C6), + 0x1D732 => array(0x3C7), + 0x1D733 => array(0x3C8), + 0x1D734 => array(0x3C9), + 0x1D747 => array(0x3C3), + 0x1D756 => array(0x3B1), + 0x1D757 => array(0x3B2), + 0x1D758 => array(0x3B3), + 0x1D759 => array(0x3B4), + 0x1D75A => array(0x3B5), + 0x1D75B => array(0x3B6), + 0x1D75C => array(0x3B7), + 0x1D75D => array(0x3B8), + 0x1D75E => array(0x3B9), + 0x1D75F => array(0x3BA), + 0x1D760 => array(0x3BB), + 0x1D761 => array(0x3BC), + 0x1D762 => array(0x3BD), + 0x1D763 => array(0x3BE), + 0x1D764 => array(0x3BF), + 0x1D765 => array(0x3C0), + 0x1D766 => array(0x3C1), + 0x1D767 => array(0x3B8), + 0x1D768 => array(0x3C3), + 0x1D769 => array(0x3C4), + 0x1D76A => array(0x3C5), + 0x1D76B => array(0x3C6), + 0x1D76C => array(0x3C7), + 0x1D76D => array(0x3C8), + 0x1D76E => array(0x3C9), + 0x1D781 => array(0x3C3), + 0x1D790 => array(0x3B1), + 0x1D791 => array(0x3B2), + 0x1D792 => array(0x3B3), + 0x1D793 => array(0x3B4), + 0x1D794 => array(0x3B5), + 0x1D795 => array(0x3B6), + 0x1D796 => array(0x3B7), + 0x1D797 => array(0x3B8), + 0x1D798 => array(0x3B9), + 0x1D799 => array(0x3BA), + 0x1D79A => array(0x3BB), + 0x1D79B => array(0x3BC), + 0x1D79C => array(0x3BD), + 0x1D79D => array(0x3BE), + 0x1D79E => array(0x3BF), + 0x1D79F => array(0x3C0), + 0x1D7A0 => array(0x3C1), + 0x1D7A1 => array(0x3B8), + 0x1D7A2 => array(0x3C3), + 0x1D7A3 => array(0x3C4), + 0x1D7A4 => array(0x3C5), + 0x1D7A5 => array(0x3C6), + 0x1D7A6 => array(0x3C7), + 0x1D7A7 => array(0x3C8), + 0x1D7A8 => array(0x3C9), + 0x1D7BB => array(0x3C3), + 0x3F9 => array(0x3C3), + 0x1D2C => array(0x61), + 0x1D2D => array(0xE6), + 0x1D2E => array(0x62), + 0x1D30 => array(0x64), + 0x1D31 => array(0x65), + 0x1D32 => array(0x1DD), + 0x1D33 => array(0x67), + 0x1D34 => array(0x68), + 0x1D35 => array(0x69), + 0x1D36 => array(0x6A), + 0x1D37 => array(0x6B), + 0x1D38 => array(0x6C), + 0x1D39 => array(0x6D), + 0x1D3A => array(0x6E), + 0x1D3C => array(0x6F), + 0x1D3D => array(0x223), + 0x1D3E => array(0x70), + 0x1D3F => array(0x72), + 0x1D40 => array(0x74), + 0x1D41 => array(0x75), + 0x1D42 => array(0x77), + 0x213B => array(0x66, 0x61, 0x78), + 0x3250 => array(0x70, 0x74, 0x65), + 0x32CC => array(0x68, 0x67), + 0x32CE => array(0x65, 0x76), + 0x32CF => array(0x6C, 0x74, 0x64), + 0x337A => array(0x69, 0x75), + 0x33DE => array(0x76, 0x2215, 0x6D), + 0x33DF => array(0x61, 0x2215, 0x6D) + ); + + /** + * Normalization Combining Classes; Code Points not listed + * got Combining Class 0. + * + * @static + * @var array + * @access private + */ + private static $_np_norm_combcls = array( + 0x334 => 1, + 0x335 => 1, + 0x336 => 1, + 0x337 => 1, + 0x338 => 1, + 0x93C => 7, + 0x9BC => 7, + 0xA3C => 7, + 0xABC => 7, + 0xB3C => 7, + 0xCBC => 7, + 0x1037 => 7, + 0x3099 => 8, + 0x309A => 8, + 0x94D => 9, + 0x9CD => 9, + 0xA4D => 9, + 0xACD => 9, + 0xB4D => 9, + 0xBCD => 9, + 0xC4D => 9, + 0xCCD => 9, + 0xD4D => 9, + 0xDCA => 9, + 0xE3A => 9, + 0xF84 => 9, + 0x1039 => 9, + 0x1714 => 9, + 0x1734 => 9, + 0x17D2 => 9, + 0x5B0 => 10, + 0x5B1 => 11, + 0x5B2 => 12, + 0x5B3 => 13, + 0x5B4 => 14, + 0x5B5 => 15, + 0x5B6 => 16, + 0x5B7 => 17, + 0x5B8 => 18, + 0x5B9 => 19, + 0x5BB => 20, + 0x5Bc => 21, + 0x5BD => 22, + 0x5BF => 23, + 0x5C1 => 24, + 0x5C2 => 25, + 0xFB1E => 26, + 0x64B => 27, + 0x64C => 28, + 0x64D => 29, + 0x64E => 30, + 0x64F => 31, + 0x650 => 32, + 0x651 => 33, + 0x652 => 34, + 0x670 => 35, + 0x711 => 36, + 0xC55 => 84, + 0xC56 => 91, + 0xE38 => 103, + 0xE39 => 103, + 0xE48 => 107, + 0xE49 => 107, + 0xE4A => 107, + 0xE4B => 107, + 0xEB8 => 118, + 0xEB9 => 118, + 0xEC8 => 122, + 0xEC9 => 122, + 0xECA => 122, + 0xECB => 122, + 0xF71 => 129, + 0xF72 => 130, + 0xF7A => 130, + 0xF7B => 130, + 0xF7C => 130, + 0xF7D => 130, + 0xF80 => 130, + 0xF74 => 132, + 0x321 => 202, + 0x322 => 202, + 0x327 => 202, + 0x328 => 202, + 0x31B => 216, + 0xF39 => 216, + 0x1D165 => 216, + 0x1D166 => 216, + 0x1D16E => 216, + 0x1D16F => 216, + 0x1D170 => 216, + 0x1D171 => 216, + 0x1D172 => 216, + 0x302A => 218, + 0x316 => 220, + 0x317 => 220, + 0x318 => 220, + 0x319 => 220, + 0x31C => 220, + 0x31D => 220, + 0x31E => 220, + 0x31F => 220, + 0x320 => 220, + 0x323 => 220, + 0x324 => 220, + 0x325 => 220, + 0x326 => 220, + 0x329 => 220, + 0x32A => 220, + 0x32B => 220, + 0x32C => 220, + 0x32D => 220, + 0x32E => 220, + 0x32F => 220, + 0x330 => 220, + 0x331 => 220, + 0x332 => 220, + 0x333 => 220, + 0x339 => 220, + 0x33A => 220, + 0x33B => 220, + 0x33C => 220, + 0x347 => 220, + 0x348 => 220, + 0x349 => 220, + 0x34D => 220, + 0x34E => 220, + 0x353 => 220, + 0x354 => 220, + 0x355 => 220, + 0x356 => 220, + 0x591 => 220, + 0x596 => 220, + 0x59B => 220, + 0x5A3 => 220, + 0x5A4 => 220, + 0x5A5 => 220, + 0x5A6 => 220, + 0x5A7 => 220, + 0x5AA => 220, + 0x655 => 220, + 0x656 => 220, + 0x6E3 => 220, + 0x6EA => 220, + 0x6ED => 220, + 0x731 => 220, + 0x734 => 220, + 0x737 => 220, + 0x738 => 220, + 0x739 => 220, + 0x73B => 220, + 0x73C => 220, + 0x73E => 220, + 0x742 => 220, + 0x744 => 220, + 0x746 => 220, + 0x748 => 220, + 0x952 => 220, + 0xF18 => 220, + 0xF19 => 220, + 0xF35 => 220, + 0xF37 => 220, + 0xFC6 => 220, + 0x193B => 220, + 0x20E8 => 220, + 0x1D17B => 220, + 0x1D17C => 220, + 0x1D17D => 220, + 0x1D17E => 220, + 0x1D17F => 220, + 0x1D180 => 220, + 0x1D181 => 220, + 0x1D182 => 220, + 0x1D18A => 220, + 0x1D18B => 220, + 0x59A => 222, + 0x5AD => 222, + 0x1929 => 222, + 0x302D => 222, + 0x302E => 224, + 0x302F => 224, + 0x1D16D => 226, + 0x5AE => 228, + 0x18A9 => 228, + 0x302B => 228, + 0x300 => 230, + 0x301 => 230, + 0x302 => 230, + 0x303 => 230, + 0x304 => 230, + 0x305 => 230, + 0x306 => 230, + 0x307 => 230, + 0x308 => 230, + 0x309 => 230, + 0x30A => 230, + 0x30B => 230, + 0x30C => 230, + 0x30D => 230, + 0x30E => 230, + 0x30F => 230, + 0x310 => 230, + 0x311 => 230, + 0x312 => 230, + 0x313 => 230, + 0x314 => 230, + 0x33D => 230, + 0x33E => 230, + 0x33F => 230, + 0x340 => 230, + 0x341 => 230, + 0x342 => 230, + 0x343 => 230, + 0x344 => 230, + 0x346 => 230, + 0x34A => 230, + 0x34B => 230, + 0x34C => 230, + 0x350 => 230, + 0x351 => 230, + 0x352 => 230, + 0x357 => 230, + 0x363 => 230, + 0x364 => 230, + 0x365 => 230, + 0x366 => 230, + 0x367 => 230, + 0x368 => 230, + 0x369 => 230, + 0x36A => 230, + 0x36B => 230, + 0x36C => 230, + 0x36D => 230, + 0x36E => 230, + 0x36F => 230, + 0x483 => 230, + 0x484 => 230, + 0x485 => 230, + 0x486 => 230, + 0x592 => 230, + 0x593 => 230, + 0x594 => 230, + 0x595 => 230, + 0x597 => 230, + 0x598 => 230, + 0x599 => 230, + 0x59C => 230, + 0x59D => 230, + 0x59E => 230, + 0x59F => 230, + 0x5A0 => 230, + 0x5A1 => 230, + 0x5A8 => 230, + 0x5A9 => 230, + 0x5AB => 230, + 0x5AC => 230, + 0x5AF => 230, + 0x5C4 => 230, + 0x610 => 230, + 0x611 => 230, + 0x612 => 230, + 0x613 => 230, + 0x614 => 230, + 0x615 => 230, + 0x653 => 230, + 0x654 => 230, + 0x657 => 230, + 0x658 => 230, + 0x6D6 => 230, + 0x6D7 => 230, + 0x6D8 => 230, + 0x6D9 => 230, + 0x6DA => 230, + 0x6DB => 230, + 0x6DC => 230, + 0x6DF => 230, + 0x6E0 => 230, + 0x6E1 => 230, + 0x6E2 => 230, + 0x6E4 => 230, + 0x6E7 => 230, + 0x6E8 => 230, + 0x6EB => 230, + 0x6EC => 230, + 0x730 => 230, + 0x732 => 230, + 0x733 => 230, + 0x735 => 230, + 0x736 => 230, + 0x73A => 230, + 0x73D => 230, + 0x73F => 230, + 0x740 => 230, + 0x741 => 230, + 0x743 => 230, + 0x745 => 230, + 0x747 => 230, + 0x749 => 230, + 0x74A => 230, + 0x951 => 230, + 0x953 => 230, + 0x954 => 230, + 0xF82 => 230, + 0xF83 => 230, + 0xF86 => 230, + 0xF87 => 230, + 0x170D => 230, + 0x193A => 230, + 0x20D0 => 230, + 0x20D1 => 230, + 0x20D4 => 230, + 0x20D5 => 230, + 0x20D6 => 230, + 0x20D7 => 230, + 0x20DB => 230, + 0x20DC => 230, + 0x20E1 => 230, + 0x20E7 => 230, + 0x20E9 => 230, + 0xFE20 => 230, + 0xFE21 => 230, + 0xFE22 => 230, + 0xFE23 => 230, + 0x1D185 => 230, + 0x1D186 => 230, + 0x1D187 => 230, + 0x1D189 => 230, + 0x1D188 => 230, + 0x1D1AA => 230, + 0x1D1AB => 230, + 0x1D1AC => 230, + 0x1D1AD => 230, + 0x315 => 232, + 0x31A => 232, + 0x302C => 232, + 0x35F => 233, + 0x362 => 233, + 0x35D => 234, + 0x35E => 234, + 0x360 => 234, + 0x361 => 234, + 0x345 => 240 + ); + // }}} + + // {{{ properties + /** + * @var string + * @access private + */ + private $_punycode_prefix = 'xn--'; + + /** + * @access private + */ + private $_invalid_ucs = 0x80000000; + + /** + * @access private + */ + private $_max_ucs = 0x10FFFF; + + /** + * @var int + * @access private + */ + private $_base = 36; + + /** + * @var int + * @access private + */ + private $_tmin = 1; + + /** + * @var int + * @access private + */ + private $_tmax = 26; + + /** + * @var int + * @access private + */ + private $_skew = 38; + + /** + * @var int + * @access private + */ + private $_damp = 700; + + /** + * @var int + * @access private + */ + private $_initial_bias = 72; + + /** + * @var int + * @access private + */ + private $_initial_n = 0x80; + + /** + * @var int + * @access private + */ + private $_slast; + + /** + * @access private + */ + private $_sbase = 0xAC00; + + /** + * @access private + */ + private $_lbase = 0x1100; + + /** + * @access private + */ + private $_vbase = 0x1161; + + /** + * @access private + */ + private $_tbase = 0x11a7; + + /** + * @var int + * @access private + */ + private $_lcount = 19; + + /** + * @var int + * @access private + */ + private $_vcount = 21; + + /** + * @var int + * @access private + */ + private $_tcount = 28; + + /** + * vcount * tcount + * + * @var int + * @access private + */ + private $_ncount = 588; + + /** + * lcount * tcount * vcount + * + * @var int + * @access private + */ + private $_scount = 11172; + + /** + * Default encoding for encode()'s input and decode()'s output is UTF-8; + * Other possible encodings are ucs4_string and ucs4_array + * See {@link setParams()} for how to select these + * + * @var bool + * @access private + */ + private $_api_encoding = 'utf8'; + + /** + * Overlong UTF-8 encodings are forbidden + * + * @var bool + * @access private + */ + private $_allow_overlong = false; + + /** + * Behave strict or not + * + * @var bool + * @access private + */ + private $_strict_mode = false; + + /** + * IDNA-version to use + * + * Values are "2003" and "2008". + * Defaults to "2003", since that was the original version and for + * compatibility with previous versions of this library. + * If you need to encode "new" characters like the German "Eszett", + * please switch to 2008 first before encoding. + * + * @var bool + * @access private + */ + private $_version = '2003'; + + /** + * Cached value indicating whether or not mbstring function overloading is + * on for strlen + * + * This is cached for optimal performance. + * + * @var boolean + * @see Net_IDNA2::_byteLength() + */ + private static $_mb_string_overload = null; + // }}} + + + // {{{ constructor + /** + * Constructor + * + * @param array $options Options to initialise the object with + * + * @access public + * @see setParams() + */ + public function __construct($options = null) + { + $this->_slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount; + + if (is_array($options)) { + $this->setParams($options); + } + + // populate mbstring overloading cache if not set + if (self::$_mb_string_overload === null) { + self::$_mb_string_overload = (extension_loaded('mbstring') + && (ini_get('mbstring.func_overload') & 0x02) === 0x02); + } + } + // }}} + + + /** + * Sets a new option value. Available options and values: + * + * [utf8 - Use either UTF-8 or ISO-8859-1 as input (true for UTF-8, false + * otherwise); The output is always UTF-8] + * [overlong - Unicode does not allow unnecessarily long encodings of chars, + * to allow this, set this parameter to true, else to false; + * default is false.] + * [strict - true: strict mode, good for registration purposes - Causes errors + * on failures; false: loose mode, ideal for "wildlife" applications + * by silently ignoring errors and returning the original input instead] + * + * @param mixed $option Parameter to set (string: single parameter; array of Parameter => Value pairs) + * @param string $value Value to use (if parameter 1 is a string) + * + * @return boolean true on success, false otherwise + * @access public + */ + public function setParams($option, $value = false) + { + if (!is_array($option)) { + $option = array($option => $value); + } + + foreach ($option as $k => $v) { + switch ($k) { + case 'encoding': + switch ($v) { + case 'utf8': + case 'ucs4_string': + case 'ucs4_array': + $this->_api_encoding = $v; + break; + + default: + throw new InvalidArgumentException('Set Parameter: Unknown parameter '.$v.' for option '.$k); + } + + break; + + case 'overlong': + $this->_allow_overlong = ($v) ? true : false; + break; + + case 'strict': + $this->_strict_mode = ($v) ? true : false; + break; + + case 'version': + if (in_array($v, array('2003', '2008'))) { + $this->_version = $v; + } else { + throw new InvalidArgumentException('Set Parameter: Invalid parameter '.$v.' for option '.$k); + } + break; + + default: + return false; + } + } + + return true; + } + + /** + * Encode a given UTF-8 domain name. + * + * @param string $decoded Domain name (UTF-8 or UCS-4) + * @param string $one_time_encoding Desired input encoding, see {@link set_parameter} + * If not given will use default-encoding + * + * @return string Encoded Domain name (ACE string) + * @return mixed processed string + * @throws Exception + * @access public + */ + public function encode($decoded, $one_time_encoding = false) + { + // Forcing conversion of input to UCS4 array + // If one time encoding is given, use this, else the objects property + switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) { + case 'utf8': + $decoded = $this->_utf8_to_ucs4($decoded); + break; + case 'ucs4_string': + $decoded = $this->_ucs4_string_to_ucs4($decoded); + case 'ucs4_array': // No break; before this line. Catch case, but do nothing + break; + default: + throw new InvalidArgumentException('Unsupported input format'); + } + + // No input, no output, what else did you expect? + if (empty($decoded)) return ''; + + // Anchors for iteration + $last_begin = 0; + // Output string + $output = ''; + + foreach ($decoded as $k => $v) { + // Make sure to use just the plain dot + switch($v) { + case 0x3002: + case 0xFF0E: + case 0xFF61: + $decoded[$k] = 0x2E; + // It's right, no break here + // The codepoints above have to be converted to dots anyway + + // Stumbling across an anchoring character + case 0x2E: + case 0x2F: + case 0x3A: + case 0x3F: + case 0x40: + // Neither email addresses nor URLs allowed in strict mode + if ($this->_strict_mode) { + throw new InvalidArgumentException('Neither email addresses nor URLs are allowed in strict mode.'); + } + // Skip first char + if ($k) { + $encoded = ''; + $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin))); + if ($encoded) { + $output .= $encoded; + } else { + $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin))); + } + $output .= chr($decoded[$k]); + } + $last_begin = $k + 1; + } + } + // Catch the rest of the string + if ($last_begin) { + $inp_len = sizeof($decoded); + $encoded = ''; + $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin))); + if ($encoded) { + $output .= $encoded; + } else { + $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin))); + } + return $output; + } + + if ($output = $this->_encode($decoded)) { + return $output; + } + + return $this->_ucs4_to_utf8($decoded); + } + + /** + * Decode a given ACE domain name. + * + * @param string $input Domain name (ACE string) + * @param string $one_time_encoding Desired output encoding, see {@link set_parameter} + * + * @return string Decoded Domain name (UTF-8 or UCS-4) + * @throws Exception + * @access public + */ + public function decode($input, $one_time_encoding = false) + { + // Optionally set + if ($one_time_encoding) { + switch ($one_time_encoding) { + case 'utf8': + case 'ucs4_string': + case 'ucs4_array': + break; + default: + throw new InvalidArgumentException('Unknown encoding '.$one_time_encoding); + } + } + // Make sure to drop any newline characters around + $input = trim($input); + + // Negotiate input and try to determine, wether it is a plain string, + // an email address or something like a complete URL + if (strpos($input, '@')) { // Maybe it is an email address + // No no in strict mode + if ($this->_strict_mode) { + throw new InvalidArgumentException('Only simple domain name parts can be handled in strict mode'); + } + list($email_pref, $input) = explode('@', $input, 2); + $arr = explode('.', $input); + foreach ($arr as $k => $v) { + $conv = $this->_decode($v); + if ($conv) $arr[$k] = $conv; + } + $return = $email_pref . '@' . join('.', $arr); + } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters) + // No no in strict mode + if ($this->_strict_mode) { + throw new InvalidArgumentException('Only simple domain name parts can be handled in strict mode'); + } + + $parsed = parse_url($input); + if (isset($parsed['host'])) { + $arr = explode('.', $parsed['host']); + foreach ($arr as $k => $v) { + $conv = $this->_decode($v); + if ($conv) $arr[$k] = $conv; + } + $parsed['host'] = join('.', $arr); + if (isset($parsed['scheme'])) { + $parsed['scheme'] .= (strtolower($parsed['scheme']) == 'mailto') ? ':' : '://'; + } + $return = $this->_unparse_url($parsed); + } else { // parse_url seems to have failed, try without it + $arr = explode('.', $input); + foreach ($arr as $k => $v) { + $conv = $this->_decode($v); + if ($conv) $arr[$k] = $conv; + } + $return = join('.', $arr); + } + } else { // Otherwise we consider it being a pure domain name string + $return = $this->_decode($input); + } + // The output is UTF-8 by default, other output formats need conversion here + // If one time encoding is given, use this, else the objects property + switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) { + case 'utf8': + return $return; + break; + case 'ucs4_string': + return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return)); + break; + case 'ucs4_array': + return $this->_utf8_to_ucs4($return); + break; + default: + throw new InvalidArgumentException('Unsupported output format'); + } + } + + + // {{{ private + /** + * Opposite function to parse_url() + * + * Inspired by code from comments of php.net-documentation for parse_url() + * + * @param array $parts_arr parts (strings) as returned by parse_url() + * + * @return string + * @access private + */ + private function _unparse_url($parts_arr) + { + if (!empty($parts_arr['scheme'])) { + $ret_url = $parts_arr['scheme']; + } + if (!empty($parts_arr['user'])) { + $ret_url .= $parts_arr['user']; + if (!empty($parts_arr['pass'])) { + $ret_url .= ':' . $parts_arr['pass']; + } + $ret_url .= '@'; + } + $ret_url .= $parts_arr['host']; + if (!empty($parts_arr['port'])) { + $ret_url .= ':' . $parts_arr['port']; + } + $ret_url .= $parts_arr['path']; + if (!empty($parts_arr['query'])) { + $ret_url .= '?' . $parts_arr['query']; + } + if (!empty($parts_arr['fragment'])) { + $ret_url .= '#' . $parts_arr['fragment']; + } + return $ret_url; + } + + /** + * The actual encoding algorithm. + * + * @param string $decoded Decoded string which should be encoded + * + * @return string Encoded string + * @throws Exception + * @access private + */ + private function _encode($decoded) + { + // We cannot encode a domain name containing the Punycode prefix + $extract = self::_byteLength($this->_punycode_prefix); + $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix); + $check_deco = array_slice($decoded, 0, $extract); + + if ($check_pref == $check_deco) { + throw new InvalidArgumentException('This is already a punycode string'); + } + + // We will not try to encode strings consisting of basic code points only + $encodable = false; + foreach ($decoded as $k => $v) { + if ($v > 0x7a) { + $encodable = true; + break; + } + } + if (!$encodable) { + if ($this->_strict_mode) { + throw new InvalidArgumentException('The given string does not contain encodable chars'); + } + + return false; + } + + // Do NAMEPREP + $decoded = $this->_nameprep($decoded); + + $deco_len = count($decoded); + + // Empty array + if (!$deco_len) { + return false; + } + + // How many chars have been consumed + $codecount = 0; + + // Start with the prefix; copy it to output + $encoded = $this->_punycode_prefix; + + $encoded = ''; + // Copy all basic code points to output + for ($i = 0; $i < $deco_len; ++$i) { + $test = $decoded[$i]; + // Will match [0-9a-zA-Z-] + if ((0x2F < $test && $test < 0x40) + || (0x40 < $test && $test < 0x5B) + || (0x60 < $test && $test <= 0x7B) + || (0x2D == $test) + ) { + $encoded .= chr($decoded[$i]); + $codecount++; + } + } + + // All codepoints were basic ones + if ($codecount == $deco_len) { + return $encoded; + } + + // Start with the prefix; copy it to output + $encoded = $this->_punycode_prefix . $encoded; + + // If we have basic code points in output, add an hyphen to the end + if ($codecount) { + $encoded .= '-'; + } + + // Now find and encode all non-basic code points + $is_first = true; + $cur_code = $this->_initial_n; + $bias = $this->_initial_bias; + $delta = 0; + + while ($codecount < $deco_len) { + // Find the smallest code point >= the current code point and + // remember the last ouccrence of it in the input + for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) { + if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) { + $next_code = $decoded[$i]; + } + } + + $delta += ($next_code - $cur_code) * ($codecount + 1); + $cur_code = $next_code; + + // Scan input again and encode all characters whose code point is $cur_code + for ($i = 0; $i < $deco_len; $i++) { + if ($decoded[$i] < $cur_code) { + $delta++; + } else if ($decoded[$i] == $cur_code) { + for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) { + $t = ($k <= $bias)? + $this->_tmin : + (($k >= $bias + $this->_tmax)? $this->_tmax : $k - $bias); + + if ($q < $t) { + break; + } + + $encoded .= $this->_encodeDigit(ceil($t + (($q - $t) % ($this->_base - $t)))); + $q = ($q - $t) / ($this->_base - $t); + } + + $encoded .= $this->_encodeDigit($q); + $bias = $this->_adapt($delta, $codecount + 1, $is_first); + $codecount++; + $delta = 0; + $is_first = false; + } + } + + $delta++; + $cur_code++; + } + + return $encoded; + } + + /** + * The actual decoding algorithm. + * + * @param string $encoded Encoded string which should be decoded + * + * @return string Decoded string + * @throws Exception + * @access private + */ + private function _decode($encoded) + { + // We do need to find the Punycode prefix + if (!preg_match('!^' . preg_quote($this->_punycode_prefix, '!') . '!', $encoded)) { + return false; + } + + $encode_test = preg_replace('!^' . preg_quote($this->_punycode_prefix, '!') . '!', '', $encoded); + + // If nothing left after removing the prefix, it is hopeless + if (!$encode_test) { + return false; + } + + // Find last occurence of the delimiter + $delim_pos = strrpos($encoded, '-'); + + if ($delim_pos > self::_byteLength($this->_punycode_prefix)) { + for ($k = self::_byteLength($this->_punycode_prefix); $k < $delim_pos; ++$k) { + $decoded[] = ord($encoded{$k}); + } + } else { + $decoded = array(); + } + + $deco_len = count($decoded); + $enco_len = self::_byteLength($encoded); + + // Wandering through the strings; init + $is_first = true; + $bias = $this->_initial_bias; + $idx = 0; + $char = $this->_initial_n; + + for ($enco_idx = ($delim_pos)? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) { + for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) { + $digit = $this->_decodeDigit($encoded{$enco_idx++}); + $idx += $digit * $w; + + $t = ($k <= $bias) ? + $this->_tmin : + (($k >= $bias + $this->_tmax)? $this->_tmax : ($k - $bias)); + + if ($digit < $t) { + break; + } + + $w = (int)($w * ($this->_base - $t)); + } + + $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first); + $is_first = false; + $char += (int) ($idx / ($deco_len + 1)); + $idx %= ($deco_len + 1); + + if ($deco_len > 0) { + // Make room for the decoded char + for ($i = $deco_len; $i > $idx; $i--) { + $decoded[$i] = $decoded[($i - 1)]; + } + } + + $decoded[$idx++] = $char; + } + + return $this->_ucs4_to_utf8($decoded); + } + + /** + * Adapt the bias according to the current code point and position. + * + * @param int $delta ... + * @param int $npoints ... + * @param boolean $is_first ... + * + * @return int + * @access private + */ + private function _adapt($delta, $npoints, $is_first) + { + $delta = (int) ($is_first ? ($delta / $this->_damp) : ($delta / 2)); + $delta += (int) ($delta / $npoints); + + for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) { + $delta = (int) ($delta / ($this->_base - $this->_tmin)); + } + + return (int) ($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew)); + } + + /** + * Encoding a certain digit. + * + * @param int $d One digit to encode + * + * @return char Encoded digit + * @access private + */ + private function _encodeDigit($d) + { + return chr($d + 22 + 75 * ($d < 26)); + } + + /** + * Decode a certain digit. + * + * @param char $cp One digit (character) to decode + * + * @return int Decoded digit + * @access private + */ + private function _decodeDigit($cp) + { + $cp = ord($cp); + return ($cp - 48 < 10)? $cp - 22 : (($cp - 65 < 26)? $cp - 65 : (($cp - 97 < 26)? $cp - 97 : $this->_base)); + } + + /** + * Do Nameprep according to RFC3491 and RFC3454. + * + * @param array $input Unicode Characters + * + * @return string Unicode Characters, Nameprep'd + * @throws Exception + * @access private + */ + private function _nameprep($input) + { + $output = array(); + + // Walking through the input array, performing the required steps on each of + // the input chars and putting the result into the output array + // While mapping required chars we apply the cannonical ordering + + foreach ($input as $v) { + // Map to nothing == skip that code point + if (in_array($v, self::$_np_map_nothing)) { + continue; + } + + // Try to find prohibited input + if (in_array($v, self::$_np_prohibit) || in_array($v, self::$_general_prohibited)) { + throw new Net_IDNA2_Exception_Nameprep('Prohibited input U+' . sprintf('%08X', $v)); + } + + foreach (self::$_np_prohibit_ranges as $range) { + if ($range[0] <= $v && $v <= $range[1]) { + throw new Net_IDNA2_Exception_Nameprep('Prohibited input U+' . sprintf('%08X', $v)); + } + } + + // Hangul syllable decomposition + if (0xAC00 <= $v && $v <= 0xD7AF) { + foreach ($this->_hangulDecompose($v) as $out) { + $output[] = $out; + } + } else if (($this->_version == '2003') && isset(self::$_np_replacemaps[$v])) { + // There's a decomposition mapping for that code point + // Decompositions only in version 2003 (original) of IDNA + foreach ($this->_applyCannonicalOrdering(self::$_np_replacemaps[$v]) as $out) { + $output[] = $out; + } + } else { + $output[] = $v; + } + } + + // Combine code points + + $last_class = 0; + $last_starter = 0; + $out_len = count($output); + + for ($i = 0; $i < $out_len; ++$i) { + $class = $this->_getCombiningClass($output[$i]); + + if ((!$last_class || $last_class != $class) && $class) { + // Try to match + $seq_len = $i - $last_starter; + $out = $this->_combine(array_slice($output, $last_starter, $seq_len)); + + // On match: Replace the last starter with the composed character and remove + // the now redundant non-starter(s) + if ($out) { + $output[$last_starter] = $out; + + if (count($out) != $seq_len) { + for ($j = $i + 1; $j < $out_len; ++$j) { + $output[$j - 1] = $output[$j]; + } + + unset($output[$out_len]); + } + + // Rewind the for loop by one, since there can be more possible compositions + $i--; + $out_len--; + $last_class = ($i == $last_starter)? 0 : $this->_getCombiningClass($output[$i - 1]); + + continue; + } + } + + // The current class is 0 + if (!$class) { + $last_starter = $i; + } + + $last_class = $class; + } + + return $output; + } + + /** + * Decomposes a Hangul syllable + * (see http://www.unicode.org/unicode/reports/tr15/#Hangul). + * + * @param integer $char 32bit UCS4 code point + * + * @return array Either Hangul Syllable decomposed or original 32bit + * value as one value array + * @access private + */ + private function _hangulDecompose($char) + { + $sindex = $char - $this->_sbase; + + if ($sindex < 0 || $sindex >= $this->_scount) { + return array($char); + } + + $result = array(); + $T = $this->_tbase + $sindex % $this->_tcount; + $result[] = (int)($this->_lbase + $sindex / $this->_ncount); + $result[] = (int)($this->_vbase + ($sindex % $this->_ncount) / $this->_tcount); + + if ($T != $this->_tbase) { + $result[] = $T; + } + + return $result; + } + + /** + * Ccomposes a Hangul syllable + * (see http://www.unicode.org/unicode/reports/tr15/#Hangul). + * + * @param array $input Decomposed UCS4 sequence + * + * @return array UCS4 sequence with syllables composed + * @access private + */ + private function _hangulCompose($input) + { + $inp_len = count($input); + + if (!$inp_len) { + return array(); + } + + $result = array(); + $last = $input[0]; + $result[] = $last; // copy first char from input to output + + for ($i = 1; $i < $inp_len; ++$i) { + $char = $input[$i]; + + // Find out, wether two current characters from L and V + $lindex = $last - $this->_lbase; + + if (0 <= $lindex && $lindex < $this->_lcount) { + $vindex = $char - $this->_vbase; + + if (0 <= $vindex && $vindex < $this->_vcount) { + // create syllable of form LV + $last = ($this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount); + $out_off = count($result) - 1; + $result[$out_off] = $last; // reset last + + // discard char + continue; + } + } + + // Find out, wether two current characters are LV and T + $sindex = $last - $this->_sbase; + + if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount) == 0) { + $tindex = $char - $this->_tbase; + + if (0 <= $tindex && $tindex <= $this->_tcount) { + // create syllable of form LVT + $last += $tindex; + $out_off = count($result) - 1; + $result[$out_off] = $last; // reset last + + // discard char + continue; + } + } + + // if neither case was true, just add the character + $last = $char; + $result[] = $char; + } + + return $result; + } + + /** + * Returns the combining class of a certain wide char. + * + * @param integer $char Wide char to check (32bit integer) + * + * @return integer Combining class if found, else 0 + * @access private + */ + private function _getCombiningClass($char) + { + return isset(self::$_np_norm_combcls[$char])? self::$_np_norm_combcls[$char] : 0; + } + + /** + * Apllies the cannonical ordering of a decomposed UCS4 sequence. + * + * @param array $input Decomposed UCS4 sequence + * + * @return array Ordered USC4 sequence + * @access private + */ + private function _applyCannonicalOrdering($input) + { + $swap = true; + $size = count($input); + + while ($swap) { + $swap = false; + $last = $this->_getCombiningClass($input[0]); + + for ($i = 0; $i < $size - 1; ++$i) { + $next = $this->_getCombiningClass($input[$i + 1]); + + if ($next != 0 && $last > $next) { + // Move item leftward until it fits + for ($j = $i + 1; $j > 0; --$j) { + if ($this->_getCombiningClass($input[$j - 1]) <= $next) { + break; + } + + $t = $input[$j]; + $input[$j] = $input[$j - 1]; + $input[$j - 1] = $t; + $swap = 1; + } + + // Reentering the loop looking at the old character again + $next = $last; + } + + $last = $next; + } + } + + return $input; + } + + /** + * Do composition of a sequence of starter and non-starter. + * + * @param array $input UCS4 Decomposed sequence + * + * @return array Ordered USC4 sequence + * @access private + */ + private function _combine($input) + { + $inp_len = count($input); + + // Is it a Hangul syllable? + if (1 != $inp_len) { + $hangul = $this->_hangulCompose($input); + + // This place is probably wrong + if (count($hangul) != $inp_len) { + return $hangul; + } + } + + foreach (self::$_np_replacemaps as $np_src => $np_target) { + if ($np_target[0] != $input[0]) { + continue; + } + + if (count($np_target) != $inp_len) { + continue; + } + + $hit = false; + + foreach ($input as $k2 => $v2) { + if ($v2 == $np_target[$k2]) { + $hit = true; + } else { + $hit = false; + break; + } + } + + if ($hit) { + return $np_src; + } + } + + return false; + } + + /** + * This converts an UTF-8 encoded string to its UCS-4 (array) representation + * By talking about UCS-4 we mean arrays of 32bit integers representing + * each of the "chars". This is due to PHP not being able to handle strings with + * bit depth different from 8. This applies to the reverse method _ucs4_to_utf8(), too. + * The following UTF-8 encodings are supported: + * + * bytes bits representation + * 1 7 0xxxxxxx + * 2 11 110xxxxx 10xxxxxx + * 3 16 1110xxxx 10xxxxxx 10xxxxxx + * 4 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + * 5 26 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + * 6 31 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + * + * Each x represents a bit that can be used to store character data. + * + * @param string $input utf8-encoded string + * + * @return array ucs4-encoded array + * @throws Exception + * @access private + */ + private function _utf8_to_ucs4($input) + { + $output = array(); + $out_len = 0; + $inp_len = self::_byteLength($input, '8bit'); + $mode = 'next'; + $test = 'none'; + for ($k = 0; $k < $inp_len; ++$k) { + $v = ord($input{$k}); // Extract byte from input string + + if ($v < 128) { // We found an ASCII char - put into stirng as is + $output[$out_len] = $v; + ++$out_len; + if ('add' == $mode) { + throw new UnexpectedValueException('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k); + } + continue; + } + if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char + $start_byte = $v; + $mode = 'add'; + $test = 'range'; + if ($v >> 5 == 6) { // &110xxxxx 10xxxxx + $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left + $v = ($v - 192) << 6; + } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx + $next_byte = 1; + $v = ($v - 224) << 12; + } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + $next_byte = 2; + $v = ($v - 240) << 18; + } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + $next_byte = 3; + $v = ($v - 248) << 24; + } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + $next_byte = 4; + $v = ($v - 252) << 30; + } else { + throw new UnexpectedValueException('This might be UTF-8, but I don\'t understand it at byte '.$k); + } + if ('add' == $mode) { + $output[$out_len] = (int) $v; + ++$out_len; + continue; + } + } + if ('add' == $mode) { + if (!$this->_allow_overlong && $test == 'range') { + $test = 'none'; + if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) { + throw new OutOfRangeException('Bogus UTF-8 character detected (out of legal range) at byte '.$k); + } + } + if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx + $v = ($v - 128) << ($next_byte * 6); + $output[($out_len - 1)] += $v; + --$next_byte; + } else { + throw new UnexpectedValueException('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k); + } + if ($next_byte < 0) { + $mode = 'next'; + } + } + } // for + return $output; + } + + /** + * Convert UCS-4 array into UTF-8 string + * + * @param array $input ucs4-encoded array + * + * @return string utf8-encoded string + * @throws Exception + * @access private + */ + private function _ucs4_to_utf8($input) + { + $output = ''; + + foreach ($input as $v) { + // $v = ord($v); + + if ($v < 128) { + // 7bit are transferred literally + $output .= chr($v); + } else if ($v < 1 << 11) { + // 2 bytes + $output .= chr(192 + ($v >> 6)) + . chr(128 + ($v & 63)); + } else if ($v < 1 << 16) { + // 3 bytes + $output .= chr(224 + ($v >> 12)) + . chr(128 + (($v >> 6) & 63)) + . chr(128 + ($v & 63)); + } else if ($v < 1 << 21) { + // 4 bytes + $output .= chr(240 + ($v >> 18)) + . chr(128 + (($v >> 12) & 63)) + . chr(128 + (($v >> 6) & 63)) + . chr(128 + ($v & 63)); + } else if ($v < 1 << 26) { + // 5 bytes + $output .= chr(248 + ($v >> 24)) + . chr(128 + (($v >> 18) & 63)) + . chr(128 + (($v >> 12) & 63)) + . chr(128 + (($v >> 6) & 63)) + . chr(128 + ($v & 63)); + } else if ($v < 1 << 31) { + // 6 bytes + $output .= chr(252 + ($v >> 30)) + . chr(128 + (($v >> 24) & 63)) + . chr(128 + (($v >> 18) & 63)) + . chr(128 + (($v >> 12) & 63)) + . chr(128 + (($v >> 6) & 63)) + . chr(128 + ($v & 63)); + } else { + throw new UnexpectedValueException('Conversion from UCS-4 to UTF-8 failed: malformed input'); + } + } + + return $output; + } + + /** + * Convert UCS-4 array into UCS-4 string + * + * @param array $input ucs4-encoded array + * + * @return string ucs4-encoded string + * @throws Exception + * @access private + */ + private function _ucs4_to_ucs4_string($input) + { + $output = ''; + // Take array values and split output to 4 bytes per value + // The bit mask is 255, which reads &11111111 + foreach ($input as $v) { + $output .= ($v & (255 << 24) >> 24) . ($v & (255 << 16) >> 16) . ($v & (255 << 8) >> 8) . ($v & 255); + } + return $output; + } + + /** + * Convert UCS-4 string into UCS-4 array + * + * @param string $input ucs4-encoded string + * + * @return array ucs4-encoded array + * @throws InvalidArgumentException + * @access private + */ + private function _ucs4_string_to_ucs4($input) + { + $output = array(); + + $inp_len = self::_byteLength($input); + // Input length must be dividable by 4 + if ($inp_len % 4) { + throw new InvalidArgumentException('Input UCS4 string is broken'); + } + + // Empty input - return empty output + if (!$inp_len) { + return $output; + } + + for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) { + // Increment output position every 4 input bytes + if (!$i % 4) { + $out_len++; + $output[$out_len] = 0; + } + $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) ); + } + return $output; + } + + /** + * Echo hex representation of UCS4 sequence. + * + * @param array $input UCS4 sequence + * @param boolean $include_bit Include bitmask in output + * + * @return void + * @static + * @access private + */ + private static function _showHex($input, $include_bit = false) + { + foreach ($input as $k => $v) { + echo '[', $k, '] => ', sprintf('%X', $v); + + if ($include_bit) { + echo ' (', Net_IDNA2::_showBitmask($v), ')'; + } + + echo "\n"; + } + } + + /** + * Gives you a bit representation of given Byte (8 bits), Word (16 bits) or DWord (32 bits) + * Output width is automagically determined + * + * @param int $octet ... + * + * @return string Bitmask-representation + * @static + * @access private + */ + private static function _showBitmask($octet) + { + if ($octet >= (1 << 16)) { + $w = 31; + } else if ($octet >= (1 << 8)) { + $w = 15; + } else { + $w = 7; + } + + $return = ''; + + for ($i = $w; $i > -1; $i--) { + $return .= ($octet & (1 << $i))? '1' : '0'; + } + + return $return; + } + + /** + * Gets the length of a string in bytes even if mbstring function + * overloading is turned on + * + * @param string $string the string for which to get the length. + * + * @return integer the length of the string in bytes. + * + * @see Net_IDNA2::$_mb_string_overload + */ + private static function _byteLength($string) + { + if (self::$_mb_string_overload) { + return mb_strlen($string, '8bit'); + } + return strlen((binary)$string); + } + + // }}}} + + // {{{ factory + /** + * Attempts to return a concrete IDNA instance for either php4 or php5. + * + * @param array $params Set of paramaters + * + * @return Net_IDNA2 + * @access public + */ + function getInstance($params = array()) + { + return new Net_IDNA2($params); + } + // }}} + + // {{{ singleton + /** + * Attempts to return a concrete IDNA instance for either php4 or php5, + * only creating a new instance if no IDNA instance with the same + * parameters currently exists. + * + * @param array $params Set of paramaters + * + * @return object Net_IDNA2 + * @access public + */ + function singleton($params = array()) + { + static $instances; + if (!isset($instances)) { + $instances = array(); + } + + $signature = serialize($params); + if (!isset($instances[$signature])) { + $instances[$signature] = Net_IDNA2::getInstance($params); + } + + return $instances[$signature]; + } + // }}} +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Vendors/Net/IDNA2CustomExceptions.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Vendors/Net/IDNA2CustomExceptions.php new file mode 100644 index 0000000..69b0f69 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/MailSo/Vendors/Net/IDNA2CustomExceptions.php @@ -0,0 +1,18 @@ +getSignature(); + } + + return $sSignature; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/LICENSE.txt b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/LICENSE.txt new file mode 100644 index 0000000..90e632a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/LICENSE.txt @@ -0,0 +1,48 @@ +MIT License + +Copyright (c) <2011-2015> Serban Ghita, Nick Ilyin and contributors. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Developer’s Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/Mobile_Detect.json b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/Mobile_Detect.json new file mode 100644 index 0000000..63d2aa7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/Mobile_Detect.json @@ -0,0 +1 @@ +{"version":"2.8.26","headerMatch":{"HTTP_ACCEPT":{"matches":["application\/x-obml2d","application\/vnd.rim.html","text\/vnd.wap.wml","application\/vnd.wap.xhtml+xml"]},"HTTP_X_WAP_PROFILE":null,"HTTP_X_WAP_CLIENTID":null,"HTTP_WAP_CONNECTION":null,"HTTP_PROFILE":null,"HTTP_X_OPERAMINI_PHONE_UA":null,"HTTP_X_NOKIA_GATEWAY_ID":null,"HTTP_X_ORANGE_ID":null,"HTTP_X_VODAFONE_3GPDPCONTEXT":null,"HTTP_X_HUAWEI_USERID":null,"HTTP_UA_OS":null,"HTTP_X_MOBILE_GATEWAY":null,"HTTP_X_ATT_DEVICEID":null,"HTTP_UA_CPU":{"matches":["ARM"]}},"uaHttpHeaders":["HTTP_USER_AGENT","HTTP_X_OPERAMINI_PHONE_UA","HTTP_X_DEVICE_USER_AGENT","HTTP_X_ORIGINAL_USER_AGENT","HTTP_X_SKYFIRE_PHONE","HTTP_X_BOLT_PHONE_UA","HTTP_DEVICE_STOCK_UA","HTTP_X_UCBROWSER_DEVICE_UA"],"uaMatch":{"phones":{"iPhone":"\\biPhone\\b|\\biPod\\b","BlackBerry":"BlackBerry|\\bBB10\\b|rim[0-9]+","HTC":"HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\\bEVO\\b|T-Mobile G1|Z520m","Nexus":"Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6","Dell":"Dell.*Streak|Dell.*Aero|Dell.*Venue|DELL.*Venue Pro|Dell Flash|Dell Smoke|Dell Mini 3iX|XCD28|XCD35|\\b001DL\\b|\\b101DL\\b|\\bGS01\\b","Motorola":"Motorola|DROIDX|DROID BIONIC|\\bDroid\\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\\bMoto E\\b","Samsung":"\\bSamsung\\b|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558|GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C","LG":"\\bLG\\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323)","Sony":"SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533","Asus":"Asus.*Galaxy|PadFone.*Mobile","NokiaLumia":"Lumia [0-9]{3,4}","Micromax":"Micromax.*\\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\\b","Palm":"PalmSource|Palm","Vertu":"Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature","Pantech":"PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790","Fly":"IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250","Wiko":"KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM","iMobile":"i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)","SimValley":"\\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\\b","Wolfgang":"AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q","Alcatel":"Alcatel","Nintendo":"Nintendo 3DS","Amoi":"Amoi","INQ":"INQ","GenericPhone":"Tapatalk|PDA;|SAGEM|\\bmmp\\b|pocket|\\bpsp\\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\\bwap\\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser"},"tablets":{"iPad":"iPad|iPad.*Mobile","NexusTablet":"Android.*Nexus[\\s]+(7|9|10)","SamsungTablet":"SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715|SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU|SM-T815Y","Kindle":"Kindle|Silk.*Accelerated|Android.*\\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\\b|Android.*Silk\/[0-9.]+ like Chrome\/[0-9.]+ (?!Mobile)","SurfaceTablet":"Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)","HPTablet":"HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10","AsusTablet":"^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\\bK00F\\b|\\bK00C\\b|\\bK00E\\b|\\bK00L\\b|TX201LA|ME176C|ME102A|\\bM80TA\\b|ME372CL|ME560CG|ME372CG|ME302KL| K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\\bME70C\\b|ME581C|ME581CL|ME8510C|ME181C|P01Y|PO1MA|P01Z|\\bP027\\b","BlackBerryTablet":"PlayBook|RIM Tablet","HTCtablet":"HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410","MotorolaTablet":"xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617","NookTablet":"Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2","AcerTablet":"Android.*; \\b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\\b|W3-810|\\bA3-A10\\b|\\bA3-A11\\b|\\bA3-A20\\b|\\bA3-A30","ToshibaTablet":"Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO","LGTablet":"\\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\\b","FujitsuTablet":"Android.*\\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\\b","PrestigioTablet":"PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD|PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002","LenovoTablet":"Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)","DellTablet":"Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7","YarvikTablet":"Android.*\\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\\b","MedionTablet":"Android.*\\bOYO\\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB","ArnovaTablet":"97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2","IntensoTablet":"INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004","IRUTablet":"M702pro","MegafonTablet":"MegaFon V9|\\bZTE V9\\b|Android.*\\bMT7A\\b","EbodaTablet":"E-Boda (Supreme|Impresspeed|Izzycomm|Essential)","AllViewTablet":"Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)","ArchosTablet":"\\b(101G9|80G9|A101IT)\\b|Qilive 97R|Archos5|\\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\\b","AinolTablet":"NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark","NokiaLumiaTablet":"Lumia 2520","SonyTablet":"Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP612|SOT31","PhilipsTablet":"\\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\\b","CubeTablet":"Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT","CobyTablet":"MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010","MIDTablet":"M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10","MSITablet":"MSI \\b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\\b","SMiTTablet":"Android.*(\\bMID\\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)","RockChipTablet":"Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A","FlyTablet":"IQ310|Fly Vision","bqTablet":"Android.*(bq)?.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris [E|M]10)|Maxwell.*Lite|Maxwell.*Plus","HuaweiTablet":"MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim","NecTablet":"\\bN-06D|\\bN-08D","PantechTablet":"Pantech.*P4100","BronchoTablet":"Broncho.*(N701|N708|N802|a710)","VersusTablet":"TOUCHPAD.*[78910]|\\bTOUCHTAB\\b","ZyncTablet":"z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900","PositivoTablet":"TB07STA|TB10STA|TB07FTA|TB10FTA","NabiTablet":"Android.*\\bNabi","KoboTablet":"Kobo Touch|\\bK080\\b|\\bVox\\b Build|\\bArc\\b Build","DanewTablet":"DSlide.*\\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\\b","TexetTablet":"NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE","PlaystationTablet":"Playstation.*(Portable|Vita)","TrekstorTablet":"ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab","PyleAudioTablet":"\\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\\b","AdvanTablet":"Android.* \\b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\\b ","DanyTechTablet":"Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1","GalapadTablet":"Android.*\\bG1\\b","MicromaxTablet":"Funbook|Micromax.*\\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\\b","KarbonnTablet":"Android.*\\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\\b","AllFineTablet":"Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide","PROSCANTablet":"\\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\\b","YONESTablet":"BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026","ChangJiaTablet":"TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503","GUTablet":"TX-A1301|TX-M9002|Q702|kf026","PointOfViewTablet":"TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10","OvermaxTablet":"OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)","HCLTablet":"HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync","DPSTablet":"DPS Dream 9|DPS Dual 7","VistureTablet":"V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10","CrestaTablet":"CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989","MediatekTablet":"\\bMT8125|MT8389|MT8135|MT8377\\b","ConcordeTablet":"Concorde([ ]+)?Tab|ConCorde ReadMan","GoCleverTablet":"GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042","ModecomTablet":"FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003","VoninoTablet":"\\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\\bQ8\\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\\b","ECSTablet":"V07OT2|TM105A|S10OT1|TR10CS1","StorexTablet":"eZee[_']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab","VodafoneTablet":"SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497","EssentielBTablet":"Smart[ ']?TAB[ ]+?[0-9]+|Family[ ']?TAB2","RossMoorTablet":"RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711","iMobileTablet":"i-mobile i-note","TolinoTablet":"tolino tab [0-9.]+|tolino shine","AudioSonicTablet":"\\bC-22Q|T7-QC|T-17B|T-17P\\b","AMPETablet":"Android.* A78 ","SkkTablet":"Android.* (SKYPAD|PHOENIX|CYCLOPS)","TecnoTablet":"TECNO P9","JXDTablet":"Android.* \\b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\\b","iJoyTablet":"Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)","FX2Tablet":"FX2 PAD7|FX2 PAD10","XoroTablet":"KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151","ViewsonicTablet":"ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a","OdysTablet":"LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\\bXELIO\\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10","CaptivaTablet":"CAPTIVA PAD","IconbitTablet":"NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S","TeclastTablet":"T98 4G|\\bP80\\b|\\bX90HD\\b|X98 Air|X98 Air 3G|\\bX89\\b|P80 3G|\\bX80h\\b|P98 Air|\\bX89HD\\b|P98 3G|\\bP90HD\\b|P89 3G|X98 3G|\\bP70h\\b|P79HD 3G|G18d 3G|\\bP79HD\\b|\\bP89s\\b|\\bA88\\b|\\bP10HD\\b|\\bP19HD\\b|G18 3G|\\bP78HD\\b|\\bA78\\b|\\bP75\\b|G17s 3G|G17h 3G|\\bP85t\\b|\\bP90\\b|\\bP11\\b|\\bP98t\\b|\\bP98HD\\b|\\bG18d\\b|\\bP85s\\b|\\bP11HD\\b|\\bP88s\\b|\\bA80HD\\b|\\bA80se\\b|\\bA10h\\b|\\bP89\\b|\\bP78s\\b|\\bG18\\b|\\bP85\\b|\\bA70h\\b|\\bA70\\b|\\bG17\\b|\\bP18\\b|\\bA80s\\b|\\bA11s\\b|\\bP88HD\\b|\\bA80h\\b|\\bP76s\\b|\\bP76h\\b|\\bP98\\b|\\bA10HD\\b|\\bP78\\b|\\bP88\\b|\\bA11\\b|\\bA10t\\b|\\bP76a\\b|\\bP76t\\b|\\bP76e\\b|\\bP85HD\\b|\\bP85a\\b|\\bP86\\b|\\bP75HD\\b|\\bP76v\\b|\\bA12\\b|\\bP75a\\b|\\bA15\\b|\\bP76Ti\\b|\\bP81HD\\b|\\bA10\\b|\\bT760VE\\b|\\bT720HD\\b|\\bP76\\b|\\bP73\\b|\\bP71\\b|\\bP72\\b|\\bT720SE\\b|\\bC520Ti\\b|\\bT760\\b|\\bT720VE\\b|T720-3GE|T720-WiFi","OndaTablet":"\\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\\b[\\s]+","JaytechTablet":"TPC-PA762","BlaupunktTablet":"Endeavour 800NG|Endeavour 1010","DigmaTablet":"\\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\\b","EvolioTablet":"ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\\bEvotab\\b|\\bNeura\\b","LavaTablet":"QPAD E704|\\bIvoryS\\b|E-TAB IVORY|\\bE-TAB\\b","AocTablet":"MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712","MpmanTablet":"MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\\bMPG7\\b|MPDCG75|MPDCG71|MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77|MP709|MID701|MID711|MID170|MPDC703|MPQC1010","CelkonTablet":"CT695|CT888|CT[\\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\\bCT-1\\b","WolderTablet":"miTab \\b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\\b","MiTablet":"\\bMI PAD\\b|\\bHM NOTE 1W\\b","NibiruTablet":"Nibiru M1|Nibiru Jupiter One","NexoTablet":"NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI","LeaderTablet":"TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G|TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100","UbislateTablet":"UbiSlate[\\s]?7C","PocketBookTablet":"Pocketbook","KocasoTablet":"\\b(TB-1207)\\b","HisenseTablet":"\\b(F5281|E2371)\\b","Hudl":"Hudl HT7S3|Hudl 2","TelstraTablet":"T-Hub2","GenericTablet":"Android.*\\b97D\\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\\bA7EB\\b|CatNova8|A1_07|CT704|CT1002|\\bM721\\b|rk30sdk|\\bEVOTAB\\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\\bM6pro\\b|CT1020W|arc 10HD|\\bTP750\\b|\\bQTAQZ3\\b"},"browsers":{"Chrome":"\\bCrMo\\b|CriOS|Android.*Chrome\/[.0-9]* (Mobile)?","Dolfin":"\\bDolfin\\b","Opera":"Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR\/[0-9.]+|Coast\/[0-9.]+","Skyfire":"Skyfire","Edge":"Mobile Safari\/[.0-9]* Edge","IE":"IEMobile|MSIEMobile","Firefox":"fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS","Bolt":"bolt","TeaShark":"teashark","Blazer":"Blazer","Safari":"Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari","UCBrowser":"UC.*Browser|UCWEB","baiduboxapp":"baiduboxapp","baidubrowser":"baidubrowser","DiigoBrowser":"DiigoBrowser","Puffin":"Puffin","Mercury":"\\bMercury\\b","ObigoBrowser":"Obigo","NetFront":"NF-Browser","GenericBrowser":"NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger","PaleMoon":"Android.*PaleMoon|Mobile.*PaleMoon"},"os":{"AndroidOS":"Android","BlackBerryOS":"blackberry|\\bBB10\\b|rim tablet os","PalmOS":"PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino","SymbianOS":"Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\\bS60\\b","WindowsMobileOS":"Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;","WindowsPhoneOS":"Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;","iOS":"\\biPhone.*Mobile|\\biPod|\\biPad","MeeGoOS":"MeeGo","MaemoOS":"Maemo","JavaOS":"J2ME\/|\\bMIDP\\b|\\bCLDC\\b","webOS":"webOS|hpwOS","badaOS":"\\bBada\\b","BREWOS":"BREW"},"utilities":{"Bot":"Googlebot|facebookexternalhit|AdsBot-Google|Google Keyword Suggestion|Facebot|YandexBot|YandexMobileBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor|Exabot|MJ12bot|YandexImages|TurnitinBot|Pingdom","MobileBot":"Googlebot-Mobile|AdsBot-Google-Mobile|YahooSeeker\/M1A1-R2D2","DesktopMode":"WPDesktop","TV":"SonyDTV|HbbTV","WebKit":"(webkit)[ \/]([\\w.]+)","Console":"\\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\\b","Watch":"SM-V700"}}} \ No newline at end of file diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/Mobile_Detect.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/Mobile_Detect.php new file mode 100644 index 0000000..6d0a36d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/Mobile_Detect.php @@ -0,0 +1,1460 @@ + + * Nick Ilyin + * + * Original author: Victor Stanciu + * + * @license Code and contributions have 'MIT License' + * More details: https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt + * + * @link Homepage: http://mobiledetect.net + * GitHub Repo: https://github.com/serbanghita/Mobile-Detect + * Google Code: http://code.google.com/p/php-mobile-detect/ + * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md + * HOWTO: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples + * + * @version 2.8.26 + */ + +class Mobile_Detect +{ + /** + * Mobile detection type. + * + * @deprecated since version 2.6.9 + */ + const DETECTION_TYPE_MOBILE = 'mobile'; + + /** + * Extended detection type. + * + * @deprecated since version 2.6.9 + */ + const DETECTION_TYPE_EXTENDED = 'extended'; + + /** + * A frequently used regular expression to extract version #s. + * + * @deprecated since version 2.6.9 + */ + const VER = '([\w._\+]+)'; + + /** + * Top-level device. + */ + const MOBILE_GRADE_A = 'A'; + + /** + * Mid-level device. + */ + const MOBILE_GRADE_B = 'B'; + + /** + * Low-level device. + */ + const MOBILE_GRADE_C = 'C'; + + /** + * Stores the version number of the current release. + */ + const VERSION = '2.8.26'; + + /** + * A type for the version() method indicating a string return value. + */ + const VERSION_TYPE_STRING = 'text'; + + /** + * A type for the version() method indicating a float return value. + */ + const VERSION_TYPE_FLOAT = 'float'; + + /** + * A cache for resolved matches + * @var array + */ + protected $cache = array(); + + /** + * The User-Agent HTTP header is stored in here. + * @var string + */ + protected $userAgent = null; + + /** + * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE. + * @var array + */ + protected $httpHeaders = array(); + + /** + * CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer. + * @var array + */ + protected $cloudfrontHeaders = array(); + + /** + * The matching Regex. + * This is good for debug. + * @var string + */ + protected $matchingRegex = null; + + /** + * The matches extracted from the regex expression. + * This is good for debug. + * @var string + */ + protected $matchesArray = null; + + /** + * The detection type, using self::DETECTION_TYPE_MOBILE or self::DETECTION_TYPE_EXTENDED. + * + * @deprecated since version 2.6.9 + * + * @var string + */ + protected $detectionType = self::DETECTION_TYPE_MOBILE; + + /** + * HTTP headers that trigger the 'isMobile' detection + * to be true. + * + * @var array + */ + protected static $mobileHeaders = array( + + 'HTTP_ACCEPT' => array('matches' => array( + // Opera Mini; @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/ + 'application/x-obml2d', + // BlackBerry devices. + 'application/vnd.rim.html', + 'text/vnd.wap.wml', + 'application/vnd.wap.xhtml+xml' + )), + 'HTTP_X_WAP_PROFILE' => null, + 'HTTP_X_WAP_CLIENTID' => null, + 'HTTP_WAP_CONNECTION' => null, + 'HTTP_PROFILE' => null, + // Reported by Opera on Nokia devices (eg. C3). + 'HTTP_X_OPERAMINI_PHONE_UA' => null, + 'HTTP_X_NOKIA_GATEWAY_ID' => null, + 'HTTP_X_ORANGE_ID' => null, + 'HTTP_X_VODAFONE_3GPDPCONTEXT' => null, + 'HTTP_X_HUAWEI_USERID' => null, + // Reported by Windows Smartphones. + 'HTTP_UA_OS' => null, + // Reported by Verizon, Vodafone proxy system. + 'HTTP_X_MOBILE_GATEWAY' => null, + // Seen this on HTC Sensation. SensationXE_Beats_Z715e. + 'HTTP_X_ATT_DEVICEID' => null, + // Seen this on a HTC. + 'HTTP_UA_CPU' => array('matches' => array('ARM')), + ); + + /** + * List of mobile devices (phones). + * + * @var array + */ + protected static $phoneDevices = array( + 'iPhone' => '\biPhone\b|\biPod\b', // |\biTunes + 'BlackBerry' => 'BlackBerry|\bBB10\b|rim[0-9]+', + 'HTC' => 'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m', + 'Nexus' => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6', + // @todo: Is 'Dell Streak' a tablet or a phone? ;) + 'Dell' => 'Dell.*Streak|Dell.*Aero|Dell.*Venue|DELL.*Venue Pro|Dell Flash|Dell Smoke|Dell Mini 3iX|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b', + 'Motorola' => 'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b', + 'Samsung' => '\bSamsung\b|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558|GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C', + 'LG' => '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323)', + 'Sony' => 'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533', + 'Asus' => 'Asus.*Galaxy|PadFone.*Mobile', + 'NokiaLumia' => 'Lumia [0-9]{3,4}', + // http://www.micromaxinfo.com/mobiles/smartphones + // Added because the codes might conflict with Acer Tablets. + 'Micromax' => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b', + // @todo Complete the regex. + 'Palm' => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ; + 'Vertu' => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature', // Just for fun ;) + // http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH) + // Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android. + 'Pantech' => 'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790', + // http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones. + 'Fly' => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250', + // http://fr.wikomobile.com + 'Wiko' => 'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM', + 'iMobile' => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)', + // Added simvalley mobile just for fun. They have some interesting devices. + // http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html + 'SimValley' => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b', + // Wolfgang - a brand that is sold by Aldi supermarkets. + // http://www.wolfgangmobile.com/ + 'Wolfgang' => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q', + 'Alcatel' => 'Alcatel', + 'Nintendo' => 'Nintendo 3DS', + // http://en.wikipedia.org/wiki/Amoi + 'Amoi' => 'Amoi', + // http://en.wikipedia.org/wiki/INQ + 'INQ' => 'INQ', + // @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039 + 'GenericPhone' => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser', + ); + + /** + * List of tablet devices. + * + * @var array + */ + protected static $tabletDevices = array( + // @todo: check for mobile friendly emails topic. + 'iPad' => 'iPad|iPad.*Mobile', + // Removed |^.*Android.*Nexus(?!(?:Mobile).)*$ + // @see #442 + 'NexusTablet' => 'Android.*Nexus[\s]+(7|9|10)', + 'SamsungTablet' => 'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715|SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU|SM-T815Y', // SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone. + // http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html + 'Kindle' => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\b|Android.*Silk/[0-9.]+ like Chrome/[0-9.]+ (?!Mobile)', + // Only the Surface tablets with Windows RT are considered mobile. + // http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx + 'SurfaceTablet' => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)', + // http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT + 'HPTablet' => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10', + // Watch out for PadFone, see #132. + // http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/ + 'AsusTablet' => '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b|\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C|ME181C|P01Y|PO1MA|P01Z|\bP027\b', + 'BlackBerryTablet' => 'PlayBook|RIM Tablet', + 'HTCtablet' => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410', + 'MotorolaTablet' => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617', + 'NookTablet' => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2', + // http://www.acer.ro/ac/ro/RO/content/drivers + // http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer) + // http://us.acer.com/ac/en/US/content/group/tablets + // http://www.acer.de/ac/de/DE/content/models/tablets/ + // Can conflict with Micromax and Motorola phones codes. + 'AcerTablet' => 'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b|W3-810|\bA3-A10\b|\bA3-A11\b|\bA3-A20\b|\bA3-A30', + // http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/ + // http://us.toshiba.com/tablets/tablet-finder + // http://www.toshiba.co.jp/regza/tablet/ + 'ToshibaTablet' => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO', + // http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html + // http://www.lg.com/us/tablets + 'LGTablet' => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b', + 'FujitsuTablet' => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b', + // Prestigio Tablets http://www.prestigio.com/support + 'PrestigioTablet' => 'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD|PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002', + // http://support.lenovo.com/en_GB/downloads/default.page?# + 'LenovoTablet' => 'Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)', + // http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets + 'DellTablet' => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7', + // http://www.yarvik.com/en/matrix/tablets/ + 'YarvikTablet' => 'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b', + 'MedionTablet' => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB', + 'ArnovaTablet' => '97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2', + // http://www.intenso.de/kategorie_en.php?kategorie=33 + // @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate + 'IntensoTablet' => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004', + // IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/ + 'IRUTablet' => 'M702pro', + 'MegafonTablet' => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b', + // http://www.e-boda.ro/tablete-pc.html + 'EbodaTablet' => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)', + // http://www.allview.ro/produse/droseries/lista-tablete-pc/ + 'AllViewTablet' => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)', + // http://wiki.archosfans.com/index.php?title=Main_Page + // @note Rewrite the regex format after we add more UAs. + 'ArchosTablet' => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b', + // http://www.ainol.com/plugin.php?identifier=ainol&module=product + 'AinolTablet' => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark', + 'NokiaLumiaTablet' => 'Lumia 2520', + // @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER + // Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser + // http://www.sony.jp/support/tablet/ + 'SonyTablet' => 'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP612|SOT31', + // http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8 + 'PhilipsTablet' => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b', + // db + http://www.cube-tablet.com/buy-products.html + 'CubeTablet' => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT', + // http://www.cobyusa.com/?p=pcat&pcat_id=3001 + 'CobyTablet' => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010', + // http://www.match.net.cn/products.asp + 'MIDTablet' => 'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10', + // http://www.msi.com/support + // @todo Research the Windows Tablets. + 'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b', + // @todo http://www.kyoceramobile.com/support/drivers/ + // 'KyoceraTablet' => null, + // @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/ + // 'IntextTablet' => null, + // http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets) + // http://www.imp3.net/14/show.php?itemid=20454 + 'SMiTTablet' => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)', + // http://www.rock-chips.com/index.php?do=prod&pid=2 + 'RockChipTablet' => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A', + // http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/ + 'FlyTablet' => 'IQ310|Fly Vision', + // http://www.bqreaders.com/gb/tablets-prices-sale.html + 'bqTablet' => 'Android.*(bq)?.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris [E|M]10)|Maxwell.*Lite|Maxwell.*Plus', + // http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290 + // http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets) + 'HuaweiTablet' => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim', + // Nec or Medias Tab + 'NecTablet' => '\bN-06D|\bN-08D', + // Pantech Tablets: http://www.pantechusa.com/phones/ + 'PantechTablet' => 'Pantech.*P4100', + // Broncho Tablets: http://www.broncho.cn/ (hard to find) + 'BronchoTablet' => 'Broncho.*(N701|N708|N802|a710)', + // http://versusuk.com/support.html + 'VersusTablet' => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b', + // http://www.zync.in/index.php/our-products/tablet-phablets + 'ZyncTablet' => 'z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900', + // http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/ + 'PositivoTablet' => 'TB07STA|TB10STA|TB07FTA|TB10FTA', + // https://www.nabitablet.com/ + 'NabiTablet' => 'Android.*\bNabi', + 'KoboTablet' => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build', + // French Danew Tablets http://www.danew.com/produits-tablette.php + 'DanewTablet' => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b', + // Texet Tablets and Readers http://www.texet.ru/tablet/ + 'TexetTablet' => 'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE', + // Avoid detecting 'PLAYSTATION 3' as mobile. + 'PlaystationTablet' => 'Playstation.*(Portable|Vita)', + // http://www.trekstor.de/surftabs.html + 'TrekstorTablet' => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab', + // http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets + 'PyleAudioTablet' => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b', + // http://www.advandigital.com/index.php?link=content-product&jns=JP001 + // because of the short codenames we have to include whitespaces to reduce the possible conflicts. + 'AdvanTablet' => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ', + // http://www.danytech.com/category/tablet-pc + 'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1', + // http://www.galapad.net/product.html + 'GalapadTablet' => 'Android.*\bG1\b', + // http://www.micromaxinfo.com/tablet/funbook + 'MicromaxTablet' => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b', + // http://www.karbonnmobiles.com/products_tablet.php + 'KarbonnTablet' => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b', + // http://www.myallfine.com/Products.asp + 'AllFineTablet' => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide', + // http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr= + 'PROSCANTablet' => '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b', + // http://www.yonesnav.com/products/products.php + 'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026', + // http://www.cjshowroom.com/eproducts.aspx?classcode=004001001 + // China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html) + 'ChangJiaTablet' => 'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503', + // http://www.gloryunion.cn/products.asp + // http://www.allwinnertech.com/en/apply/mobile.html + // http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB) + // @todo: Softwiner tablets? + // aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions. + 'GUTablet' => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G + // http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118 + 'PointOfViewTablet' => 'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10', + // http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/ + // @todo: add more tests. + 'OvermaxTablet' => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)', + // http://hclmetablet.com/India/index.php + 'HCLTablet' => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync', + // http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html + 'DPSTablet' => 'DPS Dream 9|DPS Dual 7', + // http://www.visture.com/index.asp + 'VistureTablet' => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10', + // http://www.mijncresta.nl/tablet + 'CrestaTablet' => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989', + // MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309 + 'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b', + // Concorde tab + 'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan', + // GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/ + 'GoCleverTablet' => 'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042', + // Modecom Tablets - http://www.modecom.eu/tablets/portal/ + 'ModecomTablet' => 'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003', + // Vonino Tablets - http://www.vonino.eu/tablets + 'VoninoTablet' => '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b', + // ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0 + 'ECSTablet' => 'V07OT2|TM105A|S10OT1|TR10CS1', + // Storex Tablets - http://storex.fr/espace_client/support.html + // @note: no need to add all the tablet codes since they are guided by the first regex. + 'StorexTablet' => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab', + // Generic Vodafone tablets. + 'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497', + // French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb + // Aka: http://www.essentielb.fr/ + 'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2', + // Ross & Moor - http://ross-moor.ru/ + 'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711', + // i-mobile http://product.i-mobilephone.com/Mobile_Device + 'iMobileTablet' => 'i-mobile i-note', + // http://www.tolino.de/de/vergleichen/ + 'TolinoTablet' => 'tolino tab [0-9.]+|tolino shine', + // AudioSonic - a Kmart brand + // http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72¤tPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1 + 'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b', + // AMPE Tablets - http://www.ampe.com.my/product-category/tablets/ + // @todo: add them gradually to avoid conflicts. + 'AMPETablet' => 'Android.* A78 ', + // Skk Mobile - http://skkmobile.com.ph/product_tablets.php + 'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)', + // Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1 + 'TecnoTablet' => 'TECNO P9', + // JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3 + 'JXDTablet' => 'Android.* \b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b', + // i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/ + 'iJoyTablet' => 'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)', + // http://www.intracon.eu/tablet + 'FX2Tablet' => 'FX2 PAD7|FX2 PAD10', + // http://www.xoro.de/produkte/ + // @note: Might be the same brand with 'Simply tablets' + 'XoroTablet' => 'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151', + // http://www1.viewsonic.com/products/computing/tablets/ + 'ViewsonicTablet' => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a', + // http://www.odys.de/web/internet-tablet_en.html + 'OdysTablet' => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10', + // http://www.captiva-power.de/products.html#tablets-en + 'CaptivaTablet' => 'CAPTIVA PAD', + // IconBIT - http://www.iconbit.com/products/tablets/ + 'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S', + // http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63 + 'TeclastTablet' => 'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G|\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G|\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b|\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b|\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b|\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi', + // Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price + 'OndaTablet' => '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+', + 'JaytechTablet' => 'TPC-PA762', + 'BlaupunktTablet' => 'Endeavour 800NG|Endeavour 1010', + // http://www.digma.ru/support/download/ + // @todo: Ebooks also (if requested) + 'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b', + // http://www.evolioshop.com/ro/tablete-pc.html + // http://www.evolio.ro/support/downloads_static.html?cat=2 + // @todo: Research some more + 'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b', + // @todo http://www.lavamobiles.com/tablets-data-cards + 'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b', + // http://www.breezetablet.com/ + 'AocTablet' => 'MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712', + // http://www.mpmaneurope.com/en/products/internet-tablets-14/android-tablets-14/ + 'MpmanTablet' => 'MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\bMPG7\b|MPDCG75|MPDCG71|MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77|MP709|MID701|MID711|MID170|MPDC703|MPQC1010', + // https://www.celkonmobiles.com/?_a=categoryphones&sid=2 + 'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b', + // http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab + 'WolderTablet' => 'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b', + // http://www.mi.com/en + 'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b', + // http://www.nbru.cn/index.html + 'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One', + // http://navroad.com/products/produkty/tablety/ + // http://navroad.com/products/produkty/tablety/ + 'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI', + // http://leader-online.com/new_site/product-category/tablets/ + // http://www.leader-online.net.au/List/Tablet + 'LeaderTablet' => 'TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G|TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100', + // http://www.datawind.com/ubislate/ + 'UbislateTablet' => 'UbiSlate[\s]?7C', + // http://www.pocketbook-int.com/ru/support + 'PocketBookTablet' => 'Pocketbook', + // http://www.kocaso.com/product_tablet.html + 'KocasoTablet' => '\b(TB-1207)\b', + // http://global.hisense.com/product/asia/tablet/Sero7/201412/t20141215_91832.htm + 'HisenseTablet' => '\b(F5281|E2371)\b', + // http://www.tesco.com/direct/hudl/ + 'Hudl' => 'Hudl HT7S3|Hudl 2', + // http://www.telstra.com.au/home-phone/thub-2/ + 'TelstraTablet' => 'T-Hub2', + 'GenericTablet' => 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002|\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b' + ); + + /** + * List of mobile Operating Systems. + * + * @var array + */ + protected static $operatingSystems = array( + 'AndroidOS' => 'Android', + 'BlackBerryOS' => 'blackberry|\bBB10\b|rim tablet os', + 'PalmOS' => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino', + 'SymbianOS' => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b', + // @reference: http://en.wikipedia.org/wiki/Windows_Mobile + 'WindowsMobileOS' => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;', + // @reference: http://en.wikipedia.org/wiki/Windows_Phone + // http://wifeng.cn/?r=blog&a=view&id=106 + // http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx + // http://msdn.microsoft.com/library/ms537503.aspx + // https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx + 'WindowsPhoneOS' => 'Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;', + 'iOS' => '\biPhone.*Mobile|\biPod|\biPad', + // http://en.wikipedia.org/wiki/MeeGo + // @todo: research MeeGo in UAs + 'MeeGoOS' => 'MeeGo', + // http://en.wikipedia.org/wiki/Maemo + // @todo: research Maemo in UAs + 'MaemoOS' => 'Maemo', + 'JavaOS' => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135 + 'webOS' => 'webOS|hpwOS', + 'badaOS' => '\bBada\b', + 'BREWOS' => 'BREW', + ); + + /** + * List of mobile User Agents. + * + * IMPORTANT: This is a list of only mobile browsers. + * Mobile Detect 2.x supports only mobile browsers, + * it was never designed to detect all browsers. + * The change will come in 2017 in the 3.x release for PHP7. + * + * @var array + */ + protected static $browsers = array( + //'Vivaldi' => 'Vivaldi', + // @reference: https://developers.google.com/chrome/mobile/docs/user-agent + 'Chrome' => '\bCrMo\b|CriOS|Android.*Chrome/[.0-9]* (Mobile)?', + 'Dolfin' => '\bDolfin\b', + 'Opera' => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+|Coast/[0-9.]+', + 'Skyfire' => 'Skyfire', + 'Edge' => 'Mobile Safari/[.0-9]* Edge', + 'IE' => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+ + 'Firefox' => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS', + 'Bolt' => 'bolt', + 'TeaShark' => 'teashark', + 'Blazer' => 'Blazer', + // @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3 + 'Safari' => 'Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari', + // http://en.wikipedia.org/wiki/Midori_(web_browser) + //'Midori' => 'midori', + //'Tizen' => 'Tizen', + 'UCBrowser' => 'UC.*Browser|UCWEB', + 'baiduboxapp' => 'baiduboxapp', + 'baidubrowser' => 'baidubrowser', + // https://github.com/serbanghita/Mobile-Detect/issues/7 + 'DiigoBrowser' => 'DiigoBrowser', + // http://www.puffinbrowser.com/index.php + 'Puffin' => 'Puffin', + // http://mercury-browser.com/index.html + 'Mercury' => '\bMercury\b', + // http://en.wikipedia.org/wiki/Obigo_Browser + 'ObigoBrowser' => 'Obigo', + // http://en.wikipedia.org/wiki/NetFront + 'NetFront' => 'NF-Browser', + // @reference: http://en.wikipedia.org/wiki/Minimo + // http://en.wikipedia.org/wiki/Vision_Mobile_Browser + 'GenericBrowser' => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger', + // @reference: https://en.wikipedia.org/wiki/Pale_Moon_(web_browser) + 'PaleMoon' => 'Android.*PaleMoon|Mobile.*PaleMoon', + ); + + /** + * Utilities. + * + * @var array + */ + protected static $utilities = array( + // Experimental. When a mobile device wants to switch to 'Desktop Mode'. + // http://scottcate.com/technology/windows-phone-8-ie10-desktop-or-mobile/ + // https://github.com/serbanghita/Mobile-Detect/issues/57#issuecomment-15024011 + // https://developers.facebook.com/docs/sharing/best-practices + 'Bot' => 'Googlebot|facebookexternalhit|AdsBot-Google|Google Keyword Suggestion|Facebot|YandexBot|YandexMobileBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor|Exabot|MJ12bot|YandexImages|TurnitinBot|Pingdom', + 'MobileBot' => 'Googlebot-Mobile|AdsBot-Google-Mobile|YahooSeeker/M1A1-R2D2', + 'DesktopMode' => 'WPDesktop', + 'TV' => 'SonyDTV|HbbTV', // experimental + 'WebKit' => '(webkit)[ /]([\w.]+)', + // @todo: Include JXD consoles. + 'Console' => '\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\b', + 'Watch' => 'SM-V700', + ); + + /** + * All possible HTTP headers that represent the + * User-Agent string. + * + * @var array + */ + protected static $uaHttpHeaders = array( + // The default User-Agent string. + 'HTTP_USER_AGENT', + // Header can occur on devices using Opera Mini. + 'HTTP_X_OPERAMINI_PHONE_UA', + // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/ + 'HTTP_X_DEVICE_USER_AGENT', + 'HTTP_X_ORIGINAL_USER_AGENT', + 'HTTP_X_SKYFIRE_PHONE', + 'HTTP_X_BOLT_PHONE_UA', + 'HTTP_DEVICE_STOCK_UA', + 'HTTP_X_UCBROWSER_DEVICE_UA' + ); + + /** + * The individual segments that could exist in a User-Agent string. VER refers to the regular + * expression defined in the constant self::VER. + * + * @var array + */ + protected static $properties = array( + + // Build + 'Mobile' => 'Mobile/[VER]', + 'Build' => 'Build/[VER]', + 'Version' => 'Version/[VER]', + 'VendorID' => 'VendorID/[VER]', + + // Devices + 'iPad' => 'iPad.*CPU[a-z ]+[VER]', + 'iPhone' => 'iPhone.*CPU[a-z ]+[VER]', + 'iPod' => 'iPod.*CPU[a-z ]+[VER]', + //'BlackBerry' => array('BlackBerry[VER]', 'BlackBerry [VER];'), + 'Kindle' => 'Kindle/[VER]', + + // Browser + 'Chrome' => array('Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'), + 'Coast' => array('Coast/[VER]'), + 'Dolfin' => 'Dolfin/[VER]', + // @reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox + 'Firefox' => array('Firefox/[VER]', 'FxiOS/[VER]'), + 'Fennec' => 'Fennec/[VER]', + // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx + 'Edge' => 'Edge/[VER]', + 'IE' => array('IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'), + // http://en.wikipedia.org/wiki/NetFront + 'NetFront' => 'NetFront/[VER]', + 'NokiaBrowser' => 'NokiaBrowser/[VER]', + 'Opera' => array( ' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]' ), + 'Opera Mini' => 'Opera Mini/[VER]', + 'Opera Mobi' => 'Version/[VER]', + 'UC Browser' => 'UC Browser[VER]', + 'MQQBrowser' => 'MQQBrowser/[VER]', + 'MicroMessenger' => 'MicroMessenger/[VER]', + 'baiduboxapp' => 'baiduboxapp/[VER]', + 'baidubrowser' => 'baidubrowser/[VER]', + 'SamsungBrowser' => 'SamsungBrowser/[VER]', + 'Iron' => 'Iron/[VER]', + // @note: Safari 7534.48.3 is actually Version 5.1. + // @note: On BlackBerry the Version is overwriten by the OS. + 'Safari' => array( 'Version/[VER]', 'Safari/[VER]' ), + 'Skyfire' => 'Skyfire/[VER]', + 'Tizen' => 'Tizen/[VER]', + 'Webkit' => 'webkit[ /][VER]', + 'PaleMoon' => 'PaleMoon/[VER]', + + // Engine + 'Gecko' => 'Gecko/[VER]', + 'Trident' => 'Trident/[VER]', + 'Presto' => 'Presto/[VER]', + 'Goanna' => 'Goanna/[VER]', + + // OS + 'iOS' => ' \bi?OS\b [VER][ ;]{1}', + 'Android' => 'Android [VER]', + 'BlackBerry' => array('BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'), + 'BREW' => 'BREW [VER]', + 'Java' => 'Java/[VER]', + // @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx + // @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases + 'Windows Phone OS' => array( 'Windows Phone OS [VER]', 'Windows Phone [VER]'), + 'Windows Phone' => 'Windows Phone [VER]', + 'Windows CE' => 'Windows CE/[VER]', + // http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd + 'Windows NT' => 'Windows NT [VER]', + 'Symbian' => array('SymbianOS/[VER]', 'Symbian/[VER]'), + 'webOS' => array('webOS/[VER]', 'hpwOS/[VER];'), + ); + + /** + * Construct an instance of this class. + * + * @param array $headers Specify the headers as injection. Should be PHP _SERVER flavored. + * If left empty, will use the global _SERVER['HTTP_*'] vars instead. + * @param string $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT + * from the $headers array instead. + */ + public function __construct( + array $headers = null, + $userAgent = null + ) { + $this->setHttpHeaders($headers); + $this->setUserAgent($userAgent); + } + + /** + * Get the current script version. + * This is useful for the demo.php file, + * so people can check on what version they are testing + * for mobile devices. + * + * @return string The version number in semantic version format. + */ + public static function getScriptVersion() + { + return self::VERSION; + } + + /** + * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers. + * + * @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract + * the headers. The default null is left for backwards compatibility. + */ + public function setHttpHeaders($httpHeaders = null) + { + // use global _SERVER if $httpHeaders aren't defined + if (!is_array($httpHeaders) || !count($httpHeaders)) { + $httpHeaders = $_SERVER; + } + + // clear existing headers + $this->httpHeaders = array(); + + // Only save HTTP headers. In PHP land, that means only _SERVER vars that + // start with HTTP_. + foreach ($httpHeaders as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $this->httpHeaders[$key] = $value; + } + } + + // In case we're dealing with CloudFront, we need to know. + $this->setCfHeaders($httpHeaders); + } + + /** + * Retrieves the HTTP headers. + * + * @return array + */ + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + /** + * Retrieves a particular header. If it doesn't exist, no exception/error is caused. + * Simply null is returned. + * + * @param string $header The name of the header to retrieve. Can be HTTP compliant such as + * "User-Agent" or "X-Device-User-Agent" or can be php-esque with the + * all-caps, HTTP_ prefixed, underscore seperated awesomeness. + * + * @return string|null The value of the header. + */ + public function getHttpHeader($header) + { + // are we using PHP-flavored headers? + if (strpos($header, '_') === false) { + $header = str_replace('-', '_', $header); + $header = strtoupper($header); + } + + // test the alternate, too + $altHeader = 'HTTP_' . $header; + + //Test both the regular and the HTTP_ prefix + if (isset($this->httpHeaders[$header])) { + return $this->httpHeaders[$header]; + } elseif (isset($this->httpHeaders[$altHeader])) { + return $this->httpHeaders[$altHeader]; + } + + return null; + } + + public function getMobileHeaders() + { + return self::$mobileHeaders; + } + + /** + * Get all possible HTTP headers that + * can contain the User-Agent string. + * + * @return array List of HTTP headers. + */ + public function getUaHttpHeaders() + { + return self::$uaHttpHeaders; + } + + + /** + * Set CloudFront headers + * http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device + * + * @param array $cfHeaders List of HTTP headers + * + * @return boolean If there were CloudFront headers to be set + */ + public function setCfHeaders($cfHeaders = null) { + // use global _SERVER if $cfHeaders aren't defined + if (!is_array($cfHeaders) || !count($cfHeaders)) { + $cfHeaders = $_SERVER; + } + + // clear existing headers + $this->cloudfrontHeaders = array(); + + // Only save CLOUDFRONT headers. In PHP land, that means only _SERVER vars that + // start with cloudfront-. + $response = false; + foreach ($cfHeaders as $key => $value) { + if (substr(strtolower($key), 0, 16) === 'http_cloudfront_') { + $this->cloudfrontHeaders[strtoupper($key)] = $value; + $response = true; + } + } + + return $response; + } + + /** + * Retrieves the cloudfront headers. + * + * @return array + */ + public function getCfHeaders() + { + return $this->cloudfrontHeaders; + } + + /** + * Set the User-Agent to be used. + * + * @param string $userAgent The user agent string to set. + * + * @return string|null + */ + public function setUserAgent($userAgent = null) + { + // Invalidate cache due to #375 + $this->cache = array(); + + if (false === empty($userAgent)) { + return $this->userAgent = $userAgent; + } else { + $this->userAgent = null; + foreach ($this->getUaHttpHeaders() as $altHeader) { + if (false === empty($this->httpHeaders[$altHeader])) { // @todo: should use getHttpHeader(), but it would be slow. (Serban) + $this->userAgent .= $this->httpHeaders[$altHeader] . " "; + } + } + + if (!empty($this->userAgent)) { + return $this->userAgent = trim($this->userAgent); + } + } + + if (count($this->getCfHeaders()) > 0) { + return $this->userAgent = 'Amazon CloudFront'; + } + return $this->userAgent = null; + } + + /** + * Retrieve the User-Agent. + * + * @return string|null The user agent if it's set. + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * Set the detection type. Must be one of self::DETECTION_TYPE_MOBILE or + * self::DETECTION_TYPE_EXTENDED. Otherwise, nothing is set. + * + * @deprecated since version 2.6.9 + * + * @param string $type The type. Must be a self::DETECTION_TYPE_* constant. The default + * parameter is null which will default to self::DETECTION_TYPE_MOBILE. + */ + public function setDetectionType($type = null) + { + if ($type === null) { + $type = self::DETECTION_TYPE_MOBILE; + } + + if ($type !== self::DETECTION_TYPE_MOBILE && $type !== self::DETECTION_TYPE_EXTENDED) { + return; + } + + $this->detectionType = $type; + } + + public function getMatchingRegex() + { + return $this->matchingRegex; + } + + public function getMatchesArray() + { + return $this->matchesArray; + } + + /** + * Retrieve the list of known phone devices. + * + * @return array List of phone devices. + */ + public static function getPhoneDevices() + { + return self::$phoneDevices; + } + + /** + * Retrieve the list of known tablet devices. + * + * @return array List of tablet devices. + */ + public static function getTabletDevices() + { + return self::$tabletDevices; + } + + /** + * Alias for getBrowsers() method. + * + * @return array List of user agents. + */ + public static function getUserAgents() + { + return self::getBrowsers(); + } + + /** + * Retrieve the list of known browsers. Specifically, the user agents. + * + * @return array List of browsers / user agents. + */ + public static function getBrowsers() + { + return self::$browsers; + } + + /** + * Retrieve the list of known utilities. + * + * @return array List of utilities. + */ + public static function getUtilities() + { + return self::$utilities; + } + + /** + * Method gets the mobile detection rules. This method is used for the magic methods $detect->is*(). + * + * @deprecated since version 2.6.9 + * + * @return array All the rules (but not extended). + */ + public static function getMobileDetectionRules() + { + static $rules; + + if (!$rules) { + $rules = array_merge( + self::$phoneDevices, + self::$tabletDevices, + self::$operatingSystems, + self::$browsers + ); + } + + return $rules; + + } + + /** + * Method gets the mobile detection rules + utilities. + * The reason this is separate is because utilities rules + * don't necessary imply mobile. This method is used inside + * the new $detect->is('stuff') method. + * + * @deprecated since version 2.6.9 + * + * @return array All the rules + extended. + */ + public function getMobileDetectionRulesExtended() + { + static $rules; + + if (!$rules) { + // Merge all rules together. + $rules = array_merge( + self::$phoneDevices, + self::$tabletDevices, + self::$operatingSystems, + self::$browsers, + self::$utilities + ); + } + + return $rules; + } + + /** + * Retrieve the current set of rules. + * + * @deprecated since version 2.6.9 + * + * @return array + */ + public function getRules() + { + if ($this->detectionType == self::DETECTION_TYPE_EXTENDED) { + return self::getMobileDetectionRulesExtended(); + } else { + return self::getMobileDetectionRules(); + } + } + + /** + * Retrieve the list of mobile operating systems. + * + * @return array The list of mobile operating systems. + */ + public static function getOperatingSystems() + { + return self::$operatingSystems; + } + + /** + * Check the HTTP headers for signs of mobile. + * This is the fastest mobile check possible; it's used + * inside isMobile() method. + * + * @return bool + */ + public function checkHttpHeadersForMobile() + { + + foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) { + if (isset($this->httpHeaders[$mobileHeader])) { + if (is_array($matchType['matches'])) { + foreach ($matchType['matches'] as $_match) { + if (strpos($this->httpHeaders[$mobileHeader], $_match) !== false) { + return true; + } + } + + return false; + } else { + return true; + } + } + } + + return false; + + } + + /** + * Magic overloading method. + * + * @method boolean is[...]() + * @param string $name + * @param array $arguments + * @return mixed + * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is' + */ + public function __call($name, $arguments) + { + // make sure the name starts with 'is', otherwise + if (substr($name, 0, 2) !== 'is') { + throw new BadMethodCallException("No such method exists: $name"); + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + $key = substr($name, 2); + + return $this->matchUAAgainstKey($key); + } + + /** + * Find a detection rule that matches the current User-agent. + * + * @param null $userAgent deprecated + * @return boolean + */ + protected function matchDetectionRulesAgainstUA($userAgent = null) + { + // Begin general search. + foreach ($this->getRules() as $_regex) { + if (empty($_regex)) { + continue; + } + + if ($this->match($_regex, $userAgent)) { + return true; + } + } + + return false; + } + + /** + * Search for a certain key in the rules array. + * If the key is found then try to match the corresponding + * regex against the User-Agent. + * + * @param string $key + * + * @return boolean + */ + protected function matchUAAgainstKey($key) + { + // Make the keys lowercase so we can match: isIphone(), isiPhone(), isiphone(), etc. + $key = strtolower($key); + if (false === isset($this->cache[$key])) { + + // change the keys to lower case + $_rules = array_change_key_case($this->getRules()); + + if (false === empty($_rules[$key])) { + $this->cache[$key] = $this->match($_rules[$key]); + } + + if (false === isset($this->cache[$key])) { + $this->cache[$key] = false; + } + } + + return $this->cache[$key]; + } + + /** + * Check if the device is mobile. + * Returns true if any type of mobile device detected, including special ones + * @param null $userAgent deprecated + * @param null $httpHeaders deprecated + * @return bool + */ + public function isMobile($userAgent = null, $httpHeaders = null) + { + + if ($httpHeaders) { + $this->setHttpHeaders($httpHeaders); + } + + if ($userAgent) { + $this->setUserAgent($userAgent); + } + + // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' + if ($this->getUserAgent() === 'Amazon CloudFront') { + $cfHeaders = $this->getCfHeaders(); + if(array_key_exists('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER'] === 'true') { + return true; + } + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + if ($this->checkHttpHeadersForMobile()) { + return true; + } else { + return $this->matchDetectionRulesAgainstUA(); + } + + } + + /** + * Check if the device is a tablet. + * Return true if any type of tablet device is detected. + * + * @param string $userAgent deprecated + * @param array $httpHeaders deprecated + * @return bool + */ + public function isTablet($userAgent = null, $httpHeaders = null) + { + // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' + if ($this->getUserAgent() === 'Amazon CloudFront') { + $cfHeaders = $this->getCfHeaders(); + if(array_key_exists('HTTP_CLOUDFRONT_IS_TABLET_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_TABLET_VIEWER'] === 'true') { + return true; + } + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + foreach (self::$tabletDevices as $_regex) { + if ($this->match($_regex, $userAgent)) { + return true; + } + } + + return false; + } + + /** + * This method checks for a certain property in the + * userAgent. + * @todo: The httpHeaders part is not yet used. + * + * @param string $key + * @param string $userAgent deprecated + * @param string $httpHeaders deprecated + * @return bool|int|null + */ + public function is($key, $userAgent = null, $httpHeaders = null) + { + // Set the UA and HTTP headers only if needed (eg. batch mode). + if ($httpHeaders) { + $this->setHttpHeaders($httpHeaders); + } + + if ($userAgent) { + $this->setUserAgent($userAgent); + } + + $this->setDetectionType(self::DETECTION_TYPE_EXTENDED); + + return $this->matchUAAgainstKey($key); + } + + /** + * Some detection rules are relative (not standard), + * because of the diversity of devices, vendors and + * their conventions in representing the User-Agent or + * the HTTP headers. + * + * This method will be used to check custom regexes against + * the User-Agent string. + * + * @param $regex + * @param string $userAgent + * @return bool + * + * @todo: search in the HTTP headers too. + */ + public function match($regex, $userAgent = null) + { + $match = (bool) preg_match(sprintf('#%s#is', $regex), (false === empty($userAgent) ? $userAgent : $this->userAgent), $matches); + // If positive match is found, store the results for debug. + if ($match) { + $this->matchingRegex = $regex; + $this->matchesArray = $matches; + } + + return $match; + } + + /** + * Get the properties array. + * + * @return array + */ + public static function getProperties() + { + return self::$properties; + } + + /** + * Prepare the version number. + * + * @todo Remove the error supression from str_replace() call. + * + * @param string $ver The string version, like "2.6.21.2152"; + * + * @return float + */ + public function prepareVersionNo($ver) + { + $ver = str_replace(array('_', ' ', '/'), '.', $ver); + $arrVer = explode('.', $ver, 2); + + if (isset($arrVer[1])) { + $arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions. + } + + return (float) implode('.', $arrVer); + } + + /** + * Check the version of the given property in the User-Agent. + * Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31) + * + * @param string $propertyName The name of the property. See self::getProperties() array + * keys for all possible properties. + * @param string $type Either self::VERSION_TYPE_STRING to get a string value or + * self::VERSION_TYPE_FLOAT indicating a float value. This parameter + * is optional and defaults to self::VERSION_TYPE_STRING. Passing an + * invalid parameter will default to the this type as well. + * + * @return string|float The version of the property we are trying to extract. + */ + public function version($propertyName, $type = self::VERSION_TYPE_STRING) + { + if (empty($propertyName)) { + return false; + } + + // set the $type to the default if we don't recognize the type + if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) { + $type = self::VERSION_TYPE_STRING; + } + + $properties = self::getProperties(); + + // Check if the property exists in the properties array. + if (true === isset($properties[$propertyName])) { + + // Prepare the pattern to be matched. + // Make sure we always deal with an array (string is converted). + $properties[$propertyName] = (array) $properties[$propertyName]; + + foreach ($properties[$propertyName] as $propertyMatchString) { + + $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString); + + // Identify and extract the version. + preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match); + + if (false === empty($match[1])) { + $version = ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]); + + return $version; + } + + } + + } + + return false; + } + + /** + * Retrieve the mobile grading, using self::MOBILE_GRADE_* constants. + * + * @return string One of the self::MOBILE_GRADE_* constants. + */ + public function mobileGrade() + { + $isMobile = $this->isMobile(); + + if ( + // Apple iOS 4-7.0 – Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3 / 5.1 / 6.1), iPad 3 (5.1 / 6.0), iPad Mini (6.1), iPad Retina (7.0), iPhone 3GS (4.3), iPhone 4 (4.3 / 5.1), iPhone 4S (5.1 / 6.0), iPhone 5 (6.0), and iPhone 5S (7.0) + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) >= 4.3 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) >= 4.3 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) >= 4.3 || + + // Android 2.1-2.3 - Tested on the HTC Incredible (2.2), original Droid (2.2), HTC Aria (2.1), Google Nexus S (2.3). Functional on 1.5 & 1.6 but performance may be sluggish, tested on Google G1 (1.5) + // Android 3.1 (Honeycomb) - Tested on the Samsung Galaxy Tab 10.1 and Motorola XOOM + // Android 4.0 (ICS) - Tested on a Galaxy Nexus. Note: transition performance can be poor on upgraded devices + // Android 4.1 (Jelly Bean) - Tested on a Galaxy Nexus and Galaxy 7 + ( $this->version('Android', self::VERSION_TYPE_FLOAT)>2.1 && $this->is('Webkit') ) || + + // Windows Phone 7.5-8 - Tested on the HTC Surround (7.5), HTC Trophy (7.5), LG-E900 (7.5), Nokia 800 (7.8), HTC Mazaa (7.8), Nokia Lumia 520 (8), Nokia Lumia 920 (8), HTC 8x (8) + $this->version('Windows Phone OS', self::VERSION_TYPE_FLOAT) >= 7.5 || + + // Tested on the Torch 9800 (6) and Style 9670 (6), BlackBerry® Torch 9810 (7), BlackBerry Z10 (10) + $this->is('BlackBerry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 6.0 || + // Blackberry Playbook (1.0-2.0) - Tested on PlayBook + $this->match('Playbook.*Tablet') || + + // Palm WebOS (1.4-3.0) - Tested on the Palm Pixi (1.4), Pre (1.4), Pre 2 (2.0), HP TouchPad (3.0) + ( $this->version('webOS', self::VERSION_TYPE_FLOAT) >= 1.4 && $this->match('Palm|Pre|Pixi') ) || + // Palm WebOS 3.0 - Tested on HP TouchPad + $this->match('hp.*TouchPad') || + + // Firefox Mobile 18 - Tested on Android 2.3 and 4.1 devices + ( $this->is('Firefox') && $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 18 ) || + + // Chrome for Android - Tested on Android 4.0, 4.1 device + ( $this->is('Chrome') && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 4.0 ) || + + // Skyfire 4.1 - Tested on Android 2.3 device + ( $this->is('Skyfire') && $this->version('Skyfire', self::VERSION_TYPE_FLOAT) >= 4.1 && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) || + + // Opera Mobile 11.5-12: Tested on Android 2.3 + ( $this->is('Opera') && $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11.5 && $this->is('AndroidOS') ) || + + // Meego 1.2 - Tested on Nokia 950 and N9 + $this->is('MeeGoOS') || + + // Tizen (pre-release) - Tested on early hardware + $this->is('Tizen') || + + // Samsung Bada 2.0 - Tested on a Samsung Wave 3, Dolphin browser + // @todo: more tests here! + $this->is('Dolfin') && $this->version('Bada', self::VERSION_TYPE_FLOAT) >= 2.0 || + + // UC Browser - Tested on Android 2.3 device + ( ($this->is('UC Browser') || $this->is('Dolfin')) && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) || + + // Kindle 3 and Fire - Tested on the built-in WebKit browser for each + ( $this->match('Kindle Fire') || + $this->is('Kindle') && $this->version('Kindle', self::VERSION_TYPE_FLOAT) >= 3.0 ) || + + // Nook Color 1.4.1 - Tested on original Nook Color, not Nook Tablet + $this->is('AndroidOS') && $this->is('NookTablet') || + + // Chrome Desktop 16-24 - Tested on OS X 10.7 and Windows 7 + $this->version('Chrome', self::VERSION_TYPE_FLOAT) >= 16 && !$isMobile || + + // Safari Desktop 5-6 - Tested on OS X 10.7 and Windows 7 + $this->version('Safari', self::VERSION_TYPE_FLOAT) >= 5.0 && !$isMobile || + + // Firefox Desktop 10-18 - Tested on OS X 10.7 and Windows 7 + $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 10.0 && !$isMobile || + + // Internet Explorer 7-9 - Tested on Windows XP, Vista and 7 + $this->version('IE', self::VERSION_TYPE_FLOAT) >= 7.0 && !$isMobile || + + // Opera Desktop 10-12 - Tested on OS X 10.7 and Windows 7 + $this->version('Opera', self::VERSION_TYPE_FLOAT) >= 10 && !$isMobile + ){ + return self::MOBILE_GRADE_A; + } + + if ( + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT)<4.3 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT)<4.3 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT)<4.3 || + + // Blackberry 5.0: Tested on the Storm 2 9550, Bold 9770 + $this->is('Blackberry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 5 && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT)<6 || + + //Opera Mini (5.0-6.5) - Tested on iOS 3.2/4.3 and Android 2.3 + ($this->version('Opera Mini', self::VERSION_TYPE_FLOAT) >= 5.0 && $this->version('Opera Mini', self::VERSION_TYPE_FLOAT) <= 7.0 && + ($this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 || $this->is('iOS')) ) || + + // Nokia Symbian^3 - Tested on Nokia N8 (Symbian^3), C7 (Symbian^3), also works on N97 (Symbian^1) + $this->match('NokiaN8|NokiaC7|N97.*Series60|Symbian/3') || + + // @todo: report this (tested on Nokia N71) + $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11 && $this->is('SymbianOS') + ){ + return self::MOBILE_GRADE_B; + } + + if ( + // Blackberry 4.x - Tested on the Curve 8330 + $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) <= 5.0 || + // Windows Mobile - Tested on the HTC Leo (WinMo 5.2) + $this->match('MSIEMobile|Windows CE.*Mobile') || $this->version('Windows Mobile', self::VERSION_TYPE_FLOAT) <= 5.2 || + + // Tested on original iPhone (3.1), iPhone 3 (3.2) + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) <= 3.2 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) <= 3.2 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) <= 3.2 || + + // Internet Explorer 7 and older - Tested on Windows XP + $this->version('IE', self::VERSION_TYPE_FLOAT) <= 7.0 && !$isMobile + ){ + return self::MOBILE_GRADE_C; + } + + // All older smartphone platforms and featurephones - Any device that doesn't support media queries + // will receive the basic, C grade experience. + return self::MOBILE_GRADE_C; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/README.md b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/README.md new file mode 100644 index 0000000..c33d701 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/README.md @@ -0,0 +1,283 @@ + + +> Motto: "Every business should have a mobile detection script to detect mobile readers." + +[](https://travis-ci.org/serbanghita/Mobile-Detect) +[](https://packagist.org/packages/mobiledetect/mobiledetectlib) +[](https://packagist.org/packages/mobiledetect/mobiledetectlib) +[](https://packagist.org/packages/mobiledetect/mobiledetectlib) +[](https://packagist.org/packages/mobiledetect/mobiledetectlib) + +*Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets). +It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.* + +We're committed to make Mobile_Detect the best open-source mobile detection resource and this is why before +each release we're running [unit tests](./tests), we also research and update the detection rules on **daily** +and **weekly** basis. + +Your website's _content strategy_ is important! You need a complete toolkit to deliver an experience that is _optimized_, _fast_ and _relevant_ to your users. Mobile_Detect class is a [server-side detection](http://www.w3.org/TR/mwabp/#bp-devcap-detection) tool that can help you with your RWD strategy, it is not a replacement for CSS3 media queries or other forms of client-side feature detection. + +##### Announcements + +For `2.x` branch we are no longer taking optimizations pull requests, but only new regexes and User-Agents for our tests. +On `2.x` releases we are focusing on **new tablets only**. All the pull requests about TVs, bots or optimizations will be closed and analyzed after `3.0.0-beta` is released. + +Still working on `3.0.0` branch to provide you with device detection! +We're really excited on this one! +We would like to speed this up, but life and family gets in the way ;) + +Special thanks to **JetBrains** for providing licenses for **PHPStorm**. In case you never heard or tried PHPStorm, you're +clearly missing out! [Check PHPStorm](https://www.jetbrains.com/phpstorm/) out! + +##### Download and demo + +|Download|Docs|Examples| +|-------------|-------------|-------------| +|[Go to releases](../../tags)|[Become a contributor](../../wiki/Become-a-contributor)|[Code examples](../../wiki/Code-examples) +|[Mobile_Detect.php](./Mobile_Detect.php)|[History](../../wiki/History)|[:iphone: Live demo!](http://is.gd/mobiletest) +|[Composer package](https://packagist.org/packages/mobiledetect/mobiledetectlib)| + +#### Continuous updates + +You can use [composer](https://getcomposer.org/doc/00-intro.md) in your release and update process to make sure you have the latest Mobile_Detect version. + +``` +composer require mobiledetect/mobiledetectlib +``` + +```json +{ + "require": { + "mobiledetect/mobiledetectlib": "^2.8" + } +} +``` + +##### Help + +|Pledgie|Paypal| +|-------|------| +|[Donate :+1:](https://pledgie.com/campaigns/21856)|[Donate :beer:](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=mobiledetectlib%40gmail%2ecom&lc=US&item_name=Mobile%20Detect¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted)| + + +I'm currently paying for hosting and spend a lot of my family time to maintain the project and planning the future releases. +I would highly appreciate any money donations that will keep the research going. + +Special thanks to the community :+1: for donations, [BrowserStack](https://www.browserstack.com/) - for providing access to their great platform, [Zend](http://www.zend.com/) - for donating licenses, [Dragos Gavrila](https://twitter.com/grafician) who contributed with the logo. + +##### 3rd party modules / [Submit new](../../issues/new?title=New%203rd%20party%20module&body=Name, Link and Description of the module.) + +:point_right: Keep `Mobile_Detect.php` class in a separate `module` and do NOT include it in your script core because of the high frequency of updates. +:point_right: When including the class into you `web application` or `module` always use `include_once '../path/to/Mobile_Detect.php` to prevent conflicts. + +**JavaScript** + +* mobile-detect.js - A [JavaScript port](https://github.com/hgoebl/mobile-detect.js) of Mobile-Detect class. Made by [Heinrich Goebl](https://github.com/hgoebl). + +**Varnish Cache** + +* [Varnish Mobile Detect](https://github.com/willemk/varnish-mobiletranslate) - Drop-in varnish solution to mobile user +detection based on the Mobile-Detect library. Made by [willemk](https://github.com/willemk). +* [mobiledetect2vcl](https://github.com/carlosabalde/mobiledetect2vcl) - Python script to transform the Mobile +Detect JSON database into an UA-based mobile detection VCL subroutine easily integrable in any Varnish Cache +configuration. Made by [Carlos Abalde](https://github.com/carlosabalde). + +**LUA** + +* [mobile-detect.lua](https://github.com/yourpalmark/mobile-detect.lua) is a port of Mobile-Detect to Lua for +NGINX HTTP servers. Follows closely to mobile-detect.js. Supports all methods that server-side +mobile-detect.js supports. Fully unit-tested and synced with Travis CI (Build Passing badge included). +Made by [Mark Walters](https://github.com/yourpalmark). + +**PHP** + +**WordPress** + +* [WordPress Mobile Detect](https://wordpress.org/plugins/wp-mobile-detect/) - Gives you the ability to wrap that +infographic in a `[notdevice][/notdevice]` shortcode so at the server level WordPress will +decide to show that content only if the user is NOT on a phone or tablet. +Made by [Jesse Friedman](https://profiles.wordpress.org/professor44/). + +* [mobble](https://wordpress.org/plugins/mobble/) - provides mobile related conditional functions for your site. +e.g. `is_iphone()`, `is_mobile()` and `is_tablet()`. Made by Scott Evans. + +* [WordPress Responsage](https://github.com/iamspacehead/responsage) - A small WordPress theme plugin that allows +you to make your images responsive. Made by [Adrian Ciaschetti](https://github.com/iamspacehead). + +* [WP247 Body Classes](https://wordpress.org/plugins/wp247-body-classes/) - Add unique classes to the `body` tag for +easy styling based on various attributes (archive, user, post, mobile) and various WordPress "is" functions. +Mobile attributes include type of device, Operating System, Browser, etc. Examples: .is-mobile, .is-not-mobile, +.is-tablet, .is-ios, .is-not-ios, .is-androidos, .is-chromebrowser. +Made by [wescleveland56](https://github.com/wescleveland56). + +**Drupal** + +* [Drupal Mobile Switch](https://www.drupal.org/project/mobile_switch) - The Mobile Switch Drupal module provides a +automatic theme switch functionality for mobile devices, detected by Browscap or Mobile Detect. +Made by [Siegfried Neumann](https://www.drupal.org/user/45267). + +* [Drupal Context Mobile Detect](https://www.drupal.org/project/context_mobile_detect) - This is a Drupal context module +which integrates Context and PHP Mobile Detect library. +Created by [Artem Shymko](https://www.drupal.org/user/432492). + +* [Drupal Mobile Detect](https://www.drupal.org/project/mobile_detect) - Lightweight mobile detect module for Drupal + created by [Matthew Donadio](https://www.drupal.org/user/325244). + +**Joomla** + +* [yagendoo Joomla! Mobile Detection Plugin](http://www.yagendoo.com/en/blog/free-mobile-detection-plugin-for-joomla.html) - Lightweight PHP plugin for Joomla! +that detects a mobile browser using the Mobile Detect class. +Made by yagendoo media. + +* [User Agent Detector plugin](https://github.com/renekreijveld/UserAgentDetector) - This system plugin detects the user +agent of your website visitor and sets a session variable accordingly. Based on the user agent, the plugin detects if the +site is running on a desktop pc, tablet or smartphone. It can also detect if the visitor is a spider bot (search engine). +Session variable that is set: `ualayout`. Possible values: desktop, tablet, mobile, bot. +Made by @ReneKreijveld. + +**Magento** + +* [Magento helper](http://www.magentocommerce.com/magento-connect/catalog/product/view/id/16835/) from Optimise Web enables +the use of all functions provided by Mobile Detect. Made by [Kathir Vel](http://www.kathirvel.com). + +* [Magento 2 Mobile Detect Theme Change](https://github.com/EaDesgin/magento2-mobiledetect) is an extension for Magento 2 +that will change the theme or redirect to a different URL. Also containing a helper to check for the device type. + +**PrestaShop** + +* [PrestaShop](https://www.prestashop.com) is a free, secure and open source shopping cart platform. Mobile_Detect +is included in the default package since 1.5.x. + +**Laravel** + +* [Agent](https://github.com/jenssegers/agent) is a user agent class for Laravel based on Mobile Detect with some +additional functionality. +Made by [Jens Segers](https://github.com/jenssegers). + +* [BrowserDetect](https://github.com/hisorange/browser-detect) is a browser and mobile detection package, collects +and wrap together the best user-agent identifiers for Laravel. +Created by [Varga Zsolt](https://github.com/hisorange). + +**Zend Framework** + +* [ZF2 Mobile-Detect](https://github.com/neilime/zf2-mobile-detect.git) is a Zend Framework 2 module that provides +Mobile-Detect features (Mobile_Detect class as a service, helper for views and plugin controllers). +Made by [neilime](https://github.com/neilime). + +* [ZF2 MobileDetectModule](https://github.com/nikolaposa/MobileDetectModule) facilitates integration of a PHP MobileDetect +class with some ZF2-based application. Has similar idea like the existing ZF2 Mobile-Detect module, +but differs in initialization and provision routine of the actual Mobile_Detect class. +Appropriate view helper and controller plugin also have different conceptions. +Made by [Nikola Posa](https://github.com/nikolaposa). + +**Symfony** + +* [Symfony2 Mobile Detect Bundle](https://github.com/suncat2000/MobileDetectBundle) is a bundle for detecting mobile devices, +manage mobile view and redirect to the mobile and tablet version. +Made by [Nikolay Ivlev](https://github.com/suncat2000). + +* [Silex Mobile Detect Service Provider](https://github.com/jbinfo/MobileDetectServiceProvider) is a service provider to +interact with Mobile detect class methods. +Made by [Lhassan Baazzi](https://github.com/jbinfo). + +**Slim Framework** + +* [Slim_Mobile_Detect](https://github.com/zguillez/slim_mobile_detect) implements Mobile_Detect lib for different +responses write on Slim Framework App. + +**ExpressionEngine** + +* [EE2 Detect Mobile](https://github.com/garethtdavies/detect-mobile) is a lightweight PHP plugin for EE2 that detects + a mobile browser using the Mobile Detect class. Made by [Gareth Davies](https://github.com/garethtdavies). + +**Yii Framework** + +* [Yii Extension](https://github.com/iamsalnikov/MobileDetect) - Mobile detect plugin for Yii framework. +Made by [Alexey Salnikov](https://github.com/iamsalnikov). + +* [Yii Extension](https://github.com/candasm/yii1-mobile-detect-component) - Mobile detect component for Yii framework +1.x version which supports composer package manager. Made by [Candas Minareci](https://github.com/candasm). + +* [Yii2 Device Detect](https://github.com/alexandernst/yii2-device-detect/) - Yii2 extension for Mobile-Detect library. +Made by [Alexander Nestorov](https://github.com/alexandernst). + +**CakePHP** + +* [CakePHP MobileDetect](https://github.com/chronon/CakePHP-MobileDetectComponent-Plugin) is a plugin component for +CakePHP 2.x. Made by [Gregory Gaskill](https://github.com/chronon). + +**FuelPHP** + +* [Special Agent](https://github.com/rob-bar/special_agent) is a FuelPHP package which uses php-mobile-detect to +determine whether a device is mobile or not. It overrides the Fuelphp Agent class its methods. +Made by [Robbie Bardjin](https://github.com/rob-bar). + + +**TYPO3** + +* [px_mobiledetect](https://typo3.org/extensions/repository/view/px_mobiledetect) is an extension that helps to detect +visitor's mobile device class (if that’s tablet or mobile device like smartphone). Made by Alexander Tretyak. + +**Other** + +* [PageCache](https://github.com/mmamedov/page-cache) is a lightweight PHP library for full page cache, +with built-in Mobile-Detect support. Made by [Muhammed Mamedov](https://github.com/mmamedov). + +* [Statamic CMS Mobile Detect](https://github.com/haikulab/statamic-mobile-detect) is a plugin. +Made by [Sergei Filippov](https://github.com/haikulab/statamic-mobile-detect) of Haiku Lab. + +* [Kohana Mobile Detect](https://github.com/madeinnordeste/kohana-mobile-detect) is an example of implementation of +Mobile_Detect class with Kohana framework. +Written by [Luiz Alberto S. Ribeiro](https://github.com/madeinnordeste). + +* [MemHT](https://www.memht.com) is a Free PHP CMS and Blog that permit the creation and the management online +of websites with few and easy steps. Has the class included in the core. + +* [concrete5](https://www.concrete5.org) is a CMS that is free and open source. The library is included in the core. + +* [engine7](https://github.com/QOXCorp/exengine) is PHP Open Source Framework. The Mobile_Detect class is included in +the engine. + +* [Zikula](http://zikula.org) is a free and open-source Content Management Framework, which allows you to run +impressive websites and build powerful online applications. The core uses Mobile-Detect to switch to a special +Mobile theme, using jQueryMobile. + +* [UserAgentInfo](https://github.com/quentin389/UserAgentInfo) is a PHP class for parsing user agent strings +(HTTP_USER_AGENT). Includes mobile checks, bot checks, browser types/versions and more. +Based on browscap, Mobile_Detect and ua-parser. Created for high traffic websites and fast batch processing. +Made by [quentin389](https://github.com/quentin389). + +* [LJ Mobile Detect](https://github.com/lewisjenkins/craft-lj-mobiledetect) is a simple implementation of Mobile Detect +for Craft CMS. Made by [Lewis Jenkins](https://github.com/lewisjenkins). + +* [Grav Plugin Mobile Detect](https://github.com/dimitrilongo/grav-plugin-mobile-detect/) is a simple implementation +of Mobile Detect for Grav CMS. Made by [Dimitri Longo](https://github.com/dimitrilongo). + + +**Perl** + + * [MobileDetect.pm](https://www.buzzerstar.com/development/) is a Perl module for Mobile Detect. + Made by [Sebastian Enger](https://devop.tools/). + +**Python** + +* [pymobiledetect](https://pypi.python.org/pypi/pymobiledetect) - Mobile detect python package. +Made by Bas van Oostveen. + +**Ruby** + +* [mobile_detect.rb](https://github.com/ktaragorn/mobile_detect) is a Ruby gem using the JSON data exposed by the +php project and implementing a basic subset of the API (as much as can be done by the exposed data). +Made by [Karthik T](https://github.com/ktaragorn). + +**Go** + +* [GoMobileDetect](https://github.com/Shaked/gomobiledetect) is a Go port of Mobile Detect class. +Made by [https://github.com/Shaked](Shaked). + + +**LUA** + +* [ua-lua](https://github.com/robinef/ua-lua) is a small lib written in LUA providing device type detection. +ua-lua is detecting mobile or tablet devices based on user-agent inside nginx daemon. +Made by [Frédéric Robinet](https://github.com/robinef). diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/composer.json b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/composer.json new file mode 100644 index 0000000..0131e49 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/composer.json @@ -0,0 +1,28 @@ +{ + "name": "mobiledetect/mobiledetectlib", + "type": "library", + "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", + "keywords": ["mobile", "mobile detect", "mobile detector", "php mobile detect", "detect mobile devices"], + "homepage": "https://github.com/serbanghita/Mobile-Detect", + "license": "MIT", + "authors": [ + { + "name": "Serban Ghita", + "email": "serbanghita@gmail.com", + "homepage": "http://mobiledetect.net", + "role": "Developer" + } + ], + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "autoload": { + "classmap": ["Mobile_Detect.php"], + "psr-0": { + "Detection": "namespaced/" + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/namespaced/Detection/MobileDetect.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/namespaced/Detection/MobileDetect.php new file mode 100644 index 0000000..ca7efec --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Mobile_Detect/namespaced/Detection/MobileDetect.php @@ -0,0 +1,22 @@ + + + diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHP-OAuth2/Client.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHP-OAuth2/Client.php new file mode 100644 index 0000000..6fae092 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHP-OAuth2/Client.php @@ -0,0 +1,513 @@ + + * @author Anis BerejebThe PSR-2 coding standard. ++ + + + + + + + + + + + + + + + + + + + + + + ++ ++ + + + ++ ++ + + ++ ++ + +0 ++ +0 ++ + + +0 ++ + + + + + ++ ++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++ ++ + + + +0 ++ + + + + + + + + + + + + + + + + + + +0 ++ + + + + + + + + + + + + + + + + + + + + + + + + * @version 1.2-dev + */ +namespace OAuth2; + +class Client +{ + /** + * Different AUTH method + */ + const AUTH_TYPE_URI = 0; + const AUTH_TYPE_AUTHORIZATION_BASIC = 1; + const AUTH_TYPE_FORM = 2; + + /** + * Different Access token type + */ + const ACCESS_TOKEN_URI = 0; + const ACCESS_TOKEN_BEARER = 1; + const ACCESS_TOKEN_OAUTH = 2; + const ACCESS_TOKEN_MAC = 3; + + /** + * Different Grant types + */ + const GRANT_TYPE_AUTH_CODE = 'authorization_code'; + const GRANT_TYPE_PASSWORD = 'password'; + const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials'; + const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token'; + + /** + * HTTP Methods + */ + const HTTP_METHOD_GET = 'GET'; + const HTTP_METHOD_POST = 'POST'; + const HTTP_METHOD_PUT = 'PUT'; + const HTTP_METHOD_DELETE = 'DELETE'; + const HTTP_METHOD_HEAD = 'HEAD'; + + /** + * HTTP Form content types + */ + const HTTP_FORM_CONTENT_TYPE_APPLICATION = 0; + const HTTP_FORM_CONTENT_TYPE_MULTIPART = 1; + + /** + * Client ID + * + * @var string + */ + protected $client_id = null; + + /** + * Client Secret + * + * @var string + */ + protected $client_secret = null; + + /** + * Client Authentication method + * + * @var int + */ + protected $client_auth = self::AUTH_TYPE_URI; + + /** + * Access Token + * + * @var string + */ + protected $access_token = null; + + /** + * Access Token Type + * + * @var int + */ + protected $access_token_type = self::ACCESS_TOKEN_URI; + + /** + * Access Token Secret + * + * @var string + */ + protected $access_token_secret = null; + + /** + * Access Token crypt algorithm + * + * @var string + */ + protected $access_token_algorithm = null; + + /** + * Access Token Parameter name + * + * @var string + */ + protected $access_token_param_name = 'access_token'; + + /** + * The path to the certificate file to use for https connections + * + * @var string Defaults to . + */ + protected $certificate_file = null; + + /** + * cURL options + * + * @var array + */ + protected $curl_options = array(); + + /** + * Construct + * + * @param string $client_id Client ID + * @param string $client_secret Client Secret + * @param int $client_auth (AUTH_TYPE_URI, AUTH_TYPE_AUTHORIZATION_BASIC, AUTH_TYPE_FORM) + * @param string $certificate_file Indicates if we want to use a certificate file to trust the server. Optional, defaults to null. + * @return void + */ + public function __construct($client_id, $client_secret, $client_auth = self::AUTH_TYPE_URI, $certificate_file = null) + { + if (!extension_loaded('curl')) { + throw new Exception('The PHP extension curl must be installed to use this library.', Exception::CURL_NOT_FOUND); + } + + $this->client_id = $client_id; + $this->client_secret = $client_secret; + $this->client_auth = $client_auth; + $this->certificate_file = $certificate_file; + if (!empty($this->certificate_file) && !is_file($this->certificate_file)) { + throw new InvalidArgumentException('The certificate file was not found', InvalidArgumentException::CERTIFICATE_NOT_FOUND); + } + } + + /** + * Get the client Id + * + * @return string Client ID + */ + public function getClientId() + { + return $this->client_id; + } + + /** + * Get the client Secret + * + * @return string Client Secret + */ + public function getClientSecret() + { + return $this->client_secret; + } + + /** + * getAuthenticationUrl + * + * @param string $auth_endpoint Url of the authentication endpoint + * @param string $redirect_uri Redirection URI + * @param array $extra_parameters Array of extra parameters like scope or state (Ex: array('scope' => null, 'state' => '')) + * @return string URL used for authentication + */ + public function getAuthenticationUrl($auth_endpoint, $redirect_uri, array $extra_parameters = array()) + { + $parameters = array_merge(array( + 'response_type' => 'code', + 'client_id' => $this->client_id, + 'redirect_uri' => $redirect_uri + ), $extra_parameters); + return $auth_endpoint . '?' . http_build_query($parameters, null, '&'); + } + + /** + * getAccessToken + * + * @param string $token_endpoint Url of the token endpoint + * @param int $grant_type Grant Type ('authorization_code', 'password', 'client_credentials', 'refresh_token', or a custom code (@see GrantType Classes) + * @param array $parameters Array sent to the server (depend on which grant type you're using) + * @return array Array of parameters required by the grant_type (CF SPEC) + */ + public function getAccessToken($token_endpoint, $grant_type, array $parameters) + { + if (!$grant_type) { + throw new InvalidArgumentException('The grant_type is mandatory.', InvalidArgumentException::INVALID_GRANT_TYPE); + } + $grantTypeClassName = $this->convertToCamelCase($grant_type); + $grantTypeClass = __NAMESPACE__ . '\\GrantType\\' . $grantTypeClassName; + if (!class_exists($grantTypeClass)) { + throw new InvalidArgumentException('Unknown grant type \'' . $grant_type . '\'', InvalidArgumentException::INVALID_GRANT_TYPE); + } + $grantTypeObject = new $grantTypeClass(); + $grantTypeObject->validateParameters($parameters); + if (!defined($grantTypeClass . '::GRANT_TYPE')) { + throw new Exception('Unknown constant GRANT_TYPE for class ' . $grantTypeClassName, Exception::GRANT_TYPE_ERROR); + } + $parameters['grant_type'] = $grantTypeClass::GRANT_TYPE; + $http_headers = array(); + switch ($this->client_auth) { + case self::AUTH_TYPE_URI: + case self::AUTH_TYPE_FORM: + $parameters['client_id'] = $this->client_id; + $parameters['client_secret'] = $this->client_secret; + break; + case self::AUTH_TYPE_AUTHORIZATION_BASIC: + $parameters['client_id'] = $this->client_id; + $http_headers['Authorization'] = 'Basic ' . base64_encode($this->client_id . ':' . $this->client_secret); + break; + default: + throw new Exception('Unknown client auth type.', Exception::INVALID_CLIENT_AUTHENTICATION_TYPE); + break; + } + + return $this->executeRequest($token_endpoint, $parameters, self::HTTP_METHOD_POST, $http_headers, self::HTTP_FORM_CONTENT_TYPE_APPLICATION); + } + + /** + * setToken + * + * @param string $token Set the access token + * @return void + */ + public function setAccessToken($token) + { + $this->access_token = $token; + } + + /** + * Set the client authentication type + * + * @param string $client_auth (AUTH_TYPE_URI, AUTH_TYPE_AUTHORIZATION_BASIC, AUTH_TYPE_FORM) + * @return void + */ + public function setClientAuthType($client_auth) + { + $this->client_auth = $client_auth; + } + + /** + * Set an option for the curl transfer + * + * @param int $option The CURLOPT_XXX option to set + * @param mixed $value The value to be set on option + * @return void + */ + public function setCurlOption($option, $value) + { + $this->curl_options[$option] = $value; + } + + /** + * Set multiple options for a cURL transfer + * + * @param array $options An array specifying which options to set and their values + * @return void + */ + public function setCurlOptions($options) + { + $this->curl_options = array_merge($this->curl_options, $options); + } + + /** + * Set the access token type + * + * @param int $type Access token type (ACCESS_TOKEN_BEARER, ACCESS_TOKEN_MAC, ACCESS_TOKEN_URI) + * @param string $secret The secret key used to encrypt the MAC header + * @param string $algorithm Algorithm used to encrypt the signature + * @return void + */ + public function setAccessTokenType($type, $secret = null, $algorithm = null) + { + $this->access_token_type = $type; + $this->access_token_secret = $secret; + $this->access_token_algorithm = $algorithm; + } + + /** + * Fetch a protected ressource + * + * @param string $protected_ressource_url Protected resource URL + * @param array $parameters Array of parameters + * @param string $http_method HTTP Method to use (POST, PUT, GET, HEAD, DELETE) + * @param array $http_headers HTTP headers + * @param int $form_content_type HTTP form content type to use + * @return array + */ + public function fetch($protected_resource_url, $parameters = array(), $http_method = self::HTTP_METHOD_GET, array $http_headers = array(), $form_content_type = self::HTTP_FORM_CONTENT_TYPE_MULTIPART) + { + if ($this->access_token) { + switch ($this->access_token_type) { + case self::ACCESS_TOKEN_URI: + if (is_array($parameters)) { + $parameters[$this->access_token_param_name] = $this->access_token; + } else { + throw new InvalidArgumentException( + 'You need to give parameters as array if you want to give the token within the URI.', + InvalidArgumentException::REQUIRE_PARAMS_AS_ARRAY + ); + } + break; + case self::ACCESS_TOKEN_BEARER: + $http_headers['Authorization'] = 'Bearer ' . $this->access_token; + break; + case self::ACCESS_TOKEN_OAUTH: + $http_headers['Authorization'] = 'OAuth ' . $this->access_token; + break; + case self::ACCESS_TOKEN_MAC: + $http_headers['Authorization'] = 'MAC ' . $this->generateMACSignature($protected_resource_url, $parameters, $http_method); + break; + default: + throw new Exception('Unknown access token type.', Exception::INVALID_ACCESS_TOKEN_TYPE); + break; + } + } + return $this->executeRequest($protected_resource_url, $parameters, $http_method, $http_headers, $form_content_type); + } + + /** + * Generate the MAC signature + * + * @param string $url Called URL + * @param array $parameters Parameters + * @param string $http_method Http Method + * @return string + */ + private function generateMACSignature($url, $parameters, $http_method) + { + $timestamp = time(); + $nonce = uniqid(); + $parsed_url = parse_url($url); + if (!isset($parsed_url['port'])) + { + $parsed_url['port'] = ($parsed_url['scheme'] == 'https') ? 443 : 80; + } + if ($http_method == self::HTTP_METHOD_GET) { + if (is_array($parameters)) { + $parsed_url['path'] .= '?' . http_build_query($parameters, null, '&'); + } elseif ($parameters) { + $parsed_url['path'] .= '?' . $parameters; + } + } + + $signature = base64_encode(hash_hmac($this->access_token_algorithm, + $timestamp . "\n" + . $nonce . "\n" + . $http_method . "\n" + . $parsed_url['path'] . "\n" + . $parsed_url['host'] . "\n" + . $parsed_url['port'] . "\n\n" + , $this->access_token_secret, true)); + + return 'id="' . $this->access_token . '", ts="' . $timestamp . '", nonce="' . $nonce . '", mac="' . $signature . '"'; + } + + /** + * Execute a request (with curl) + * + * @param string $url URL + * @param mixed $parameters Array of parameters + * @param string $http_method HTTP Method + * @param array $http_headers HTTP Headers + * @param int $form_content_type HTTP form content type to use + * @return array + */ + private function executeRequest($url, $parameters = array(), $http_method = self::HTTP_METHOD_GET, array $http_headers = null, $form_content_type = self::HTTP_FORM_CONTENT_TYPE_MULTIPART) + { + $curl_options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_CUSTOMREQUEST => $http_method + ); + + switch($http_method) { + case self::HTTP_METHOD_POST: + $curl_options[CURLOPT_POST] = true; + /* No break */ + case self::HTTP_METHOD_PUT: + + /** + * Passing an array to CURLOPT_POSTFIELDS will encode the data as multipart/form-data, + * while passing a URL-encoded string will encode the data as application/x-www-form-urlencoded. + * http://php.net/manual/en/function.curl-setopt.php + */ + if(is_array($parameters) && self::HTTP_FORM_CONTENT_TYPE_APPLICATION === $form_content_type) { + $parameters = http_build_query($parameters, null, '&'); + } + $curl_options[CURLOPT_POSTFIELDS] = $parameters; + break; + case self::HTTP_METHOD_HEAD: + $curl_options[CURLOPT_NOBODY] = true; + /* No break */ + case self::HTTP_METHOD_DELETE: + case self::HTTP_METHOD_GET: + if (is_array($parameters)) { + $url .= '?' . http_build_query($parameters, null, '&'); + } elseif ($parameters) { + $url .= '?' . $parameters; + } + break; + default: + break; + } + + $curl_options[CURLOPT_URL] = $url; + + if (is_array($http_headers)) { + $header = array(); + foreach($http_headers as $key => $parsed_urlvalue) { + $header[] = "$key: $parsed_urlvalue"; + } + $curl_options[CURLOPT_HTTPHEADER] = $header; + } + + $ch = curl_init(); + curl_setopt_array($ch, $curl_options); + // https handling + if (!empty($this->certificate_file)) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_CAINFO, $this->certificate_file); + } else { + // bypass ssl verification + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + } + if (!empty($this->curl_options)) { + curl_setopt_array($ch, $this->curl_options); + } + $result = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); + if ($curl_error = curl_error($ch)) { + throw new Exception($curl_error, Exception::CURL_ERROR); + } else { + $json_decode = json_decode($result, true); + } + curl_close($ch); + + return array( + 'result' => (null === $json_decode) ? $result : $json_decode, + 'code' => $http_code, + 'content_type' => $content_type + ); + } + + /** + * Set the name of the parameter that carry the access token + * + * @param string $name Token parameter name + * @return void + */ + public function setAccessTokenParamName($name) + { + $this->access_token_param_name = $name; + } + + /** + * Converts the class name to camel case + * + * @param mixed $grant_type the grant type + * @return string + */ + private function convertToCamelCase($grant_type) + { + $parts = explode('_', $grant_type); + array_walk($parts, function(&$item) { $item = ucfirst($item);}); + return implode('', $parts); + } +} + +class Exception extends \Exception +{ + const CURL_NOT_FOUND = 0x01; + const CURL_ERROR = 0x02; + const GRANT_TYPE_ERROR = 0x03; + const INVALID_CLIENT_AUTHENTICATION_TYPE = 0x04; + const INVALID_ACCESS_TOKEN_TYPE = 0x05; +} + +class InvalidArgumentException extends \InvalidArgumentException +{ + const INVALID_GRANT_TYPE = 0x01; + const CERTIFICATE_NOT_FOUND = 0x02; + const REQUIRE_PARAMS_AS_ARRAY = 0x03; + const MISSING_PARAMETER = 0x04; +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHP-OAuth2/GrantType/AuthorizationCode.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHP-OAuth2/GrantType/AuthorizationCode.php new file mode 100644 index 0000000..f3436e4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHP-OAuth2/GrantType/AuthorizationCode.php @@ -0,0 +1,41 @@ +getAuthenticationUrl(AUTHORIZATION_ENDPOINT, REDIRECT_URI); + header('Location: ' . $auth_url); + die('Redirect'); +} +else +{ + $params = array('code' => $_GET['code'], 'redirect_uri' => REDIRECT_URI); + $response = $client->getAccessToken(TOKEN_ENDPOINT, 'authorization_code', $params); + parse_str($response['result'], $info); + $client->setAccessToken($info['access_token']); + $response = $client->fetch('https://graph.facebook.com/me'); + var_dump($response, $response['result']); +} + +How can I add a new Grant Type ? +================================ +Simply write a new class in the namespace OAuth2\GrantType. You can place the class file under GrantType. +Here is an example : + +namespace OAuth2\GrantType; + +/** + * MyCustomGrantType Grant Type + */ +class MyCustomGrantType implements IGrantType +{ + /** + * Defines the Grant Type + * + * @var string Defaults to 'my_custom_grant_type'. + */ + const GRANT_TYPE = 'my_custom_grant_type'; + + /** + * Adds a specific Handling of the parameters + * + * @return array of Specific parameters to be sent. + * @param mixed $parameters the parameters array (passed by reference) + */ + public function validateParameters(&$parameters) + { + if (!isset($parameters['first_mandatory_parameter'])) + { + throw new \Exception('The \'first_mandatory_parameter\' parameter must be defined for the Password grant type'); + } + elseif (!isset($parameters['second_mandatory_parameter'])) + { + throw new \Exception('The \'seconde_mandatory_parameter\' parameter must be defined for the Password grant type'); + } + } +} + +call the OAuth client getAccessToken with the grantType you defined in the GRANT_TYPE constant, As following : +$response = $client->getAccessToken(TOKEN_ENDPOINT, 'my_custom_grant_type', $params); + diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPGangsta/GoogleAuthenticator.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPGangsta/GoogleAuthenticator.php new file mode 100644 index 0000000..5e70eed --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPGangsta/GoogleAuthenticator.php @@ -0,0 +1,201 @@ +_getBase32LookupTable(); + unset($validChars[32]); + + $secret = ''; + for ($i = 0; $i < $secretLength; $i++) { + $secret .= $validChars[array_rand($validChars)]; + } + return $secret; + } + + /** + * Calculate the code, with given secret and point in time + * + * @param string $secret + * @param int|null $timeSlice + * @return string + */ + public function getCode($secret, $timeSlice = null) + { + if ($timeSlice === null) { + $timeSlice = floor(time() / 30); + } + + $secretkey = $this->_base32Decode($secret); + + // Pack time into binary string + $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + // Hash it with users secret key + $hm = hash_hmac('SHA1', $time, $secretkey, true); + // Use last nipple of result as index/offset + $offset = ord(substr($hm, -1)) & 0x0F; + // grab 4 bytes of the result + $hashpart = substr($hm, $offset, 4); + + // Unpak binary value + $value = unpack('N', $hashpart); + $value = $value[1]; + // Only 32 bits + $value = $value & 0x7FFFFFFF; + + $modulo = pow(10, $this->_codeLength); + return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); + } + + /** + * Get QR-Code URL for image, from google charts + * + * @param string $name + * @param string $secret + * @return string + */ + public function getQRCodeGoogleUrl($name, $secret) { + $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); + return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.''; + } + + /** + * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now + * + * @param string $secret + * @param string $code + * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) + * @return bool + */ + public function verifyCode($secret, $code, $discrepancy = 1) + { + $currentTimeSlice = floor(time() / 30); + + for ($i = -$discrepancy; $i <= $discrepancy; $i++) { + $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); + if ($calculatedCode == $code ) { + return true; + } + } + + return false; + } + + /** + * Set the code length, should be >=6 + * + * @param int $length + * @return PHPGangsta_GoogleAuthenticator + */ + public function setCodeLength($length) + { + $this->_codeLength = $length; + return $this; + } + + /** + * Helper class to decode base32 + * + * @param $secret + * @return bool|string + */ + protected function _base32Decode($secret) + { + if (empty($secret)) return ''; + + $base32chars = $this->_getBase32LookupTable(); + $base32charsFlipped = array_flip($base32chars); + + $paddingCharCount = substr_count($secret, $base32chars[32]); + $allowedValues = array(6, 4, 3, 1, 0); + if (!in_array($paddingCharCount, $allowedValues)) return false; + for ($i = 0; $i < 4; $i++){ + if ($paddingCharCount == $allowedValues[$i] && + substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false; + } + $secret = str_replace('=','', $secret); + $secret = str_split($secret); + $binaryString = ""; + for ($i = 0; $i < count($secret); $i = $i+8) { + $x = ""; + if (!in_array($secret[$i], $base32chars)) return false; + for ($j = 0; $j < 8; $j++) { + $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); + } + $eightBits = str_split($x, 8); + for ($z = 0; $z < count($eightBits); $z++) { + $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; + } + } + return $binaryString; + } + + /** + * Helper class to encode base32 + * + * @param string $secret + * @param bool $padding + * @return string + */ + protected function _base32Encode($secret, $padding = true) + { + if (empty($secret)) return ''; + + $base32chars = $this->_getBase32LookupTable(); + + $secret = str_split($secret); + $binaryString = ""; + for ($i = 0; $i < count($secret); $i++) { + $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT); + } + $fiveBitBinaryArray = str_split($binaryString, 5); + $base32 = ""; + $i = 0; + while ($i < count($fiveBitBinaryArray)) { + $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)]; + $i++; + } + if ($padding && ($x = strlen($binaryString) % 40) != 0) { + if ($x == 8) $base32 .= str_repeat($base32chars[32], 6); + elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4); + elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3); + elseif ($x == 32) $base32 .= $base32chars[32]; + } + return $base32; + } + + /** + * Get array with all 32 characters for decoding from/encoding to base32 + * + * @return array + */ + protected function _getBase32LookupTable() + { + return array( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 + 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 + '=' // padding char + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/GD.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/GD.php new file mode 100644 index 0000000..733fe47 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/GD.php @@ -0,0 +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); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/PHPThumb.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/PHPThumb.php new file mode 100644 index 0000000..a86c8c9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/PHPThumb.php @@ -0,0 +1,143 @@ + + * 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 + */ + +abstract class PHPThumb +{ + /** + * The name of the file we're manipulating + * This must include the path to the file (absolute paths recommended) + * + * @var string + */ + protected $fileName; + + /** + * What the file format is (mime-type) + * + * @var string + */ + protected $format; + + /** + * Whether or not the image is hosted remotely + * + * @var bool + */ + protected $remoteImage; + + /** + * An array of attached plugins to execute in order. + * @var array + */ + protected $plugins; + + /** + * @param $fileName + * @param array $options + * @param array $plugins + */ + public function __construct($fileName, array $options = array(), array $plugins = array()) + { + $this->fileName = $fileName; + $this->remoteImage = false; + + if(!$this->validateRequestedResource($fileName)) { + throw new \InvalidArgumentException("Image file not found: {$fileName}"); + } + + $this->setOptions($options); + + $this->plugins = $plugins; + } + + abstract public function setOptions(array $options = array()); + + /** + * Check the provided filename/url. If it is a url, validate that it is properly + * formatted. If it is a file, check to make sure that it actually exists on + * the filesystem. + * + * @param $filename + * @return bool + */ + protected function validateRequestedResource($filename) + { + if(false !== filter_var($filename, FILTER_VALIDATE_URL)) { + $this->remoteImage = true; + return true; + } + + if (file_exists($filename)) { + return true; + } + + return false; + } + + /** + * Returns the filename. + * @return string + */ + public function getFileName() + { + return $this->fileName; + } + + /** + * Sets the filename. + * @param $fileName + * @return PHPThumb + */ + public function setFileName($fileName) + { + $this->fileName = $fileName; + + return $this; + } + + /** + * Returns the format. + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Sets the format. + * @param $format + * @return PHPThumb + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } + + /** + * Returns whether the image exists remotely, i.e. it was loaded via a URL. + * @return bool + */ + public function getIsRemoteImage() + { + return $this->remoteImage; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/PluginInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/PluginInterface.php new file mode 100644 index 0000000..56df976 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/PHPThumb/PluginInterface.php @@ -0,0 +1,12 @@ + + * 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 + * @version 3.0 + * @package PhpThumb + * @filesource + */ + +/** + * GD Reflection Lib Plugin + * + * This plugin allows you to create those fun Apple(tm)-style reflections in your images + * + * @package PhpThumb + * @subpackage Plugins + */ +class Reflection implements \PHPThumb\PluginInterface +{ + protected $currentDimensions; + protected $workingImage; + protected $newImage; + protected $options; + + protected $percent; + protected $reflection; + protected $white; + protected $border; + protected $borderColor; + + public function __construct($percent, $reflection, $white, $border, $borderColor) + { + $this->percent = $percent; + $this->reflection = $reflection; + $this->white = $white; + $this->border = $border; + $this->borderColor = $borderColor; + } + + /** + * @param \PHPThumb\PHPThumb $phpthumb + * @return \PHPThumb\PHPThumb + */ + public function execute($phpthumb) + { + $this->currentDimensions = $phpthumb->getCurrentDimensions(); + $this->workingImage = $phpthumb->getWorkingImage(); + $this->newImage = $phpthumb->getOldImage(); + $this->options = $phpthumb->getOptions(); + + $width = $this->currentDimensions['width']; + $height = $this->currentDimensions['height']; + $this->reflectionHeight = intval($height * ($this->reflection / 100)); + $newHeight = $height + $this->reflectionHeight; + $reflectedPart = $height * ($this->percent / 100); + + $this->workingImage = imagecreatetruecolor($width, $newHeight); + + imagealphablending($this->workingImage, true); + + $colorToPaint = imagecolorallocatealpha( + $this->workingImage, + 255, + 255, + 255, + 0 + ); + + imagefilledrectangle( + $this->workingImage, + 0, + 0, + $width, + $newHeight, + $colorToPaint + ); + + imagecopyresampled( + $this->workingImage, + $this->newImage, + 0, + 0, + 0, + $reflectedPart, + $width, + $this->reflectionHeight, + $width, + ($height - $reflectedPart) + ); + + $this->imageFlipVertical(); + + imagecopy( + $this->workingImage, + $this->newImage, + 0, + 0, + 0, + 0, + $width, + $height + ); + + imagealphablending($this->workingImage, true); + + for ($i = 0; $i < $this->reflectionHeight; $i++) { + $colorToPaint = imagecolorallocatealpha( + $this->workingImage, + 255, + 255, + 255, + ($i / $this->reflectionHeight * -1 + 1) * $this->white + ); + + imagefilledrectangle( + $this->workingImage, + 0, + $height + $i, + $width, + $height + $i, + $colorToPaint + ); + } + + if ($this->border == true) { + $rgb = $this->hex2rgb($this->borderColor, false); + $colorToPaint = imagecolorallocate($this->workingImage, $rgb[0], $rgb[1], $rgb[2]); + + //top line + imageline( + $this->workingImage, + 0, + 0, + $width, + 0, + $colorToPaint + ); + + //bottom line + imageline( + $this->workingImage, + 0, + $height, + $width, + $height, + $colorToPaint + ); + + //left line + imageline( + $this->workingImage, + 0, + 0, + 0, + $height, + $colorToPaint + ); + + //right line + imageline( + $this->workingImage, + $width - 1, + 0, + $width - 1, + $height, + $colorToPaint + ); + } + + if ($phpthumb->getFormat() == 'PNG') { + $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); + } + + $phpthumb->setOldImage($this->workingImage); + $this->currentDimensions['width'] = $width; + $this->currentDimensions['height'] = $newHeight; + $phpthumb->setCurrentDimensions($this->currentDimensions); + + return $phpthumb; + } + + /** + * Flips the image vertically + * + */ + protected function imageFlipVertical () + { + $x_i = imagesx($this->workingImage); + $y_i = imagesy($this->workingImage); + + for ($x = 0; $x < $x_i; $x++) { + for ($y = 0; $y < $y_i; $y++) { + imagecopy( + $this->workingImage, + $this->workingImage, + $x, + $y_i - $y - 1, + $x, + $y, + 1, + 1 + ); + } + } + } + + /** + * Converts a hex color to rgb tuples + * + * @return mixed + * @param string $hex + * @param bool $asString + */ + protected function hex2rgb ($hex, $asString = false) + { + // strip off any leading # + if (0 === strpos($hex, '#')) { + $hex = substr($hex, 1); + } elseif (0 === strpos($hex, '&H')) { + $hex = substr($hex, 2); + } + + // break into hex 3-tuple + $cutpoint = ceil(strlen($hex) / 2)-1; + $rgb = explode(':', wordwrap($hex, $cutpoint, ':', $cutpoint), 3); + + // convert each tuple to decimal + $rgb[0] = (isset($rgb[0]) ? hexdec($rgb[0]) : 0); + $rgb[1] = (isset($rgb[1]) ? hexdec($rgb[1]) : 0); + $rgb[2] = (isset($rgb[2]) ? hexdec($rgb[2]) : 0); + + return ($asString ? "{$rgb[0]} {$rgb[1]} {$rgb[2]}" : $rgb); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Autoloader.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Autoloader.php new file mode 100644 index 0000000..17ec2ff --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Autoloader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +/** + * Implements a lightweight PSR-0 compliant autoloader for Predis. + * + * @author Eric Naeseth + * @author Daniele Alessandri + */ +class Autoloader +{ + private $directory; + private $prefix; + private $prefixLength; + + /** + * @param string $baseDirectory Base directory where the source files are located. + */ + public function __construct($baseDirectory = __DIR__) + { + $this->directory = $baseDirectory; + $this->prefix = __NAMESPACE__.'\\'; + $this->prefixLength = strlen($this->prefix); + } + + /** + * Registers the autoloader class with the PHP SPL autoloader. + * + * @param bool $prepend Prepend the autoloader on the stack instead of appending it. + */ + public static function register($prepend = false) + { + spl_autoload_register(array(new self(), 'autoload'), true, $prepend); + } + + /** + * Loads a class from a file using its fully qualified name. + * + * @param string $className Fully qualified name of a class. + */ + public function autoload($className) + { + if (0 === strpos($className, $this->prefix)) { + $parts = explode('\\', substr($className, $this->prefixLength)); + $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php'; + + if (is_file($filepath)) { + require $filepath; + } + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Client.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Client.php new file mode 100644 index 0000000..87596ec --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Client.php @@ -0,0 +1,523 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; +use Predis\Command\ScriptCommand; +use Predis\Configuration\Options; +use Predis\Configuration\OptionsInterface; +use Predis\Connection\AggregateConnectionInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Connection\ParametersInterface; +use Predis\Monitor\Consumer as MonitorConsumer; +use Predis\Pipeline\Pipeline; +use Predis\PubSub\Consumer as PubSubConsumer; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ResponseInterface; +use Predis\Response\ServerException; +use Predis\Transaction\MultiExec as MultiExecTransaction; + +/** + * Client class used for connecting and executing commands on Redis. + * + * This is the main high-level abstraction of Predis upon which various other + * abstractions are built. Internally it aggregates various other classes each + * one with its own responsibility and scope. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Client implements ClientInterface +{ + const VERSION = '1.0.3'; + + protected $connection; + protected $options; + private $profile; + + /** + * @param mixed $parameters Connection parameters for one or more servers. + * @param mixed $options Options to configure some behaviours of the client. + */ + public function __construct($parameters = null, $options = null) + { + $this->options = $this->createOptions($options ?: array()); + $this->connection = $this->createConnection($parameters ?: array()); + $this->profile = $this->options->profile; + } + + /** + * Creates a new instance of Predis\Configuration\Options from different + * types of arguments or simply returns the passed argument if it is an + * instance of Predis\Configuration\OptionsInterface. + * + * @param mixed $options Client options. + * + * @throws \InvalidArgumentException + * + * @return OptionsInterface + */ + protected function createOptions($options) + { + if (is_array($options)) { + return new Options($options); + } + + if ($options instanceof OptionsInterface) { + return $options; + } + + throw new \InvalidArgumentException('Invalid type for client options.'); + } + + /** + * Creates single or aggregate connections from different types of arguments + * (string, array) or returns the passed argument if it is an instance of a + * class implementing Predis\Connection\ConnectionInterface. + * + * Accepted types for connection parameters are: + * + * - Instance of Predis\Connection\ConnectionInterface. + * - Instance of Predis\Connection\ParametersInterface. + * - Array + * - String + * - Callable + * + * @param mixed $parameters Connection parameters or connection instance. + * + * @throws \InvalidArgumentException + * + * @return ConnectionInterface + */ + protected function createConnection($parameters) + { + if ($parameters instanceof ConnectionInterface) { + return $parameters; + } + + if ($parameters instanceof ParametersInterface || is_string($parameters)) { + return $this->options->connections->create($parameters); + } + + if (is_array($parameters)) { + if (!isset($parameters[0])) { + return $this->options->connections->create($parameters); + } + + $options = $this->options; + + if ($options->defined('aggregate')) { + $initializer = $this->getConnectionInitializerWrapper($options->aggregate); + $connection = $initializer($parameters, $options); + } else { + if ($options->defined('replication') && $replication = $options->replication) { + $connection = $replication; + } else { + $connection = $options->cluster; + } + + $options->connections->aggregate($connection, $parameters); + } + + return $connection; + } + + if (is_callable($parameters)) { + $initializer = $this->getConnectionInitializerWrapper($parameters); + $connection = $initializer($this->options); + + return $connection; + } + + throw new \InvalidArgumentException('Invalid type for connection parameters.'); + } + + /** + * Wraps a callable to make sure that its returned value represents a valid + * connection type. + * + * @param mixed $callable + * + * @return \Closure + */ + protected function getConnectionInitializerWrapper($callable) + { + return function () use ($callable) { + $connection = call_user_func_array($callable, func_get_args()); + + if (!$connection instanceof ConnectionInterface) { + throw new \UnexpectedValueException( + 'The callable connection initializer returned an invalid type.' + ); + } + + return $connection; + }; + } + + /** + * {@inheritdoc} + */ + public function getProfile() + { + return $this->profile; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * Creates a new client instance for the specified connection ID or alias, + * only when working with an aggregate connection (cluster, replication). + * The new client instances uses the same options of the original one. + * + * @param string $connectionID Identifier of a connection. + * + * @throws \InvalidArgumentException + * + * @return Client + */ + public function getClientFor($connectionID) + { + if (!$connection = $this->getConnectionById($connectionID)) { + throw new \InvalidArgumentException("Invalid connection ID: $connectionID."); + } + + return new static($connection, $this->options); + } + + /** + * Opens the underlying connection and connects to the server. + */ + public function connect() + { + $this->connection->connect(); + } + + /** + * Closes the underlying connection and disconnects from the server. + */ + public function disconnect() + { + $this->connection->disconnect(); + } + + /** + * Closes the underlying connection and disconnects from the server. + * + * This is the same as `Client::disconnect()` as it does not actually send + * the `QUIT` command to Redis, but simply closes the connection. + */ + public function quit() + { + $this->disconnect(); + } + + /** + * Returns the current state of the underlying connection. + * + * @return bool + */ + public function isConnected() + { + return $this->connection->isConnected(); + } + + /** + * {@inheritdoc} + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Retrieves the specified connection from the aggregate connection when the + * client is in cluster or replication mode. + * + * @param string $connectionID Index or alias of the single connection. + * + * @throws NotSupportedException + * + * @return Connection\NodeConnectionInterface + */ + public function getConnectionById($connectionID) + { + if (!$this->connection instanceof AggregateConnectionInterface) { + throw new NotSupportedException( + 'Retrieving connections by ID is supported only by aggregate connections.' + ); + } + + return $this->connection->getConnectionById($connectionID); + } + + /** + * Executes a command without filtering its arguments, parsing the response, + * applying any prefix to keys or throwing exceptions on Redis errors even + * regardless of client options. + * + * It is possibile to indentify Redis error responses from normal responses + * using the second optional argument which is populated by reference. + * + * @param array $arguments Command arguments as defined by the command signature. + * @param bool $error Set to TRUE when Redis returned an error response. + * + * @return mixed + */ + public function executeRaw(array $arguments, &$error = null) + { + $error = false; + + $response = $this->connection->executeCommand( + new RawCommand($arguments) + ); + + if ($response instanceof ResponseInterface) { + if ($response instanceof ErrorResponseInterface) { + $error = true; + } + + return (string) $response; + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function __call($commandID, $arguments) + { + return $this->executeCommand( + $this->createCommand($commandID, $arguments) + ); + } + + /** + * {@inheritdoc} + */ + public function createCommand($commandID, $arguments = array()) + { + return $this->profile->createCommand($commandID, $arguments); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $response = $this->connection->executeCommand($command); + + if ($response instanceof ResponseInterface) { + if ($response instanceof ErrorResponseInterface) { + $response = $this->onErrorResponse($command, $response); + } + + return $response; + } + + return $command->parseResponse($response); + } + + /** + * Handles -ERR responses returned by Redis. + * + * @param CommandInterface $command Redis command that generated the error. + * @param ErrorResponseInterface $response Instance of the error response. + * + * @throws ServerException + * + * @return mixed + */ + protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response) + { + if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') { + $eval = $this->createCommand('EVAL'); + $eval->setRawArguments($command->getEvalArguments()); + + $response = $this->executeCommand($eval); + + if (!$response instanceof ResponseInterface) { + $response = $command->parseResponse($response); + } + + return $response; + } + + if ($this->options->exceptions) { + throw new ServerException($response->getMessage()); + } + + return $response; + } + + /** + * Executes the specified initializer method on `$this` by adjusting the + * actual invokation depending on the arity (0, 1 or 2 arguments). This is + * simply an utility method to create Redis contexts instances since they + * follow a common initialization path. + * + * @param string $initializer Method name. + * @param array $argv Arguments for the method. + * + * @return mixed + */ + private function sharedContextFactory($initializer, $argv = null) + { + switch (count($argv)) { + case 0: + return $this->$initializer(); + + case 1: + return is_array($argv[0]) + ? $this->$initializer($argv[0]) + : $this->$initializer(null, $argv[0]); + + case 2: + list($arg0, $arg1) = $argv; + + return $this->$initializer($arg0, $arg1); + + default: + return $this->$initializer($this, $argv); + } + } + + /** + * Creates a new pipeline context and returns it, or returns the results of + * a pipeline executed inside the optionally provided callable object. + * + * @param mixed ... Array of options, a callable for execution, or both. + * + * @return Pipeline|array + */ + public function pipeline(/* arguments */) + { + return $this->sharedContextFactory('createPipeline', func_get_args()); + } + + /** + * Actual pipeline context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return Pipeline|array + */ + protected function createPipeline(array $options = null, $callable = null) + { + if (isset($options['atomic']) && $options['atomic']) { + $class = 'Predis\Pipeline\Atomic'; + } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { + $class = 'Predis\Pipeline\FireAndForget'; + } else { + $class = 'Predis\Pipeline\Pipeline'; + } + + /* + * @var ClientContextInterface + */ + $pipeline = new $class($this); + + if (isset($callable)) { + return $pipeline->execute($callable); + } + + return $pipeline; + } + + /** + * Creates a new transaction context and returns it, or returns the results + * of a transaction executed inside the optionally provided callable object. + * + * @param mixed ... Array of options, a callable for execution, or both. + * + * @return MultiExecTransaction|array + */ + public function transaction(/* arguments */) + { + return $this->sharedContextFactory('createTransaction', func_get_args()); + } + + /** + * Actual transaction context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return MultiExecTransaction|array + */ + protected function createTransaction(array $options = null, $callable = null) + { + $transaction = new MultiExecTransaction($this, $options); + + if (isset($callable)) { + return $transaction->execute($callable); + } + + return $transaction; + } + + /** + * Creates a new publis/subscribe context and returns it, or starts its loop + * inside the optionally provided callable object. + * + * @param mixed ... Array of options, a callable for execution, or both. + * + * @return PubSubConsumer|null + */ + public function pubSubLoop(/* arguments */) + { + return $this->sharedContextFactory('createPubSub', func_get_args()); + } + + /** + * Actual publish/subscribe context initializer method. + * + * @param array $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. + * + * @return PubSubConsumer|null + */ + protected function createPubSub(array $options = null, $callable = null) + { + $pubsub = new PubSubConsumer($this, $options); + + if (!isset($callable)) { + return $pubsub; + } + + foreach ($pubsub as $message) { + if (call_user_func($callable, $pubsub, $message) === false) { + $pubsub->stop(); + } + } + } + + /** + * Creates a new monitor consumer and returns it. + * + * @return MonitorConsumer + */ + public function monitor() + { + return new MonitorConsumer($this); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientContextInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientContextInterface.php new file mode 100644 index 0000000..7f695b7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientContextInterface.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Command\CommandInterface; + +/** + * Interface defining a client-side context such as a pipeline or transaction. + * + * @method $this del(array $keys) + * @method $this dump($key) + * @method $this exists($key) + * @method $this expire($key, $seconds) + * @method $this expireat($key, $timestamp) + * @method $this keys($pattern) + * @method $this move($key, $db) + * @method $this object($subcommand, $key) + * @method $this persist($key) + * @method $this pexpire($key, $milliseconds) + * @method $this pexpireat($key, $timestamp) + * @method $this pttl($key) + * @method $this randomkey() + * @method $this rename($key, $target) + * @method $this renamenx($key, $target) + * @method $this scan($cursor, array $options = null) + * @method $this sort($key, array $options = null) + * @method $this ttl($key) + * @method $this type($key) + * @method $this append($key, $value) + * @method $this bitcount($key, $start = null, $end = null) + * @method $this bitop($operation, $destkey, $key) + * @method $this decr($key) + * @method $this decrby($key, $decrement) + * @method $this get($key) + * @method $this getbit($key, $offset) + * @method $this getrange($key, $start, $end) + * @method $this getset($key, $value) + * @method $this incr($key) + * @method $this incrby($key, $increment) + * @method $this incrbyfloat($key, $increment) + * @method $this mget(array $keys) + * @method $this mset(array $dictionary) + * @method $this msetnx(array $dictionary) + * @method $this psetex($key, $milliseconds, $value) + * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null) + * @method $this setbit($key, $offset, $value) + * @method $this setex($key, $seconds, $value) + * @method $this setnx($key, $value) + * @method $this setrange($key, $offset, $value) + * @method $this strlen($key) + * @method $this hdel($key, array $fields) + * @method $this hexists($key, $field) + * @method $this hget($key, $field) + * @method $this hgetall($key) + * @method $this hincrby($key, $field, $increment) + * @method $this hincrbyfloat($key, $field, $increment) + * @method $this hkeys($key) + * @method $this hlen($key) + * @method $this hmget($key, array $fields) + * @method $this hmset($key, array $dictionary) + * @method $this hscan($key, $cursor, array $options = null) + * @method $this hset($key, $field, $value) + * @method $this hsetnx($key, $field, $value) + * @method $this hvals($key) + * @method $this blpop(array $keys, $timeout) + * @method $this brpop(array $keys, $timeout) + * @method $this brpoplpush($source, $destination, $timeout) + * @method $this lindex($key, $index) + * @method $this linsert($key, $whence, $pivot, $value) + * @method $this llen($key) + * @method $this lpop($key) + * @method $this lpush($key, array $values) + * @method $this lpushx($key, $value) + * @method $this lrange($key, $start, $stop) + * @method $this lrem($key, $count, $value) + * @method $this lset($key, $index, $value) + * @method $this ltrim($key, $start, $stop) + * @method $this rpop($key) + * @method $this rpoplpush($source, $destination) + * @method $this rpush($key, array $values) + * @method $this rpushx($key, $value) + * @method $this sadd($key, array $members) + * @method $this scard($key) + * @method $this sdiff(array $keys) + * @method $this sdiffstore($destination, array $keys) + * @method $this sinter(array $keys) + * @method $this sinterstore($destination, array $keys) + * @method $this sismember($key, $member) + * @method $this smembers($key) + * @method $this smove($source, $destination, $member) + * @method $this spop($key) + * @method $this srandmember($key, $count = null) + * @method $this srem($key, $member) + * @method $this sscan($key, $cursor, array $options = null) + * @method $this sunion(array $keys) + * @method $this sunionstore($destination, array $keys) + * @method $this zadd($key, array $membersAndScoresDictionary) + * @method $this zcard($key) + * @method $this zcount($key, $min, $max) + * @method $this zincrby($key, $increment, $member) + * @method $this zinterstore($destination, array $keys, array $options = null) + * @method $this zrange($key, $start, $stop, array $options = null) + * @method $this zrangebyscore($key, $min, $max, array $options = null) + * @method $this zrank($key, $member) + * @method $this zrem($key, $member) + * @method $this zremrangebyrank($key, $start, $stop) + * @method $this zremrangebyscore($key, $min, $max) + * @method $this zrevrange($key, $start, $stop, array $options = null) + * @method $this zrevrangebyscore($key, $min, $max, array $options = null) + * @method $this zrevrank($key, $member) + * @method $this zunionstore($destination, array $keys, array $options = null) + * @method $this zscore($key, $member) + * @method $this zscan($key, $cursor, array $options = null) + * @method $this zrangebylex($key, $start, $stop, array $options = null) + * @method $this zremrangebylex($key, $min, $max) + * @method $this zlexcount($key, $min, $max) + * @method $this pfadd($key, array $elements) + * @method $this pfmerge($destinationKey, array $sourceKeys) + * @method $this pfcount(array $keys) + * @method $this pubsub($subcommand, $argument) + * @method $this publish($channel, $message) + * @method $this discard() + * @method $this exec() + * @method $this multi() + * @method $this unwatch() + * @method $this watch($key) + * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method $this script($subcommand, $argument = null) + * @method $this auth($password) + * @method $this echo($message) + * @method $this ping($message = null) + * @method $this select($database) + * @method $this bgrewriteaof() + * @method $this bgsave() + * @method $this client($subcommand, $argument = null) + * @method $this config($subcommand, $argument = null) + * @method $this dbsize() + * @method $this flushall() + * @method $this flushdb() + * @method $this info($section = null) + * @method $this lastsave() + * @method $this save() + * @method $this slaveof($host, $port) + * @method $this slowlog($subcommand, $argument = null) + * @method $this time() + * @method $this command() + * + * @author Daniele Alessandri + */ +interface ClientContextInterface +{ + /** + * Sends the specified command instance to Redis. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function executeCommand(CommandInterface $command); + + /** + * Sends the specified command with its arguments to Redis. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + */ + public function __call($method, $arguments); + + /** + * Starts the execution of the context. + * + * @param mixed $callable Optional callback for execution. + * + * @return array + */ + public function execute($callable = null); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientException.php new file mode 100644 index 0000000..6c07aaf --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +/** + * Exception class that identifies client-side errors. + * + * @author Daniele Alessandri + */ +class ClientException extends PredisException +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientInterface.php new file mode 100644 index 0000000..f216c47 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/ClientInterface.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Command\CommandInterface; +use Predis\Configuration\OptionsInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Profile\ProfileInterface; + +/** + * Interface defining a client able to execute commands against Redis. + * + * All the commands exposed by the client generally have the same signature as + * described by the Redis documentation, but some of them offer an additional + * and more friendly interface to ease programming which is described in the + * following list of methods: + * + * @method int del(array $keys) + * @method string dump($key) + * @method int exists($key) + * @method int expire($key, $seconds) + * @method int expireat($key, $timestamp) + * @method array keys($pattern) + * @method int move($key, $db) + * @method mixed object($subcommand, $key) + * @method int persist($key) + * @method int pexpire($key, $milliseconds) + * @method int pexpireat($key, $timestamp) + * @method int pttl($key) + * @method string randomkey() + * @method mixed rename($key, $target) + * @method int renamenx($key, $target) + * @method array scan($cursor, array $options = null) + * @method array sort($key, array $options = null) + * @method int ttl($key) + * @method mixed type($key) + * @method int append($key, $value) + * @method int bitcount($key, $start = null, $end = null) + * @method int bitop($operation, $destkey, $key) + * @method int decr($key) + * @method int decrby($key, $decrement) + * @method string get($key) + * @method int getbit($key, $offset) + * @method string getrange($key, $start, $end) + * @method string getset($key, $value) + * @method int incr($key) + * @method int incrby($key, $increment) + * @method string incrbyfloat($key, $increment) + * @method array mget(array $keys) + * @method mixed mset(array $dictionary) + * @method int msetnx(array $dictionary) + * @method mixed psetex($key, $milliseconds, $value) + * @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null) + * @method int setbit($key, $offset, $value) + * @method int setex($key, $seconds, $value) + * @method int setnx($key, $value) + * @method int setrange($key, $offset, $value) + * @method int strlen($key) + * @method int hdel($key, array $fields) + * @method int hexists($key, $field) + * @method string hget($key, $field) + * @method array hgetall($key) + * @method int hincrby($key, $field, $increment) + * @method string hincrbyfloat($key, $field, $increment) + * @method array hkeys($key) + * @method int hlen($key) + * @method array hmget($key, array $fields) + * @method mixed hmset($key, array $dictionary) + * @method array hscan($key, $cursor, array $options = null) + * @method int hset($key, $field, $value) + * @method int hsetnx($key, $field, $value) + * @method array hvals($key) + * @method array blpop(array $keys, $timeout) + * @method array brpop(array $keys, $timeout) + * @method array brpoplpush($source, $destination, $timeout) + * @method string lindex($key, $index) + * @method int linsert($key, $whence, $pivot, $value) + * @method int llen($key) + * @method string lpop($key) + * @method int lpush($key, array $values) + * @method int lpushx($key, $value) + * @method array lrange($key, $start, $stop) + * @method int lrem($key, $count, $value) + * @method mixed lset($key, $index, $value) + * @method mixed ltrim($key, $start, $stop) + * @method string rpop($key) + * @method string rpoplpush($source, $destination) + * @method int rpush($key, array $values) + * @method int rpushx($key, $value) + * @method int sadd($key, array $members) + * @method int scard($key) + * @method array sdiff(array $keys) + * @method int sdiffstore($destination, array $keys) + * @method array sinter(array $keys) + * @method int sinterstore($destination, array $keys) + * @method int sismember($key, $member) + * @method array smembers($key) + * @method int smove($source, $destination, $member) + * @method string spop($key) + * @method string srandmember($key, $count = null) + * @method int srem($key, $member) + * @method array sscan($key, $cursor, array $options = null) + * @method array sunion(array $keys) + * @method int sunionstore($destination, array $keys) + * @method int zadd($key, array $membersAndScoresDictionary) + * @method int zcard($key) + * @method string zcount($key, $min, $max) + * @method string zincrby($key, $increment, $member) + * @method int zinterstore($destination, array $keys, array $options = null) + * @method array zrange($key, $start, $stop, array $options = null) + * @method array zrangebyscore($key, $min, $max, array $options = null) + * @method int zrank($key, $member) + * @method int zrem($key, $member) + * @method int zremrangebyrank($key, $start, $stop) + * @method int zremrangebyscore($key, $min, $max) + * @method array zrevrange($key, $start, $stop, array $options = null) + * @method array zrevrangebyscore($key, $min, $max, array $options = null) + * @method int zrevrank($key, $member) + * @method int zunionstore($destination, array $keys, array $options = null) + * @method string zscore($key, $member) + * @method array zscan($key, $cursor, array $options = null) + * @method array zrangebylex($key, $start, $stop, array $options = null) + * @method int zremrangebylex($key, $min, $max) + * @method int zlexcount($key, $min, $max) + * @method int pfadd($key, array $elements) + * @method mixed pfmerge($destinationKey, array $sourceKeys) + * @method int pfcount(array $keys) + * @method mixed pubsub($subcommand, $argument) + * @method int publish($channel, $message) + * @method mixed discard() + * @method array exec() + * @method mixed multi() + * @method mixed unwatch() + * @method mixed watch($key) + * @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null) + * @method mixed script($subcommand, $argument = null) + * @method mixed auth($password) + * @method string echo($message) + * @method mixed ping($message = null) + * @method mixed select($database) + * @method mixed bgrewriteaof() + * @method mixed bgsave() + * @method mixed client($subcommand, $argument = null) + * @method mixed config($subcommand, $argument = null) + * @method int dbsize() + * @method mixed flushall() + * @method mixed flushdb() + * @method array info($section = null) + * @method int lastsave() + * @method mixed save() + * @method mixed slaveof($host, $port) + * @method mixed slowlog($subcommand, $argument = null) + * @method array time() + * @method array command() + * + * @author Daniele Alessandri + */ +interface ClientInterface +{ + /** + * Returns the server profile used by the client. + * + * @return ProfileInterface + */ + public function getProfile(); + + /** + * Returns the client options specified upon initialization. + * + * @return OptionsInterface + */ + public function getOptions(); + + /** + * Opens the underlying connection to the server. + */ + public function connect(); + + /** + * Closes the underlying connection from the server. + */ + public function disconnect(); + + /** + * Returns the underlying connection instance. + * + * @return ConnectionInterface + */ + public function getConnection(); + + /** + * Creates a new instance of the specified Redis command. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return CommandInterface + */ + public function createCommand($method, $arguments = array()); + + /** + * Executes the specified Redis command. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function executeCommand(CommandInterface $command); + + /** + * Creates a Redis command with the specified arguments and sends a request + * to the server. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return mixed + */ + public function __call($method, $arguments); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/ClusterStrategy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/ClusterStrategy.php new file mode 100644 index 0000000..7635000 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/ClusterStrategy.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Command\CommandInterface; +use Predis\Command\ScriptCommand; + +/** + * Common class implementing the logic needed to support clustering strategies. + * + * @author Daniele Alessandri + */ +abstract class ClusterStrategy implements StrategyInterface +{ + protected $commands; + + /** + * + */ + public function __construct() + { + $this->commands = $this->getDefaultCommands(); + } + + /** + * Returns the default map of supported commands with their handlers. + * + * @return array + */ + protected function getDefaultCommands() + { + $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument'); + $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments'); + + return array( + /* commands operating on the key space */ + 'EXISTS' => $getKeyFromFirstArgument, + 'DEL' => $getKeyFromAllArguments, + 'TYPE' => $getKeyFromFirstArgument, + 'EXPIRE' => $getKeyFromFirstArgument, + 'EXPIREAT' => $getKeyFromFirstArgument, + 'PERSIST' => $getKeyFromFirstArgument, + 'PEXPIRE' => $getKeyFromFirstArgument, + 'PEXPIREAT' => $getKeyFromFirstArgument, + 'TTL' => $getKeyFromFirstArgument, + 'PTTL' => $getKeyFromFirstArgument, + 'SORT' => $getKeyFromFirstArgument, // TODO + 'DUMP' => $getKeyFromFirstArgument, + 'RESTORE' => $getKeyFromFirstArgument, + + /* commands operating on string values */ + 'APPEND' => $getKeyFromFirstArgument, + 'DECR' => $getKeyFromFirstArgument, + 'DECRBY' => $getKeyFromFirstArgument, + 'GET' => $getKeyFromFirstArgument, + 'GETBIT' => $getKeyFromFirstArgument, + 'MGET' => $getKeyFromAllArguments, + 'SET' => $getKeyFromFirstArgument, + 'GETRANGE' => $getKeyFromFirstArgument, + 'GETSET' => $getKeyFromFirstArgument, + 'INCR' => $getKeyFromFirstArgument, + 'INCRBY' => $getKeyFromFirstArgument, + 'INCRBYFLOAT' => $getKeyFromFirstArgument, + 'SETBIT' => $getKeyFromFirstArgument, + 'SETEX' => $getKeyFromFirstArgument, + 'MSET' => array($this, 'getKeyFromInterleavedArguments'), + 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'), + 'SETNX' => $getKeyFromFirstArgument, + 'SETRANGE' => $getKeyFromFirstArgument, + 'STRLEN' => $getKeyFromFirstArgument, + 'SUBSTR' => $getKeyFromFirstArgument, + 'BITOP' => array($this, 'getKeyFromBitOp'), + 'BITCOUNT' => $getKeyFromFirstArgument, + + /* commands operating on lists */ + 'LINSERT' => $getKeyFromFirstArgument, + 'LINDEX' => $getKeyFromFirstArgument, + 'LLEN' => $getKeyFromFirstArgument, + 'LPOP' => $getKeyFromFirstArgument, + 'RPOP' => $getKeyFromFirstArgument, + 'RPOPLPUSH' => $getKeyFromAllArguments, + 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'), + 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'), + 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'), + 'LPUSH' => $getKeyFromFirstArgument, + 'LPUSHX' => $getKeyFromFirstArgument, + 'RPUSH' => $getKeyFromFirstArgument, + 'RPUSHX' => $getKeyFromFirstArgument, + 'LRANGE' => $getKeyFromFirstArgument, + 'LREM' => $getKeyFromFirstArgument, + 'LSET' => $getKeyFromFirstArgument, + 'LTRIM' => $getKeyFromFirstArgument, + + /* commands operating on sets */ + 'SADD' => $getKeyFromFirstArgument, + 'SCARD' => $getKeyFromFirstArgument, + 'SDIFF' => $getKeyFromAllArguments, + 'SDIFFSTORE' => $getKeyFromAllArguments, + 'SINTER' => $getKeyFromAllArguments, + 'SINTERSTORE' => $getKeyFromAllArguments, + 'SUNION' => $getKeyFromAllArguments, + 'SUNIONSTORE' => $getKeyFromAllArguments, + 'SISMEMBER' => $getKeyFromFirstArgument, + 'SMEMBERS' => $getKeyFromFirstArgument, + 'SSCAN' => $getKeyFromFirstArgument, + 'SPOP' => $getKeyFromFirstArgument, + 'SRANDMEMBER' => $getKeyFromFirstArgument, + 'SREM' => $getKeyFromFirstArgument, + + /* commands operating on sorted sets */ + 'ZADD' => $getKeyFromFirstArgument, + 'ZCARD' => $getKeyFromFirstArgument, + 'ZCOUNT' => $getKeyFromFirstArgument, + 'ZINCRBY' => $getKeyFromFirstArgument, + 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), + 'ZRANGE' => $getKeyFromFirstArgument, + 'ZRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZRANK' => $getKeyFromFirstArgument, + 'ZREM' => $getKeyFromFirstArgument, + 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument, + 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZREVRANGE' => $getKeyFromFirstArgument, + 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument, + 'ZREVRANK' => $getKeyFromFirstArgument, + 'ZSCORE' => $getKeyFromFirstArgument, + 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), + 'ZSCAN' => $getKeyFromFirstArgument, + 'ZLEXCOUNT' => $getKeyFromFirstArgument, + 'ZRANGEBYLEX' => $getKeyFromFirstArgument, + 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument, + 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument, + + /* commands operating on hashes */ + 'HDEL' => $getKeyFromFirstArgument, + 'HEXISTS' => $getKeyFromFirstArgument, + 'HGET' => $getKeyFromFirstArgument, + 'HGETALL' => $getKeyFromFirstArgument, + 'HMGET' => $getKeyFromFirstArgument, + 'HMSET' => $getKeyFromFirstArgument, + 'HINCRBY' => $getKeyFromFirstArgument, + 'HINCRBYFLOAT' => $getKeyFromFirstArgument, + 'HKEYS' => $getKeyFromFirstArgument, + 'HLEN' => $getKeyFromFirstArgument, + 'HSET' => $getKeyFromFirstArgument, + 'HSETNX' => $getKeyFromFirstArgument, + 'HVALS' => $getKeyFromFirstArgument, + 'HSCAN' => $getKeyFromFirstArgument, + 'HSTRLEN' => $getKeyFromFirstArgument, + + /* commands operating on HyperLogLog */ + 'PFADD' => $getKeyFromFirstArgument, + 'PFCOUNT' => $getKeyFromAllArguments, + 'PFMERGE' => $getKeyFromAllArguments, + + /* scripting */ + 'EVAL' => array($this, 'getKeyFromScriptingCommands'), + 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'), + ); + } + + /** + * Returns the list of IDs for the supported commands. + * + * @return array + */ + public function getSupportedCommands() + { + return array_keys($this->commands); + } + + /** + * Sets an handler for the specified command ID. + * + * The signature of the callback must have a single parameter of type + * Predis\Command\CommandInterface. + * + * When the callback argument is omitted or NULL, the previously associated + * handler for the specified command ID is removed. + * + * @param string $commandID Command ID. + * @param mixed $callback A valid callable object, or NULL to unset the handler. + * + * @throws \InvalidArgumentException + */ + public function setCommandHandler($commandID, $callback = null) + { + $commandID = strtoupper($commandID); + + if (!isset($callback)) { + unset($this->commands[$commandID]); + + return; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + 'The argument must be a callable object or NULL.' + ); + } + + $this->commands[$commandID] = $callback; + } + + /** + * Extracts the key from the first argument of a command instance. + * + * @param CommandInterface $command Command instance. + * + * @return string + */ + protected function getKeyFromFirstArgument(CommandInterface $command) + { + return $command->getArgument(0); + } + + /** + * Extracts the key from a command with multiple keys only when all keys in + * the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromAllArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameSlotForKeys($arguments)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from a command with multiple keys only when all keys in + * the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromInterleavedArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array(); + + for ($i = 0; $i < count($arguments); $i += 2) { + $keys[] = $arguments[$i]; + } + + if ($this->checkSameSlotForKeys($keys)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from BLPOP and BRPOP commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromBlockingListCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { + return $arguments[0]; + } + } + + /** + * Extracts the key from BITOP command. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromBitOp(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { + return $arguments[1]; + } + } + + /** + * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromZsetAggregationCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1])); + + if ($this->checkSameSlotForKeys($keys)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from EVAL and EVALSHA commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromScriptingCommands(CommandInterface $command) + { + if ($command instanceof ScriptCommand) { + $keys = $command->getKeys(); + } else { + $keys = array_slice($args = $command->getArguments(), 2, $args[1]); + } + + if ($keys && $this->checkSameSlotForKeys($keys)) { + return $keys[0]; + } + } + + /** + * {@inheritdoc} + */ + public function getSlot(CommandInterface $command) + { + $slot = $command->getSlot(); + + if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) { + $key = call_user_func($this->commands[$cmdID], $command); + + if (isset($key)) { + $slot = $this->getSlotByKey($key); + $command->setSlot($slot); + } + } + + return $slot; + } + + /** + * Checks if the specified array of keys will generate the same hash. + * + * @param array $keys Array of keys. + * + * @return bool + */ + protected function checkSameSlotForKeys(array $keys) + { + if (!$count = count($keys)) { + return false; + } + + $currentSlot = $this->getSlotByKey($keys[0]); + + for ($i = 1; $i < $count; ++$i) { + $nextSlot = $this->getSlotByKey($keys[$i]); + + if ($currentSlot !== $nextSlot) { + return false; + } + + $currentSlot = $nextSlot; + } + + return true; + } + + /** + * Returns only the hashable part of a key (delimited by "{...}"), or the + * whole key if a key tag is not found in the string. + * + * @param string $key A key. + * + * @return string + */ + protected function extractKeyTag($key) + { + if (false !== $start = strpos($key, '{')) { + if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) { + $key = substr($key, $start, $end - $start); + } + } + + return $key; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/DistributorInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/DistributorInterface.php new file mode 100644 index 0000000..831f52c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/DistributorInterface.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Distributor; + +use Predis\Cluster\Hash\HashGeneratorInterface; + +/** + * A distributor implements the logic to automatically distribute keys among + * several nodes for client-side sharding. + * + * @author Daniele Alessandri + */ +interface DistributorInterface +{ + /** + * Adds a node to the distributor with an optional weight. + * + * @param mixed $node Node object. + * @param int $weight Weight for the node. + */ + public function add($node, $weight = null); + + /** + * Removes a node from the distributor. + * + * @param mixed $node Node object. + */ + public function remove($node); + + /** + * Returns the corresponding slot of a node from the distributor using the + * computed hash of a key. + * + * @param mixed $hash + * + * @return mixed + */ + public function getSlot($hash); + + /** + * Returns a node from the distributor using its assigned slot ID. + * + * @param mixed $slot + * + * @return mixed|null + */ + public function getBySlot($slot); + + /** + * Returns a node from the distributor using the computed hash of a key. + * + * @param mixed $hash + * + * @return mixed + */ + public function getByHash($hash); + + /** + * Returns a node from the distributor mapping to the specified value. + * + * @param string $value + * + * @return mixed + */ + public function get($value); + + /** + * Returns the underlying hash generator instance. + * + * @return HashGeneratorInterface + */ + public function getHashGenerator(); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/EmptyRingException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/EmptyRingException.php new file mode 100644 index 0000000..039f2f2 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/EmptyRingException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Distributor; + +/** + * Exception class that identifies empty rings. + * + * @author Daniele Alessandri + */ +class EmptyRingException extends \Exception +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/HashRing.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/HashRing.php new file mode 100644 index 0000000..db864d9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/HashRing.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Distributor; + +use Predis\Cluster\Hash\HashGeneratorInterface; + +/** + * This class implements an hashring-based distributor that uses the same + * algorithm of memcache to distribute keys in a cluster using client-side + * sharding. + * + * @author Daniele Alessandri + * @author Lorenzo Castelli + */ +class HashRing implements DistributorInterface, HashGeneratorInterface +{ + const DEFAULT_REPLICAS = 128; + const DEFAULT_WEIGHT = 100; + + private $ring; + private $ringKeys; + private $ringKeysCount; + private $replicas; + private $nodeHashCallback; + private $nodes = array(); + + /** + * @param int $replicas Number of replicas in the ring. + * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes. + */ + public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null) + { + $this->replicas = $replicas; + $this->nodeHashCallback = $nodeHashCallback; + } + + /** + * Adds a node to the ring with an optional weight. + * + * @param mixed $node Node object. + * @param int $weight Weight for the node. + */ + public function add($node, $weight = null) + { + // In case of collisions in the hashes of the nodes, the node added + // last wins, thus the order in which nodes are added is significant. + $this->nodes[] = array( + 'object' => $node, + 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT, + ); + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove($node) + { + // A node is removed by resetting the ring so that it's recreated from + // scratch, in order to reassign possible hashes with collisions to the + // right node according to the order in which they were added in the + // first place. + for ($i = 0; $i < count($this->nodes); ++$i) { + if ($this->nodes[$i]['object'] === $node) { + array_splice($this->nodes, $i, 1); + $this->reset(); + + break; + } + } + } + + /** + * Resets the distributor. + */ + private function reset() + { + unset( + $this->ring, + $this->ringKeys, + $this->ringKeysCount + ); + } + + /** + * Returns the initialization status of the distributor. + * + * @return bool + */ + private function isInitialized() + { + return isset($this->ringKeys); + } + + /** + * Calculates the total weight of all the nodes in the distributor. + * + * @return int + */ + private function computeTotalWeight() + { + $totalWeight = 0; + + foreach ($this->nodes as $node) { + $totalWeight += $node['weight']; + } + + return $totalWeight; + } + + /** + * Initializes the distributor. + */ + private function initialize() + { + if ($this->isInitialized()) { + return; + } + + if (!$this->nodes) { + throw new EmptyRingException('Cannot initialize an empty hashring.'); + } + + $this->ring = array(); + $totalWeight = $this->computeTotalWeight(); + $nodesCount = count($this->nodes); + + foreach ($this->nodes as $node) { + $weightRatio = $node['weight'] / $totalWeight; + $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio); + } + + ksort($this->ring, SORT_NUMERIC); + $this->ringKeys = array_keys($this->ring); + $this->ringKeysCount = count($this->ringKeys); + } + + /** + * Implements the logic needed to add a node to the hashring. + * + * @param array $ring Source hashring. + * @param mixed $node Node object to be added. + * @param int $totalNodes Total number of nodes. + * @param int $replicas Number of replicas in the ring. + * @param float $weightRatio Weight ratio for the node. + */ + protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) + { + $nodeObject = $node['object']; + $nodeHash = $this->getNodeHash($nodeObject); + $replicas = (int) round($weightRatio * $totalNodes * $replicas); + + for ($i = 0; $i < $replicas; ++$i) { + $key = crc32("$nodeHash:$i"); + $ring[$key] = $nodeObject; + } + } + + /** + * {@inheritdoc} + */ + protected function getNodeHash($nodeObject) + { + if (!isset($this->nodeHashCallback)) { + return (string) $nodeObject; + } + + return call_user_func($this->nodeHashCallback, $nodeObject); + } + + /** + * {@inheritdoc} + */ + public function hash($value) + { + return crc32($value); + } + + /** + * {@inheritdoc} + */ + public function getByHash($hash) + { + return $this->ring[$this->getSlot($hash)]; + } + + /** + * {@inheritdoc} + */ + public function getBySlot($slot) + { + $this->initialize(); + + if (isset($this->ring[$slot])) { + return $this->ring[$slot]; + } + } + + /** + * {@inheritdoc} + */ + public function getSlot($hash) + { + $this->initialize(); + + $ringKeys = $this->ringKeys; + $upper = $this->ringKeysCount - 1; + $lower = 0; + + while ($lower <= $upper) { + $index = ($lower + $upper) >> 1; + $item = $ringKeys[$index]; + + if ($item > $hash) { + $upper = $index - 1; + } elseif ($item < $hash) { + $lower = $index + 1; + } else { + return $item; + } + } + + return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)]; + } + + /** + * {@inheritdoc} + */ + public function get($value) + { + $hash = $this->hash($value); + $node = $this->getByHash($hash); + + return $node; + } + + /** + * Implements a strategy to deal with wrap-around errors during binary searches. + * + * @param int $upper + * @param int $lower + * @param int $ringKeysCount + * + * @return int + */ + protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) + { + // Binary search for the last item in ringkeys with a value less or + // equal to the key. If no such item exists, return the last item. + return $upper >= 0 ? $upper : $ringKeysCount - 1; + } + + /** + * {@inheritdoc} + */ + public function getHashGenerator() + { + return $this; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/KetamaRing.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/KetamaRing.php new file mode 100644 index 0000000..dc77f32 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Distributor/KetamaRing.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Distributor; + +/** + * This class implements an hashring-based distributor that uses the same + * algorithm of libketama to distribute keys in a cluster using client-side + * sharding. + * + * @author Daniele Alessandri + * @author Lorenzo Castelli + */ +class KetamaRing extends HashRing +{ + const DEFAULT_REPLICAS = 160; + + /** + * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes. + */ + public function __construct($nodeHashCallback = null) + { + parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback); + } + + /** + * {@inheritdoc} + */ + protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio) + { + $nodeObject = $node['object']; + $nodeHash = $this->getNodeHash($nodeObject); + $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4)); + + for ($i = 0; $i < $replicas; ++$i) { + $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true)); + + foreach ($unpackedDigest as $key) { + $ring[$key] = $nodeObject; + } + } + } + + /** + * {@inheritdoc} + */ + public function hash($value) + { + $hash = unpack('V', md5($value, true)); + + return $hash[1]; + } + + /** + * {@inheritdoc} + */ + protected function wrapAroundStrategy($upper, $lower, $ringKeysCount) + { + // Binary search for the first item in ringkeys with a value greater + // or equal to the key. If no such item exists, return the first item. + return $lower < $ringKeysCount ? $lower : 0; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Hash/CRC16.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Hash/CRC16.php new file mode 100644 index 0000000..3add0ce --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Hash/CRC16.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Hash; + +/** + * Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster. + * + * @author Daniele Alessandri + */ +class CRC16 implements HashGeneratorInterface +{ + private static $CCITT_16 = array( + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, + ); + + /** + * {@inheritdoc} + */ + public function hash($value) + { + // CRC-CCITT-16 algorithm + $crc = 0; + $CCITT_16 = self::$CCITT_16; + $strlen = strlen($value); + + for ($i = 0; $i < $strlen; ++$i) { + $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF; + } + + return $crc; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Hash/HashGeneratorInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Hash/HashGeneratorInterface.php new file mode 100644 index 0000000..271b9e7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/Hash/HashGeneratorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster\Hash; + +/** + * An hash generator implements the logic used to calculate the hash of a key to + * distribute operations among Redis nodes. + * + * @author Daniele Alessandri + */ +interface HashGeneratorInterface +{ + /** + * Generates an hash from a string to be used for distribution. + * + * @param string $value String value. + * + * @return int + */ + public function hash($value); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/PredisStrategy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/PredisStrategy.php new file mode 100644 index 0000000..2066842 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/PredisStrategy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Cluster\Distributor\DistributorInterface; +use Predis\Cluster\Distributor\HashRing; + +/** + * Default cluster strategy used by Predis to handle client-side sharding. + * + * @author Daniele Alessandri + */ +class PredisStrategy extends ClusterStrategy +{ + protected $distributor; + + /** + * @param DistributorInterface $distributor Optional distributor instance. + */ + public function __construct(DistributorInterface $distributor = null) + { + parent::__construct(); + + $this->distributor = $distributor ?: new HashRing(); + } + + /** + * {@inheritdoc} + */ + public function getSlotByKey($key) + { + $key = $this->extractKeyTag($key); + $hash = $this->distributor->hash($key); + $slot = $this->distributor->getSlot($hash); + + return $slot; + } + + /** + * {@inheritdoc} + */ + protected function checkSameSlotForKeys(array $keys) + { + if (!$count = count($keys)) { + return false; + } + + $currentKey = $this->extractKeyTag($keys[0]); + + for ($i = 1; $i < $count; ++$i) { + $nextKey = $this->extractKeyTag($keys[$i]); + + if ($currentKey !== $nextKey) { + return false; + } + + $currentKey = $nextKey; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getDistributor() + { + return $this->distributor; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/RedisStrategy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/RedisStrategy.php new file mode 100644 index 0000000..df0bdb4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/RedisStrategy.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Cluster\Hash\CRC16; +use Predis\Cluster\Hash\HashGeneratorInterface; +use Predis\NotSupportedException; + +/** + * Default class used by Predis to calculate hashes out of keys of + * commands supported by redis-cluster. + * + * @author Daniele Alessandri + */ +class RedisStrategy extends ClusterStrategy +{ + protected $hashGenerator; + + /** + * @param HashGeneratorInterface $hashGenerator Hash generator instance. + */ + public function __construct(HashGeneratorInterface $hashGenerator = null) + { + parent::__construct(); + + $this->hashGenerator = $hashGenerator ?: new CRC16(); + } + + /** + * {@inheritdoc} + */ + public function getSlotByKey($key) + { + $key = $this->extractKeyTag($key); + $slot = $this->hashGenerator->hash($key) & 0x3FFF; + + return $slot; + } + + /** + * {@inheritdoc} + */ + public function getDistributor() + { + throw new NotSupportedException( + 'This cluster strategy does not provide an external distributor' + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/StrategyInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/StrategyInterface.php new file mode 100644 index 0000000..cdf7d09 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Cluster/StrategyInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Cluster; + +use Predis\Cluster\Distributor\DistributorInterface; +use Predis\Command\CommandInterface; + +/** + * Interface for classes defining the strategy used to calculate an hash out of + * keys extracted from supported commands. + * + * This is mostly useful to support clustering via client-side sharding. + * + * @author Daniele Alessandri + */ +interface StrategyInterface +{ + /** + * Returns a slot for the given command used for clustering distribution or + * NULL when this is not possible. + * + * @param CommandInterface $command Command instance. + * + * @return int + */ + public function getSlot(CommandInterface $command); + + /** + * Returns a slot for the given key used for clustering distribution or NULL + * when this is not possible. + * + * @param string $key Key string. + * + * @return int + */ + public function getSlotByKey($key); + + /** + * Returns a distributor instance to be used by the cluster. + * + * @return DistributorInterface + */ + public function getDistributor(); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/CursorBasedIterator.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/CursorBasedIterator.php new file mode 100644 index 0000000..922883f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/CursorBasedIterator.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; +use Predis\NotSupportedException; + +/** + * Provides the base implementation for a fully-rewindable PHP iterator that can + * incrementally iterate over cursor-based collections stored on Redis using the + * commands in the `SCAN` family. + * + * Given their incremental nature with multiple fetches, these kind of iterators + * offer limited guarantees about the returned elements because the collection + * can change several times during the iteration process. + * + * @see http://redis.io/commands/scan + * + * @author Daniele Alessandri + */ +abstract class CursorBasedIterator implements \Iterator +{ + protected $client; + protected $match; + protected $count; + + protected $valid; + protected $fetchmore; + protected $elements; + protected $cursor; + protected $position; + protected $current; + + /** + * @param ClientInterface $client Client connected to Redis. + * @param string $match Pattern to match during the server-side iteration. + * @param int $count Hint used by Redis to compute the number of results per iteration. + */ + public function __construct(ClientInterface $client, $match = null, $count = null) + { + $this->client = $client; + $this->match = $match; + $this->count = $count; + + $this->reset(); + } + + /** + * Ensures that the client supports the specified Redis command required to + * fetch elements from the server to perform the iteration. + * + * @param ClientInterface $client Client connected to Redis. + * @param string $commandID Command ID. + * + * @throws NotSupportedException + */ + protected function requiredCommand(ClientInterface $client, $commandID) + { + if (!$client->getProfile()->supportsCommand($commandID)) { + throw new NotSupportedException("The current profile does not support '$commandID'."); + } + } + + /** + * Resets the inner state of the iterator. + */ + protected function reset() + { + $this->valid = true; + $this->fetchmore = true; + $this->elements = array(); + $this->cursor = 0; + $this->position = -1; + $this->current = null; + } + + /** + * Returns an array of options for the `SCAN` command. + * + * @return array + */ + protected function getScanOptions() + { + $options = array(); + + if (strlen($this->match) > 0) { + $options['MATCH'] = $this->match; + } + + if ($this->count > 0) { + $options['COUNT'] = $this->count; + } + + return $options; + } + + /** + * Fetches a new set of elements from the remote collection, effectively + * advancing the iteration process. + * + * @return array + */ + abstract protected function executeCommand(); + + /** + * Populates the local buffer of elements fetched from the server during + * the iteration. + */ + protected function fetch() + { + list($cursor, $elements) = $this->executeCommand(); + + if (!$cursor) { + $this->fetchmore = false; + } + + $this->cursor = $cursor; + $this->elements = $elements; + } + + /** + * Extracts next values for key() and current(). + */ + protected function extractNext() + { + ++$this->position; + $this->current = array_shift($this->elements); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + tryFetch: { + if (!$this->elements && $this->fetchmore) { + $this->fetch(); + } + + if ($this->elements) { + $this->extractNext(); + } elseif ($this->cursor) { + goto tryFetch; + } else { + $this->valid = false; + } + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->valid; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/HashKey.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/HashKey.php new file mode 100644 index 0000000..aa8aeaf --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/HashKey.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of fields and values of an hash by leveraging the + * HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class HashKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'HSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions()); + } + + /** + * {@inheritdoc} + */ + protected function extractNext() + { + $this->position = key($this->elements); + $this->current = array_shift($this->elements); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/Keyspace.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/Keyspace.php new file mode 100644 index 0000000..5d985b9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/Keyspace.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of the keyspace on a Redis instance by leveraging the + * SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class Keyspace extends CursorBasedIterator +{ + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $match = null, $count = null) + { + $this->requiredCommand($client, 'SCAN'); + + parent::__construct($client, $match, $count); + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->scan($this->cursor, $this->getScanOptions()); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/ListKey.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/ListKey.php new file mode 100644 index 0000000..7a6eb47 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/ListKey.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; +use Predis\NotSupportedException; + +/** + * Abstracts the iteration of items stored in a list by leveraging the LRANGE + * command wrapped in a fully-rewindable PHP iterator. + * + * This iterator tries to emulate the behaviour of cursor-based iterators based + * on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due + * to its incremental nature with multiple fetches it can only offer limited + * guarantees on the returned elements because the collection can change several + * times (trimmed, deleted, overwritten) during the iteration process. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/lrange + */ +class ListKey implements \Iterator +{ + protected $client; + protected $count; + protected $key; + + protected $valid; + protected $fetchmore; + protected $elements; + protected $position; + protected $current; + + /** + * @param ClientInterface $client Client connected to Redis. + * @param string $key Redis list key. + * @param int $count Number of items retrieved on each fetch operation. + * + * @throws \InvalidArgumentException + */ + public function __construct(ClientInterface $client, $key, $count = 10) + { + $this->requiredCommand($client, 'LRANGE'); + + if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) { + throw new \InvalidArgumentException('The $count argument must be a positive integer.'); + } + + $this->client = $client; + $this->key = $key; + $this->count = $count; + + $this->reset(); + } + + /** + * Ensures that the client instance supports the specified Redis command + * required to fetch elements from the server to perform the iteration. + * + * @param ClientInterface $client Client connected to Redis. + * @param string $commandID Command ID. + * + * @throws NotSupportedException + */ + protected function requiredCommand(ClientInterface $client, $commandID) + { + if (!$client->getProfile()->supportsCommand($commandID)) { + throw new NotSupportedException("The current profile does not support '$commandID'."); + } + } + + /** + * Resets the inner state of the iterator. + */ + protected function reset() + { + $this->valid = true; + $this->fetchmore = true; + $this->elements = array(); + $this->position = -1; + $this->current = null; + } + + /** + * Fetches a new set of elements from the remote collection, effectively + * advancing the iteration process. + * + * @return array + */ + protected function executeCommand() + { + return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count); + } + + /** + * Populates the local buffer of elements fetched from the server during the + * iteration. + */ + protected function fetch() + { + $elements = $this->executeCommand(); + + if (count($elements) < $this->count) { + $this->fetchmore = false; + } + + $this->elements = $elements; + } + + /** + * Extracts next values for key() and current(). + */ + protected function extractNext() + { + ++$this->position; + $this->current = array_shift($this->elements); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + if (!$this->elements && $this->fetchmore) { + $this->fetch(); + } + + if ($this->elements) { + $this->extractNext(); + } else { + $this->valid = false; + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->valid; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/SetKey.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/SetKey.php new file mode 100644 index 0000000..bf25439 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/SetKey.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of members stored in a set by leveraging the SSCAN + * command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class SetKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'SSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions()); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/SortedSetKey.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/SortedSetKey.php new file mode 100644 index 0000000..e2f1789 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Collection/Iterator/SortedSetKey.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Collection\Iterator; + +use Predis\ClientInterface; + +/** + * Abstracts the iteration of members stored in a sorted set by leveraging the + * ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator. + * + * @author Daniele Alessandri + * + * @link http://redis.io/commands/scan + */ +class SortedSetKey extends CursorBasedIterator +{ + protected $key; + + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client, $key, $match = null, $count = null) + { + $this->requiredCommand($client, 'ZSCAN'); + + parent::__construct($client, $match, $count); + + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + protected function executeCommand() + { + return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions()); + } + + /** + * {@inheritdoc} + */ + protected function extractNext() + { + if ($kv = each($this->elements)) { + $this->position = $kv[0]; + $this->current = $kv[1]; + + unset($this->elements[$this->position]); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Command.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Command.php new file mode 100644 index 0000000..bb538e7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Command.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Base class for Redis commands. + * + * @author Daniele Alessandri + */ +abstract class Command implements CommandInterface +{ + private $slot; + private $arguments = array(); + + /** + * Returns a filtered array of the arguments. + * + * @param array $arguments List of arguments. + * + * @return array + */ + protected function filterArguments(array $arguments) + { + return $arguments; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $this->arguments = $this->filterArguments($arguments); + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function setRawArguments(array $arguments) + { + $this->arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($index) + { + if (isset($this->arguments[$index])) { + return $this->arguments[$index]; + } + } + + /** + * {@inheritdoc} + */ + public function setSlot($slot) + { + $this->slot = $slot; + } + + /** + * {@inheritdoc} + */ + public function getSlot() + { + if (isset($this->slot)) { + return $this->slot; + } + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data; + } + + /** + * Normalizes the arguments array passed to a Redis command. + * + * @param array $arguments Arguments for a command. + * + * @return array + */ + public static function normalizeArguments(array $arguments) + { + if (count($arguments) === 1 && is_array($arguments[0])) { + return $arguments[0]; + } + + return $arguments; + } + + /** + * Normalizes the arguments array passed to a variadic Redis command. + * + * @param array $arguments Arguments for a command. + * + * @return array + */ + public static function normalizeVariadic(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + return array_merge(array($arguments[0]), $arguments[1]); + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/CommandInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/CommandInterface.php new file mode 100644 index 0000000..9f349e1 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/CommandInterface.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Defines an abstraction representing a Redis command. + * + * @author Daniele Alessandri + */ +interface CommandInterface +{ + /** + * Returns the ID of the Redis command. By convention, command identifiers + * must always be uppercase. + * + * @return string + */ + public function getId(); + + /** + * Assign the specified slot to the command for clustering distribution. + * + * @param int $slot Slot ID. + */ + public function setSlot($slot); + + /** + * Returns the assigned slot of the command for clustering distribution. + * + * @return int|null + */ + public function getSlot(); + + /** + * Sets the arguments for the command. + * + * @param array $arguments List of arguments. + */ + public function setArguments(array $arguments); + + /** + * Sets the raw arguments for the command without processing them. + * + * @param array $arguments List of arguments. + */ + public function setRawArguments(array $arguments); + + /** + * Gets the arguments of the command. + * + * @return array + */ + public function getArguments(); + + /** + * Gets the argument of the command at the specified index. + * + * @param int $index Index of the desired argument. + * + * @return mixed|null + */ + public function getArgument($index); + + /** + * Parses a raw response and returns a PHP object. + * + * @param string $data Binary string containing the whole response. + * + * @return mixed + */ + public function parseResponse($data); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionAuth.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionAuth.php new file mode 100644 index 0000000..c8c9ded --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionAuth.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/auth + * + * @author Daniele Alessandri + */ +class ConnectionAuth extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'AUTH'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionEcho.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionEcho.php new file mode 100644 index 0000000..fd49609 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionEcho.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/echo + * + * @author Daniele Alessandri + */ +class ConnectionEcho extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ECHO'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionPing.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionPing.php new file mode 100644 index 0000000..fa9d734 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionPing.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/ping + * + * @author Daniele Alessandri + */ +class ConnectionPing extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PING'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionQuit.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionQuit.php new file mode 100644 index 0000000..e59e31e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionQuit.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/quit + * + * @author Daniele Alessandri + */ +class ConnectionQuit extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'QUIT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionSelect.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionSelect.php new file mode 100644 index 0000000..1da8256 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ConnectionSelect.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/select + * + * @author Daniele Alessandri + */ +class ConnectionSelect extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SELECT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashDelete.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashDelete.php new file mode 100644 index 0000000..d5d4c38 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashDelete.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hdel + * + * @author Daniele Alessandri + */ +class HashDelete extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HDEL'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashExists.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashExists.php new file mode 100644 index 0000000..a2c69b9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashExists.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hexists + * + * @author Daniele Alessandri + */ +class HashExists extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HEXISTS'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGet.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGet.php new file mode 100644 index 0000000..20f33da --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGet.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hget + * + * @author Daniele Alessandri + */ +class HashGet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HGET'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGetAll.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGetAll.php new file mode 100644 index 0000000..d698675 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGetAll.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hgetall + * + * @author Daniele Alessandri + */ +class HashGetAll extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HGETALL'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $result = array(); + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGetMultiple.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGetMultiple.php new file mode 100644 index 0000000..820ce95 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashGetMultiple.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hmget + * + * @author Daniele Alessandri + */ +class HashGetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HMGET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashIncrementBy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashIncrementBy.php new file mode 100644 index 0000000..a37359f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashIncrementBy.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hincrby + * + * @author Daniele Alessandri + */ +class HashIncrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HINCRBY'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashIncrementByFloat.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashIncrementByFloat.php new file mode 100644 index 0000000..bce9714 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashIncrementByFloat.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hincrbyfloat + * + * @author Daniele Alessandri + */ +class HashIncrementByFloat extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HINCRBYFLOAT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashKeys.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashKeys.php new file mode 100644 index 0000000..2826602 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashKeys.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hkeys + * + * @author Daniele Alessandri + */ +class HashKeys extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HKEYS'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashLength.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashLength.php new file mode 100644 index 0000000..d70926f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashLength.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hlen + * + * @author Daniele Alessandri + */ +class HashLength extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HLEN'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashScan.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashScan.php new file mode 100644 index 0000000..afde74e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashScan.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hscan + * + * @author Daniele Alessandri + */ +class HashScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $fields = $data[1]; + $result = array(); + + for ($i = 0; $i < count($fields); ++$i) { + $result[$fields[$i]] = $fields[++$i]; + } + + $data[1] = $result; + } + + return $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSet.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSet.php new file mode 100644 index 0000000..d3154a9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSet.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hset + * + * @author Daniele Alessandri + */ +class HashSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSET'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSetMultiple.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSetMultiple.php new file mode 100644 index 0000000..6069e2a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSetMultiple.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hmset + * + * @author Daniele Alessandri + */ +class HashSetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HMSET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $flattenedKVs = array($arguments[0]); + $args = $arguments[1]; + + foreach ($args as $k => $v) { + $flattenedKVs[] = $k; + $flattenedKVs[] = $v; + } + + return $flattenedKVs; + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSetPreserve.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSetPreserve.php new file mode 100644 index 0000000..582100d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashSetPreserve.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hsetnx + * + * @author Daniele Alessandri + */ +class HashSetPreserve extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSETNX'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashStringLength.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashStringLength.php new file mode 100644 index 0000000..7cfda80 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashStringLength.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hstrlen + * + * @author Daniele Alessandri + */ +class HashStringLength extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HSTRLEN'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashValues.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashValues.php new file mode 100644 index 0000000..0a5ea5f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HashValues.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/hvals + * + * @author Daniele Alessandri + */ +class HashValues extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'HVALS'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogAdd.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogAdd.php new file mode 100644 index 0000000..18d2bd7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogAdd.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pfadd + * + * @author Daniele Alessandri + */ +class HyperLogLogAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PFADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogCount.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogCount.php new file mode 100644 index 0000000..0afe542 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogCount.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pfcount + * + * @author Daniele Alessandri + */ +class HyperLogLogCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PFCOUNT'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogMerge.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogMerge.php new file mode 100644 index 0000000..c160be5 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/HyperLogLogMerge.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pfmerge + * + * @author Daniele Alessandri + */ +class HyperLogLogMerge extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PFMERGE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyDelete.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyDelete.php new file mode 100644 index 0000000..89bdfdb --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyDelete.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/del + * + * @author Daniele Alessandri + */ +class KeyDelete extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DEL'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyDump.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyDump.php new file mode 100644 index 0000000..6d9c488 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyDump.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/dump + * + * @author Daniele Alessandri + */ +class KeyDump extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DUMP'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExists.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExists.php new file mode 100644 index 0000000..5196ca1 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExists.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/exists + * + * @author Daniele Alessandri + */ +class KeyExists extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXISTS'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExpire.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExpire.php new file mode 100644 index 0000000..fd7c9c8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExpire.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/expire + * + * @author Daniele Alessandri + */ +class KeyExpire extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXPIRE'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExpireAt.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExpireAt.php new file mode 100644 index 0000000..e2fe7ae --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyExpireAt.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/expireat + * + * @author Daniele Alessandri + */ +class KeyExpireAt extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXPIREAT'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyKeys.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyKeys.php new file mode 100644 index 0000000..6d74c40 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyKeys.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/keys + * + * @author Daniele Alessandri + */ +class KeyKeys extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'KEYS'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyMigrate.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyMigrate.php new file mode 100644 index 0000000..3324ef9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyMigrate.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/migrate + * + * @author Daniele Alessandri + */ +class KeyMigrate extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MIGRATE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (is_array(end($arguments))) { + foreach (array_pop($arguments) as $modifier => $value) { + $modifier = strtoupper($modifier); + + if ($modifier === 'COPY' && $value == true) { + $arguments[] = $modifier; + } + + if ($modifier === 'REPLACE' && $value == true) { + $arguments[] = $modifier; + } + } + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyMove.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyMove.php new file mode 100644 index 0000000..8f1ab2a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyMove.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/move + * + * @author Daniele Alessandri + */ +class KeyMove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MOVE'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPersist.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPersist.php new file mode 100644 index 0000000..e772955 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPersist.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/persist + * + * @author Daniele Alessandri + */ +class KeyPersist extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PERSIST'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseExpire.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseExpire.php new file mode 100644 index 0000000..258ec47 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseExpire.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pexpire + * + * @author Daniele Alessandri + */ +class KeyPreciseExpire extends KeyExpire +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PEXPIRE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseExpireAt.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseExpireAt.php new file mode 100644 index 0000000..e419218 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseExpireAt.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pexpireat + * + * @author Daniele Alessandri + */ +class KeyPreciseExpireAt extends KeyExpireAt +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PEXPIREAT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseTimeToLive.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseTimeToLive.php new file mode 100644 index 0000000..bdcd34b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyPreciseTimeToLive.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pttl + * + * @author Daniele Alessandri + */ +class KeyPreciseTimeToLive extends KeyTimeToLive +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PTTL'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRandom.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRandom.php new file mode 100644 index 0000000..b208b2d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRandom.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/randomkey + * + * @author Daniele Alessandri + */ +class KeyRandom extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RANDOMKEY'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data !== '' ? $data : null; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRename.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRename.php new file mode 100644 index 0000000..82e44fb --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRename.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/rename + * + * @author Daniele Alessandri + */ +class KeyRename extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RENAME'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRenamePreserve.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRenamePreserve.php new file mode 100644 index 0000000..773ece6 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRenamePreserve.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/renamenx + * + * @author Daniele Alessandri + */ +class KeyRenamePreserve extends KeyRename +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RENAMENX'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRestore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRestore.php new file mode 100644 index 0000000..a5b0b2d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyRestore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/restore + * + * @author Daniele Alessandri + */ +class KeyRestore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RESTORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyScan.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyScan.php new file mode 100644 index 0000000..05f5bb3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyScan.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/scan + * + * @author Daniele Alessandri + */ +class KeyScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeySort.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeySort.php new file mode 100644 index 0000000..fd449f1 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeySort.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sort + * + * @author Daniele Alessandri + */ +class KeySort extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SORT'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 1) { + return $arguments; + } + + $query = array($arguments[0]); + $sortParams = array_change_key_case($arguments[1], CASE_UPPER); + + if (isset($sortParams['BY'])) { + $query[] = 'BY'; + $query[] = $sortParams['BY']; + } + + if (isset($sortParams['GET'])) { + $getargs = $sortParams['GET']; + + if (is_array($getargs)) { + foreach ($getargs as $getarg) { + $query[] = 'GET'; + $query[] = $getarg; + } + } else { + $query[] = 'GET'; + $query[] = $getargs; + } + } + + if (isset($sortParams['LIMIT']) && + is_array($sortParams['LIMIT']) && + count($sortParams['LIMIT']) == 2) { + $query[] = 'LIMIT'; + $query[] = $sortParams['LIMIT'][0]; + $query[] = $sortParams['LIMIT'][1]; + } + + if (isset($sortParams['SORT'])) { + $query[] = strtoupper($sortParams['SORT']); + } + + if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) { + $query[] = 'ALPHA'; + } + + if (isset($sortParams['STORE'])) { + $query[] = 'STORE'; + $query[] = $sortParams['STORE']; + } + + return $query; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyTimeToLive.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyTimeToLive.php new file mode 100644 index 0000000..67697a6 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyTimeToLive.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/ttl + * + * @author Daniele Alessandri + */ +class KeyTimeToLive extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'TTL'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyType.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyType.php new file mode 100644 index 0000000..f4f06e4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/KeyType.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/type + * + * @author Daniele Alessandri + */ +class KeyType extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'TYPE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListIndex.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListIndex.php new file mode 100644 index 0000000..27c64be --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListIndex.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lindex + * + * @author Daniele Alessandri + */ +class ListIndex extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LINDEX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListInsert.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListInsert.php new file mode 100644 index 0000000..7d53d11 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListInsert.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/linsert + * + * @author Daniele Alessandri + */ +class ListInsert extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LINSERT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListLength.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListLength.php new file mode 100644 index 0000000..6495beb --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListLength.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/llen + * + * @author Daniele Alessandri + */ +class ListLength extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LLEN'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopFirst.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopFirst.php new file mode 100644 index 0000000..84d5d67 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopFirst.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lpop + * + * @author Daniele Alessandri + */ +class ListPopFirst extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LPOP'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopFirstBlocking.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopFirstBlocking.php new file mode 100644 index 0000000..7dc7c00 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopFirstBlocking.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/blpop + * + * @author Daniele Alessandri + */ +class ListPopFirstBlocking extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BLPOP'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[0])) { + list($arguments, $timeout) = $arguments; + array_push($arguments, $timeout); + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLast.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLast.php new file mode 100644 index 0000000..9e92db5 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLast.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/rpop + * + * @author Daniele Alessandri + */ +class ListPopLast extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPOP'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastBlocking.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastBlocking.php new file mode 100644 index 0000000..781eb91 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastBlocking.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/brpop + * + * @author Daniele Alessandri + */ +class ListPopLastBlocking extends ListPopFirstBlocking +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BRPOP'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastPushHead.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastPushHead.php new file mode 100644 index 0000000..f430eb2 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastPushHead.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/rpoplpush + * + * @author Daniele Alessandri + */ +class ListPopLastPushHead extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPOPLPUSH'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastPushHeadBlocking.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastPushHeadBlocking.php new file mode 100644 index 0000000..ee9c93c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPopLastPushHeadBlocking.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/brpoplpush + * + * @author Daniele Alessandri + */ +class ListPopLastPushHeadBlocking extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BRPOPLPUSH'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushHead.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushHead.php new file mode 100644 index 0000000..74bf7c4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushHead.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lpush + * + * @author Daniele Alessandri + */ +class ListPushHead extends ListPushTail +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LPUSH'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushHeadX.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushHeadX.php new file mode 100644 index 0000000..8e136b8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushHeadX.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lpushx + * + * @author Daniele Alessandri + */ +class ListPushHeadX extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LPUSHX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushTail.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushTail.php new file mode 100644 index 0000000..f2a057c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushTail.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/rpush + * + * @author Daniele Alessandri + */ +class ListPushTail extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPUSH'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushTailX.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushTailX.php new file mode 100644 index 0000000..1af3645 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListPushTailX.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/rpushx + * + * @author Daniele Alessandri + */ +class ListPushTailX extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'RPUSHX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListRange.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListRange.php new file mode 100644 index 0000000..32a21a6 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListRange.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lrange + * + * @author Daniele Alessandri + */ +class ListRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LRANGE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListRemove.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListRemove.php new file mode 100644 index 0000000..c580089 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListRemove.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lrem + * + * @author Daniele Alessandri + */ +class ListRemove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LREM'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListSet.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListSet.php new file mode 100644 index 0000000..5e59864 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListSet.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lset + * + * @author Daniele Alessandri + */ +class ListSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LSET'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListTrim.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListTrim.php new file mode 100644 index 0000000..1931418 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ListTrim.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/ltrim + * + * @author Daniele Alessandri + */ +class ListTrim extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LTRIM'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PrefixableCommandInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PrefixableCommandInterface.php new file mode 100644 index 0000000..6d54554 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PrefixableCommandInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Defines a command whose keys can be prefixed. + * + * @author Daniele Alessandri + */ +interface PrefixableCommandInterface extends CommandInterface +{ + /** + * Prefixes all the keys found in the arguments of the command. + * + * @param string $prefix String used to prefix the keys. + */ + public function prefixKeys($prefix); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/KeyPrefixProcessor.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/KeyPrefixProcessor.php new file mode 100644 index 0000000..d966d0e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/KeyPrefixProcessor.php @@ -0,0 +1,415 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Processor; + +use Predis\Command\CommandInterface; +use Predis\Command\PrefixableCommandInterface; + +/** + * Command processor capable of prefixing keys stored in the arguments of Redis + * commands supported. + * + * @author Daniele Alessandri + */ +class KeyPrefixProcessor implements ProcessorInterface +{ + private $prefix; + private $commands; + + /** + * @param string $prefix Prefix for the keys. + */ + public function __construct($prefix) + { + $this->prefix = $prefix; + $this->commands = array( + /* ---------------- Redis 1.2 ---------------- */ + 'EXISTS' => 'static::first', + 'DEL' => 'static::all', + 'TYPE' => 'static::first', + 'KEYS' => 'static::first', + 'RENAME' => 'static::all', + 'RENAMENX' => 'static::all', + 'EXPIRE' => 'static::first', + 'EXPIREAT' => 'static::first', + 'TTL' => 'static::first', + 'MOVE' => 'static::first', + 'SORT' => 'static::sort', + 'DUMP' => 'static::first', + 'RESTORE' => 'static::first', + 'SET' => 'static::first', + 'SETNX' => 'static::first', + 'MSET' => 'static::interleaved', + 'MSETNX' => 'static::interleaved', + 'GET' => 'static::first', + 'MGET' => 'static::all', + 'GETSET' => 'static::first', + 'INCR' => 'static::first', + 'INCRBY' => 'static::first', + 'DECR' => 'static::first', + 'DECRBY' => 'static::first', + 'RPUSH' => 'static::first', + 'LPUSH' => 'static::first', + 'LLEN' => 'static::first', + 'LRANGE' => 'static::first', + 'LTRIM' => 'static::first', + 'LINDEX' => 'static::first', + 'LSET' => 'static::first', + 'LREM' => 'static::first', + 'LPOP' => 'static::first', + 'RPOP' => 'static::first', + 'RPOPLPUSH' => 'static::all', + 'SADD' => 'static::first', + 'SREM' => 'static::first', + 'SPOP' => 'static::first', + 'SMOVE' => 'static::skipLast', + 'SCARD' => 'static::first', + 'SISMEMBER' => 'static::first', + 'SINTER' => 'static::all', + 'SINTERSTORE' => 'static::all', + 'SUNION' => 'static::all', + 'SUNIONSTORE' => 'static::all', + 'SDIFF' => 'static::all', + 'SDIFFSTORE' => 'static::all', + 'SMEMBERS' => 'static::first', + 'SRANDMEMBER' => 'static::first', + 'ZADD' => 'static::first', + 'ZINCRBY' => 'static::first', + 'ZREM' => 'static::first', + 'ZRANGE' => 'static::first', + 'ZREVRANGE' => 'static::first', + 'ZRANGEBYSCORE' => 'static::first', + 'ZCARD' => 'static::first', + 'ZSCORE' => 'static::first', + 'ZREMRANGEBYSCORE' => 'static::first', + /* ---------------- Redis 2.0 ---------------- */ + 'SETEX' => 'static::first', + 'APPEND' => 'static::first', + 'SUBSTR' => 'static::first', + 'BLPOP' => 'static::skipLast', + 'BRPOP' => 'static::skipLast', + 'ZUNIONSTORE' => 'static::zsetStore', + 'ZINTERSTORE' => 'static::zsetStore', + 'ZCOUNT' => 'static::first', + 'ZRANK' => 'static::first', + 'ZREVRANK' => 'static::first', + 'ZREMRANGEBYRANK' => 'static::first', + 'HSET' => 'static::first', + 'HSETNX' => 'static::first', + 'HMSET' => 'static::first', + 'HINCRBY' => 'static::first', + 'HGET' => 'static::first', + 'HMGET' => 'static::first', + 'HDEL' => 'static::first', + 'HEXISTS' => 'static::first', + 'HLEN' => 'static::first', + 'HKEYS' => 'static::first', + 'HVALS' => 'static::first', + 'HGETALL' => 'static::first', + 'SUBSCRIBE' => 'static::all', + 'UNSUBSCRIBE' => 'static::all', + 'PSUBSCRIBE' => 'static::all', + 'PUNSUBSCRIBE' => 'static::all', + 'PUBLISH' => 'static::first', + /* ---------------- Redis 2.2 ---------------- */ + 'PERSIST' => 'static::first', + 'STRLEN' => 'static::first', + 'SETRANGE' => 'static::first', + 'GETRANGE' => 'static::first', + 'SETBIT' => 'static::first', + 'GETBIT' => 'static::first', + 'RPUSHX' => 'static::first', + 'LPUSHX' => 'static::first', + 'LINSERT' => 'static::first', + 'BRPOPLPUSH' => 'static::skipLast', + 'ZREVRANGEBYSCORE' => 'static::first', + 'WATCH' => 'static::all', + /* ---------------- Redis 2.6 ---------------- */ + 'PTTL' => 'static::first', + 'PEXPIRE' => 'static::first', + 'PEXPIREAT' => 'static::first', + 'PSETEX' => 'static::first', + 'INCRBYFLOAT' => 'static::first', + 'BITOP' => 'static::skipFirst', + 'BITCOUNT' => 'static::first', + 'HINCRBYFLOAT' => 'static::first', + 'EVAL' => 'static::evalKeys', + 'EVALSHA' => 'static::evalKeys', + 'MIGRATE' => 'static::migrate', + /* ---------------- Redis 2.8 ---------------- */ + 'SSCAN' => 'static::first', + 'ZSCAN' => 'static::first', + 'HSCAN' => 'static::first', + 'PFADD' => 'static::first', + 'PFCOUNT' => 'static::all', + 'PFMERGE' => 'static::all', + 'ZLEXCOUNT' => 'static::first', + 'ZRANGEBYLEX' => 'static::first', + 'ZREMRANGEBYLEX' => 'static::first', + 'ZREVRANGEBYLEX' => 'static::first', + 'BITPOS' => 'static::first', + /* ---------------- Redis 3.2 ---------------- */ + 'HSTRLEN' => 'static::first', + ); + } + + /** + * Sets a prefix that is applied to all the keys. + * + * @param string $prefix Prefix for the keys. + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Gets the current prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * {@inheritdoc} + */ + public function process(CommandInterface $command) + { + if ($command instanceof PrefixableCommandInterface) { + $command->prefixKeys($this->prefix); + } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) { + call_user_func($this->commands[$commandID], $command, $this->prefix); + } + } + + /** + * Sets an handler for the specified command ID. + * + * The callback signature must have 2 parameters of the following types: + * + * - Predis\Command\CommandInterface (command instance) + * - String (prefix) + * + * When the callback argument is omitted or NULL, the previously + * associated handler for the specified command ID is removed. + * + * @param string $commandID The ID of the command to be handled. + * @param mixed $callback A valid callable object or NULL. + * + * @throws \InvalidArgumentException + */ + public function setCommandHandler($commandID, $callback = null) + { + $commandID = strtoupper($commandID); + + if (!isset($callback)) { + unset($this->commands[$commandID]); + + return; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException( + 'Callback must be a valid callable object or NULL' + ); + } + + $this->commands[$commandID] = $callback; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->getPrefix(); + } + + /** + * Applies the specified prefix only the first argument. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function first(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function all(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + foreach ($arguments as &$key) { + $key = "$prefix$key"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix only to even arguments in the list. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function interleaved(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 0; $i < $length; $i += 2) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments but the first one. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipFirst(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 1; $i < $length; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to all the arguments but the last one. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipLast(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $length = count($arguments); + + for ($i = 0; $i < $length - 1; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of a SORT command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function sort(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + + if (($count = count($arguments)) > 1) { + for ($i = 1; $i < $count; ++$i) { + switch ($arguments[$i]) { + case 'BY': + case 'STORE': + $arguments[$i] = "$prefix{$arguments[++$i]}"; + break; + + case 'GET': + $value = $arguments[++$i]; + if ($value !== '#') { + $arguments[$i] = "$prefix$value"; + } + break; + + case 'LIMIT'; + $i += 2; + break; + } + } + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of an EVAL-based command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function evalKeys(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + for ($i = 2; $i < $arguments[1] + 2; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function zsetStore(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $length = ((int) $arguments[1]) + 2; + + for ($i = 2; $i < $length; ++$i) { + $arguments[$i] = "$prefix{$arguments[$i]}"; + } + + $command->setRawArguments($arguments); + } + } + + /** + * Applies the specified prefix to the key of a MIGRATE command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function migrate(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[2] = "$prefix{$arguments[2]}"; + $command->setRawArguments($arguments); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/ProcessorChain.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/ProcessorChain.php new file mode 100644 index 0000000..0a4768b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/ProcessorChain.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Processor; + +use Predis\Command\CommandInterface; + +/** + * Default implementation of a command processors chain. + * + * @author Daniele Alessandri + */ +class ProcessorChain implements \ArrayAccess, ProcessorInterface +{ + private $processors = array(); + + /** + * @param array $processors List of instances of ProcessorInterface. + */ + public function __construct($processors = array()) + { + foreach ($processors as $processor) { + $this->add($processor); + } + } + + /** + * {@inheritdoc} + */ + public function add(ProcessorInterface $processor) + { + $this->processors[] = $processor; + } + + /** + * {@inheritdoc} + */ + public function remove(ProcessorInterface $processor) + { + if (false !== $index = array_search($processor, $this->processors, true)) { + unset($this[$index]); + } + } + + /** + * {@inheritdoc} + */ + public function process(CommandInterface $command) + { + for ($i = 0; $i < $count = count($this->processors); ++$i) { + $this->processors[$i]->process($command); + } + } + + /** + * {@inheritdoc} + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Returns an iterator over the list of command processor in the chain. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->processors); + } + + /** + * Returns the number of command processors in the chain. + * + * @return int + */ + public function count() + { + return count($this->processors); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($index) + { + return isset($this->processors[$index]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($index) + { + return $this->processors[$index]; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($index, $processor) + { + if (!$processor instanceof ProcessorInterface) { + throw new \InvalidArgumentException( + 'A processor chain accepts only instances of '. + "'Predis\Command\Processor\ProcessorInterface'." + ); + } + + $this->processors[$index] = $processor; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($index) + { + unset($this->processors[$index]); + $this->processors = array_values($this->processors); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/ProcessorInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/ProcessorInterface.php new file mode 100644 index 0000000..2f91058 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/Processor/ProcessorInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Processor; + +use Predis\Command\CommandInterface; + +/** + * A command processor processes Redis commands before they are sent to Redis. + * + * @author Daniele Alessandri + */ +interface ProcessorInterface +{ + /** + * Processes the given Redis command. + * + * @param CommandInterface $command Command instance. + */ + public function process(CommandInterface $command); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubPublish.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubPublish.php new file mode 100644 index 0000000..55508f8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubPublish.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/publish + * + * @author Daniele Alessandri + */ +class PubSubPublish extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PUBLISH'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubPubsub.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubPubsub.php new file mode 100644 index 0000000..8cf8129 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubPubsub.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/pubsub + * + * @author Daniele Alessandri + */ +class PubSubPubsub extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PUBSUB'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + switch (strtolower($this->getArgument(0))) { + case 'numsub': + return self::processNumsub($data); + + default: + return $data; + } + } + + /** + * Returns the processed response to PUBSUB NUMSUB. + * + * @param array $channels List of channels + * + * @return array + */ + protected static function processNumsub(array $channels) + { + $processed = array(); + $count = count($channels); + + for ($i = 0; $i < $count; ++$i) { + $processed[$channels[$i]] = $channels[++$i]; + } + + return $processed; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubSubscribe.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubSubscribe.php new file mode 100644 index 0000000..e477b31 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubSubscribe.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/subscribe + * + * @author Daniele Alessandri + */ +class PubSubSubscribe extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUBSCRIBE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubSubscribeByPattern.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubSubscribeByPattern.php new file mode 100644 index 0000000..0118280 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubSubscribeByPattern.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/psubscribe + * + * @author Daniele Alessandri + */ +class PubSubSubscribeByPattern extends PubSubSubscribe +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PSUBSCRIBE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubUnsubscribe.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubUnsubscribe.php new file mode 100644 index 0000000..d57c3ac --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubUnsubscribe.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/unsubscribe + * + * @author Daniele Alessandri + */ +class PubSubUnsubscribe extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'UNSUBSCRIBE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubUnsubscribeByPattern.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubUnsubscribeByPattern.php new file mode 100644 index 0000000..4d76508 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/PubSubUnsubscribeByPattern.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/punsubscribe + * + * @author Daniele Alessandri + */ +class PubSubUnsubscribeByPattern extends PubSubUnsubscribe +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PUNSUBSCRIBE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/RawCommand.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/RawCommand.php new file mode 100644 index 0000000..2dd48ca --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/RawCommand.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Class for generic "anonymous" Redis commands. + * + * This command class does not filter input arguments or parse responses, but + * can be used to leverage the standard Predis API to execute any command simply + * by providing the needed arguments following the command signature as defined + * by Redis in its documentation. + * + * @author Daniele Alessandri + */ +class RawCommand implements CommandInterface +{ + private $slot; + private $commandID; + private $arguments; + + /** + * @param array $arguments Command ID and its arguments. + * + * @throws \InvalidArgumentException + */ + public function __construct(array $arguments) + { + if (!$arguments) { + throw new \InvalidArgumentException( + 'The arguments array must contain at least the command ID.' + ); + } + + $this->commandID = strtoupper(array_shift($arguments)); + $this->arguments = $arguments; + } + + /** + * Creates a new raw command using a variadic method. + * + * @param string $commandID Redis command ID. + * @param string ... Arguments list for the command. + * + * @return CommandInterface + */ + public static function create($commandID /* [ $arg, ... */) + { + $arguments = func_get_args(); + $command = new self($arguments); + + return $command; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->commandID; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + unset($this->slot); + } + + /** + * {@inheritdoc} + */ + public function setRawArguments(array $arguments) + { + $this->setArguments($arguments); + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getArgument($index) + { + if (isset($this->arguments[$index])) { + return $this->arguments[$index]; + } + } + + /** + * {@inheritdoc} + */ + public function setSlot($slot) + { + $this->slot = $slot; + } + + /** + * {@inheritdoc} + */ + public function getSlot() + { + if (isset($this->slot)) { + return $this->slot; + } + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ScriptCommand.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ScriptCommand.php new file mode 100644 index 0000000..a30bc1d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ScriptCommand.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * Base class used to implement an higher level abstraction for commands based + * on Lua scripting with EVAL and EVALSHA. + * + * @link http://redis.io/commands/eval + * + * @author Daniele Alessandri + */ +abstract class ScriptCommand extends ServerEvalSHA +{ + /** + * Gets the body of a Lua script. + * + * @return string + */ + abstract public function getScript(); + + /** + * Specifies the number of arguments that should be considered as keys. + * + * The default behaviour for the base class is to return 0 to indicate that + * all the elements of the arguments array should be considered as keys, but + * subclasses can enforce a static number of keys. + * + * @return int + */ + protected function getKeysCount() + { + return 0; + } + + /** + * Returns the elements from the arguments that are identified as keys. + * + * @return array + */ + public function getKeys() + { + return array_slice($this->getArguments(), 2, $this->getKeysCount()); + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (($numkeys = $this->getKeysCount()) && $numkeys < 0) { + $numkeys = count($arguments) + $numkeys; + } + + return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments); + } + + /** + * @return array + */ + public function getEvalArguments() + { + $arguments = $this->getArguments(); + $arguments[0] = $this->getScript(); + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerBackgroundRewriteAOF.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerBackgroundRewriteAOF.php new file mode 100644 index 0000000..c66a294 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerBackgroundRewriteAOF.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/bgrewriteaof + * + * @author Daniele Alessandri + */ +class ServerBackgroundRewriteAOF extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BGREWRITEAOF'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data == 'Background append only file rewriting started'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerBackgroundSave.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerBackgroundSave.php new file mode 100644 index 0000000..4bf67ef --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerBackgroundSave.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/bgsave + * + * @author Daniele Alessandri + */ +class ServerBackgroundSave extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BGSAVE'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return $data === 'Background saving started' ? true : $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerClient.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerClient.php new file mode 100644 index 0000000..d00ebbf --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerClient.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/client-list + * @link http://redis.io/commands/client-kill + * @link http://redis.io/commands/client-getname + * @link http://redis.io/commands/client-setname + * + * @author Daniele Alessandri + */ +class ServerClient extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'CLIENT'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $args = array_change_key_case($this->getArguments(), CASE_UPPER); + + switch (strtoupper($args[0])) { + case 'LIST': + return $this->parseClientList($data); + case 'KILL': + case 'GETNAME': + case 'SETNAME': + default: + return $data; + } + } + + /** + * Parses the response to CLIENT LIST and returns a structured list. + * + * @param string $data Response buffer. + * + * @return array + */ + protected function parseClientList($data) + { + $clients = array(); + + foreach (explode("\n", $data, -1) as $clientData) { + $client = array(); + + foreach (explode(' ', $clientData) as $kv) { + @list($k, $v) = explode('=', $kv); + $client[$k] = $v; + } + + $clients[] = $client; + } + + return $clients; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerCommand.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerCommand.php new file mode 100644 index 0000000..e9b3393 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerCommand.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/command + * + * @author Daniele Alessandri + */ +class ServerCommand extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'COMMAND'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerConfig.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerConfig.php new file mode 100644 index 0000000..81e497a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerConfig.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/config-set + * @link http://redis.io/commands/config-get + * @link http://redis.io/commands/config-resetstat + * @link http://redis.io/commands/config-rewrite + * + * @author Daniele Alessandri + */ +class ServerConfig extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'CONFIG'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $result = array(); + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } + + return $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerDatabaseSize.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerDatabaseSize.php new file mode 100644 index 0000000..6bc8972 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerDatabaseSize.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/dbsize + * + * @author Daniele Alessandri + */ +class ServerDatabaseSize extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DBSIZE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerEval.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerEval.php new file mode 100644 index 0000000..f5eefd8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerEval.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/eval + * + * @author Daniele Alessandri + */ +class ServerEval extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EVAL'; + } + + /** + * Calculates the SHA1 hash of the body of the script. + * + * @return string SHA1 hash. + */ + public function getScriptHash() + { + return sha1($this->getArgument(0)); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerEvalSHA.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerEvalSHA.php new file mode 100644 index 0000000..520a8e9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerEvalSHA.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/evalsha + * + * @author Daniele Alessandri + */ +class ServerEvalSHA extends ServerEval +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EVALSHA'; + } + + /** + * Returns the SHA1 hash of the body of the script. + * + * @return string SHA1 hash. + */ + public function getScriptHash() + { + return $this->getArgument(0); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerFlushAll.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerFlushAll.php new file mode 100644 index 0000000..c35b2ad --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerFlushAll.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/flushall + * + * @author Daniele Alessandri + */ +class ServerFlushAll extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'FLUSHALL'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerFlushDatabase.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerFlushDatabase.php new file mode 100644 index 0000000..3da6b32 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerFlushDatabase.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/flushdb + * + * @author Daniele Alessandri + */ +class ServerFlushDatabase extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'FLUSHDB'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerInfo.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerInfo.php new file mode 100644 index 0000000..96d6ada --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerInfo.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/info + * + * @author Daniele Alessandri + */ +class ServerInfo extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INFO'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $info = array(); + $infoLines = preg_split('/\r?\n/', $data); + + foreach ($infoLines as $row) { + if (strpos($row, ':') === false) { + continue; + } + + list($k, $v) = $this->parseRow($row); + $info[$k] = $v; + } + + return $info; + } + + /** + * Parses a single row of the response and returns the key-value pair. + * + * @param string $row Single row of the response. + * + * @return array + */ + protected function parseRow($row) + { + list($k, $v) = explode(':', $row, 2); + + if (preg_match('/^db\d+$/', $k)) { + $v = $this->parseDatabaseStats($v); + } + + return array($k, $v); + } + + /** + * Extracts the statistics of each logical DB from the string buffer. + * + * @param string $str Response buffer. + * + * @return array + */ + protected function parseDatabaseStats($str) + { + $db = array(); + + foreach (explode(',', $str) as $dbvar) { + list($dbvk, $dbvv) = explode('=', $dbvar); + $db[trim($dbvk)] = $dbvv; + } + + return $db; + } + + /** + * Parses the response and extracts the allocation statistics. + * + * @param string $str Response buffer. + * + * @return array + */ + protected function parseAllocationStats($str) + { + $stats = array(); + + foreach (explode(',', $str) as $kv) { + @list($size, $objects, $extra) = explode('=', $kv); + + // hack to prevent incorrect values when parsing the >=256 key + if (isset($extra)) { + $size = ">=$objects"; + $objects = $extra; + } + + $stats[$size] = $objects; + } + + return $stats; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerInfoV26x.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerInfoV26x.php new file mode 100644 index 0000000..90c9b71 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerInfoV26x.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/info + * + * @author Daniele Alessandri + */ +class ServerInfoV26x extends ServerInfo +{ + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if ($data === '') { + return array(); + } + + $info = array(); + + $current = null; + $infoLines = preg_split('/\r?\n/', $data); + + if (isset($infoLines[0]) && $infoLines[0][0] !== '#') { + return parent::parseResponse($data); + } + + foreach ($infoLines as $row) { + if ($row === '') { + continue; + } + + if (preg_match('/^# (\w+)$/', $row, $matches)) { + $info[$matches[1]] = array(); + $current = &$info[$matches[1]]; + continue; + } + + list($k, $v) = $this->parseRow($row); + $current[$k] = $v; + } + + return $info; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerLastSave.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerLastSave.php new file mode 100644 index 0000000..feeb19a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerLastSave.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/lastsave + * + * @author Daniele Alessandri + */ +class ServerLastSave extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'LASTSAVE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerMonitor.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerMonitor.php new file mode 100644 index 0000000..1c3d330 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerMonitor.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/monitor + * + * @author Daniele Alessandri + */ +class ServerMonitor extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MONITOR'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerObject.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerObject.php new file mode 100644 index 0000000..f921701 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerObject.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/object + * + * @author Daniele Alessandri + */ +class ServerObject extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'OBJECT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSave.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSave.php new file mode 100644 index 0000000..addefe2 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSave.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/save + * + * @author Daniele Alessandri + */ +class ServerSave extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SAVE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerScript.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerScript.php new file mode 100644 index 0000000..7a01018 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerScript.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/script + * + * @author Daniele Alessandri + */ +class ServerScript extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SCRIPT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSentinel.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSentinel.php new file mode 100644 index 0000000..c0962db --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSentinel.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/topics/sentinel + * + * @author Daniele Alessandri + */ +class ServerSentinel extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SENTINEL'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + switch (strtolower($this->getArgument(0))) { + case 'masters': + case 'slaves': + return self::processMastersOrSlaves($data); + + default: + return $data; + } + } + + /** + * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES. + * + * @param array $servers List of Redis servers. + * + * @return array + */ + protected static function processMastersOrSlaves(array $servers) + { + foreach ($servers as $idx => $node) { + $processed = array(); + $count = count($node); + + for ($i = 0; $i < $count; ++$i) { + $processed[$node[$i]] = $node[++$i]; + } + + $servers[$idx] = $processed; + } + + return $servers; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerShutdown.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerShutdown.php new file mode 100644 index 0000000..f5b745a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerShutdown.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/shutdown + * + * @author Daniele Alessandri + */ +class ServerShutdown extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SHUTDOWN'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSlaveOf.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSlaveOf.php new file mode 100644 index 0000000..4ff4455 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSlaveOf.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/slaveof + * + * @author Daniele Alessandri + */ +class ServerSlaveOf extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SLAVEOF'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 0 || $arguments[0] === 'NO ONE') { + return array('NO', 'ONE'); + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSlowlog.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSlowlog.php new file mode 100644 index 0000000..137ff59 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerSlowlog.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/slowlog + * + * @author Daniele Alessandri + */ +class ServerSlowlog extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SLOWLOG'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $log = array(); + + foreach ($data as $index => $entry) { + $log[$index] = array( + 'id' => $entry[0], + 'timestamp' => $entry[1], + 'duration' => $entry[2], + 'command' => $entry[3], + ); + } + + return $log; + } + + return $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerTime.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerTime.php new file mode 100644 index 0000000..589f92c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ServerTime.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/time + * + * @author Daniele Alessandri + */ +class ServerTime extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'TIME'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetAdd.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetAdd.php new file mode 100644 index 0000000..c118818 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetAdd.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sadd + * + * @author Daniele Alessandri + */ +class SetAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetCardinality.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetCardinality.php new file mode 100644 index 0000000..a9f959b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetCardinality.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/scard + * + * @author Daniele Alessandri + */ +class SetCardinality extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SCARD'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetDifference.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetDifference.php new file mode 100644 index 0000000..35f23f9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetDifference.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sdiff + * + * @author Daniele Alessandri + */ +class SetDifference extends SetIntersection +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SDIFF'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetDifferenceStore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetDifferenceStore.php new file mode 100644 index 0000000..0cb7815 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetDifferenceStore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sdiffstore + * + * @author Daniele Alessandri + */ +class SetDifferenceStore extends SetIntersectionStore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SDIFFSTORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIntersection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIntersection.php new file mode 100644 index 0000000..d18258f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIntersection.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sinter + * + * @author Daniele Alessandri + */ +class SetIntersection extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SINTER'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIntersectionStore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIntersectionStore.php new file mode 100644 index 0000000..b748618 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIntersectionStore.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sinterstore + * + * @author Daniele Alessandri + */ +class SetIntersectionStore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SINTERSTORE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + return array_merge(array($arguments[0]), $arguments[1]); + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIsMember.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIsMember.php new file mode 100644 index 0000000..1b48490 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetIsMember.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sismember + * + * @author Daniele Alessandri + */ +class SetIsMember extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SISMEMBER'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetMembers.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetMembers.php new file mode 100644 index 0000000..f4076ae --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetMembers.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/smembers + * + * @author Daniele Alessandri + */ +class SetMembers extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SMEMBERS'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetMove.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetMove.php new file mode 100644 index 0000000..72d514b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetMove.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/smove + * + * @author Daniele Alessandri + */ +class SetMove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SMOVE'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetPop.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetPop.php new file mode 100644 index 0000000..b78d3f3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetPop.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/spop + * + * @author Daniele Alessandri + */ +class SetPop extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SPOP'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetRandomMember.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetRandomMember.php new file mode 100644 index 0000000..2cb79a0 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetRandomMember.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/srandmember + * + * @author Daniele Alessandri + */ +class SetRandomMember extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SRANDMEMBER'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetRemove.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetRemove.php new file mode 100644 index 0000000..b34710c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetRemove.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/srem + * + * @author Daniele Alessandri + */ +class SetRemove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SREM'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetScan.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetScan.php new file mode 100644 index 0000000..d42b28d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetScan.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sscan + * + * @author Daniele Alessandri + */ +class SetScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SSCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetUnion.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetUnion.php new file mode 100644 index 0000000..7da842b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetUnion.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sunion + * + * @author Daniele Alessandri + */ +class SetUnion extends SetIntersection +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUNION'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetUnionStore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetUnionStore.php new file mode 100644 index 0000000..eac821a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/SetUnionStore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/sunionstore + * + * @author Daniele Alessandri + */ +class SetUnionStore extends SetIntersectionStore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUNIONSTORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringAppend.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringAppend.php new file mode 100644 index 0000000..dac8b84 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringAppend.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/append + * + * @author Daniele Alessandri + */ +class StringAppend extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'APPEND'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitCount.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitCount.php new file mode 100644 index 0000000..193cce9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitCount.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/bitcount + * + * @author Daniele Alessandri + */ +class StringBitCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITCOUNT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitOp.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitOp.php new file mode 100644 index 0000000..e04ee79 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitOp.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/bitop + * + * @author Daniele Alessandri + */ +class StringBitOp extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITOP'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + list($operation, $destination) = $arguments; + $arguments = $arguments[2]; + array_unshift($arguments, $operation, $destination); + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitPos.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitPos.php new file mode 100644 index 0000000..4295766 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringBitPos.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/bitpos + * + * @author Daniele Alessandri + */ +class StringBitPos extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITPOS'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringDecrement.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringDecrement.php new file mode 100644 index 0000000..aa5808c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringDecrement.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/decr + * + * @author Daniele Alessandri + */ +class StringDecrement extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DECR'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringDecrementBy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringDecrementBy.php new file mode 100644 index 0000000..cbf3e11 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringDecrementBy.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/decrby + * + * @author Daniele Alessandri + */ +class StringDecrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DECRBY'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGet.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGet.php new file mode 100644 index 0000000..138e915 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGet.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/get + * + * @author Daniele Alessandri + */ +class StringGet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GET'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetBit.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetBit.php new file mode 100644 index 0000000..3c5b4f9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetBit.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/getbit + * + * @author Daniele Alessandri + */ +class StringGetBit extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GETBIT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetMultiple.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetMultiple.php new file mode 100644 index 0000000..e340f9c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetMultiple.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/mget + * + * @author Daniele Alessandri + */ +class StringGetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MGET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeArguments($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetRange.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetRange.php new file mode 100644 index 0000000..bb10565 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetRange.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/getrange + * + * @author Daniele Alessandri + */ +class StringGetRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GETRANGE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetSet.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetSet.php new file mode 100644 index 0000000..b68870d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringGetSet.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/getset + * + * @author Daniele Alessandri + */ +class StringGetSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GETSET'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrement.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrement.php new file mode 100644 index 0000000..fa1846e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrement.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/incr + * + * @author Daniele Alessandri + */ +class StringIncrement extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INCR'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrementBy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrementBy.php new file mode 100644 index 0000000..9d8241a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrementBy.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/incrby + * + * @author Daniele Alessandri + */ +class StringIncrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INCRBY'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrementByFloat.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrementByFloat.php new file mode 100644 index 0000000..164a086 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringIncrementByFloat.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/incrbyfloat + * + * @author Daniele Alessandri + */ +class StringIncrementByFloat extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'INCRBYFLOAT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringPreciseSetExpire.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringPreciseSetExpire.php new file mode 100644 index 0000000..2faa954 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringPreciseSetExpire.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/psetex + * + * @author Daniele Alessandri + */ +class StringPreciseSetExpire extends StringSetExpire +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'PSETEX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSet.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSet.php new file mode 100644 index 0000000..b146994 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSet.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/set + * + * @author Daniele Alessandri + */ +class StringSet extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SET'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetBit.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetBit.php new file mode 100644 index 0000000..7933b6b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetBit.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/setbit + * + * @author Daniele Alessandri + */ +class StringSetBit extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETBIT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetExpire.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetExpire.php new file mode 100644 index 0000000..f088170 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetExpire.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/setex + * + * @author Daniele Alessandri + */ +class StringSetExpire extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETEX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetMultiple.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetMultiple.php new file mode 100644 index 0000000..a3c5324 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetMultiple.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/mset + * + * @author Daniele Alessandri + */ +class StringSetMultiple extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MSET'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 1 && is_array($arguments[0])) { + $flattenedKVs = array(); + $args = $arguments[0]; + + foreach ($args as $k => $v) { + $flattenedKVs[] = $k; + $flattenedKVs[] = $v; + } + + return $flattenedKVs; + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetMultiplePreserve.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetMultiplePreserve.php new file mode 100644 index 0000000..f98f1f7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetMultiplePreserve.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/msetnx + * + * @author Daniele Alessandri + */ +class StringSetMultiplePreserve extends StringSetMultiple +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MSETNX'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetPreserve.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetPreserve.php new file mode 100644 index 0000000..726c35c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetPreserve.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/setnx + * + * @author Daniele Alessandri + */ +class StringSetPreserve extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETNX'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + return (bool) $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetRange.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetRange.php new file mode 100644 index 0000000..4d9389f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSetRange.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/setrange + * + * @author Daniele Alessandri + */ +class StringSetRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SETRANGE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringStrlen.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringStrlen.php new file mode 100644 index 0000000..10f492f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringStrlen.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/strlen + * + * @author Daniele Alessandri + */ +class StringStrlen extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'STRLEN'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSubstr.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSubstr.php new file mode 100644 index 0000000..3aab7ad --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/StringSubstr.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/substr + * + * @author Daniele Alessandri + */ +class StringSubstr extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'SUBSTR'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionDiscard.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionDiscard.php new file mode 100644 index 0000000..44aca2b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionDiscard.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/discard + * + * @author Daniele Alessandri + */ +class TransactionDiscard extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'DISCARD'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionExec.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionExec.php new file mode 100644 index 0000000..dbd81aa --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionExec.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/exec + * + * @author Daniele Alessandri + */ +class TransactionExec extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'EXEC'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionMulti.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionMulti.php new file mode 100644 index 0000000..673bf55 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionMulti.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/multi + * + * @author Daniele Alessandri + */ +class TransactionMulti extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'MULTI'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionUnwatch.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionUnwatch.php new file mode 100644 index 0000000..7925554 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionUnwatch.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/unwatch + * + * @author Daniele Alessandri + */ +class TransactionUnwatch extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'UNWATCH'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionWatch.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionWatch.php new file mode 100644 index 0000000..d360780 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/TransactionWatch.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/watch + * + * @author Daniele Alessandri + */ +class TransactionWatch extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'WATCH'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (isset($arguments[0]) && is_array($arguments[0])) { + return $arguments[0]; + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetAdd.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetAdd.php new file mode 100644 index 0000000..55e4729 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetAdd.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zadd + * + * @author Daniele Alessandri + */ +class ZSetAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (is_array(end($arguments))) { + foreach (array_pop($arguments) as $member => $score) { + $arguments[] = $score; + $arguments[] = $member; + } + } + + return $arguments; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetCardinality.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetCardinality.php new file mode 100644 index 0000000..1033200 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetCardinality.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zcard + * + * @author Daniele Alessandri + */ +class ZSetCardinality extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZCARD'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetCount.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetCount.php new file mode 100644 index 0000000..918bd2b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetCount.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zcount + * + * @author Daniele Alessandri + */ +class ZSetCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZCOUNT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetIncrementBy.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetIncrementBy.php new file mode 100644 index 0000000..245a8e0 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetIncrementBy.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zincrby + * + * @author Daniele Alessandri + */ +class ZSetIncrementBy extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZINCRBY'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetIntersectionStore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetIntersectionStore.php new file mode 100644 index 0000000..572a7a3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetIntersectionStore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zinterstore + * + * @author Daniele Alessandri + */ +class ZSetIntersectionStore extends ZSetUnionStore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZINTERSTORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetLexCount.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetLexCount.php new file mode 100644 index 0000000..447b8eb --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetLexCount.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zlexcount + * + * @author Daniele Alessandri + */ +class ZSetLexCount extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZLEXCOUNT'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRange.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRange.php new file mode 100644 index 0000000..ce72c7c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRange.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrange + * + * @author Daniele Alessandri + */ +class ZSetRange extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANGE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 4) { + $lastType = gettype($arguments[3]); + + if ($lastType === 'string' && strtoupper($arguments[3]) === 'WITHSCORES') { + // Used for compatibility with older versions + $arguments[3] = array('WITHSCORES' => true); + $lastType = 'array'; + } + + if ($lastType === 'array') { + $options = $this->prepareOptions(array_pop($arguments)); + + return array_merge($arguments, $options); + } + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (!empty($opts['WITHSCORES'])) { + $finalizedOpts[] = 'WITHSCORES'; + } + + return $finalizedOpts; + } + + /** + * Checks for the presence of the WITHSCORES modifier. + * + * @return bool + */ + protected function withScores() + { + $arguments = $this->getArguments(); + + if (count($arguments) < 4) { + return false; + } + + return strtoupper($arguments[3]) === 'WITHSCORES'; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if ($this->withScores()) { + $result = array(); + + for ($i = 0; $i < count($data); ++$i) { + $result[$data[$i]] = $data[++$i]; + } + + return $result; + } + + return $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRangeByLex.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRangeByLex.php new file mode 100644 index 0000000..9b2991a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRangeByLex.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrangebylex + * + * @author Daniele Alessandri + */ +class ZSetRangeByLex extends ZSetRange +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANGEBYLEX'; + } + + /** + * {@inheritdoc} + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) { + $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER); + + $finalizedOpts[] = 'LIMIT'; + $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0]; + $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1]; + } + + return $finalizedOpts; + } + + /** + * {@inheritdoc} + */ + protected function withScores() + { + return false; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRangeByScore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRangeByScore.php new file mode 100644 index 0000000..961a5bc --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRangeByScore.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrangebyscore + * + * @author Daniele Alessandri + */ +class ZSetRangeByScore extends ZSetRange +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANGEBYSCORE'; + } + + /** + * {@inheritdoc} + */ + protected function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) { + $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER); + + $finalizedOpts[] = 'LIMIT'; + $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0]; + $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1]; + } + + return array_merge($finalizedOpts, parent::prepareOptions($options)); + } + + /** + * {@inheritdoc} + */ + protected function withScores() + { + $arguments = $this->getArguments(); + + for ($i = 3; $i < count($arguments); ++$i) { + switch (strtoupper($arguments[$i])) { + case 'WITHSCORES': + return true; + + case 'LIMIT': + $i += 2; + break; + } + } + + return false; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRank.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRank.php new file mode 100644 index 0000000..d0c9c53 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRank.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrank + * + * @author Daniele Alessandri + */ +class ZSetRank extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZRANK'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemove.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemove.php new file mode 100644 index 0000000..cd8ada0 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemove.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrem + * + * @author Daniele Alessandri + */ +class ZSetRemove extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREM'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + return self::normalizeVariadic($arguments); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByLex.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByLex.php new file mode 100644 index 0000000..9ea2d9e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByLex.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zremrangebylex + * + * @author Daniele Alessandri + */ +class ZSetRemoveRangeByLex extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREMRANGEBYLEX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByRank.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByRank.php new file mode 100644 index 0000000..89cd5ba --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByRank.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zremrangebyrank + * + * @author Daniele Alessandri + */ +class ZSetRemoveRangeByRank extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREMRANGEBYRANK'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByScore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByScore.php new file mode 100644 index 0000000..a7c3081 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetRemoveRangeByScore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zremrangebyscore + * + * @author Daniele Alessandri + */ +class ZSetRemoveRangeByScore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREMRANGEBYSCORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRange.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRange.php new file mode 100644 index 0000000..6a46a7a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRange.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrevrange + * + * @author Daniele Alessandri + */ +class ZSetReverseRange extends ZSetRange +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANGE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRangeByLex.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRangeByLex.php new file mode 100644 index 0000000..cdd8ba6 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRangeByLex.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +class ZSetReverseRangeByLex extends ZSetRangeByLex +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANGEBYLEX'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRangeByScore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRangeByScore.php new file mode 100644 index 0000000..1078eb7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRangeByScore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrevrangebyscore + * + * @author Daniele Alessandri + */ +class ZSetReverseRangeByScore extends ZSetRangeByScore +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANGEBYSCORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRank.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRank.php new file mode 100644 index 0000000..33fb815 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetReverseRank.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zrevrank + * + * @author Daniele Alessandri + */ +class ZSetReverseRank extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZREVRANK'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetScan.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetScan.php new file mode 100644 index 0000000..1dc2352 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetScan.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zscan + * + * @author Daniele Alessandri + */ +class ZSetScan extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZSCAN'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 3 && is_array($arguments[2])) { + $options = $this->prepareOptions(array_pop($arguments)); + $arguments = array_merge($arguments, $options); + } + + return $arguments; + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + protected function prepareOptions($options) + { + $options = array_change_key_case($options, CASE_UPPER); + $normalized = array(); + + if (!empty($options['MATCH'])) { + $normalized[] = 'MATCH'; + $normalized[] = $options['MATCH']; + } + + if (!empty($options['COUNT'])) { + $normalized[] = 'COUNT'; + $normalized[] = $options['COUNT']; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_array($data)) { + $members = $data[1]; + $result = array(); + + for ($i = 0; $i < count($members); ++$i) { + $result[$members[$i]] = (float) $members[++$i]; + } + + $data[1] = $result; + } + + return $data; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetScore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetScore.php new file mode 100644 index 0000000..2e7fce8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetScore.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zscore + * + * @author Daniele Alessandri + */ +class ZSetScore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZSCORE'; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetUnionStore.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetUnionStore.php new file mode 100644 index 0000000..befc5ce --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Command/ZSetUnionStore.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @link http://redis.io/commands/zunionstore + * + * @author Daniele Alessandri + */ +class ZSetUnionStore extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'ZUNIONSTORE'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + $options = array(); + $argc = count($arguments); + + if ($argc > 2 && is_array($arguments[$argc - 1])) { + $options = $this->prepareOptions(array_pop($arguments)); + } + + if (is_array($arguments[1])) { + $arguments = array_merge( + array($arguments[0], count($arguments[1])), + $arguments[1] + ); + } + + return array_merge($arguments, $options); + } + + /** + * Returns a list of options and modifiers compatible with Redis. + * + * @param array $options List of options. + * + * @return array + */ + private function prepareOptions($options) + { + $opts = array_change_key_case($options, CASE_UPPER); + $finalizedOpts = array(); + + if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) { + $finalizedOpts[] = 'WEIGHTS'; + + foreach ($opts['WEIGHTS'] as $weight) { + $finalizedOpts[] = $weight; + } + } + + if (isset($opts['AGGREGATE'])) { + $finalizedOpts[] = 'AGGREGATE'; + $finalizedOpts[] = $opts['AGGREGATE']; + } + + return $finalizedOpts; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/CommunicationException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/CommunicationException.php new file mode 100644 index 0000000..13fe357 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/CommunicationException.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +use Predis\Connection\NodeConnectionInterface; + +/** + * Base exception class for network-related errors. + * + * @author Daniele Alessandri + */ +abstract class CommunicationException extends PredisException +{ + private $connection; + + /** + * @param NodeConnectionInterface $connection Connection that generated the exception. + * @param string $message Error message. + * @param int $code Error code. + * @param \Exception $innerException Inner exception for wrapping the original error. + */ + public function __construct( + NodeConnectionInterface $connection, + $message = null, + $code = null, + \Exception $innerException = null + ) { + parent::__construct($message, $code, $innerException); + $this->connection = $connection; + } + + /** + * Gets the connection that generated the exception. + * + * @return NodeConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Indicates if the receiver should reset the underlying connection. + * + * @return bool + */ + public function shouldResetConnection() + { + return true; + } + + /** + * Helper method to handle exceptions generated by a connection object. + * + * @param CommunicationException $exception Exception. + * + * @throws CommunicationException + */ + public static function handle(CommunicationException $exception) + { + if ($exception->shouldResetConnection()) { + $connection = $exception->getConnection(); + + if ($connection->isConnected()) { + $connection->disconnect(); + } + } + + throw $exception; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ClusterOption.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ClusterOption.php new file mode 100644 index 0000000..69e36de --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ClusterOption.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\PredisCluster; +use Predis\Connection\Aggregate\RedisCluster; + +/** + * Configures an aggregate connection used for clustering + * multiple Redis nodes using various implementations with + * different algorithms or strategies. + * + * @author Daniele Alessandri + */ +class ClusterOption implements OptionInterface +{ + /** + * Creates a new cluster connection from on a known descriptive name. + * + * @param OptionsInterface $options Instance of the client options. + * @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`) + * + * @return ClusterInterface|null + */ + protected function createByDescription(OptionsInterface $options, $id) + { + switch ($id) { + case 'predis': + case 'predis-cluster': + return new PredisCluster(); + + case 'redis': + case 'redis-cluster': + return new RedisCluster($options->connections); + + default: + return; + } + } + + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if (is_string($value)) { + $value = $this->createByDescription($options, $value); + } + + if (!$value instanceof ClusterInterface) { + throw new \InvalidArgumentException( + "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected." + ); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return new PredisCluster(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ConnectionFactoryOption.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ConnectionFactoryOption.php new file mode 100644 index 0000000..ba38df9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ConnectionFactoryOption.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Connection\Factory; +use Predis\Connection\FactoryInterface; + +/** + * Configures a connection factory used by the client to create new connection + * instances for single Redis nodes. + * + * @author Daniele Alessandri + */ +class ConnectionFactoryOption implements OptionInterface +{ + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if ($value instanceof FactoryInterface) { + return $value; + } elseif (is_array($value)) { + $factory = $this->getDefault($options); + + foreach ($value as $scheme => $initializer) { + $factory->define($scheme, $initializer); + } + + return $factory; + } else { + throw new \InvalidArgumentException( + 'Invalid value provided for the connections option.' + ); + } + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return new Factory(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ExceptionsOption.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ExceptionsOption.php new file mode 100644 index 0000000..337733e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ExceptionsOption.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +/** + * Configures whether consumers (such as the client) should throw exceptions on + * Redis errors (-ERR responses) or just return instances of error responses. + * + * @author Daniele Alessandri + */ +class ExceptionsOption implements OptionInterface +{ + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + return filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return true; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/OptionInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/OptionInterface.php new file mode 100644 index 0000000..b31e0c9 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/OptionInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +/** + * Defines an handler used by Predis\Configuration\Options to filter, validate + * or return default values for a given option. + * + * @author Daniele Alessandri + */ +interface OptionInterface +{ + /** + * Filters and validates the passed value. + * + * @param OptionsInterface $options Options container. + * @param mixed $value Input value. + * + * @return mixed + */ + public function filter(OptionsInterface $options, $value); + + /** + * Returns the default value for the option. + * + * @param OptionsInterface $options Options container. + * + * @return mixed + */ + public function getDefault(OptionsInterface $options); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/Options.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/Options.php new file mode 100644 index 0000000..6f3b331 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/Options.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +/** + * Manages Predis options with filtering, conversion and lazy initialization of + * values using a mini-DI container approach. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Options implements OptionsInterface +{ + protected $input; + protected $options; + protected $handlers; + + /** + * @param array $options Array of options with their values + */ + public function __construct(array $options = array()) + { + $this->input = $options; + $this->options = array(); + $this->handlers = $this->getHandlers(); + } + + /** + * Ensures that the default options are initialized. + * + * @return array + */ + protected function getHandlers() + { + return array( + 'cluster' => 'Predis\Configuration\ClusterOption', + 'connections' => 'Predis\Configuration\ConnectionFactoryOption', + 'exceptions' => 'Predis\Configuration\ExceptionsOption', + 'prefix' => 'Predis\Configuration\PrefixOption', + 'profile' => 'Predis\Configuration\ProfileOption', + 'replication' => 'Predis\Configuration\ReplicationOption', + ); + } + + /** + * {@inheritdoc} + */ + public function getDefault($option) + { + if (isset($this->handlers[$option])) { + $handler = $this->handlers[$option]; + $handler = new $handler(); + + return $handler->getDefault($this); + } + } + + /** + * {@inheritdoc} + */ + public function defined($option) + { + return ( + array_key_exists($option, $this->options) || + array_key_exists($option, $this->input) + ); + } + + /** + * {@inheritdoc} + */ + public function __isset($option) + { + return ( + array_key_exists($option, $this->options) || + array_key_exists($option, $this->input) + ) && $this->__get($option) !== null; + } + + /** + * {@inheritdoc} + */ + public function __get($option) + { + if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + + if (isset($this->input[$option]) || array_key_exists($option, $this->input)) { + $value = $this->input[$option]; + unset($this->input[$option]); + + if (is_object($value) && method_exists($value, '__invoke')) { + $value = $value($this, $option); + } + + if (isset($this->handlers[$option])) { + $handler = $this->handlers[$option]; + $handler = new $handler(); + $value = $handler->filter($this, $value); + } + + return $this->options[$option] = $value; + } + + if (isset($this->handlers[$option])) { + return $this->options[$option] = $this->getDefault($option); + } + + return; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/OptionsInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/OptionsInterface.php new file mode 100644 index 0000000..f811647 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/OptionsInterface.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +/** + * Interface defining a container for client options. + * + * @property-read mixed aggregate Custom connection aggregator. + * @property-read mixed cluster Aggregate connection for clustering. + * @property-read mixed connections Connection factory. + * @property-read mixed exceptions Toggles exceptions in client for -ERR responses. + * @property-read mixed prefix Key prefixing strategy using the given prefix. + * @property-read mixed profile Server profile. + * @property-read mixed replication Aggregate connection for replication. + * + * @author Daniele Alessandri + */ +interface OptionsInterface +{ + /** + * Returns the default value for the given option. + * + * @param string $option Name of the option. + * + * @return mixed|null + */ + public function getDefault($option); + + /** + * Checks if the given option has been set by the user upon initialization. + * + * @param string $option Name of the option. + * + * @return bool + */ + public function defined($option); + + /** + * Checks if the given option has been set and does not evaluate to NULL. + * + * @param string $option Name of the option. + * + * @return bool + */ + public function __isset($option); + + /** + * Returns the value of the given option. + * + * @param string $option Name of the option. + * + * @return mixed|null + */ + public function __get($option); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/PrefixOption.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/PrefixOption.php new file mode 100644 index 0000000..5827cdc --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/PrefixOption.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Command\Processor\KeyPrefixProcessor; +use Predis\Command\Processor\ProcessorInterface; + +/** + * Configures a command processor that apply the specified prefix string to a + * series of Redis commands considered prefixable. + * + * @author Daniele Alessandri + */ +class PrefixOption implements OptionInterface +{ + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if ($value instanceof ProcessorInterface) { + return $value; + } + + return new KeyPrefixProcessor($value); + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + // NOOP + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ProfileOption.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ProfileOption.php new file mode 100644 index 0000000..864936e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ProfileOption.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Profile\Factory; +use Predis\Profile\ProfileInterface; +use Predis\Profile\RedisProfile; + +/** + * Configures the server profile to be used by the client to create command + * instances depending on the specified version of the Redis server. + * + * @author Daniele Alessandri + */ +class ProfileOption implements OptionInterface +{ + /** + * Sets the commands processors that need to be applied to the profile. + * + * @param OptionsInterface $options Client options. + * @param ProfileInterface $profile Server profile. + */ + protected function setProcessors(OptionsInterface $options, ProfileInterface $profile) + { + if (isset($options->prefix) && $profile instanceof RedisProfile) { + // NOTE: directly using __get('prefix') is actually a workaround for + // HHVM 2.3.0. It's correct and respects the options interface, it's + // just ugly. We will remove this hack when HHVM will fix re-entrant + // calls to __get() once and for all. + + $profile->setProcessor($options->__get('prefix')); + } + } + + /** + * {@inheritdoc} + */ + public function filter(OptionsInterface $options, $value) + { + if (is_string($value)) { + $value = Factory::get($value); + $this->setProcessors($options, $value); + } elseif (!$value instanceof ProfileInterface) { + throw new \InvalidArgumentException('Invalid value for the profile option.'); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + $profile = Factory::getDefault(); + $this->setProcessors($options, $profile); + + return $profile; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ReplicationOption.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ReplicationOption.php new file mode 100644 index 0000000..fd2c810 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Configuration/ReplicationOption.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Configuration; + +use Predis\Connection\Aggregate\MasterSlaveReplication; +use Predis\Connection\Aggregate\ReplicationInterface; + +/** + * Configures an aggregate connection used for master/slave replication among + * multiple Redis nodes. + * + * @author Daniele Alessandri + */ +class ReplicationOption implements OptionInterface +{ + /** + * {@inheritdoc} + * + * @todo There's more code than needed due to a bug in filter_var() as + * discussed here https://bugs.php.net/bug.php?id=49510 and different + * behaviours when encountering NULL values on PHP 5.3. + */ + public function filter(OptionsInterface $options, $value) + { + if ($value instanceof ReplicationInterface) { + return $value; + } + + if (is_bool($value) || $value === null) { + return $value ? $this->getDefault($options) : null; + } + + if ( + !is_object($value) && + null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) + ) { + return $asbool ? $this->getDefault($options) : null; + } + + throw new \InvalidArgumentException( + "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected." + ); + } + + /** + * {@inheritdoc} + */ + public function getDefault(OptionsInterface $options) + { + return new MasterSlaveReplication(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/AbstractConnection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/AbstractConnection.php new file mode 100644 index 0000000..029a337 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/AbstractConnection.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\CommunicationException; +use Predis\Protocol\ProtocolException; + +/** + * Base class with the common logic used by connection classes to communicate + * with Redis. + * + * @author Daniele Alessandri + */ +abstract class AbstractConnection implements NodeConnectionInterface +{ + private $resource; + private $cachedId; + + protected $parameters; + protected $initCommands = array(); + + /** + * @param ParametersInterface $parameters Initialization parameters for the connection. + */ + public function __construct(ParametersInterface $parameters) + { + $this->parameters = $this->assertParameters($parameters); + } + + /** + * Disconnects from the server and destroys the underlying resource when + * PHP's garbage collector kicks in. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Checks some of the parameters used to initialize the connection. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @throws \InvalidArgumentException + * + * @return ParametersInterface + */ + protected function assertParameters(ParametersInterface $parameters) + { + switch ($parameters->scheme) { + case 'tcp': + case 'redis': + case 'unix': + break; + + default: + throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'."); + } + + return $parameters; + } + + /** + * Creates the underlying resource used to communicate with Redis. + * + * @return mixed + */ + abstract protected function createResource(); + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return isset($this->resource); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (!$this->isConnected()) { + $this->resource = $this->createResource(); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + unset($this->resource); + } + + /** + * {@inheritdoc} + */ + public function addConnectCommand(CommandInterface $command) + { + $this->initCommands[] = $command; + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $this->writeRequest($command); + + return $this->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->read(); + } + + /** + * Helper method that returns an exception message augmented with useful + * details from the connection parameters. + * + * @param string $message Error message. + * + * @return string + */ + private function createExceptionMessage($message) + { + $parameters = $this->parameters; + + if ($parameters->scheme === 'unix') { + return "$message [$parameters->scheme:$parameters->path]"; + } + + if (filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + return "$message [$parameters->scheme://[$parameters->host]:$parameters->port]"; + } + + return "$message [$parameters->scheme://$parameters->host:$parameters->port]"; + } + + /** + * Helper method to handle connection errors. + * + * @param string $message Error message. + * @param int $code Error code. + */ + protected function onConnectionError($message, $code = null) + { + CommunicationException::handle( + new ConnectionException($this, static::createExceptionMessage($message), $code) + ); + } + + /** + * Helper method to handle protocol errors. + * + * @param string $message Error message. + */ + protected function onProtocolError($message) + { + CommunicationException::handle( + new ProtocolException($this, static::createExceptionMessage($message)) + ); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + if (isset($this->resource)) { + return $this->resource; + } + + $this->connect(); + + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets an identifier for the connection. + * + * @return string + */ + protected function getIdentifier() + { + if ($this->parameters->scheme === 'unix') { + return $this->parameters->path; + } + + return "{$this->parameters->host}:{$this->parameters->port}"; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + if (!isset($this->cachedId)) { + $this->cachedId = $this->getIdentifier(); + } + + return $this->cachedId; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('parameters', 'initCommands'); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/ClusterInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/ClusterInterface.php new file mode 100644 index 0000000..af0f5aa --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/ClusterInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Connection\AggregateConnectionInterface; + +/** + * Defines a cluster of Redis servers formed by aggregating multiple connection + * instances to single Redis nodes. + * + * @author Daniele Alessandri + */ +interface ClusterInterface extends AggregateConnectionInterface +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/MasterSlaveReplication.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/MasterSlaveReplication.php new file mode 100644 index 0000000..3104a75 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/MasterSlaveReplication.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Command\CommandInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\Replication\ReplicationStrategy; + +/** + * Aggregate connection handling replication of Redis nodes configured in a + * single master / multiple slaves setup. + * + * @author Daniele Alessandri + */ +class MasterSlaveReplication implements ReplicationInterface +{ + protected $strategy; + protected $master; + protected $slaves; + protected $current; + + /** + * {@inheritdoc} + */ + public function __construct(ReplicationStrategy $strategy = null) + { + $this->slaves = array(); + $this->strategy = $strategy ?: new ReplicationStrategy(); + } + + /** + * Checks if one master and at least one slave have been defined. + */ + protected function check() + { + if (!isset($this->master) || !$this->slaves) { + throw new \RuntimeException('Replication needs one master and at least one slave.'); + } + } + + /** + * Resets the connection state. + */ + protected function reset() + { + $this->current = null; + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $alias = $connection->getParameters()->alias; + + if ($alias === 'master') { + $this->master = $connection; + } else { + $this->slaves[$alias ?: count($this->slaves)] = $connection; + } + + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if ($connection->getParameters()->alias === 'master') { + $this->master = null; + $this->reset(); + + return true; + } else { + if (($id = array_search($connection, $this->slaves, true)) !== false) { + unset($this->slaves[$id]); + $this->reset(); + + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + if ($this->current === null) { + $this->check(); + $this->current = $this->strategy->isReadOperation($command) + ? $this->pickSlave() + : $this->master; + + return $this->current; + } + + if ($this->current === $this->master) { + return $this->current; + } + + if (!$this->strategy->isReadOperation($command)) { + $this->current = $this->master; + } + + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionId) + { + if ($connectionId === 'master') { + return $this->master; + } + + if (isset($this->slaves[$connectionId])) { + return $this->slaves[$connectionId]; + } + + return; + } + + /** + * {@inheritdoc} + */ + public function switchTo($connection) + { + $this->check(); + + if (!$connection instanceof NodeConnectionInterface) { + $connection = $this->getConnectionById($connection); + } + if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) { + throw new \InvalidArgumentException('Invalid connection or connection not found.'); + } + + $this->current = $connection; + } + + /** + * {@inheritdoc} + */ + public function getCurrent() + { + return $this->current; + } + + /** + * {@inheritdoc} + */ + public function getMaster() + { + return $this->master; + } + + /** + * {@inheritdoc} + */ + public function getSlaves() + { + return array_values($this->slaves); + } + + /** + * Returns the underlying replication strategy. + * + * @return ReplicationStrategy + */ + public function getReplicationStrategy() + { + return $this->strategy; + } + + /** + * Returns a random slave. + * + * @return NodeConnectionInterface + */ + protected function pickSlave() + { + return $this->slaves[array_rand($this->slaves)]; + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return $this->current ? $this->current->isConnected() : false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if ($this->current === null) { + $this->check(); + $this->current = $this->pickSlave(); + } + + $this->current->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->master) { + $this->master->disconnect(); + } + + foreach ($this->slaves as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->getConnection($command)->writeRequest($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->getConnection($command)->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->getConnection($command)->executeCommand($command); + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('master', 'slaves', 'strategy'); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/PredisCluster.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/PredisCluster.php new file mode 100644 index 0000000..33f98bf --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/PredisCluster.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Cluster\PredisStrategy; +use Predis\Cluster\StrategyInterface; +use Predis\Command\CommandInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\NotSupportedException; + +/** + * Abstraction for a cluster of aggregate connections to various Redis servers + * implementing client-side sharding based on pluggable distribution strategies. + * + * @author Daniele Alessandri + * + * @todo Add the ability to remove connections from pool. + */ +class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable +{ + private $pool; + private $strategy; + private $distributor; + + /** + * @param StrategyInterface $strategy Optional cluster strategy. + */ + public function __construct(StrategyInterface $strategy = null) + { + $this->pool = array(); + $this->strategy = $strategy ?: new PredisStrategy(); + $this->distributor = $this->strategy->getDistributor(); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + foreach ($this->pool as $connection) { + if ($connection->isConnected()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + foreach ($this->pool as $connection) { + $connection->connect(); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + if (isset($parameters->alias)) { + $this->pool[$parameters->alias] = $connection; + } else { + $this->pool[] = $connection; + } + + $weight = isset($parameters->weight) ? $parameters->weight : null; + $this->distributor->add($connection, $weight); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if (($id = array_search($connection, $this->pool, true)) !== false) { + unset($this->pool[$id]); + $this->distributor->remove($connection); + + return true; + } + + return false; + } + + /** + * Removes a connection instance using its alias or index. + * + * @param string $connectionID Alias or index of a connection. + * + * @return bool Returns true if the connection was in the pool. + */ + public function removeById($connectionID) + { + if ($connection = $this->getConnectionById($connectionID)) { + return $this->remove($connection); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + $slot = $this->strategy->getSlot($command); + + if (!isset($slot)) { + throw new NotSupportedException( + "Cannot use '{$command->getId()}' over clusters of connections." + ); + } + + $node = $this->distributor->getBySlot($slot); + + return $node; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionID) + { + return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null; + } + + /** + * Retrieves a connection instance from the cluster using a key. + * + * @param string $key Key string. + * + * @return NodeConnectionInterface + */ + public function getConnectionByKey($key) + { + $hash = $this->strategy->getSlotByKey($key); + $node = $this->distributor->getBySlot($hash); + + return $node; + } + + /** + * Returns the underlying command hash strategy used to hash commands by + * using keys found in their arguments. + * + * @return StrategyInterface + */ + public function getClusterStrategy() + { + return $this->strategy; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->pool); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->pool); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->getConnection($command)->writeRequest($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->getConnection($command)->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + return $this->getConnection($command)->executeCommand($command); + } + + /** + * Executes the specified Redis command on all the nodes of a cluster. + * + * @param CommandInterface $command A Redis command. + * + * @return array + */ + public function executeCommandOnNodes(CommandInterface $command) + { + $responses = array(); + + foreach ($this->pool as $connection) { + $responses[] = $connection->executeCommand($command); + } + + return $responses; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/RedisCluster.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/RedisCluster.php new file mode 100644 index 0000000..337c287 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/RedisCluster.php @@ -0,0 +1,553 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Cluster\RedisStrategy as RedisClusterStrategy; +use Predis\Cluster\StrategyInterface; +use Predis\Command\CommandInterface; +use Predis\Command\RawCommand; +use Predis\Connection\FactoryInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\NotSupportedException; +use Predis\Response\ErrorInterface as ErrorResponseInterface; + +/** + * Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0). + * + * This connection backend offers smart support for redis-cluster by handling + * automatic slots map (re)generation upon -MOVED or -ASK responses returned by + * Redis when redirecting a client to a different node. + * + * The cluster can be pre-initialized using only a subset of the actual nodes in + * the cluster, Predis will do the rest by adjusting the slots map and creating + * the missing underlying connection instances on the fly. + * + * It is possible to pre-associate connections to a slots range with the "slots" + * parameter in the form "$first-$last". This can greatly reduce runtime node + * guessing and redirections. + * + * It is also possible to ask for the full and updated slots map directly to one + * of the nodes and optionally enable such a behaviour upon -MOVED redirections. + * Asking for the cluster configuration to Redis is actually done by issuing a + * CLUSTER SLOTS command to a random node in the pool. + * + * @author Daniele Alessandri + */ +class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable +{ + private $useClusterSlots = true; + private $defaultParameters = array(); + private $pool = array(); + private $slots = array(); + private $slotsMap; + private $strategy; + private $connections; + + /** + * @param FactoryInterface $connections Optional connection factory. + * @param StrategyInterface $strategy Optional cluster strategy. + */ + public function __construct( + FactoryInterface $connections, + StrategyInterface $strategy = null + ) { + $this->connections = $connections; + $this->strategy = $strategy ?: new RedisClusterStrategy(); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + foreach ($this->pool as $connection) { + if ($connection->isConnected()) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if ($connection = $this->getRandomConnection()) { + $connection->connect(); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + foreach ($this->pool as $connection) { + $connection->disconnect(); + } + } + + /** + * {@inheritdoc} + */ + public function add(NodeConnectionInterface $connection) + { + $this->pool[(string) $connection] = $connection; + unset($this->slotsMap); + } + + /** + * {@inheritdoc} + */ + public function remove(NodeConnectionInterface $connection) + { + if (false !== $id = array_search($connection, $this->pool, true)) { + unset( + $this->pool[$id], + $this->slotsMap + ); + + return true; + } + + return false; + } + + /** + * Removes a connection instance by using its identifier. + * + * @param string $connectionID Connection identifier. + * + * @return bool True if the connection was in the pool. + */ + public function removeById($connectionID) + { + if (isset($this->pool[$connectionID])) { + unset( + $this->pool[$connectionID], + $this->slotsMap + ); + + return true; + } + + return false; + } + + /** + * Generates the current slots map by guessing the cluster configuration out + * of the connection parameters of the connections in the pool. + * + * Generation is based on the same algorithm used by Redis to generate the + * cluster, so it is most effective when all of the connections supplied on + * initialization have the "slots" parameter properly set accordingly to the + * current cluster configuration. + */ + public function buildSlotsMap() + { + $this->slotsMap = array(); + + foreach ($this->pool as $connectionID => $connection) { + $parameters = $connection->getParameters(); + + if (!isset($parameters->slots)) { + continue; + } + + $slots = explode('-', $parameters->slots, 2); + $this->setSlots($slots[0], $slots[1], $connectionID); + } + } + + /** + * Generates an updated slots map fetching the cluster configuration using + * the CLUSTER SLOTS command against the specified node or a random one from + * the pool. + * + * @param NodeConnectionInterface $connection Optional connection instance. + * + * @return array + */ + public function askSlotsMap(NodeConnectionInterface $connection = null) + { + if (!$connection && !$connection = $this->getRandomConnection()) { + return array(); + } + + $command = RawCommand::create('CLUSTER', 'SLOTS'); + $response = $connection->executeCommand($command); + + foreach ($response as $slots) { + // We only support master servers for now, so we ignore subsequent + // elements in the $slots array identifying slaves. + list($start, $end, $master) = $slots; + + if ($master[0] === '') { + $this->setSlots($start, $end, (string) $connection); + } else { + $this->setSlots($start, $end, "{$master[0]}:{$master[1]}"); + } + } + + return $this->slotsMap; + } + + /** + * Returns the current slots map for the cluster. + * + * @return array + */ + public function getSlotsMap() + { + if (!isset($this->slotsMap)) { + $this->slotsMap = array(); + } + + return $this->slotsMap; + } + + /** + * Pre-associates a connection to a slots range to avoid runtime guessing. + * + * @param int $first Initial slot of the range. + * @param int $last Last slot of the range. + * @param NodeConnectionInterface|string $connection ID or connection instance. + * + * @throws \OutOfBoundsException + */ + public function setSlots($first, $last, $connection) + { + if ($first < 0x0000 || $first > 0x3FFF || + $last < 0x0000 || $last > 0x3FFF || + $last < $first + ) { + throw new \OutOfBoundsException( + "Invalid slot range for $connection: [$first-$last]." + ); + } + + $slots = array_fill($first, $last - $first + 1, (string) $connection); + $this->slotsMap = $this->getSlotsMap() + $slots; + } + + /** + * Guesses the correct node associated to a given slot using a precalculated + * slots map, falling back to the same logic used by Redis to initialize a + * cluster (best-effort). + * + * @param int $slot Slot index. + * + * @return string Connection ID. + */ + protected function guessNode($slot) + { + if (!isset($this->slotsMap)) { + $this->buildSlotsMap(); + } + + if (isset($this->slotsMap[$slot])) { + return $this->slotsMap[$slot]; + } + + $count = count($this->pool); + $index = min((int) ($slot / (int) (16384 / $count)), $count - 1); + $nodes = array_keys($this->pool); + + return $nodes[$index]; + } + + /** + * Creates a new connection instance from the given connection ID. + * + * @param string $connectionID Identifier for the connection. + * + * @return NodeConnectionInterface + */ + protected function createConnection($connectionID) + { + $separator = strrpos($connectionID, ':'); + + $parameters = array_merge($this->defaultParameters, array( + 'host' => substr($connectionID, 0, $separator), + 'port' => substr($connectionID, $separator + 1), + )); + + $connection = $this->connections->create($parameters); + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function getConnection(CommandInterface $command) + { + $slot = $this->strategy->getSlot($command); + + if (!isset($slot)) { + throw new NotSupportedException( + "Cannot use '{$command->getId()}' with redis-cluster." + ); + } + + if (isset($this->slots[$slot])) { + return $this->slots[$slot]; + } else { + return $this->getConnectionBySlot($slot); + } + } + + /** + * Returns the connection currently associated to a given slot. + * + * @param int $slot Slot index. + * + * @throws \OutOfBoundsException + * + * @return NodeConnectionInterface + */ + public function getConnectionBySlot($slot) + { + if ($slot < 0x0000 || $slot > 0x3FFF) { + throw new \OutOfBoundsException("Invalid slot [$slot]."); + } + + if (isset($this->slots[$slot])) { + return $this->slots[$slot]; + } + + $connectionID = $this->guessNode($slot); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + $this->pool[$connectionID] = $connection; + } + + return $this->slots[$slot] = $connection; + } + + /** + * {@inheritdoc} + */ + public function getConnectionById($connectionID) + { + if (isset($this->pool[$connectionID])) { + return $this->pool[$connectionID]; + } + } + + /** + * Returns a random connection from the pool. + * + * @return NodeConnectionInterface|null + */ + protected function getRandomConnection() + { + if ($this->pool) { + return $this->pool[array_rand($this->pool)]; + } + } + + /** + * Permanently associates the connection instance to a new slot. + * The connection is added to the connections pool if not yet included. + * + * @param NodeConnectionInterface $connection Connection instance. + * @param int $slot Target slot index. + */ + protected function move(NodeConnectionInterface $connection, $slot) + { + $this->pool[(string) $connection] = $connection; + $this->slots[(int) $slot] = $connection; + } + + /** + * Handles -ERR responses returned by Redis. + * + * @param CommandInterface $command Command that generated the -ERR response. + * @param ErrorResponseInterface $error Redis error response object. + * + * @return mixed + */ + protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error) + { + $details = explode(' ', $error->getMessage(), 2); + + switch ($details[0]) { + case 'MOVED': + return $this->onMovedResponse($command, $details[1]); + + case 'ASK': + return $this->onAskResponse($command, $details[1]); + + default: + return $error; + } + } + + /** + * Handles -MOVED responses by executing again the command against the node + * indicated by the Redis response. + * + * @param CommandInterface $command Command that generated the -MOVED response. + * @param string $details Parameters of the -MOVED response. + * + * @return mixed + */ + protected function onMovedResponse(CommandInterface $command, $details) + { + list($slot, $connectionID) = explode(' ', $details, 2); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + } + + if ($this->useClusterSlots) { + $this->askSlotsMap($connection); + } + + $this->move($connection, $slot); + $response = $this->executeCommand($command); + + return $response; + } + + /** + * Handles -ASK responses by executing again the command against the node + * indicated by the Redis response. + * + * @param CommandInterface $command Command that generated the -ASK response. + * @param string $details Parameters of the -ASK response. + * + * @return mixed + */ + protected function onAskResponse(CommandInterface $command, $details) + { + list($slot, $connectionID) = explode(' ', $details, 2); + + if (!$connection = $this->getConnectionById($connectionID)) { + $connection = $this->createConnection($connectionID); + } + + $connection->executeCommand(RawCommand::create('ASKING')); + $response = $connection->executeCommand($command); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->getConnection($command)->writeRequest($command); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + return $this->getConnection($command)->readResponse($command); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $connection = $this->getConnection($command); + $response = $connection->executeCommand($command); + + if ($response instanceof ErrorResponseInterface) { + return $this->onErrorResponse($command, $response); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->pool); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator(array_values($this->pool)); + } + + /** + * Returns the underlying command hash strategy used to hash commands by + * using keys found in their arguments. + * + * @return StrategyInterface + */ + public function getClusterStrategy() + { + return $this->strategy; + } + + /** + * Returns the underlying connection factory used to create new connection + * instances to Redis nodes indicated by redis-cluster. + * + * @return FactoryInterface + */ + public function getConnectionFactory() + { + return $this->connections; + } + + /** + * Enables automatic fetching of the current slots map from one of the nodes + * using the CLUSTER SLOTS command. This option is disabled by default but + * asking the current slots map to Redis upon -MOVED responses may reduce + * overhead by eliminating the trial-and-error nature of the node guessing + * procedure, mostly when targeting many keys that would end up in a lot of + * redirections. + * + * The slots map can still be manually fetched using the askSlotsMap() + * method whether or not this option is enabled. + * + * @param bool $value Enable or disable the use of CLUSTER SLOTS. + */ + public function useClusterSlots($value) + { + $this->useClusterSlots = (bool) $value; + } + + /** + * Sets a default array of connection parameters to be applied when creating + * new connection instances on the fly when they are not part of the initial + * pool supplied upon cluster initialization. + * + * These parameters are not applied to connections added to the pool using + * the add() method. + * + * @param array $parameters Array of connection parameters. + */ + public function setDefaultParameters(array $parameters) + { + $this->defaultParameters = array_merge( + $this->defaultParameters, + $parameters ?: array() + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/ReplicationInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/ReplicationInterface.php new file mode 100644 index 0000000..e09e826 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Aggregate/ReplicationInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection\Aggregate; + +use Predis\Connection\AggregateConnectionInterface; +use Predis\Connection\NodeConnectionInterface; + +/** + * Defines a group of Redis nodes in a master / slave replication setup. + * + * @author Daniele Alessandri + */ +interface ReplicationInterface extends AggregateConnectionInterface +{ + /** + * Switches the internal connection instance in use. + * + * @param string $connection Alias of a connection + */ + public function switchTo($connection); + + /** + * Returns the connection instance currently in use by the aggregate + * connection. + * + * @return NodeConnectionInterface + */ + public function getCurrent(); + + /** + * Returns the connection instance for the master Redis node. + * + * @return NodeConnectionInterface + */ + public function getMaster(); + + /** + * Returns a list of connection instances to slave nodes. + * + * @return NodeConnectionInterface + */ + public function getSlaves(); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/AggregateConnectionInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/AggregateConnectionInterface.php new file mode 100644 index 0000000..7eeaede --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/AggregateConnectionInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; + +/** + * Defines a virtual connection composed of multiple connection instances to + * single Redis nodes. + * + * @author Daniele Alessandri + */ +interface AggregateConnectionInterface extends ConnectionInterface +{ + /** + * Adds a connection instance to the aggregate connection. + * + * @param NodeConnectionInterface $connection Connection instance. + */ + public function add(NodeConnectionInterface $connection); + + /** + * Removes the specified connection instance from the aggregate connection. + * + * @param NodeConnectionInterface $connection Connection instance. + * + * @return bool Returns true if the connection was in the pool. + */ + public function remove(NodeConnectionInterface $connection); + + /** + * Returns the connection instance in charge for the given command. + * + * @param CommandInterface $command Command instance. + * + * @return NodeConnectionInterface + */ + public function getConnection(CommandInterface $command); + + /** + * Returns a connection instance from the aggregate connection by its alias. + * + * @param string $connectionID Connection alias. + * + * @return NodeConnectionInterface|null + */ + public function getConnectionById($connectionID); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/CompositeConnectionInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/CompositeConnectionInterface.php new file mode 100644 index 0000000..286e082 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/CompositeConnectionInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Defines a connection to communicate with a single Redis server that leverages + * an external protocol processor to handle pluggable protocol handlers. + * + * @author Daniele Alessandri + */ +interface CompositeConnectionInterface extends NodeConnectionInterface +{ + /** + * Returns the protocol processor used by the connection. + */ + public function getProtocol(); + + /** + * Writes the buffer containing over the connection. + * + * @param string $buffer String buffer to be sent over the connection. + */ + public function writeBuffer($buffer); + + /** + * Reads the given number of bytes from the connection. + * + * @param int $length Number of bytes to read from the connection. + * + * @return string + */ + public function readBuffer($length); + + /** + * Reads a line from the connection. + * + * @param string + */ + public function readLine(); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/CompositeStreamConnection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/CompositeStreamConnection.php new file mode 100644 index 0000000..7a35340 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/CompositeStreamConnection.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\Protocol\ProtocolProcessorInterface; +use Predis\Protocol\Text\ProtocolProcessor as TextProtocolProcessor; + +/** + * Connection abstraction to Redis servers based on PHP's stream that uses an + * external protocol processor defining the protocol used for the communication. + * + * @author Daniele Alessandri + */ +class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface +{ + protected $protocol; + + /** + * @param ParametersInterface $parameters Initialization parameters for the connection. + * @param ProtocolProcessorInterface $protocol Protocol processor. + */ + public function __construct( + ParametersInterface $parameters, + ProtocolProcessorInterface $protocol = null + ) { + $this->parameters = $this->assertParameters($parameters); + $this->protocol = $protocol ?: new TextProtocolProcessor(); + } + + /** + * {@inheritdoc} + */ + public function getProtocol() + { + return $this->protocol; + } + + /** + * {@inheritdoc} + */ + public function writeBuffer($buffer) + { + $this->write($buffer); + } + + /** + * {@inheritdoc} + */ + public function readBuffer($length) + { + if ($length <= 0) { + throw new \InvalidArgumentException('Length parameter must be greater than 0.'); + } + + $value = ''; + $socket = $this->getResource(); + + do { + $chunk = fread($socket, $length); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + $value .= $chunk; + } while (($length -= strlen($chunk)) > 0); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function readLine() + { + $value = ''; + $socket = $this->getResource(); + + do { + $chunk = fgets($socket); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading line from the server.'); + } + + $value .= $chunk; + } while (substr($value, -2) !== "\r\n"); + + return substr($value, 0, -2); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->protocol->write($this, $command); + } + + /** + * {@inheritdoc} + */ + public function read() + { + return $this->protocol->read($this); + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array_merge(parent::__sleep(), array('protocol')); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ConnectionException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ConnectionException.php new file mode 100644 index 0000000..ef2e9d7 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ConnectionException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\CommunicationException; + +/** + * Exception class that identifies connection-related errors. + * + * @author Daniele Alessandri + */ +class ConnectionException extends CommunicationException +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ConnectionInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ConnectionInterface.php new file mode 100644 index 0000000..11ace1b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ConnectionInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; + +/** + * Defines a connection object used to communicate with one or multiple + * Redis servers. + * + * @author Daniele Alessandri + */ +interface ConnectionInterface +{ + /** + * Opens the connection to Redis. + */ + public function connect(); + + /** + * Closes the connection to Redis. + */ + public function disconnect(); + + /** + * Checks if the connection to Redis is considered open. + * + * @return bool + */ + public function isConnected(); + + /** + * Writes the request for the given command over the connection. + * + * @param CommandInterface $command Command instance. + */ + public function writeRequest(CommandInterface $command); + + /** + * Reads the response to the given command from the connection. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function readResponse(CommandInterface $command); + + /** + * Writes a request for the given command over the connection and reads back + * the response returned by Redis. + * + * @param CommandInterface $command Command instance. + * + * @return mixed + */ + public function executeCommand(CommandInterface $command); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Factory.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Factory.php new file mode 100644 index 0000000..c2e93f8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Factory.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\RawCommand; + +/** + * Standard connection factory for creating connections to Redis nodes. + * + * @author Daniele Alessandri + */ +class Factory implements FactoryInterface +{ + protected $schemes = array( + 'tcp' => 'Predis\Connection\StreamConnection', + 'unix' => 'Predis\Connection\StreamConnection', + 'redis' => 'Predis\Connection\StreamConnection', + 'http' => 'Predis\Connection\WebdisConnection', + ); + + /** + * Checks if the provided argument represents a valid connection class + * implementing Predis\Connection\NodeConnectionInterface. Optionally, + * callable objects are used for lazy initialization of connection objects. + * + * @param mixed $initializer FQN of a connection class or a callable for lazy initialization. + * + * @throws \InvalidArgumentException + * + * @return mixed + */ + protected function checkInitializer($initializer) + { + if (is_callable($initializer)) { + return $initializer; + } + + $class = new \ReflectionClass($initializer); + + if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) { + throw new \InvalidArgumentException( + 'A connection initializer must be a valid connection class or a callable object.' + ); + } + + return $initializer; + } + + /** + * {@inheritdoc} + */ + public function define($scheme, $initializer) + { + $this->schemes[$scheme] = $this->checkInitializer($initializer); + } + + /** + * {@inheritdoc} + */ + public function undefine($scheme) + { + unset($this->schemes[$scheme]); + } + + /** + * {@inheritdoc} + */ + public function create($parameters) + { + if (!$parameters instanceof ParametersInterface) { + $parameters = $this->createParameters($parameters); + } + + $scheme = $parameters->scheme; + + if (!isset($this->schemes[$scheme])) { + throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'."); + } + + $initializer = $this->schemes[$scheme]; + + if (is_callable($initializer)) { + $connection = call_user_func($initializer, $parameters, $this); + } else { + $connection = new $initializer($parameters); + $this->prepareConnection($connection); + } + + if (!$connection instanceof NodeConnectionInterface) { + throw new \UnexpectedValueException( + 'Objects returned by connection initializers must implement '. + "'Predis\Connection\NodeConnectionInterface'." + ); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + public function aggregate(AggregateConnectionInterface $connection, array $parameters) + { + foreach ($parameters as $node) { + $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node)); + } + } + + /** + * Creates a connection parameters instance from the supplied argument. + * + * @param mixed $parameters Original connection parameters. + * + * @return ParametersInterface + */ + protected function createParameters($parameters) + { + return Parameters::create($parameters); + } + + /** + * Prepares a connection instance after its initialization. + * + * @param NodeConnectionInterface $connection Connection instance. + */ + protected function prepareConnection(NodeConnectionInterface $connection) + { + $parameters = $connection->getParameters(); + + if (isset($parameters->password)) { + $connection->addConnectCommand( + new RawCommand(array('AUTH', $parameters->password)) + ); + } + + if (isset($parameters->database)) { + $connection->addConnectCommand( + new RawCommand(array('SELECT', $parameters->database)) + ); + } + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/FactoryInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/FactoryInterface.php new file mode 100644 index 0000000..2bae083 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/FactoryInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Interface for classes providing a factory of connections to Redis nodes. + * + * @author Daniele Alessandri + */ +interface FactoryInterface +{ + /** + * Defines or overrides the connection class identified by a scheme prefix. + * + * @param string $scheme Target connection scheme. + * @param mixed $initializer Fully-qualified name of a class or a callable for lazy initialization. + */ + public function define($scheme, $initializer); + + /** + * Undefines the connection identified by a scheme prefix. + * + * @param string $scheme Target connection scheme. + */ + public function undefine($scheme); + + /** + * Creates a new connection object. + * + * @param mixed $parameters Initialization parameters for the connection. + * + * @return NodeConnectionInterface + */ + public function create($parameters); + + /** + * Aggregates single connections into an aggregate connection instance. + * + * @param AggregateConnectionInterface $aggregate Aggregate connection instance. + * @param array $parameters List of parameters for each connection. + */ + public function aggregate(AggregateConnectionInterface $aggregate, array $parameters); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/NodeConnectionInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/NodeConnectionInterface.php new file mode 100644 index 0000000..665b862 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/NodeConnectionInterface.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; + +/** + * Defines a connection used to communicate with a single Redis node. + * + * @author Daniele Alessandri + */ +interface NodeConnectionInterface extends ConnectionInterface +{ + /** + * Returns a string representation of the connection. + * + * @return string + */ + public function __toString(); + + /** + * Returns the underlying resource used to communicate with Redis. + * + * @return mixed + */ + public function getResource(); + + /** + * Returns the parameters used to initialize the connection. + * + * @return ParametersInterface + */ + public function getParameters(); + + /** + * Pushes the given command into a queue of commands executed when + * establishing the actual connection to Redis. + * + * @param CommandInterface $command Instance of a Redis command. + */ + public function addConnectCommand(CommandInterface $command); + + /** + * Reads a response from the server. + * + * @return mixed + */ + public function read(); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Parameters.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Parameters.php new file mode 100644 index 0000000..b7d9861 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/Parameters.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Container for connection parameters used to initialize connections to Redis. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Parameters implements ParametersInterface +{ + private $parameters; + + private static $defaults = array( + 'scheme' => 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + 'timeout' => 5.0, + ); + + /** + * @param array $parameters Named array of connection parameters. + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $this->filter($parameters) + $this->getDefaults(); + } + + /** + * Returns some default parameters with their values. + * + * @return array + */ + protected function getDefaults() + { + return self::$defaults; + } + + /** + * Creates a new instance by supplying the initial parameters either in the + * form of an URI string or a named array. + * + * @param array|string $parameters Set of connection parameters. + * + * @return Parameters + */ + public static function create($parameters) + { + if (is_string($parameters)) { + $parameters = static::parse($parameters); + } + + return new static($parameters ?: array()); + } + + /** + * Parses an URI string returning an array of connection parameters. + * + * When using the "redis" and "rediss" schemes the URI is parsed according + * to the rules defined by the provisional registration documents approved + * by IANA. If the URI has a password in its "user-information" part or a + * database number in the "path" part these values override the values of + * "password" and "database" if they are present in the "query" part. + * + * @link http://www.iana.org/assignments/uri-schemes/prov/redis + * @link http://www.iana.org/assignments/uri-schemes/prov/redis + * + * @param string $uri URI string. + * + * @throws \InvalidArgumentException + * + * @return array + */ + public static function parse($uri) + { + if (stripos($uri, 'unix') === 0) { + // Hack to support URIs for UNIX sockets with minimal effort. + $uri = str_ireplace('unix:///', 'unix://localhost/', $uri); + } + + if (!$parsed = parse_url($uri)) { + throw new \InvalidArgumentException("Invalid parameters URI: $uri"); + } + + if ( + isset($parsed['host']) + && false !== strpos($parsed['host'], '[') + && false !== strpos($parsed['host'], ']') + ) { + $parsed['host'] = substr($parsed['host'], 1, -1); + } + + if (isset($parsed['query'])) { + parse_str($parsed['query'], $queryarray); + unset($parsed['query']); + + $parsed = array_merge($parsed, $queryarray); + } + + if (stripos($uri, 'redis') === 0) { + if (isset($parsed['pass'])) { + $parsed['password'] = $parsed['pass']; + unset($parsed['pass']); + } + + if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) { + $parsed['database'] = $path[1]; + + if (isset($path[2])) { + $parsed['path'] = $path[2]; + } else { + unset($parsed['path']); + } + } + } + + return $parsed; + } + + /** + * Validates and converts each value of the connection parameters array. + * + * @param array $parameters Connection parameters. + * + * @return array + */ + protected function filter(array $parameters) + { + return $parameters ?: array(); + } + + /** + * {@inheritdoc} + */ + public function __get($parameter) + { + if (isset($this->parameters[$parameter])) { + return $this->parameters[$parameter]; + } + } + + /** + * {@inheritdoc} + */ + public function __isset($parameter) + { + return isset($this->parameters[$parameter]); + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('parameters'); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ParametersInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ParametersInterface.php new file mode 100644 index 0000000..fd8a908 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/ParametersInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +/** + * Interface defining a container for connection parameters. + * + * The actual list of connection parameters depends on the features supported by + * each connection backend class (please refer to their specific documentation), + * but the most common parameters used through the library are: + * + * @property-read string scheme Connection scheme, such as 'tcp' or 'unix'. + * @property-read string host IP address or hostname of Redis. + * @property-read int port TCP port on which Redis is listening to. + * @property-read string path Path of a UNIX domain socket file. + * @property-read string alias Alias for the connection. + * @property-read float timeout Timeout for the connect() operation. + * @property-read float read_write_timeout Timeout for read() and write() operations. + * @property-read bool async_connect Performs the connect() operation asynchronously. + * @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing. + * @property-read bool persistent Leaves the connection open after a GC collection. + * @property-read string password Password to access Redis (see the AUTH command). + * @property-read string database Database index (see the SELECT command). + * + * @author Daniele Alessandri + */ +interface ParametersInterface +{ + /** + * Checks if the specified parameters is set. + * + * @param string $parameter Name of the parameter. + * + * @return bool + */ + public function __isset($parameter); + + /** + * Returns the value of the specified parameter. + * + * @param string $parameter Name of the parameter. + * + * @return mixed|null + */ + public function __get($parameter); + + /** + * Returns an array representation of the connection parameters. + * + * @return array + */ + public function toArray(); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/PhpiredisSocketConnection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/PhpiredisSocketConnection.php new file mode 100644 index 0000000..6948f03 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/PhpiredisSocketConnection.php @@ -0,0 +1,393 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Status as StatusResponse; + +/** + * This class provides the implementation of a Predis connection that uses the + * PHP socket extension for network communication and wraps the phpiredis C + * extension (PHP bindings for hiredis) to parse the Redis protocol. + * + * This class is intended to provide an optional low-overhead alternative for + * processing responses from Redis compared to the standard pure-PHP classes. + * Differences in speed when dealing with short inline responses are practically + * nonexistent, the actual speed boost is for big multibulk responses when this + * protocol processor can parse and return responses very fast. + * + * For instructions on how to build and install the phpiredis extension, please + * consult the repository of the project. + * + * The connection parameters supported by this class are: + * + * - scheme: it can be either 'redis', 'tcp' or 'unix'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - path: path of a UNIX domain socket when scheme is 'unix'. + * - timeout: timeout to perform the connection. + * - read_write_timeout: timeout of read / write operations. + * + * @link http://github.com/nrk/phpiredis + * + * @author Daniele Alessandri + */ +class PhpiredisSocketConnection extends AbstractConnection +{ + private $reader; + + /** + * {@inheritdoc} + */ + public function __construct(ParametersInterface $parameters) + { + $this->assertExtensions(); + + parent::__construct($parameters); + + $this->reader = $this->createReader(); + } + + /** + * Disconnects from the server and destroys the underlying resource and the + * protocol reader resource when PHP's garbage collector kicks in. + */ + public function __destruct() + { + phpiredis_reader_destroy($this->reader); + + parent::__destruct(); + } + + /** + * Checks if the socket and phpiredis extensions are loaded in PHP. + */ + protected function assertExtensions() + { + if (!extension_loaded('sockets')) { + throw new NotSupportedException( + 'The "sockets" extension is required by this connection backend.' + ); + } + + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + parent::assertParameters($parameters); + + if (isset($parameters->persistent)) { + throw new NotSupportedException( + 'Persistent connections are not supported by this connection backend.' + ); + } + + return $parameters; + } + + /** + * Creates a new instance of the protocol reader resource. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the underlying protocol reader resource. + * + * @return resource + */ + protected function getReader() + { + return $this->reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return \Closure + */ + private function getStatusHandler() + { + return function ($payload) { + return StatusResponse::get($payload); + }; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return \Closure + */ + protected function getErrorHandler() + { + return function ($payload) { + return new ErrorResponse($payload); + }; + } + + /** + * Helper method used to throw exceptions on socket errors. + */ + private function emitSocketError() + { + $errno = socket_last_error(); + $errstr = socket_strerror($errno); + + $this->disconnect(); + + $this->onConnectionError(trim($errstr), $errno); + } + + /** + * Gets the address of an host from connection parameters. + * + * @param ParametersInterface $parameters Parameters used to initialize the connection. + * + * @return string + */ + protected static function getAddress(ParametersInterface $parameters) + { + if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { + return $host; + } + + if ($host === $address = gethostbyname($host)) { + return false; + } + + return $address; + } + + /** + * {@inheritdoc} + */ + protected function createResource() + { + $parameters = $this->parameters; + + if ($parameters->scheme === 'unix') { + $address = $parameters->path; + $domain = AF_UNIX; + $protocol = 0; + } else { + if (false === $address = self::getAddress($parameters)) { + $this->onConnectionError("Cannot resolve the address of '$parameters->host'."); + } + + $domain = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? AF_INET6 : AF_INET; + $protocol = SOL_TCP; + } + + $socket = @socket_create($domain, SOCK_STREAM, $protocol); + + if (!is_resource($socket)) { + $this->emitSocketError(); + } + + $this->setSocketOptions($socket, $parameters); + $this->connectWithTimeout($socket, $address, $parameters); + + return $socket; + } + + /** + * Sets options on the socket resource from the connection parameters. + * + * @param resource $socket Socket resource. + * @param ParametersInterface $parameters Parameters used to initialize the connection. + */ + private function setSocketOptions($socket, ParametersInterface $parameters) + { + if ($parameters->scheme !== 'unix') { + if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) { + $this->emitSocketError(); + } + + if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { + $this->emitSocketError(); + } + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $timeoutSec = floor($rwtimeout); + $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000; + + $timeout = array( + 'sec' => $timeoutSec, + 'usec' => $timeoutUsec, + ); + + if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) { + $this->emitSocketError(); + } + + if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) { + $this->emitSocketError(); + } + } + } + + /** + * Opens the actual connection to the server with a timeout. + * + * @param resource $socket Socket resource. + * @param string $address IP address (DNS-resolved from hostname) + * @param ParametersInterface $parameters Parameters used to initialize the connection. + * + * @return string + */ + private function connectWithTimeout($socket, $address, ParametersInterface $parameters) + { + socket_set_nonblock($socket); + + if (@socket_connect($socket, $address, (int) $parameters->port) === false) { + $error = socket_last_error(); + + if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) { + $this->emitSocketError(); + } + } + + socket_set_block($socket); + + $null = null; + $selectable = array($socket); + + $timeout = (float) $parameters->timeout; + $timeoutSecs = floor($timeout); + $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000; + + $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs); + + if ($selected === 2) { + $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED); + } + + if ($selected === 0) { + $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT); + } + + if ($selected === false) { + $this->emitSocketError(); + } + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (parent::connect() && $this->initCommands) { + foreach ($this->initCommands as $command) { + $this->executeCommand($command); + } + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->isConnected()) { + socket_close($this->getResource()); + parent::disconnect(); + } + } + + /** + * {@inheritdoc} + */ + protected function write($buffer) + { + $socket = $this->getResource(); + + while (($length = strlen($buffer)) > 0) { + $written = socket_write($socket, $buffer, $length); + + if ($length === $written) { + return; + } + + if ($written === false) { + $this->onConnectionError('Error while writing bytes to the server.'); + } + + $buffer = substr($buffer, $written); + } + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $reader = $this->reader; + + while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { + if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '' || $buffer === null) { + $this->emitSocketError(); + } + + phpiredis_reader_feed($reader, $buffer); + } + + if ($state === PHPIREDIS_READER_STATE_COMPLETE) { + return phpiredis_reader_get_reply($reader); + } else { + $this->onProtocolError(phpiredis_reader_get_error($reader)); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $arguments = $command->getArguments(); + array_unshift($arguments, $command->getId()); + + $this->write(phpiredis_format_command($arguments)); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->reader = $this->createReader(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/PhpiredisStreamConnection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/PhpiredisStreamConnection.php new file mode 100644 index 0000000..beb2357 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/PhpiredisStreamConnection.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Status as StatusResponse; + +/** + * This class provides the implementation of a Predis connection that uses PHP's + * streams for network communication and wraps the phpiredis C extension (PHP + * bindings for hiredis) to parse and serialize the Redis protocol. + * + * This class is intended to provide an optional low-overhead alternative for + * processing responses from Redis compared to the standard pure-PHP classes. + * Differences in speed when dealing with short inline responses are practically + * nonexistent, the actual speed boost is for big multibulk responses when this + * protocol processor can parse and return responses very fast. + * + * For instructions on how to build and install the phpiredis extension, please + * consult the repository of the project. + * + * The connection parameters supported by this class are: + * + * - scheme: it can be either 'redis', 'tcp' or 'unix'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - path: path of a UNIX domain socket when scheme is 'unix'. + * - timeout: timeout to perform the connection. + * - read_write_timeout: timeout of read / write operations. + * - async_connect: performs the connection asynchronously. + * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing. + * - persistent: the connection is left intact after a GC collection. + * + * @link https://github.com/nrk/phpiredis + * + * @author Daniele Alessandri + */ +class PhpiredisStreamConnection extends StreamConnection +{ + private $reader; + + /** + * {@inheritdoc} + */ + public function __construct(ParametersInterface $parameters) + { + $this->assertExtensions(); + + parent::__construct($parameters); + + $this->reader = $this->createReader(); + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + phpiredis_reader_destroy($this->reader); + + parent::__destruct(); + } + + /** + * Checks if the phpiredis extension is loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function tcpStreamInitializer(ParametersInterface $parameters) + { + $uri = "tcp://[{$parameters->host}]:{$parameters->port}"; + $flags = STREAM_CLIENT_CONNECT; + $socket = null; + + if (isset($parameters->async_connect) && (bool) $parameters->async_connect) { + $flags |= STREAM_CLIENT_ASYNC_CONNECT; + } + + if (isset($parameters->persistent) && (bool) $parameters->persistent) { + $flags |= STREAM_CLIENT_PERSISTENT; + $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path"; + } + + $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags); + + if (!$resource) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + + $timeout = array( + 'sec' => $timeoutSeconds = floor($rwtimeout), + 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000, + ); + + $socket = $socket ?: socket_import_stream($resource); + @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout); + @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout); + } + + if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { + $socket = $socket ?: socket_import_stream($resource); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); + } + + return $resource; + } + + /** + * Creates a new instance of the protocol reader resource. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the underlying protocol reader resource. + * + * @return resource + */ + protected function getReader() + { + return $this->reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return \Closure + */ + protected function getStatusHandler() + { + return function ($payload) { + return StatusResponse::get($payload); + }; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return \Closure + */ + protected function getErrorHandler() + { + return function ($errorMessage) { + return new ErrorResponse($errorMessage); + }; + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $reader = $this->reader; + + while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) { + $buffer = stream_socket_recvfrom($socket, 4096); + + if ($buffer === false || $buffer === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + phpiredis_reader_feed($reader, $buffer); + } + + if ($state === PHPIREDIS_READER_STATE_COMPLETE) { + return phpiredis_reader_get_reply($reader); + } else { + $this->onProtocolError(phpiredis_reader_get_error($reader)); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $arguments = $command->getArguments(); + array_unshift($arguments, $command->getId()); + + $this->write(phpiredis_format_command($arguments)); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->reader = $this->createReader(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/StreamConnection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/StreamConnection.php new file mode 100644 index 0000000..ed6540b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/StreamConnection.php @@ -0,0 +1,292 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Status as StatusResponse; + +/** + * Standard connection to Redis servers implemented on top of PHP's streams. + * The connection parameters supported by this class are:. + * + * - scheme: it can be either 'redis', 'tcp' or 'unix'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - path: path of a UNIX domain socket when scheme is 'unix'. + * - timeout: timeout to perform the connection. + * - read_write_timeout: timeout of read / write operations. + * - async_connect: performs the connection asynchronously. + * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing. + * - persistent: the connection is left intact after a GC collection. + * + * @author Daniele Alessandri + */ +class StreamConnection extends AbstractConnection +{ + /** + * Disconnects from the server and destroys the underlying resource when the + * garbage collector kicks in only if the connection has not been marked as + * persistent. + */ + public function __destruct() + { + if (isset($this->parameters->persistent) && $this->parameters->persistent) { + return; + } + + $this->disconnect(); + } + + /** + * {@inheritdoc} + */ + protected function createResource() + { + switch ($this->parameters->scheme) { + case 'tcp': + case 'redis': + return $this->tcpStreamInitializer($this->parameters); + + case 'unix': + return $this->unixStreamInitializer($this->parameters); + + default: + throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'."); + } + } + + /** + * Initializes a TCP stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function tcpStreamInitializer(ParametersInterface $parameters) + { + if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $uri = "tcp://$parameters->host:$parameters->port"; + } else { + $uri = "tcp://[$parameters->host]:$parameters->port"; + } + + $flags = STREAM_CLIENT_CONNECT; + + if (isset($parameters->async_connect) && (bool) $parameters->async_connect) { + $flags |= STREAM_CLIENT_ASYNC_CONNECT; + } + + if (isset($parameters->persistent) && (bool) $parameters->persistent) { + $flags |= STREAM_CLIENT_PERSISTENT; + $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path"; + } + + $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags); + + if (!$resource) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + $timeoutSeconds = floor($rwtimeout); + $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; + stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds); + } + + if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) { + $socket = socket_import_stream($resource); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay); + } + + return $resource; + } + + /** + * Initializes a UNIX stream resource. + * + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @return resource + */ + protected function unixStreamInitializer(ParametersInterface $parameters) + { + if (!isset($parameters->path)) { + throw new InvalidArgumentException('Missing UNIX domain socket path.'); + } + + $uri = "unix://{$parameters->path}"; + $flags = STREAM_CLIENT_CONNECT; + + if ((bool) $parameters->persistent) { + $flags |= STREAM_CLIENT_PERSISTENT; + } + + $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags); + + if (!$resource) { + $this->onConnectionError(trim($errstr), $errno); + } + + if (isset($parameters->read_write_timeout)) { + $rwtimeout = (float) $parameters->read_write_timeout; + $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1; + $timeoutSeconds = floor($rwtimeout); + $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000; + stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds); + } + + return $resource; + } + + /** + * {@inheritdoc} + */ + public function connect() + { + if (parent::connect() && $this->initCommands) { + foreach ($this->initCommands as $command) { + $this->executeCommand($command); + } + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->isConnected()) { + fclose($this->getResource()); + parent::disconnect(); + } + } + + /** + * Performs a write operation over the stream of the buffer containing a + * command serialized with the Redis wire protocol. + * + * @param string $buffer Representation of a command in the Redis wire protocol. + */ + protected function write($buffer) + { + $socket = $this->getResource(); + + while (($length = strlen($buffer)) > 0) { + $written = @fwrite($socket, $buffer); + + if ($length === $written) { + return; + } + + if ($written === false || $written === 0) { + $this->onConnectionError('Error while writing bytes to the server.'); + } + + $buffer = substr($buffer, $written); + } + } + + /** + * {@inheritdoc} + */ + public function read() + { + $socket = $this->getResource(); + $chunk = fgets($socket); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading line from the server.'); + } + + $prefix = $chunk[0]; + $payload = substr($chunk, 1, -2); + + switch ($prefix) { + case '+': + return StatusResponse::get($payload); + + case '$': + $size = (int) $payload; + + if ($size === -1) { + return; + } + + $bulkData = ''; + $bytesLeft = ($size += 2); + + do { + $chunk = fread($socket, min($bytesLeft, 4096)); + + if ($chunk === false || $chunk === '') { + $this->onConnectionError('Error while reading bytes from the server.'); + } + + $bulkData .= $chunk; + $bytesLeft = $size - strlen($bulkData); + } while ($bytesLeft > 0); + + return substr($bulkData, 0, -2); + + case '*': + $count = (int) $payload; + + if ($count === -1) { + return; + } + + $multibulk = array(); + + for ($i = 0; $i < $count; ++$i) { + $multibulk[$i] = $this->read(); + } + + return $multibulk; + + case ':': + return (int) $payload; + + case '-': + return new ErrorResponse($payload); + + default: + $this->onProtocolError("Unknown response prefix: '$prefix'."); + + return; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $commandID = $command->getId(); + $arguments = $command->getArguments(); + + $cmdlen = strlen($commandID); + $reqlen = count($arguments) + 1; + + $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n"; + + for ($i = 0, $reqlen--; $i < $reqlen; ++$i) { + $argument = $arguments[$i]; + $arglen = strlen($argument); + $buffer .= "\${$arglen}\r\n{$argument}\r\n"; + } + + $this->write($buffer); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/WebdisConnection.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/WebdisConnection.php new file mode 100644 index 0000000..9cff9d0 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Connection/WebdisConnection.php @@ -0,0 +1,353 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Connection; + +use Predis\Command\CommandInterface; +use Predis\NotSupportedException; +use Predis\Protocol\ProtocolException; +use Predis\Response\Error as ErrorResponse; +use Predis\Response\Status as StatusResponse; + +/** + * This class implements a Predis connection that actually talks with Webdis + * instead of connecting directly to Redis. It relies on the cURL extension to + * communicate with the web server and the phpiredis extension to parse the + * protocol for responses returned in the http response bodies. + * + * Some features are not yet available or they simply cannot be implemented: + * - Pipelining commands. + * - Publish / Subscribe. + * - MULTI / EXEC transactions (not yet supported by Webdis). + * + * The connection parameters supported by this class are: + * + * - scheme: must be 'http'. + * - host: hostname or IP address of the server. + * - port: TCP port of the server. + * - timeout: timeout to perform the connection. + * - user: username for authentication. + * - pass: password for authentication. + * + * @link http://webd.is + * @link http://github.com/nicolasff/webdis + * @link http://github.com/seppo0010/phpiredis + * + * @author Daniele Alessandri + */ +class WebdisConnection implements NodeConnectionInterface +{ + private $parameters; + private $resource; + private $reader; + + /** + * @param ParametersInterface $parameters Initialization parameters for the connection. + * + * @throws \InvalidArgumentException + */ + public function __construct(ParametersInterface $parameters) + { + $this->assertExtensions(); + + if ($parameters->scheme !== 'http') { + throw new \InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'."); + } + + $this->parameters = $parameters; + + $this->resource = $this->createCurl(); + $this->reader = $this->createReader(); + } + + /** + * Frees the underlying cURL and protocol reader resources when the garbage + * collector kicks in. + */ + public function __destruct() + { + curl_close($this->resource); + phpiredis_reader_destroy($this->reader); + } + + /** + * Helper method used to throw on unsupported methods. + * + * @param string $method Name of the unsupported method. + * + * @throws NotSupportedException + */ + private function throwNotSupportedException($method) + { + $class = __CLASS__; + throw new NotSupportedException("The method $class::$method() is not supported."); + } + + /** + * Checks if the cURL and phpiredis extensions are loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('curl')) { + throw new NotSupportedException( + 'The "curl" extension is required by this connection backend.' + ); + } + + if (!extension_loaded('phpiredis')) { + throw new NotSupportedException( + 'The "phpiredis" extension is required by this connection backend.' + ); + } + } + + /** + * Initializes cURL. + * + * @return resource + */ + private function createCurl() + { + $parameters = $this->getParameters(); + + if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) { + $host = "[$host]"; + } + + $options = array( + CURLOPT_FAILONERROR => true, + CURLOPT_CONNECTTIMEOUT_MS => $parameters->timeout * 1000, + CURLOPT_URL => "$parameters->scheme://$host:$parameters->port", + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_POST => true, + CURLOPT_WRITEFUNCTION => array($this, 'feedReader'), + ); + + if (isset($parameters->user, $parameters->pass)) { + $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}"; + } + + curl_setopt_array($resource = curl_init(), $options); + + return $resource; + } + + /** + * Initializes the phpiredis protocol reader. + * + * @return resource + */ + private function createReader() + { + $reader = phpiredis_reader_create(); + + phpiredis_reader_set_status_handler($reader, $this->getStatusHandler()); + phpiredis_reader_set_error_handler($reader, $this->getErrorHandler()); + + return $reader; + } + + /** + * Returns the handler used by the protocol reader for inline responses. + * + * @return \Closure + */ + protected function getStatusHandler() + { + return function ($payload) { + return StatusResponse::get($payload); + }; + } + + /** + * Returns the handler used by the protocol reader for error responses. + * + * @return \Closure + */ + protected function getErrorHandler() + { + return function ($payload) { + return new ErrorResponse($payload); + }; + } + + /** + * Feeds the phpredis reader resource with the data read from the network. + * + * @param resource $resource Reader resource. + * @param string $buffer Buffer of data read from a connection. + * + * @return int + */ + protected function feedReader($resource, $buffer) + { + phpiredis_reader_feed($this->reader, $buffer); + + return strlen($buffer); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + // NOOP + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return true; + } + + /** + * Checks if the specified command is supported by this connection class. + * + * @param CommandInterface $command Command instance. + * + * @throws NotSupportedException + * + * @return string + */ + protected function getCommandId(CommandInterface $command) + { + switch ($commandID = $command->getId()) { + case 'AUTH': + case 'SELECT': + case 'MULTI': + case 'EXEC': + case 'WATCH': + case 'UNWATCH': + case 'DISCARD': + case 'MONITOR': + throw new NotSupportedException("Command '$commandID' is not allowed by Webdis."); + + default: + return $commandID; + } + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + $resource = $this->resource; + $commandId = $this->getCommandId($command); + + if ($arguments = $command->getArguments()) { + $arguments = implode('/', array_map('urlencode', $arguments)); + $serializedCommand = "$commandId/$arguments.raw"; + } else { + $serializedCommand = "$commandId.raw"; + } + + curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand); + + if (curl_exec($resource) === false) { + $error = curl_error($resource); + $errno = curl_errno($resource); + + throw new ConnectionException($this, trim($error), $errno); + } + + if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) { + throw new ProtocolException($this, phpiredis_reader_get_error($this->reader)); + } + + return phpiredis_reader_get_reply($this->reader); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function addConnectCommand(CommandInterface $command) + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function read() + { + $this->throwNotSupportedException(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return "{$this->parameters->host}:{$this->parameters->port}"; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array('parameters'); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + + $this->resource = $this->createCurl(); + $this->reader = $this->createReader(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Monitor/Consumer.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Monitor/Consumer.php new file mode 100644 index 0000000..d10bad1 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Monitor/Consumer.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Monitor; + +use Predis\ClientInterface; +use Predis\Connection\AggregateConnectionInterface; +use Predis\NotSupportedException; + +/** + * Redis MONITOR consumer. + * + * @author Daniele Alessandri + */ +class Consumer implements \Iterator +{ + private $client; + private $valid; + private $position; + + /** + * @param ClientInterface $client Client instance used by the consumer. + */ + public function __construct(ClientInterface $client) + { + $this->assertClient($client); + + $this->client = $client; + + $this->start(); + } + + /** + * Automatically stops the consumer when the garbage collector kicks in. + */ + public function __destruct() + { + $this->stop(); + } + + /** + * Checks if the passed client instance satisfies the required conditions + * needed to initialize a monitor consumer. + * + * @param ClientInterface $client Client instance used by the consumer. + * + * @throws NotSupportedException + */ + private function assertClient(ClientInterface $client) + { + if ($client->getConnection() instanceof AggregateConnectionInterface) { + throw new NotSupportedException( + 'Cannot initialize a monitor consumer over aggregate connections.' + ); + } + + if ($client->getProfile()->supportsCommand('MONITOR') === false) { + throw new NotSupportedException("The current profile does not support 'MONITOR'."); + } + } + + /** + * Initializes the consumer and sends the MONITOR command to the server. + */ + protected function start() + { + $this->client->executeCommand( + $this->client->createCommand('MONITOR') + ); + $this->valid = true; + } + + /** + * Stops the consumer. Internally this is done by disconnecting from server + * since there is no way to terminate the stream initialized by MONITOR. + */ + public function stop() + { + $this->client->disconnect(); + $this->valid = false; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + // NOOP + } + + /** + * Returns the last message payload retrieved from the server. + * + * @return Object + */ + public function current() + { + return $this->getValue(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->position; + } + + /** + * {@inheritdoc} + */ + public function next() + { + ++$this->position; + } + + /** + * Checks if the the consumer is still in a valid state to continue. + * + * @return bool + */ + public function valid() + { + return $this->valid; + } + + /** + * Waits for a new message from the server generated by MONITOR and returns + * it when available. + * + * @return Object + */ + private function getValue() + { + $database = 0; + $client = null; + $event = $this->client->getConnection()->read(); + + $callback = function ($matches) use (&$database, &$client) { + if (2 === $count = count($matches)) { + // Redis <= 2.4 + $database = (int) $matches[1]; + } + + if (4 === $count) { + // Redis >= 2.6 + $database = (int) $matches[2]; + $client = $matches[3]; + } + + return ' '; + }; + + $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1); + @list($timestamp, $command, $arguments) = explode(' ', $event, 3); + + return (object) array( + 'timestamp' => (float) $timestamp, + 'database' => $database, + 'client' => $client, + 'command' => substr($command, 1, -1), + 'arguments' => $arguments, + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/NotSupportedException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/NotSupportedException.php new file mode 100644 index 0000000..be82aba --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/NotSupportedException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +/** + * Exception class thrown when trying to use features not supported by certain + * classes or abstractions of Predis. + * + * @author Daniele Alessandri + */ +class NotSupportedException extends PredisException +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/Atomic.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/Atomic.php new file mode 100644 index 0000000..1c9c92a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/Atomic.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\ClientException; +use Predis\ClientInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ResponseInterface; +use Predis\Response\ServerException; + +/** + * Command pipeline wrapped into a MULTI / EXEC transaction. + * + * @author Daniele Alessandri + */ +class Atomic extends Pipeline +{ + /** + * {@inheritdoc} + */ + public function __construct(ClientInterface $client) + { + if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) { + throw new ClientException( + "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'." + ); + } + + parent::__construct($client); + } + + /** + * {@inheritdoc} + */ + protected function getConnection() + { + $connection = $this->getClient()->getConnection(); + + if (!$connection instanceof NodeConnectionInterface) { + $class = __CLASS__; + + throw new ClientException("The class '$class' does not support aggregate connections."); + } + + return $connection; + } + + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + $profile = $this->getClient()->getProfile(); + $connection->executeCommand($profile->createCommand('multi')); + + foreach ($commands as $command) { + $connection->writeRequest($command); + } + + foreach ($commands as $command) { + $response = $connection->readResponse($command); + + if ($response instanceof ErrorResponseInterface) { + $connection->executeCommand($profile->createCommand('discard')); + throw new ServerException($response->getMessage()); + } + } + + $executed = $connection->executeCommand($profile->createCommand('exec')); + + if (!isset($executed)) { + // TODO: should be throwing a more appropriate exception. + throw new ClientException( + 'The underlying transaction has been aborted by the server.' + ); + } + + if (count($executed) !== count($commands)) { + $expected = count($commands); + $received = count($executed); + + throw new ClientException( + "Invalid number of responses [expected $expected, received $received]." + ); + } + + $responses = array(); + $sizeOfPipe = count($commands); + $exceptions = $this->throwServerExceptions(); + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + $response = $executed[$i]; + + if (!$response instanceof ResponseInterface) { + $responses[] = $command->parseResponse($response); + } elseif ($response instanceof ErrorResponseInterface && $exceptions) { + $this->exception($connection, $response); + } else { + $responses[] = $response; + } + + unset($executed[$i]); + } + + return $responses; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/ConnectionErrorProof.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/ConnectionErrorProof.php new file mode 100644 index 0000000..d3bc732 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/ConnectionErrorProof.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\CommunicationException; +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Connection\NodeConnectionInterface; +use Predis\NotSupportedException; + +/** + * Command pipeline that does not throw exceptions on connection errors, but + * returns the exception instances as the rest of the response elements. + * + * @todo Awful naming! + * + * @author Daniele Alessandri + */ +class ConnectionErrorProof extends Pipeline +{ + /** + * {@inheritdoc} + */ + protected function getConnection() + { + return $this->getClient()->getConnection(); + } + + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + if ($connection instanceof NodeConnectionInterface) { + return $this->executeSingleNode($connection, $commands); + } elseif ($connection instanceof ClusterInterface) { + return $this->executeCluster($connection, $commands); + } else { + $class = get_class($connection); + + throw new NotSupportedException("The connection class '$class' is not supported."); + } + } + + /** + * {@inheritdoc} + */ + protected function executeSingleNode(NodeConnectionInterface $connection, \SplQueue $commands) + { + $responses = array(); + $sizeOfPipe = count($commands); + + foreach ($commands as $command) { + try { + $connection->writeRequest($command); + } catch (CommunicationException $exception) { + return array_fill(0, $sizeOfPipe, $exception); + } + } + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + + try { + $responses[$i] = $connection->readResponse($command); + } catch (CommunicationException $exception) { + $add = count($commands) - count($responses); + $responses = array_merge($responses, array_fill(0, $add, $exception)); + + break; + } + } + + return $responses; + } + + /** + * {@inheritdoc} + */ + protected function executeCluster(ClusterInterface $connection, \SplQueue $commands) + { + $responses = array(); + $sizeOfPipe = count($commands); + $exceptions = array(); + + foreach ($commands as $command) { + $cmdConnection = $connection->getConnection($command); + + if (isset($exceptions[spl_object_hash($cmdConnection)])) { + continue; + } + + try { + $cmdConnection->writeRequest($command); + } catch (CommunicationException $exception) { + $exceptions[spl_object_hash($cmdConnection)] = $exception; + } + } + + for ($i = 0; $i < $sizeOfPipe; ++$i) { + $command = $commands->dequeue(); + + $cmdConnection = $connection->getConnection($command); + $connectionHash = spl_object_hash($cmdConnection); + + if (isset($exceptions[$connectionHash])) { + $responses[$i] = $exceptions[$connectionHash]; + continue; + } + + try { + $responses[$i] = $cmdConnection->readResponse($command); + } catch (CommunicationException $exception) { + $responses[$i] = $exception; + $exceptions[$connectionHash] = $exception; + } + } + + return $responses; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/FireAndForget.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/FireAndForget.php new file mode 100644 index 0000000..95a062b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/FireAndForget.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\Connection\ConnectionInterface; + +/** + * Command pipeline that writes commands to the servers but discards responses. + * + * @author Daniele Alessandri + */ +class FireAndForget extends Pipeline +{ + /** + * {@inheritdoc} + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + while (!$commands->isEmpty()) { + $connection->writeRequest($commands->dequeue()); + } + + $connection->disconnect(); + + return array(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/Pipeline.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/Pipeline.php new file mode 100644 index 0000000..cf9c59e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Pipeline/Pipeline.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Pipeline; + +use Predis\ClientContextInterface; +use Predis\ClientException; +use Predis\ClientInterface; +use Predis\Command\CommandInterface; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Connection\ConnectionInterface; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use Predis\Response\ResponseInterface; +use Predis\Response\ServerException; + +/** + * Implementation of a command pipeline in which write and read operations of + * Redis commands are pipelined to alleviate the effects of network round-trips. + * + * {@inheritdoc} + * + * @author Daniele Alessandri + */ +class Pipeline implements ClientContextInterface +{ + private $client; + private $pipeline; + + private $responses = array(); + private $running = false; + + /** + * @param ClientInterface $client Client instance used by the context. + */ + public function __construct(ClientInterface $client) + { + $this->client = $client; + $this->pipeline = new \SplQueue(); + } + + /** + * Queues a command into the pipeline buffer. + * + * @param string $method Command ID. + * @param array $arguments Arguments for the command. + * + * @return $this + */ + public function __call($method, $arguments) + { + $command = $this->client->createCommand($method, $arguments); + $this->recordCommand($command); + + return $this; + } + + /** + * Queues a command instance into the pipeline buffer. + * + * @param CommandInterface $command Command to be queued in the buffer. + */ + protected function recordCommand(CommandInterface $command) + { + $this->pipeline->enqueue($command); + } + + /** + * Queues a command instance into the pipeline buffer. + * + * @param CommandInterface $command Command instance to be queued in the buffer. + * + * @return $this + */ + public function executeCommand(CommandInterface $command) + { + $this->recordCommand($command); + + return $this; + } + + /** + * Throws an exception on -ERR responses returned by Redis. + * + * @param ConnectionInterface $connection Redis connection that returned the error. + * @param ErrorResponseInterface $response Instance of the error response. + * + * @throws ServerException + */ + protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response) + { + $connection->disconnect(); + $message = $response->getMessage(); + + throw new ServerException($message); + } + + /** + * Returns the underlying connection to be used by the pipeline. + * + * @return ConnectionInterface + */ + protected function getConnection() + { + $connection = $this->getClient()->getConnection(); + + if ($connection instanceof ReplicationInterface) { + $connection->switchTo('master'); + } + + return $connection; + } + + /** + * Implements the logic to flush the queued commands and read the responses + * from the current connection. + * + * @param ConnectionInterface $connection Current connection instance. + * @param \SplQueue $commands Queued commands. + * + * @return array + */ + protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands) + { + foreach ($commands as $command) { + $connection->writeRequest($command); + } + + $responses = array(); + $exceptions = $this->throwServerExceptions(); + + while (!$commands->isEmpty()) { + $command = $commands->dequeue(); + $response = $connection->readResponse($command); + + if (!$response instanceof ResponseInterface) { + $responses[] = $command->parseResponse($response); + } elseif ($response instanceof ErrorResponseInterface && $exceptions) { + $this->exception($connection, $response); + } else { + $responses[] = $response; + } + } + + return $responses; + } + + /** + * Flushes the buffer holding all of the commands queued so far. + * + * @param bool $send Specifies if the commands in the buffer should be sent to Redis. + * + * @return $this + */ + public function flushPipeline($send = true) + { + if ($send && !$this->pipeline->isEmpty()) { + $responses = $this->executePipeline($this->getConnection(), $this->pipeline); + $this->responses = array_merge($this->responses, $responses); + } else { + $this->pipeline = new \SplQueue(); + } + + return $this; + } + + /** + * Marks the running status of the pipeline. + * + * @param bool $bool Sets the running status of the pipeline. + * + * @throws ClientException + */ + private function setRunning($bool) + { + if ($bool && $this->running) { + throw new ClientException('The current pipeline context is already being executed.'); + } + + $this->running = $bool; + } + + /** + * Handles the actual execution of the whole pipeline. + * + * @param mixed $callable Optional callback for execution. + * + * @throws \Exception + * @throws \InvalidArgumentException + * + * @return array + */ + public function execute($callable = null) + { + if ($callable && !is_callable($callable)) { + throw new \InvalidArgumentException('The argument must be a callable object.'); + } + + $exception = null; + $this->setRunning(true); + + try { + if ($callable) { + call_user_func($callable, $this); + } + + $this->flushPipeline(); + } catch (\Exception $exception) { + // NOOP + } + + $this->setRunning(false); + + if ($exception) { + throw $exception; + } + + return $this->responses; + } + + /** + * Returns if the pipeline should throw exceptions on server errors. + * + * @return bool + */ + protected function throwServerExceptions() + { + return (bool) $this->client->getOptions()->exceptions; + } + + /** + * Returns the underlying client instance used by the pipeline object. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->client; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/PredisException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/PredisException.php new file mode 100644 index 0000000..122bde1 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/PredisException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis; + +/** + * Base exception class for Predis-related errors. + * + * @author Daniele Alessandri + */ +abstract class PredisException extends \Exception +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/Factory.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/Factory.php new file mode 100644 index 0000000..bcee3c2 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/Factory.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +use Predis\ClientException; + +/** + * Factory class for creating profile instances from strings. + * + * @author Daniele Alessandri + */ +final class Factory +{ + private static $profiles = array( + '2.0' => 'Predis\Profile\RedisVersion200', + '2.2' => 'Predis\Profile\RedisVersion220', + '2.4' => 'Predis\Profile\RedisVersion240', + '2.6' => 'Predis\Profile\RedisVersion260', + '2.8' => 'Predis\Profile\RedisVersion280', + '3.0' => 'Predis\Profile\RedisVersion300', + 'dev' => 'Predis\Profile\RedisUnstable', + 'default' => 'Predis\Profile\RedisVersion300', + ); + + /** + * + */ + private function __construct() + { + // NOOP + } + + /** + * Returns the default server profile. + * + * @return ProfileInterface + */ + public static function getDefault() + { + return self::get('default'); + } + + /** + * Returns the development server profile. + * + * @return ProfileInterface + */ + public static function getDevelopment() + { + return self::get('dev'); + } + + /** + * Registers a new server profile. + * + * @param string $alias Profile version or alias. + * @param string $class FQN of a class implementing Predis\Profile\ProfileInterface. + * + * @throws \InvalidArgumentException + */ + public static function define($alias, $class) + { + $reflection = new \ReflectionClass($class); + + if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) { + throw new \InvalidArgumentException("The class '$class' is not a valid profile class."); + } + + self::$profiles[$alias] = $class; + } + + /** + * Returns the specified server profile. + * + * @param string $version Profile version or alias. + * + * @throws ClientException + * + * @return ProfileInterface + */ + public static function get($version) + { + if (!isset(self::$profiles[$version])) { + throw new ClientException("Unknown server profile: '$version'."); + } + + $profile = self::$profiles[$version]; + + return new $profile(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/ProfileInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/ProfileInterface.php new file mode 100644 index 0000000..abe71aa --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/ProfileInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +use Predis\Command\CommandInterface; + +/** + * A profile defines all the features and commands supported by certain versions + * of Redis. Instances of Predis\Client should use a server profile matching the + * version of Redis being used. + * + * @author Daniele Alessandri + */ +interface ProfileInterface +{ + /** + * Returns the profile version corresponding to the Redis version. + * + * @return string + */ + public function getVersion(); + + /** + * Checks if the profile supports the specified command. + * + * @param string $commandID Command ID. + * + * @return bool + */ + public function supportsCommand($commandID); + + /** + * Checks if the profile supports the specified list of commands. + * + * @param array $commandIDs List of command IDs. + * + * @return string + */ + public function supportsCommands(array $commandIDs); + + /** + * Creates a new command instance. + * + * @param string $commandID Command ID. + * @param array $arguments Arguments for the command. + * + * @return CommandInterface + */ + public function createCommand($commandID, array $arguments = array()); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisProfile.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisProfile.php new file mode 100644 index 0000000..3ef3168 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisProfile.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +use Predis\ClientException; +use Predis\Command\Processor\ProcessorInterface; + +/** + * Base class implementing common functionalities for Redis server profiles. + * + * @author Daniele Alessandri + */ +abstract class RedisProfile implements ProfileInterface +{ + private $commands; + private $processor; + + /** + * + */ + public function __construct() + { + $this->commands = $this->getSupportedCommands(); + } + + /** + * Returns a map of all the commands supported by the profile and their + * actual PHP classes. + * + * @return array + */ + abstract protected function getSupportedCommands(); + + /** + * {@inheritdoc} + */ + public function supportsCommand($commandID) + { + return isset($this->commands[strtoupper($commandID)]); + } + + /** + * {@inheritdoc} + */ + public function supportsCommands(array $commandIDs) + { + foreach ($commandIDs as $commandID) { + if (!$this->supportsCommand($commandID)) { + return false; + } + } + + return true; + } + + /** + * Returns the fully-qualified name of a class representing the specified + * command ID registered in the current server profile. + * + * @param string $commandID Command ID. + * + * @return string|null + */ + public function getCommandClass($commandID) + { + if (isset($this->commands[$commandID = strtoupper($commandID)])) { + return $this->commands[$commandID]; + } + } + + /** + * {@inheritdoc} + */ + public function createCommand($commandID, array $arguments = array()) + { + $commandID = strtoupper($commandID); + + if (!isset($this->commands[$commandID])) { + throw new ClientException("Command '$commandID' is not a registered Redis command."); + } + + $commandClass = $this->commands[$commandID]; + $command = new $commandClass(); + $command->setArguments($arguments); + + if (isset($this->processor)) { + $this->processor->process($command); + } + + return $command; + } + + /** + * Defines a new command in the server profile. + * + * @param string $commandID Command ID. + * @param string $class Fully-qualified name of a Predis\Command\CommandInterface. + * + * @throws \InvalidArgumentException + */ + public function defineCommand($commandID, $class) + { + $reflection = new \ReflectionClass($class); + + if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) { + throw new \InvalidArgumentException("The class '$class' is not a valid command class."); + } + + $this->commands[strtoupper($commandID)] = $class; + } + + /** + * {@inheritdoc} + */ + public function setProcessor(ProcessorInterface $processor = null) + { + $this->processor = $processor; + } + + /** + * {@inheritdoc} + */ + public function getProcessor() + { + return $this->processor; + } + + /** + * Returns the version of server profile as its string representation. + * + * @return string + */ + public function __toString() + { + return $this->getVersion(); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisUnstable.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisUnstable.php new file mode 100644 index 0000000..cf6174f --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisUnstable.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for the current unstable version of Redis. + * + * @author Daniele Alessandri + */ +class RedisUnstable extends RedisVersion300 +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '3.2'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array_merge(parent::getSupportedCommands(), array( + /* ---------------- Redis 3.2 ---------------- */ + + /* commands operating on hashes */ + 'HSTRLEN' => 'Predis\Command\HashStringLength', + )); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion200.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion200.php new file mode 100644 index 0000000..234d53c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion200.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.0. + * + * @author Daniele Alessandri + */ +class RedisVersion200 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.0'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfo', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion220.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion220.php new file mode 100644 index 0000000..899014e --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion220.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.2. + * + * @author Daniele Alessandri + */ +class RedisVersion220 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.2'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfo', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion240.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion240.php new file mode 100644 index 0000000..0856c37 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion240.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.4. + * + * @author Daniele Alessandri + */ +class RedisVersion240 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.4'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfo', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion260.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion260.php new file mode 100644 index 0000000..ba5084a --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion260.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.6. + * + * @author Daniele Alessandri + */ +class RedisVersion260 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.6'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion280.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion280.php new file mode 100644 index 0000000..ea17e68 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion280.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 2.8. + * + * @author Daniele Alessandri + */ +class RedisVersion280 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '2.8'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + + /* ---------------- Redis 2.8 ---------------- */ + + /* commands operating on the key space */ + 'SCAN' => 'Predis\Command\KeyScan', + + /* commands operating on string values */ + 'BITPOS' => 'Predis\Command\StringBitPos', + + /* commands operating on sets */ + 'SSCAN' => 'Predis\Command\SetScan', + + /* commands operating on sorted sets */ + 'ZSCAN' => 'Predis\Command\ZSetScan', + 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', + 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', + 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', + 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', + + /* commands operating on hashes */ + 'HSCAN' => 'Predis\Command\HashScan', + + /* publish - subscribe */ + 'PUBSUB' => 'Predis\Command\PubSubPubsub', + + /* commands operating on HyperLogLog */ + 'PFADD' => 'Predis\Command\HyperLogLogAdd', + 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', + 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', + + /* remote server control commands */ + 'COMMAND' => 'Predis\Command\ServerCommand', + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion300.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion300.php new file mode 100644 index 0000000..8a2fac8 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Profile/RedisVersion300.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 3.0. + * + * @author Daniele Alessandri + */ +class RedisVersion300 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '3.0'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* commands operating on string values */ + 'PSETEX' => 'Predis\Command\StringPreciseSetExpire', + 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat', + 'BITOP' => 'Predis\Command\StringBitOp', + 'BITCOUNT' => 'Predis\Command\StringBitCount', + + /* commands operating on hashes */ + 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + + /* ---------------- Redis 2.8 ---------------- */ + + /* commands operating on the key space */ + 'SCAN' => 'Predis\Command\KeyScan', + + /* commands operating on string values */ + 'BITPOS' => 'Predis\Command\StringBitPos', + + /* commands operating on sets */ + 'SSCAN' => 'Predis\Command\SetScan', + + /* commands operating on sorted sets */ + 'ZSCAN' => 'Predis\Command\ZSetScan', + 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', + 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', + 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', + 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', + + /* commands operating on hashes */ + 'HSCAN' => 'Predis\Command\HashScan', + + /* publish - subscribe */ + 'PUBSUB' => 'Predis\Command\PubSubPubsub', + + /* commands operating on HyperLogLog */ + 'PFADD' => 'Predis\Command\HyperLogLogAdd', + 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', + 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', + + /* remote server control commands */ + 'COMMAND' => 'Predis\Command\ServerCommand', + + /* ---------------- Redis 3.0 ---------------- */ + + ); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ProtocolException.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ProtocolException.php new file mode 100644 index 0000000..6fe5d6d --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ProtocolException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\CommunicationException; + +/** + * Exception used to indentify errors encountered while parsing the Redis wire + * protocol. + * + * @author Daniele Alessandri + */ +class ProtocolException extends CommunicationException +{ +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ProtocolProcessorInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ProtocolProcessorInterface.php new file mode 100644 index 0000000..b34ea18 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ProtocolProcessorInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\Command\CommandInterface; +use Predis\Connection\CompositeConnectionInterface; + +/** + * Defines a pluggable protocol processor capable of serializing commands and + * deserializing responses into PHP objects directly from a connection. + * + * @author Daniele Alessandri + */ +interface ProtocolProcessorInterface +{ + /** + * Writes a request over a connection to Redis. + * + * @param CompositeConnectionInterface $connection Redis connection. + * @param CommandInterface $command Command instance. + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command); + + /** + * Reads a response from a connection to Redis. + * + * @param CompositeConnectionInterface $connection Redis connection. + * + * @return mixed + */ + public function read(CompositeConnectionInterface $connection); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/RequestSerializerInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/RequestSerializerInterface.php new file mode 100644 index 0000000..eef72a6 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/RequestSerializerInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\Command\CommandInterface; + +/** + * Defines a pluggable serializer for Redis commands. + * + * @author Daniele Alessandri + */ +interface RequestSerializerInterface +{ + /** + * Serializes a Redis command. + * + * @param CommandInterface $command Redis command. + * + * @return string + */ + public function serialize(CommandInterface $command); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ResponseReaderInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ResponseReaderInterface.php new file mode 100644 index 0000000..86a7bdc --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/ResponseReaderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol; + +use Predis\Connection\CompositeConnectionInterface; + +/** + * Defines a pluggable reader capable of parsing responses returned by Redis and + * deserializing them to PHP objects. + * + * @author Daniele Alessandri + */ +interface ResponseReaderInterface +{ + /** + * Reads a response from a connection to Redis. + * + * @param CompositeConnectionInterface $connection Redis connection. + * + * @return mixed + */ + public function read(CompositeConnectionInterface $connection); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/CompositeProtocolProcessor.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/CompositeProtocolProcessor.php new file mode 100644 index 0000000..ea85ed3 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/CompositeProtocolProcessor.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text; + +use Predis\Command\CommandInterface; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolProcessorInterface; +use Predis\Protocol\RequestSerializerInterface; +use Predis\Protocol\ResponseReaderInterface; + +/** + * Composite protocol processor for the standard Redis wire protocol using + * pluggable handlers to serialize requests and deserialize responses. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class CompositeProtocolProcessor implements ProtocolProcessorInterface +{ + /* + * @var RequestSerializerInterface + */ + protected $serializer; + + /* + * @var ResponseReaderInterface + */ + protected $reader; + + /** + * @param RequestSerializerInterface $serializer Request serializer. + * @param ResponseReaderInterface $reader Response reader. + */ + public function __construct( + RequestSerializerInterface $serializer = null, + ResponseReaderInterface $reader = null + ) { + $this->setRequestSerializer($serializer ?: new RequestSerializer()); + $this->setResponseReader($reader ?: new ResponseReader()); + } + + /** + * {@inheritdoc} + */ + public function write(CompositeConnectionInterface $connection, CommandInterface $command) + { + $connection->writeBuffer($this->serializer->serialize($command)); + } + + /** + * {@inheritdoc} + */ + public function read(CompositeConnectionInterface $connection) + { + return $this->reader->read($connection); + } + + /** + * Sets the request serializer used by the protocol processor. + * + * @param RequestSerializerInterface $serializer Request serializer. + */ + public function setRequestSerializer(RequestSerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * Returns the request serializer used by the protocol processor. + * + * @return RequestSerializerInterface + */ + public function getRequestSerializer() + { + return $this->serializer; + } + + /** + * Sets the response reader used by the protocol processor. + * + * @param ResponseReaderInterface $reader Response reader. + */ + public function setResponseReader(ResponseReaderInterface $reader) + { + $this->reader = $reader; + } + + /** + * Returns the Response reader used by the protocol processor. + * + * @return ResponseReaderInterface + */ + public function getResponseReader() + { + return $this->reader; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/BulkResponse.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/BulkResponse.php new file mode 100644 index 0000000..5b0bf3c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/BulkResponse.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; + +/** + * Handler for the bulk response type in the standard Redis wire protocol. + * It translates the payload to a string or a NULL. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class BulkResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + $length = (int) $payload; + + if ("$length" !== $payload) { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid length for a bulk response." + )); + } + + if ($length >= 0) { + return substr($connection->readBuffer($length + 2), 0, -2); + } + + if ($length == -1) { + return; + } + + CommunicationException::handle(new ProtocolException( + $connection, "Value '$payload' is not a valid length for a bulk response." + )); + + return; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/ErrorResponse.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/ErrorResponse.php new file mode 100644 index 0000000..3e18b7b --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/ErrorResponse.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\Connection\CompositeConnectionInterface; +use Predis\Response\Error; + +/** + * Handler for the error response type in the standard Redis wire protocol. + * It translates the payload to a complex response object for Predis. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class ErrorResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + return new Error($payload); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/IntegerResponse.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/IntegerResponse.php new file mode 100644 index 0000000..4639d77 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/IntegerResponse.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; + +/** + * Handler for the integer response type in the standard Redis wire protocol. + * It translates the payload an integer or NULL. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class IntegerResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + if (is_numeric($payload)) { + return (int) $payload; + } + + if ($payload !== 'nil') { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid numeric response." + )); + } + + return; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/MultiBulkResponse.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/MultiBulkResponse.php new file mode 100644 index 0000000..820b9b4 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/MultiBulkResponse.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; + +/** + * Handler for the multibulk response type in the standard Redis wire protocol. + * It returns multibulk responses as PHP arrays. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class MultiBulkResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + $length = (int) $payload; + + if ("$length" !== $payload) { + CommunicationException::handle(new ProtocolException( + $connection, "Cannot parse '$payload' as a valid length of a multi-bulk response." + )); + } + + if ($length === -1) { + return; + } + + $list = array(); + + if ($length > 0) { + $handlersCache = array(); + $reader = $connection->getProtocol()->getResponseReader(); + + for ($i = 0; $i < $length; ++$i) { + $header = $connection->readLine(); + $prefix = $header[0]; + + if (isset($handlersCache[$prefix])) { + $handler = $handlersCache[$prefix]; + } else { + $handler = $reader->getHandler($prefix); + $handlersCache[$prefix] = $handler; + } + + $list[$i] = $handler->handle($connection, substr($header, 1)); + } + } + + return $list; + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/ResponseHandlerInterface.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/ResponseHandlerInterface.php new file mode 100644 index 0000000..ca08a9c --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/ResponseHandlerInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\Connection\CompositeConnectionInterface; + +/** + * Defines a pluggable handler used to parse a particular type of response. + * + * @author Daniele Alessandri + */ +interface ResponseHandlerInterface +{ + /** + * Deserializes a response returned by Redis and reads more data from the + * connection if needed. + * + * @param CompositeConnectionInterface $connection Redis connection. + * @param string $payload String payload. + * + * @return mixed + */ + public function handle(CompositeConnectionInterface $connection, $payload); +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/StatusResponse.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/StatusResponse.php new file mode 100644 index 0000000..7bde555 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/StatusResponse.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\Connection\CompositeConnectionInterface; +use Predis\Response\Status; + +/** + * Handler for the status response type in the standard Redis wire protocol. It + * translates certain classes of status response to PHP objects or just returns + * the payload as a string. + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri + */ +class StatusResponse implements ResponseHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(CompositeConnectionInterface $connection, $payload) + { + return Status::get($payload); + } +} diff --git a/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/StreamableMultiBulkResponse.php b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/StreamableMultiBulkResponse.php new file mode 100644 index 0000000..7cdb736 --- /dev/null +++ b/www-var/rainloop/rainloop/v/1.14.0/app/libraries/Predis/Protocol/Text/Handler/StreamableMultiBulkResponse.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Protocol\Text\Handler; + +use Predis\CommunicationException; +use Predis\Connection\CompositeConnectionInterface; +use Predis\Protocol\ProtocolException; +use Predis\Response\Iterator\MultiBulk as MultiBulkIterator; + +/** + * Handler for the multibulk response type in the standard Redis wire protocol. + * It returns multibulk responses as iterators that can stream bulk elements. + * + * Streamable multibulk responses are not globally supported by the abstractions + * built-in into Predis, such as transactions or pipelines. Use them with care! + * + * @link http://redis.io/topics/protocol + * + * @author Daniele Alessandri