diff --git a/rainloop/INSTALL b/rainloop/INSTALL index 68a9f1661c28f015b80f3c9075fcb04894f06be5..72e259c7ab6b993bf27027f5e93155553b30ed07 100755 --- a/rainloop/INSTALL +++ b/rainloop/INSTALL @@ -1,19 +1,28 @@ -************************************************************************ -* -* Nextcloud - RainLoop Webmail package -* -* @author RainLoop Team & Pierre-Alain Bandinelli -* -* -************************************************************************ - -REQUIREMENTS: -- nextCloud version 10 to 13 - - -INSTALL & CONFIGURATION: -Start within Nextcloud, and click on the "+ Apps" button in the upper-left corner dropdown menu: -Then, enable the Rainloop plugin that is in the "Social & communication" section. -After a quick wait, Rainloop is installed. Even if it is really attractive, it is too soon to click on the newly appeared "Email" icon in the apps list. You should configure Rainloop before using it (which makes some sense, doesn'it): go to Nextcloud admin panel (upper-right corner dropdown menu) and go to "Additionnal settings". There click on the "Go to RainLoop Webmail admin panel". -In the Rainloop admin prompt, the default login is "admin" and the default password is "12345". No need to advise you to change it once in the admin panel! -This is it, you are now free to configure Rainloop as you wish. One important point is the Domains section when you will set up the IMAP/SMTP parameters that shall be associated with the email adresses of your users. +************************************************************************ +* +* ownCloud/Nextcloud - RainLoop Webmail package +* +* @author RainLoop Team +* @copyright 2018 RainLoop Team +* +* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud +* +************************************************************************ + +REQUIREMENTS: +- ownCloud version 6 or higher +or +- Nextcloud version 10 or higher + + +INSTALL: +- Unpack the RainLoop Webmail application package in the apps directory of your ownCloud or Nextcloud instance +- Make sure the rights are appropriately set + +CONFIGURATION: +- ownCloud: + 1) In the Apps > Enable 'RainLoop' plugin + 2) In the Settings > Personal > Type your email server login and password +- Nextcloud: + 1) In the Apps > Enable 'RainLoop' plugin after checking 'Enable experimental apps' + 2) In the Settings > Personal > Type your email server login and password diff --git a/rainloop/VERSION b/rainloop/VERSION index 831446cbd27a6de403344b21c9fa93a25357f43d..3bff059174b83d43cdf93771ce441423e4284d88 100755 --- a/rainloop/VERSION +++ b/rainloop/VERSION @@ -1 +1 @@ -5.1.0 +5.1.1 \ No newline at end of file diff --git a/rainloop/admin.php b/rainloop/admin.php index eda65df51a749730665c788f307e6cd4e273dc77..492e848cfc0f7bc396d6f1c1fe7fef0b3bdcd4ed 100755 --- a/rainloop/admin.php +++ b/rainloop/admin.php @@ -1,10 +1,9 @@ getContentSecurityPolicyNonceManager()->getNonce().'\'; ' - . 'style-src \'self\' \'unsafe-inline\'; ' - . 'frame-src *; ' - . 'img-src * data: blob:; ' - . 'font-src \'self\' data:; ' - . 'media-src *; ' - . 'connect-src *; ' - . 'object-src \'self\'; ' - . 'base-uri \'self\'; '; -header('Content-Security-Policy:' . $policy); - -if (@file_exists(__DIR__.'/app/index.php')) -{ - include_once OC_App::getAppPath('rainloop').'/lib/RainLoopHelper.php'; - - OC_RainLoop_Helper::regRainLoopDataFunction(); - - if (isset($_GET['OwnCloudAuth'])) - { - $sEmail = ''; - $sEncodedPassword = ''; - - $sUser = OCP\User::getUser(); - - if (OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false)) - { - $sEmail = $sUser; - $sEncodedPassword = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-autologin-password', ''); - } - else - { - $sEmail = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-email', ''); - $sEncodedPassword = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-password', ''); - } - - $sDecodedPassword = OC_RainLoop_Helper::decodePassword($sEncodedPassword, md5($sEmail)); - - $_ENV['___rainloop_owncloud_email'] = $sEmail; - $_ENV['___rainloop_owncloud_password'] = $sDecodedPassword; - } - - include __DIR__.'/app/index.php'; -} +value = $accessToken; + if ($expiresAt) { + $this->setExpiresAtFromTimeStamp($expiresAt); + } + } + + /** + * Generate an app secret proof to sign a request to Graph. + * + * @param string $appSecret The app secret. + * + * @return string + */ + public function getAppSecretProof($appSecret) + { + return hash_hmac('sha256', $this->value, $appSecret); + } + + /** + * Getter for expiresAt. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->expiresAt; + } + + /** + * Determines whether or not this is an app access token. + * + * @return bool + */ + public function isAppAccessToken() + { + return strpos($this->value, '|') !== false; + } + + /** + * Determines whether or not this is a long-lived token. + * + * @return bool + */ + public function isLongLived() + { + if ($this->expiresAt) { + return $this->expiresAt->getTimestamp() > time() + (60 * 60 * 2); + } + + if ($this->isAppAccessToken()) { + return true; + } + + return false; + } + + /** + * Checks the expiration of the access token. + * + * @return boolean|null + */ + public function isExpired() + { + if ($this->getExpiresAt() instanceof \DateTime) { + return $this->getExpiresAt()->getTimestamp() < time(); + } + + if ($this->isAppAccessToken()) { + return false; + } + + return null; + } + + /** + * Returns the access token as a string. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the access token as a string. + * + * @return string + */ + public function __toString() + { + return $this->getValue(); + } + + /** + * Setter for expires_at. + * + * @param int $timeStamp + */ + protected function setExpiresAtFromTimeStamp($timeStamp) + { + $dt = new \DateTime(); + $dt->setTimestamp($timeStamp); + $this->expiresAt = $dt; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Authentication/AccessTokenMetadata.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Authentication/AccessTokenMetadata.php new file mode 100644 index 0000000000000000000000000000000000000000..f302a6d2fb3f60704b3c41e0f599dc2ca005c3db --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Authentication/AccessTokenMetadata.php @@ -0,0 +1,390 @@ +metadata = $metadata['data']; + + $this->castTimestampsToDateTime(); + } + + /** + * Returns a value from the metadata. + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getField($field, $default = null) + { + if (isset($this->metadata[$field])) { + return $this->metadata[$field]; + } + + return $default; + } + + /** + * Returns a value from the metadata. + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + * + * @deprecated 5.0.0 getProperty() has been renamed to getField() + * @todo v6: Remove this method + */ + public function getProperty($field, $default = null) + { + return $this->getField($field, $default); + } + + /** + * Returns a value from a child property in the metadata. + * + * @param string $parentField The parent property. + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getChildProperty($parentField, $field, $default = null) + { + if (!isset($this->metadata[$parentField])) { + return $default; + } + + if (!isset($this->metadata[$parentField][$field])) { + return $default; + } + + return $this->metadata[$parentField][$field]; + } + + /** + * Returns a value from the error metadata. + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getErrorProperty($field, $default = null) + { + return $this->getChildProperty('error', $field, $default); + } + + /** + * Returns a value from the "metadata" metadata. *Brain explodes* + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getMetadataProperty($field, $default = null) + { + return $this->getChildProperty('metadata', $field, $default); + } + + /** + * The ID of the application this access token is for. + * + * @return string|null + */ + public function getAppId() + { + return $this->getField('app_id'); + } + + /** + * Name of the application this access token is for. + * + * @return string|null + */ + public function getApplication() + { + return $this->getField('application'); + } + + /** + * Any error that a request to the graph api + * would return due to the access token. + * + * @return bool|null + */ + public function isError() + { + return $this->getField('error') !== null; + } + + /** + * The error code for the error. + * + * @return int|null + */ + public function getErrorCode() + { + return $this->getErrorProperty('code'); + } + + /** + * The error message for the error. + * + * @return string|null + */ + public function getErrorMessage() + { + return $this->getErrorProperty('message'); + } + + /** + * The error subcode for the error. + * + * @return int|null + */ + public function getErrorSubcode() + { + return $this->getErrorProperty('subcode'); + } + + /** + * DateTime when this access token expires. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->getField('expires_at'); + } + + /** + * Whether the access token is still valid or not. + * + * @return boolean|null + */ + public function getIsValid() + { + return $this->getField('is_valid'); + } + + /** + * DateTime when this access token was issued. + * + * Note that the issued_at field is not returned + * for short-lived access tokens. + * + * @see https://developers.facebook.com/docs/facebook-login/access-tokens#debug + * + * @return \DateTime|null + */ + public function getIssuedAt() + { + return $this->getField('issued_at'); + } + + /** + * General metadata associated with the access token. + * Can contain data like 'sso', 'auth_type', 'auth_nonce'. + * + * @return array|null + */ + public function getMetadata() + { + return $this->getField('metadata'); + } + + /** + * The 'sso' child property from the 'metadata' parent property. + * + * @return string|null + */ + public function getSso() + { + return $this->getMetadataProperty('sso'); + } + + /** + * The 'auth_type' child property from the 'metadata' parent property. + * + * @return string|null + */ + public function getAuthType() + { + return $this->getMetadataProperty('auth_type'); + } + + /** + * The 'auth_nonce' child property from the 'metadata' parent property. + * + * @return string|null + */ + public function getAuthNonce() + { + return $this->getMetadataProperty('auth_nonce'); + } + + /** + * For impersonated access tokens, the ID of + * the page this token contains. + * + * @return string|null + */ + public function getProfileId() + { + return $this->getField('profile_id'); + } + + /** + * List of permissions that the user has granted for + * the app in this access token. + * + * @return array + */ + public function getScopes() + { + return $this->getField('scopes'); + } + + /** + * The ID of the user this access token is for. + * + * @return string|null + */ + public function getUserId() + { + return $this->getField('user_id'); + } + + /** + * Ensures the app ID from the access token + * metadata is what we expect. + * + * @param string $appId + * + * @throws FacebookSDKException + */ + public function validateAppId($appId) + { + if ($this->getAppId() !== $appId) { + throw new FacebookSDKException('Access token metadata contains unexpected app ID.', 401); + } + } + + /** + * Ensures the user ID from the access token + * metadata is what we expect. + * + * @param string $userId + * + * @throws FacebookSDKException + */ + public function validateUserId($userId) + { + if ($this->getUserId() !== $userId) { + throw new FacebookSDKException('Access token metadata contains unexpected user ID.', 401); + } + } + + /** + * Ensures the access token has not expired yet. + * + * @throws FacebookSDKException + */ + public function validateExpiration() + { + if (!$this->getExpiresAt() instanceof \DateTime) { + return; + } + + if ($this->getExpiresAt()->getTimestamp() < time()) { + throw new FacebookSDKException('Inspection of access token metadata shows that the access token has expired.', 401); + } + } + + /** + * Converts a unix timestamp into a DateTime entity. + * + * @param int $timestamp + * + * @return \DateTime + */ + private function convertTimestampToDateTime($timestamp) + { + $dt = new \DateTime(); + $dt->setTimestamp($timestamp); + + return $dt; + } + + /** + * Casts the unix timestamps as DateTime entities. + */ + private function castTimestampsToDateTime() + { + foreach (static::$dateProperties as $key) { + if (isset($this->metadata[$key])) { + $this->metadata[$key] = $this->convertTimestampToDateTime($this->metadata[$key]); + } + } + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Authentication/OAuth2Client.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Authentication/OAuth2Client.php new file mode 100644 index 0000000000000000000000000000000000000000..8e364ec83273f28e8777f18ca00f16a4b0b167c3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Authentication/OAuth2Client.php @@ -0,0 +1,292 @@ +app = $app; + $this->client = $client; + $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; + } + + /** + * Returns the last FacebookRequest that was sent. + * Useful for debugging and testing. + * + * @return FacebookRequest|null + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the metadata associated with the access token. + * + * @param AccessToken|string $accessToken The access token to debug. + * + * @return AccessTokenMetadata + */ + public function debugToken($accessToken) + { + $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; + $params = ['input_token' => $accessToken]; + + $this->lastRequest = new FacebookRequest( + $this->app, + $this->app->getAccessToken(), + 'GET', + '/debug_token', + $params, + null, + $this->graphVersion + ); + $response = $this->client->sendRequest($this->lastRequest); + $metadata = $response->getDecodedBody(); + + return new AccessTokenMetadata($metadata); + } + + /** + * Generates an authorization URL to begin the process of authenticating a user. + * + * @param string $redirectUrl The callback URL to redirect to. + * @param array $scope An array of permissions to request. + * @param string $state The CSPRNG-generated CSRF value. + * @param array $params An array of parameters to generate URL. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getAuthorizationUrl($redirectUrl, $state, array $scope = [], array $params = [], $separator = '&') + { + $params += [ + 'client_id' => $this->app->getId(), + 'state' => $state, + 'response_type' => 'code', + 'sdk' => 'php-sdk-' . Facebook::VERSION, + 'redirect_uri' => $redirectUrl, + 'scope' => implode(',', $scope) + ]; + + return static::BASE_AUTHORIZATION_URL . '/' . $this->graphVersion . '/dialog/oauth?' . http_build_query($params, null, $separator); + } + + /** + * Get a valid access token from a code. + * + * @param string $code + * @param string $redirectUri + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + public function getAccessTokenFromCode($code, $redirectUri = '') + { + $params = [ + 'code' => $code, + 'redirect_uri' => $redirectUri, + ]; + + return $this->requestAnAccessToken($params); + } + + /** + * Exchanges a short-lived access token with a long-lived access token. + * + * @param AccessToken|string $accessToken + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + public function getLongLivedAccessToken($accessToken) + { + $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; + $params = [ + 'grant_type' => 'fb_exchange_token', + 'fb_exchange_token' => $accessToken, + ]; + + return $this->requestAnAccessToken($params); + } + + /** + * Get a valid code from an access token. + * + * @param AccessToken|string $accessToken + * @param string $redirectUri + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + public function getCodeFromLongLivedAccessToken($accessToken, $redirectUri = '') + { + $params = [ + 'redirect_uri' => $redirectUri, + ]; + + $response = $this->sendRequestWithClientParams('/oauth/client_code', $params, $accessToken); + $data = $response->getDecodedBody(); + + if (!isset($data['code'])) { + throw new FacebookSDKException('Code was not returned from Graph.', 401); + } + + return $data['code']; + } + + /** + * Send a request to the OAuth endpoint. + * + * @param array $params + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + protected function requestAnAccessToken(array $params) + { + $response = $this->sendRequestWithClientParams('/oauth/access_token', $params); + $data = $response->getDecodedBody(); + + if (!isset($data['access_token'])) { + throw new FacebookSDKException('Access token was not returned from Graph.', 401); + } + + // Graph returns two different key names for expiration time + // on the same endpoint. Doh! :/ + $expiresAt = 0; + if (isset($data['expires'])) { + // For exchanging a short lived token with a long lived token. + // The expiration time in seconds will be returned as "expires". + $expiresAt = time() + $data['expires']; + } elseif (isset($data['expires_in'])) { + // For exchanging a code for a short lived access token. + // The expiration time in seconds will be returned as "expires_in". + // See: https://developers.facebook.com/docs/facebook-login/access-tokens#long-via-code + $expiresAt = time() + $data['expires_in']; + } + + return new AccessToken($data['access_token'], $expiresAt); + } + + /** + * Send a request to Graph with an app access token. + * + * @param string $endpoint + * @param array $params + * @param string|null $accessToken + * + * @return FacebookResponse + * + * @throws FacebookResponseException + */ + protected function sendRequestWithClientParams($endpoint, array $params, $accessToken = null) + { + $params += $this->getClientParams(); + + $accessToken = $accessToken ?: $this->app->getAccessToken(); + + $this->lastRequest = new FacebookRequest( + $this->app, + $accessToken, + 'GET', + $endpoint, + $params, + null, + $this->graphVersion + ); + + return $this->client->sendRequest($this->lastRequest); + } + + /** + * Returns the client_* params for OAuth requests. + * + * @return array + */ + protected function getClientParams() + { + return [ + 'client_id' => $this->app->getId(), + 'client_secret' => $this->app->getSecret(), + ]; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Exceptions/FacebookAuthenticationException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Exceptions/FacebookAuthenticationException.php new file mode 100644 index 0000000000000000000000000000000000000000..449cf93e61406b84e19e9636d52b5aaecfd8facc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Exceptions/FacebookAuthenticationException.php @@ -0,0 +1,33 @@ +response = $response; + $this->responseData = $response->getDecodedBody(); + + $errorMessage = $this->get('message', 'Unknown error from Graph.'); + $errorCode = $this->get('code', -1); + + parent::__construct($errorMessage, $errorCode, $previousException); + } + + /** + * A factory for creating the appropriate exception based on the response from Graph. + * + * @param FacebookResponse $response The response that threw the exception. + * + * @return FacebookResponseException + */ + public static function create(FacebookResponse $response) + { + $data = $response->getDecodedBody(); + + if (!isset($data['error']['code']) && isset($data['code'])) { + $data = ['error' => $data]; + } + + $code = isset($data['error']['code']) ? $data['error']['code'] : null; + $message = isset($data['error']['message']) ? $data['error']['message'] : 'Unknown error from Graph.'; + + $previousException = null; + + if (isset($data['error']['error_subcode'])) { + switch ($data['error']['error_subcode']) { + // Other authentication issues + case 458: + case 459: + case 460: + case 463: + case 464: + case 467: + return new static($response, new FacebookAuthenticationException($message, $code)); + } + } + + switch ($code) { + // Login status or token expired, revoked, or invalid + case 100: + case 102: + case 190: + return new static($response, new FacebookAuthenticationException($message, $code)); + + // Server issue, possible downtime + case 1: + case 2: + return new static($response, new FacebookServerException($message, $code)); + + // API Throttling + case 4: + case 17: + case 341: + return new static($response, new FacebookThrottleException($message, $code)); + + // Duplicate Post + case 506: + return new static($response, new FacebookClientException($message, $code)); + } + + // Missing Permissions + if ($code == 10 || ($code >= 200 && $code <= 299)) { + return new static($response, new FacebookAuthorizationException($message, $code)); + } + + // OAuth authentication error + if (isset($data['error']['type']) && $data['error']['type'] === 'OAuthException') { + return new static($response, new FacebookAuthenticationException($message, $code)); + } + + // All others + return new static($response, new FacebookOtherException($message, $code)); + } + + /** + * Checks isset and returns that or a default value. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + private function get($key, $default = null) + { + if (isset($this->responseData['error'][$key])) { + return $this->responseData['error'][$key]; + } + + return $default; + } + + /** + * Returns the HTTP status code + * + * @return int + */ + public function getHttpStatusCode() + { + return $this->response->getHttpStatusCode(); + } + + /** + * Returns the sub-error code + * + * @return int + */ + public function getSubErrorCode() + { + return $this->get('error_subcode', -1); + } + + /** + * Returns the error type + * + * @return string + */ + public function getErrorType() + { + return $this->get('type', ''); + } + + /** + * Returns the raw response used to create the exception. + * + * @return string + */ + public function getRawResponse() + { + return $this->response->getBody(); + } + + /** + * Returns the decoded response used to create the exception. + * + * @return array + */ + public function getResponseData() + { + return $this->responseData; + } + + /** + * Returns the response entity used to create the exception. + * + * @return FacebookResponse + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Exceptions/FacebookSDKException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Exceptions/FacebookSDKException.php new file mode 100644 index 0000000000000000000000000000000000000000..03219b0ef539b84f6cedf3463a9005e9ae4a1ea1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Exceptions/FacebookSDKException.php @@ -0,0 +1,33 @@ +app = new FacebookApp($appId, $appSecret); + + $httpClientHandler = null; + if (isset($config['http_client_handler'])) { + if ($config['http_client_handler'] instanceof FacebookHttpClientInterface) { + $httpClientHandler = $config['http_client_handler']; + } elseif ($config['http_client_handler'] === 'curl') { + $httpClientHandler = new FacebookCurlHttpClient(); + } elseif ($config['http_client_handler'] === 'stream') { + $httpClientHandler = new FacebookStreamHttpClient(); + } elseif ($config['http_client_handler'] === 'guzzle') { + $httpClientHandler = new FacebookGuzzleHttpClient(); + } else { + throw new \InvalidArgumentException('The http_client_handler must be set to "curl", "stream", "guzzle", or be an instance of Facebook\HttpClients\FacebookHttpClientInterface'); + } + } + + $enableBeta = isset($config['enable_beta_mode']) && $config['enable_beta_mode'] === true; + $this->client = new FacebookClient($httpClientHandler, $enableBeta); + + if (isset($config['url_detection_handler'])) { + if ($config['url_detection_handler'] instanceof UrlDetectionInterface) { + $this->urlDetectionHandler = $config['url_detection_handler']; + } else { + throw new \InvalidArgumentException('The url_detection_handler must be an instance of Facebook\Url\UrlDetectionInterface'); + } + } + + if (isset($config['pseudo_random_string_generator'])) { + if ($config['pseudo_random_string_generator'] instanceof PseudoRandomStringGeneratorInterface) { + $this->pseudoRandomStringGenerator = $config['pseudo_random_string_generator']; + } elseif ($config['pseudo_random_string_generator'] === 'mcrypt') { + $this->pseudoRandomStringGenerator = new McryptPseudoRandomStringGenerator(); + } elseif ($config['pseudo_random_string_generator'] === 'openssl') { + $this->pseudoRandomStringGenerator = new OpenSslPseudoRandomStringGenerator(); + } elseif ($config['pseudo_random_string_generator'] === 'urandom') { + $this->pseudoRandomStringGenerator = new UrandomPseudoRandomStringGenerator(); + } else { + throw new \InvalidArgumentException('The pseudo_random_string_generator must be set to "mcrypt", "openssl", or "urandom", or be an instance of Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface'); + } + } + + if (isset($config['persistent_data_handler'])) { + if ($config['persistent_data_handler'] instanceof PersistentDataInterface) { + $this->persistentDataHandler = $config['persistent_data_handler']; + } elseif ($config['persistent_data_handler'] === 'session') { + $this->persistentDataHandler = new FacebookSessionPersistentDataHandler(); + } elseif ($config['persistent_data_handler'] === 'memory') { + $this->persistentDataHandler = new FacebookMemoryPersistentDataHandler(); + } else { + throw new \InvalidArgumentException('The persistent_data_handler must be set to "session", "memory", or be an instance of Facebook\PersistentData\PersistentDataInterface'); + } + } + + if (isset($config['default_access_token'])) { + $this->setDefaultAccessToken($config['default_access_token']); + } + + if (isset($config['default_graph_version'])) { + $this->defaultGraphVersion = $config['default_graph_version']; + } else { + // @todo v6: Throw an InvalidArgumentException if "default_graph_version" is not set + $this->defaultGraphVersion = static::DEFAULT_GRAPH_VERSION; + } + } + + /** + * Returns the FacebookApp entity. + * + * @return FacebookApp + */ + public function getApp() + { + return $this->app; + } + + /** + * Returns the FacebookClient service. + * + * @return FacebookClient + */ + public function getClient() + { + return $this->client; + } + + /** + * Returns the OAuth 2.0 client service. + * + * @return OAuth2Client + */ + public function getOAuth2Client() + { + if (!$this->oAuth2Client instanceof OAuth2Client) { + $app = $this->getApp(); + $client = $this->getClient(); + $this->oAuth2Client = new OAuth2Client($app, $client, $this->defaultGraphVersion); + } + + return $this->oAuth2Client; + } + + /** + * Returns the last response returned from Graph. + * + * @return FacebookResponse|FacebookBatchResponse|null + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + /** + * Returns the URL detection handler. + * + * @return UrlDetectionInterface + */ + public function getUrlDetectionHandler() + { + if (!$this->urlDetectionHandler instanceof UrlDetectionInterface) { + $this->urlDetectionHandler = new FacebookUrlDetectionHandler(); + } + + return $this->urlDetectionHandler; + } + + /** + * Returns the default AccessToken entity. + * + * @return AccessToken|null + */ + public function getDefaultAccessToken() + { + return $this->defaultAccessToken; + } + + /** + * Sets the default access token to use with requests. + * + * @param AccessToken|string $accessToken The access token to save. + * + * @throws \InvalidArgumentException + */ + public function setDefaultAccessToken($accessToken) + { + if (is_string($accessToken)) { + $this->defaultAccessToken = new AccessToken($accessToken); + + return; + } + + if ($accessToken instanceof AccessToken) { + $this->defaultAccessToken = $accessToken; + + return; + } + + throw new \InvalidArgumentException('The default access token must be of type "string" or Facebook\AccessToken'); + } + + /** + * Returns the default Graph version. + * + * @return string + */ + public function getDefaultGraphVersion() + { + return $this->defaultGraphVersion; + } + + /** + * Returns the redirect login helper. + * + * @return FacebookRedirectLoginHelper + */ + public function getRedirectLoginHelper() + { + return new FacebookRedirectLoginHelper( + $this->getOAuth2Client(), + $this->persistentDataHandler, + $this->urlDetectionHandler, + $this->pseudoRandomStringGenerator + ); + } + + /** + * Returns the JavaScript helper. + * + * @return FacebookJavaScriptHelper + */ + public function getJavaScriptHelper() + { + return new FacebookJavaScriptHelper($this->app, $this->client, $this->defaultGraphVersion); + } + + /** + * Returns the canvas helper. + * + * @return FacebookCanvasHelper + */ + public function getCanvasHelper() + { + return new FacebookCanvasHelper($this->app, $this->client, $this->defaultGraphVersion); + } + + /** + * Returns the page tab helper. + * + * @return FacebookPageTabHelper + */ + public function getPageTabHelper() + { + return new FacebookPageTabHelper($this->app, $this->client, $this->defaultGraphVersion); + } + + /** + * Sends a GET request to Graph and returns the result. + * + * @param string $endpoint + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function get($endpoint, $accessToken = null, $eTag = null, $graphVersion = null) + { + return $this->sendRequest( + 'GET', + $endpoint, + $params = [], + $accessToken, + $eTag, + $graphVersion + ); + } + + /** + * Sends a POST request to Graph and returns the result. + * + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function post($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + return $this->sendRequest( + 'POST', + $endpoint, + $params, + $accessToken, + $eTag, + $graphVersion + ); + } + + /** + * Sends a DELETE request to Graph and returns the result. + * + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function delete($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + return $this->sendRequest( + 'DELETE', + $endpoint, + $params, + $accessToken, + $eTag, + $graphVersion + ); + } + + /** + * Sends a request to Graph for the next page of results. + * + * @param GraphEdge $graphEdge The GraphEdge to paginate over. + * + * @return GraphEdge|null + * + * @throws FacebookSDKException + */ + public function next(GraphEdge $graphEdge) + { + return $this->getPaginationResults($graphEdge, 'next'); + } + + /** + * Sends a request to Graph for the previous page of results. + * + * @param GraphEdge $graphEdge The GraphEdge to paginate over. + * + * @return GraphEdge|null + * + * @throws FacebookSDKException + */ + public function previous(GraphEdge $graphEdge) + { + return $this->getPaginationResults($graphEdge, 'previous'); + } + + /** + * Sends a request to Graph for the next page of results. + * + * @param GraphEdge $graphEdge The GraphEdge to paginate over. + * @param string $direction The direction of the pagination: next|previous. + * + * @return GraphEdge|null + * + * @throws FacebookSDKException + */ + public function getPaginationResults(GraphEdge $graphEdge, $direction) + { + $paginationRequest = $graphEdge->getPaginationRequest($direction); + if (!$paginationRequest) { + return null; + } + + $this->lastResponse = $this->client->sendRequest($paginationRequest); + + // Keep the same GraphNode subclass + $subClassName = $graphEdge->getSubClassName(); + $graphEdge = $this->lastResponse->getGraphEdge($subClassName, false); + + return count($graphEdge) > 0 ? $graphEdge : null; + } + + /** + * Sends a request to Graph and returns the result. + * + * @param string $method + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function sendRequest($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + $accessToken = $accessToken ?: $this->defaultAccessToken; + $graphVersion = $graphVersion ?: $this->defaultGraphVersion; + $request = $this->request($method, $endpoint, $params, $accessToken, $eTag, $graphVersion); + + return $this->lastResponse = $this->client->sendRequest($request); + } + + /** + * Sends a batched request to Graph and returns the result. + * + * @param array $requests + * @param AccessToken|string|null $accessToken + * @param string|null $graphVersion + * + * @return FacebookBatchResponse + * + * @throws FacebookSDKException + */ + public function sendBatchRequest(array $requests, $accessToken = null, $graphVersion = null) + { + $accessToken = $accessToken ?: $this->defaultAccessToken; + $graphVersion = $graphVersion ?: $this->defaultGraphVersion; + $batchRequest = new FacebookBatchRequest( + $this->app, + $requests, + $accessToken, + $graphVersion + ); + + return $this->lastResponse = $this->client->sendBatchRequest($batchRequest); + } + + /** + * Instantiates a new FacebookRequest entity. + * + * @param string $method + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function request($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + $accessToken = $accessToken ?: $this->defaultAccessToken; + $graphVersion = $graphVersion ?: $this->defaultGraphVersion; + + return new FacebookRequest( + $this->app, + $accessToken, + $method, + $endpoint, + $params, + $eTag, + $graphVersion + ); + } + + /** + * Factory to create FacebookFile's. + * + * @param string $pathToFile + * + * @return FacebookFile + * + * @throws FacebookSDKException + */ + public function fileToUpload($pathToFile) + { + return new FacebookFile($pathToFile); + } + + /** + * Factory to create FacebookVideo's. + * + * @param string $pathToFile + * + * @return FacebookVideo + * + * @throws FacebookSDKException + */ + public function videoToUpload($pathToFile) + { + return new FacebookVideo($pathToFile); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookApp.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookApp.php new file mode 100644 index 0000000000000000000000000000000000000000..84956ce98d7582e113df73bc3e33b36fb34df6ac --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookApp.php @@ -0,0 +1,101 @@ +id = $id; + $this->secret = $secret; + } + + /** + * Returns the app ID. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Returns the app secret. + * + * @return string + */ + public function getSecret() + { + return $this->secret; + } + + /** + * Returns an app access token. + * + * @return AccessToken + */ + public function getAccessToken() + { + return new AccessToken($this->id . '|' . $this->secret); + } + + /** + * Serializes the FacebookApp entity as a string. + * + * @return string + */ + public function serialize() + { + return serialize([$this->id, $this->secret]); + } + + /** + * Unserializes a string as a FacebookApp entity. + * + * @param string $serialized + */ + public function unserialize($serialized) + { + list($id, $secret) = unserialize($serialized); + + $this->__construct($id, $secret); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookBatchRequest.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookBatchRequest.php new file mode 100644 index 0000000000000000000000000000000000000000..33c489cf8d309ad78cc7a2957c24b77963b43143 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookBatchRequest.php @@ -0,0 +1,303 @@ +add($requests); + } + + /** + * A a new request to the array. + * + * @param FacebookRequest|array $request + * @param string|null $name + * + * @return FacebookBatchRequest + * + * @throws \InvalidArgumentException + */ + public function add($request, $name = null) + { + if (is_array($request)) { + foreach ($request as $key => $req) { + $this->add($req, $key); + } + + return $this; + } + + if (!$request instanceof FacebookRequest) { + throw new \InvalidArgumentException('Argument for add() must be of type array or FacebookRequest.'); + } + + $this->addFallbackDefaults($request); + $requestToAdd = [ + 'name' => $name, + 'request' => $request, + ]; + + // File uploads + $attachedFiles = $this->extractFileAttachments($request); + if ($attachedFiles) { + $requestToAdd['attached_files'] = $attachedFiles; + } + $this->requests[] = $requestToAdd; + + return $this; + } + + /** + * Ensures that the FacebookApp and access token fall back when missing. + * + * @param FacebookRequest $request + * + * @throws FacebookSDKException + */ + public function addFallbackDefaults(FacebookRequest $request) + { + if (!$request->getApp()) { + $app = $this->getApp(); + if (!$app) { + throw new FacebookSDKException('Missing FacebookApp on FacebookRequest and no fallback detected on FacebookBatchRequest.'); + } + $request->setApp($app); + } + + if (!$request->getAccessToken()) { + $accessToken = $this->getAccessToken(); + if (!$accessToken) { + throw new FacebookSDKException('Missing access token on FacebookRequest and no fallback detected on FacebookBatchRequest.'); + } + $request->setAccessToken($accessToken); + } + } + + /** + * Extracts the files from a request. + * + * @param FacebookRequest $request + * + * @return string|null + * + * @throws FacebookSDKException + */ + public function extractFileAttachments(FacebookRequest $request) + { + if (!$request->containsFileUploads()) { + return null; + } + + $files = $request->getFiles(); + $fileNames = []; + foreach ($files as $file) { + $fileName = uniqid(); + $this->addFile($fileName, $file); + $fileNames[] = $fileName; + } + + $request->resetFiles(); + + // @TODO Does Graph support multiple uploads on one endpoint? + return implode(',', $fileNames); + } + + /** + * Return the FacebookRequest entities. + * + * @return array + */ + public function getRequests() + { + return $this->requests; + } + + /** + * Prepares the requests to be sent as a batch request. + * + * @return string + */ + public function prepareRequestsForBatch() + { + $this->validateBatchRequestCount(); + + $params = [ + 'batch' => $this->convertRequestsToJson(), + 'include_headers' => true, + ]; + $this->setParams($params); + } + + /** + * Converts the requests into a JSON(P) string. + * + * @return string + */ + public function convertRequestsToJson() + { + $requests = []; + foreach ($this->requests as $request) { + $attachedFiles = isset($request['attached_files']) ? $request['attached_files'] : null; + $requests[] = $this->requestEntityToBatchArray($request['request'], $request['name'], $attachedFiles); + } + + return json_encode($requests); + } + + /** + * Validate the request count before sending them as a batch. + * + * @throws FacebookSDKException + */ + public function validateBatchRequestCount() + { + $batchCount = count($this->requests); + if ($batchCount === 0) { + throw new FacebookSDKException('There are no batch requests to send.'); + } elseif ($batchCount > 50) { + // Per: https://developers.facebook.com/docs/graph-api/making-multiple-requests#limits + throw new FacebookSDKException('You cannot send more than 50 batch requests at a time.'); + } + } + + /** + * Converts a Request entity into an array that is batch-friendly. + * + * @param FacebookRequest $request The request entity to convert. + * @param string|null $requestName The name of the request. + * @param string|null $attachedFiles Names of files associated with the request. + * + * @return array + */ + public function requestEntityToBatchArray(FacebookRequest $request, $requestName = null, $attachedFiles = null) + { + $compiledHeaders = []; + $headers = $request->getHeaders(); + foreach ($headers as $name => $value) { + $compiledHeaders[] = $name . ': ' . $value; + } + + $batch = [ + 'headers' => $compiledHeaders, + 'method' => $request->getMethod(), + 'relative_url' => $request->getUrl(), + ]; + + // Since file uploads are moved to the root request of a batch request, + // the child requests will always be URL-encoded. + $body = $request->getUrlEncodedBody()->getBody(); + if ($body) { + $batch['body'] = $body; + } + + if (isset($requestName)) { + $batch['name'] = $requestName; + } + + if (isset($attachedFiles)) { + $batch['attached_files'] = $attachedFiles; + } + + // @TODO Add support for "omit_response_on_success" + // @TODO Add support for "depends_on" + // @TODO Add support for JSONP with "callback" + + return $batch; + } + + /** + * Get an iterator for the items. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->requests); + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->add($value, $offset); + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return isset($this->requests[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->requests[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + return isset($this->requests[$offset]) ? $this->requests[$offset] : null; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookBatchResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookBatchResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..5ea765e302af7148aae67685fce6e68dd0b96fcc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookBatchResponse.php @@ -0,0 +1,154 @@ +batchRequest = $batchRequest; + + $request = $response->getRequest(); + $body = $response->getBody(); + $httpStatusCode = $response->getHttpStatusCode(); + $headers = $response->getHeaders(); + parent::__construct($request, $body, $httpStatusCode, $headers); + + $responses = $response->getDecodedBody(); + $this->setResponses($responses); + } + + /** + * Returns an array of FacebookResponse entities. + * + * @return array + */ + public function getResponses() + { + return $this->responses; + } + + /** + * The main batch response will be an array of requests so + * we need to iterate over all the responses. + * + * @param array $responses + */ + public function setResponses(array $responses) + { + $this->responses = []; + + foreach ($responses as $key => $graphResponse) { + $this->addResponse($key, $graphResponse); + } + } + + /** + * Add a response to the list. + * + * @param int $key + * @param array|null $response + */ + public function addResponse($key, $response) + { + $originalRequestName = isset($this->batchRequest[$key]['name']) ? $this->batchRequest[$key]['name'] : $key; + $originalRequest = isset($this->batchRequest[$key]['request']) ? $this->batchRequest[$key]['request'] : null; + + $httpResponseBody = isset($response['body']) ? $response['body'] : null; + $httpResponseCode = isset($response['code']) ? $response['code'] : null; + $httpResponseHeaders = isset($response['headers']) ? $response['headers'] : []; + + $this->responses[$originalRequestName] = new FacebookResponse( + $originalRequest, + $httpResponseBody, + $httpResponseCode, + $httpResponseHeaders + ); + } + + /** + * @inheritdoc + */ + public function getIterator() + { + return new ArrayIterator($this->responses); + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->addResponse($offset, $value); + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return isset($this->responses[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->responses[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + return isset($this->responses[$offset]) ? $this->responses[$offset] : null; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookClient.php new file mode 100644 index 0000000000000000000000000000000000000000..b10762f1ba92a18a9db5626e21904fd0a8e978c3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookClient.php @@ -0,0 +1,250 @@ +httpClientHandler = $httpClientHandler ?: $this->detectHttpClientHandler(); + $this->enableBetaMode = $enableBeta; + } + + /** + * Sets the HTTP client handler. + * + * @param FacebookHttpClientInterface $httpClientHandler + */ + public function setHttpClientHandler(FacebookHttpClientInterface $httpClientHandler) + { + $this->httpClientHandler = $httpClientHandler; + } + + /** + * Returns the HTTP client handler. + * + * @return FacebookHttpClientInterface + */ + public function getHttpClientHandler() + { + return $this->httpClientHandler; + } + + /** + * Detects which HTTP client handler to use. + * + * @return FacebookHttpClientInterface + */ + public function detectHttpClientHandler() + { + return function_exists('curl_init') ? new FacebookCurlHttpClient() : new FacebookStreamHttpClient(); + } + + /** + * Toggle beta mode. + * + * @param boolean $betaMode + */ + public function enableBetaMode($betaMode = true) + { + $this->enableBetaMode = $betaMode; + } + + /** + * Returns the base Graph URL. + * + * @param boolean $postToVideoUrl Post to the video API if videos are being uploaded. + * + * @return string + */ + public function getBaseGraphUrl($postToVideoUrl = false) + { + if ($postToVideoUrl) { + return $this->enableBetaMode ? static::BASE_GRAPH_VIDEO_URL_BETA : static::BASE_GRAPH_VIDEO_URL; + } + + return $this->enableBetaMode ? static::BASE_GRAPH_URL_BETA : static::BASE_GRAPH_URL; + } + + /** + * Prepares the request for sending to the client handler. + * + * @param FacebookRequest $request + * + * @return array + */ + public function prepareRequestMessage(FacebookRequest $request) + { + $postToVideoUrl = $request->containsVideoUploads(); + $url = $this->getBaseGraphUrl($postToVideoUrl) . $request->getUrl(); + + // If we're sending files they should be sent as multipart/form-data + if ($request->containsFileUploads()) { + $requestBody = $request->getMultipartBody(); + $request->setHeaders([ + 'Content-Type' => 'multipart/form-data; boundary=' . $requestBody->getBoundary(), + ]); + } else { + $requestBody = $request->getUrlEncodedBody(); + $request->setHeaders([ + 'Content-Type' => 'application/x-www-form-urlencoded', + ]); + } + + return [ + $url, + $request->getMethod(), + $request->getHeaders(), + $requestBody->getBody(), + ]; + } + + /** + * Makes the request to Graph and returns the result. + * + * @param FacebookRequest $request + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function sendRequest(FacebookRequest $request) + { + if (get_class($request) === 'FacebookRequest') { + $request->validateAccessToken(); + } + + list($url, $method, $headers, $body) = $this->prepareRequestMessage($request); + + // Since file uploads can take a while, we need to give more time for uploads + $timeOut = static::DEFAULT_REQUEST_TIMEOUT; + if ($request->containsFileUploads()) { + $timeOut = static::DEFAULT_FILE_UPLOAD_REQUEST_TIMEOUT; + } elseif ($request->containsVideoUploads()) { + $timeOut = static::DEFAULT_VIDEO_UPLOAD_REQUEST_TIMEOUT; + } + + // Should throw `FacebookSDKException` exception on HTTP client error. + // Don't catch to allow it to bubble up. + $rawResponse = $this->httpClientHandler->send($url, $method, $body, $headers, $timeOut); + + static::$requestCount++; + + $returnResponse = new FacebookResponse( + $request, + $rawResponse->getBody(), + $rawResponse->getHttpResponseCode(), + $rawResponse->getHeaders() + ); + + if ($returnResponse->isError()) { + throw $returnResponse->getThrownException(); + } + + return $returnResponse; + } + + /** + * Makes a batched request to Graph and returns the result. + * + * @param FacebookBatchRequest $request + * + * @return FacebookBatchResponse + * + * @throws FacebookSDKException + */ + public function sendBatchRequest(FacebookBatchRequest $request) + { + $request->prepareRequestsForBatch(); + $facebookResponse = $this->sendRequest($request); + + return new FacebookBatchResponse($request, $facebookResponse); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookRequest.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookRequest.php new file mode 100644 index 0000000000000000000000000000000000000000..5e4083f8a8652bbc9ada99c061e0c4f73b63a40d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookRequest.php @@ -0,0 +1,536 @@ +setApp($app); + $this->setAccessToken($accessToken); + $this->setMethod($method); + $this->setEndpoint($endpoint); + $this->setParams($params); + $this->setETag($eTag); + $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; + } + + /** + * Set the access token for this request. + * + * @param AccessToken|string + * + * @return FacebookRequest + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + if ($accessToken instanceof AccessToken) { + $this->accessToken = $accessToken->getValue(); + } + + return $this; + } + + /** + * Sets the access token with one harvested from a URL or POST params. + * + * @param string $accessToken The access token. + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function setAccessTokenFromParams($accessToken) + { + $existingAccessToken = $this->getAccessToken(); + if (!$existingAccessToken) { + $this->setAccessToken($accessToken); + } elseif ($accessToken !== $existingAccessToken) { + throw new FacebookSDKException('Access token mismatch. The access token provided in the FacebookRequest and the one provided in the URL or POST params do not match.'); + } + + return $this; + } + + /** + * Return the access token for this request. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * Return the access token for this request an an AccessToken entity. + * + * @return AccessToken|null + */ + public function getAccessTokenEntity() + { + return $this->accessToken ? new AccessToken($this->accessToken) : null; + } + + /** + * Set the FacebookApp entity used for this request. + * + * @param FacebookApp|null $app + */ + public function setApp(FacebookApp $app = null) + { + $this->app = $app; + } + + /** + * Return the FacebookApp entity used for this request. + * + * @return FacebookApp + */ + public function getApp() + { + return $this->app; + } + + /** + * Generate an app secret proof to sign this request. + * + * @return string|null + */ + public function getAppSecretProof() + { + if (!$accessTokenEntity = $this->getAccessTokenEntity()) { + return null; + } + + return $accessTokenEntity->getAppSecretProof($this->app->getSecret()); + } + + /** + * Validate that an access token exists for this request. + * + * @throws FacebookSDKException + */ + public function validateAccessToken() + { + $accessToken = $this->getAccessToken(); + if (!$accessToken) { + throw new FacebookSDKException('You must provide an access token.'); + } + } + + /** + * Set the HTTP method for this request. + * + * @param string + * + * @return FacebookRequest + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + } + + /** + * Return the HTTP method for this request. + * + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Validate that the HTTP method is set. + * + * @throws FacebookSDKException + */ + public function validateMethod() + { + if (!$this->method) { + throw new FacebookSDKException('HTTP method not specified.'); + } + + if (!in_array($this->method, ['GET', 'POST', 'DELETE'])) { + throw new FacebookSDKException('Invalid HTTP method specified.'); + } + } + + /** + * Set the endpoint for this request. + * + * @param string + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function setEndpoint($endpoint) + { + // Harvest the access token from the endpoint to keep things in sync + $params = FacebookUrlManipulator::getParamsAsArray($endpoint); + if (isset($params['access_token'])) { + $this->setAccessTokenFromParams($params['access_token']); + } + + // Clean the token & app secret proof from the endpoint. + $filterParams = ['access_token', 'appsecret_proof']; + $this->endpoint = FacebookUrlManipulator::removeParamsFromUrl($endpoint, $filterParams); + + return $this; + } + + /** + * Return the HTTP method for this request. + * + * @return string + */ + public function getEndpoint() + { + // For batch requests, this will be empty + return $this->endpoint; + } + + /** + * Generate and return the headers for this request. + * + * @return array + */ + public function getHeaders() + { + $headers = static::getDefaultHeaders(); + + if ($this->eTag) { + $headers['If-None-Match'] = $this->eTag; + } + + return array_merge($this->headers, $headers); + } + + /** + * Set the headers for this request. + * + * @param array $headers + */ + public function setHeaders(array $headers) + { + $this->headers = array_merge($this->headers, $headers); + } + + /** + * Sets the eTag value. + * + * @param string $eTag + */ + public function setETag($eTag) + { + $this->eTag = $eTag; + } + + /** + * Set the params for this request. + * + * @param array $params + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function setParams(array $params = []) + { + if (isset($params['access_token'])) { + $this->setAccessTokenFromParams($params['access_token']); + } + + // Don't let these buggers slip in. + unset($params['access_token'], $params['appsecret_proof']); + + // @TODO Refactor code above with this + //$params = $this->sanitizeAuthenticationParams($params); + $params = $this->sanitizeFileParams($params); + $this->dangerouslySetParams($params); + + return $this; + } + + /** + * Set the params for this request without filtering them first. + * + * @param array $params + * + * @return FacebookRequest + */ + public function dangerouslySetParams(array $params = []) + { + $this->params = array_merge($this->params, $params); + + return $this; + } + + /** + * Iterate over the params and pull out the file uploads. + * + * @param array $params + * + * @return array + */ + public function sanitizeFileParams(array $params) + { + foreach ($params as $key => $value) { + if ($value instanceof FacebookFile) { + $this->addFile($key, $value); + unset($params[$key]); + } + } + + return $params; + } + + /** + * Add a file to be uploaded. + * + * @param string $key + * @param FacebookFile $file + */ + public function addFile($key, FacebookFile $file) + { + $this->files[$key] = $file; + } + + /** + * Removes all the files from the upload queue. + */ + public function resetFiles() + { + $this->files = []; + } + + /** + * Get the list of files to be uploaded. + * + * @return array + */ + public function getFiles() + { + return $this->files; + } + + /** + * Let's us know if there is a file upload with this request. + * + * @return boolean + */ + public function containsFileUploads() + { + return !empty($this->files); + } + + /** + * Let's us know if there is a video upload with this request. + * + * @return boolean + */ + public function containsVideoUploads() + { + foreach ($this->files as $file) { + if ($file instanceof FacebookVideo) { + return true; + } + } + + return false; + } + + /** + * Returns the body of the request as multipart/form-data. + * + * @return RequestBodyMultipart + */ + public function getMultipartBody() + { + $params = $this->getPostParams(); + + return new RequestBodyMultipart($params, $this->files); + } + + /** + * Returns the body of the request as URL-encoded. + * + * @return RequestBodyUrlEncoded + */ + public function getUrlEncodedBody() + { + $params = $this->getPostParams(); + + return new RequestBodyUrlEncoded($params); + } + + /** + * Generate and return the params for this request. + * + * @return array + */ + public function getParams() + { + $params = $this->params; + + $accessToken = $this->getAccessToken(); + if ($accessToken) { + $params['access_token'] = $accessToken; + $params['appsecret_proof'] = $this->getAppSecretProof(); + } + + return $params; + } + + /** + * Only return params on POST requests. + * + * @return array + */ + public function getPostParams() + { + if ($this->getMethod() === 'POST') { + return $this->getParams(); + } + + return []; + } + + /** + * The graph version used for this request. + * + * @return string + */ + public function getGraphVersion() + { + return $this->graphVersion; + } + + /** + * Generate and return the URL for this request. + * + * @return string + */ + public function getUrl() + { + $this->validateMethod(); + + $graphVersion = FacebookUrlManipulator::forceSlashPrefix($this->graphVersion); + $endpoint = FacebookUrlManipulator::forceSlashPrefix($this->getEndpoint()); + + $url = $graphVersion . $endpoint; + + if ($this->getMethod() !== 'POST') { + $params = $this->getParams(); + $url = FacebookUrlManipulator::appendParamsToUrl($url, $params); + } + + return $url; + } + + /** + * Return the default headers that every request should use. + * + * @return array + */ + public static function getDefaultHeaders() + { + return [ + 'User-Agent' => 'fb-php-' . Facebook::VERSION, + 'Accept-Encoding' => '*', + ]; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..ce55b143390e3e2e0c9bc5f79d365d5d32f10118 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FacebookResponse.php @@ -0,0 +1,410 @@ +request = $request; + $this->body = $body; + $this->httpStatusCode = $httpStatusCode; + $this->headers = $headers; + + $this->decodeBody(); + } + + /** + * Return the original request that returned this response. + * + * @return FacebookRequest + */ + public function getRequest() + { + return $this->request; + } + + /** + * Return the FacebookApp entity used for this response. + * + * @return FacebookApp + */ + public function getApp() + { + return $this->request->getApp(); + } + + /** + * Return the access token that was used for this response. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->request->getAccessToken(); + } + + /** + * Return the HTTP status code for this response. + * + * @return int + */ + public function getHttpStatusCode() + { + return $this->httpStatusCode; + } + + /** + * Return the HTTP headers for this response. + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Return the raw body response. + * + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * Return the decoded body response. + * + * @return array + */ + public function getDecodedBody() + { + return $this->decodedBody; + } + + /** + * Get the app secret proof that was used for this response. + * + * @return string|null + */ + public function getAppSecretProof() + { + return $this->request->getAppSecretProof(); + } + + /** + * Get the ETag associated with the response. + * + * @return string|null + */ + public function getETag() + { + return isset($this->headers['ETag']) ? $this->headers['ETag'] : null; + } + + /** + * Get the version of Graph that returned this response. + * + * @return string|null + */ + public function getGraphVersion() + { + return isset($this->headers['Facebook-API-Version']) ? $this->headers['Facebook-API-Version'] : null; + } + + /** + * Returns true if Graph returned an error message. + * + * @return boolean + */ + public function isError() + { + return isset($this->decodedBody['error']); + } + + /** + * Throws the exception. + * + * @throws FacebookSDKException + */ + public function throwException() + { + throw $this->thrownException; + } + + /** + * Instantiates an exception to be thrown later. + */ + public function makeException() + { + $this->thrownException = FacebookResponseException::create($this); + } + + /** + * Returns the exception that was thrown for this request. + * + * @return FacebookSDKException|null + */ + public function getThrownException() + { + return $this->thrownException; + } + + /** + * Convert the raw response into an array if possible. + * + * Graph will return 2 types of responses: + * - JSON(P) + * Most responses from Grpah are JSON(P) + * - application/x-www-form-urlencoded key/value pairs + * Happens on the `/oauth/access_token` endpoint when exchanging + * a short-lived access token for a long-lived access token + * - And sometimes nothing :/ but that'd be a bug. + */ + public function decodeBody() + { + $this->decodedBody = json_decode($this->body, true); + + if ($this->decodedBody === null) { + $this->decodedBody = []; + parse_str($this->body, $this->decodedBody); + } elseif (is_bool($this->decodedBody)) { + // Backwards compatibility for Graph < 2.1. + // Mimics 2.1 responses. + // @TODO Remove this after Graph 2.0 is no longer supported + $this->decodedBody = ['success' => $this->decodedBody]; + } elseif (is_numeric($this->decodedBody)) { + $this->decodedBody = ['id' => $this->decodedBody]; + } + + if (!is_array($this->decodedBody)) { + $this->decodedBody = []; + } + + if ($this->isError()) { + $this->makeException(); + } + } + + /** + * Instantiate a new GraphObject from response. + * + * @param string|null $subclassName The GraphNode sub class to cast to. + * + * @return \Facebook\GraphNodes\GraphObject + * + * @throws FacebookSDKException + * + * @deprecated 5.0.0 getGraphObject() has been renamed to getGraphNode() + * @todo v6: Remove this method + */ + public function getGraphObject($subclassName = null) + { + return $this->getGraphNode($subclassName); + } + + /** + * Instantiate a new GraphNode from response. + * + * @param string|null $subclassName The GraphNode sub class to cast to. + * + * @return \Facebook\GraphNodes\GraphNode + * + * @throws FacebookSDKException + */ + public function getGraphNode($subclassName = null) + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphNode($subclassName); + } + + /** + * Convenience method for creating a GraphAlbum collection. + * + * @return \Facebook\GraphNodes\GraphAlbum + * + * @throws FacebookSDKException + */ + public function getGraphAlbum() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphAlbum(); + } + + /** + * Convenience method for creating a GraphPage collection. + * + * @return \Facebook\GraphNodes\GraphPage + * + * @throws FacebookSDKException + */ + public function getGraphPage() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphPage(); + } + + /** + * Convenience method for creating a GraphSessionInfo collection. + * + * @return \Facebook\GraphNodes\GraphSessionInfo + * + * @throws FacebookSDKException + */ + public function getGraphSessionInfo() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphSessionInfo(); + } + + /** + * Convenience method for creating a GraphUser collection. + * + * @return \Facebook\GraphNodes\GraphUser + * + * @throws FacebookSDKException + */ + public function getGraphUser() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphUser(); + } + + /** + * Convenience method for creating a GraphEvent collection. + * + * @return \Facebook\GraphNodes\GraphEvent + * + * @throws FacebookSDKException + */ + public function getGraphEvent() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphEvent(); + } + + /** + * Convenience method for creating a GraphGroup collection. + * + * @return \Facebook\GraphNodes\GraphGroup + * + * @throws FacebookSDKException + */ + public function getGraphGroup() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphGroup(); + } + + /** + * Instantiate a new GraphList from response. + * + * @param string|null $subclassName The GraphNode sub class to cast list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return \Facebook\GraphNodes\GraphList + * + * @throws FacebookSDKException + * + * @deprecated 5.0.0 getGraphList() has been renamed to getGraphEdge() + * @todo v6: Remove this method + */ + public function getGraphList($subclassName = null, $auto_prefix = true) + { + return $this->getGraphEdge($subclassName, $auto_prefix); + } + + /** + * Instantiate a new GraphEdge from response. + * + * @param string|null $subclassName The GraphNode sub class to cast list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return \Facebook\GraphNodes\GraphEdge + * + * @throws FacebookSDKException + */ + public function getGraphEdge($subclassName = null, $auto_prefix = true) + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphEdge($subclassName, $auto_prefix); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FileUpload/FacebookFile.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FileUpload/FacebookFile.php new file mode 100644 index 0000000000000000000000000000000000000000..f8b990548d2c2a32c5e5f268336f424c01749bf4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FileUpload/FacebookFile.php @@ -0,0 +1,135 @@ +path = $filePath; + $this->open(); + } + + /** + * Closes the stream when destructed. + */ + public function __destruct() + { + $this->close(); + } + + /** + * Opens a stream for the file. + * + * @throws FacebookSDKException + */ + public function open() + { + if (!$this->isRemoteFile($this->path) && !is_readable($this->path)) { + throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to read resource: ' . $this->path . '.'); + } + + $this->stream = fopen($this->path, 'r'); + + if (!$this->stream) { + throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to open resource: ' . $this->path . '.'); + } + } + + /** + * Stops the file stream. + */ + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + } + + /** + * Return the contents of the file. + * + * @return string + */ + public function getContents() + { + return stream_get_contents($this->stream); + } + + /** + * Return the name of the file. + * + * @return string + */ + public function getFileName() + { + return basename($this->path); + } + + /** + * Return the mimetype of the file. + * + * @return string + */ + public function getMimetype() + { + return Mimetypes::getInstance()->fromFilename($this->path) ?: 'text/plain'; + } + + /** + * Returns true if the path to the file is remote. + * + * @param string $pathToFile + * + * @return boolean + */ + protected function isRemoteFile($pathToFile) + { + return preg_match('/^(https?|ftp):\/\/.*/', $pathToFile) === 1; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FileUpload/FacebookVideo.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FileUpload/FacebookVideo.php new file mode 100644 index 0000000000000000000000000000000000000000..1e8c55aa8070a741d2108ad28c5ecbb58e9a9012 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/FileUpload/FacebookVideo.php @@ -0,0 +1,33 @@ + 'text/vnd.in3d.3dml', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'asa' => 'text/plain', + 'asax' => 'application/octet-stream', + 'asc' => 'application/pgp-signature', + 'ascx' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'ashx' => 'text/plain', + 'asm' => 'text/x-asm', + 'asmx' => 'text/plain', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asp' => 'text/plain', + 'aspx' => 'text/plain', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'axd' => 'text/plain', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfc' => 'application/x-coldfusion', + 'cfm' => 'application/x-coldfusion', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'cs' => 'text/plain', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hta' => 'application/octet-stream', + 'htc' => 'text/html', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/octet-stream', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/mp4', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsf' => 'application/vnd.lotus-notes', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'text/x-php', + 'phps' => 'application/x-httpd-phps', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rb' => 'text/plain', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'resx' => 'text/xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sig' => 'application/pgp-signature', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'src' => 'application/x-wais-source', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'image/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvx' => 'application/vnd.dece.unspecified', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-ms-wmz', + 'woff' => 'application/x-font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml' + ]; + + /** + * Get a singleton instance of the class + * + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get a mimetype value from a file extension + * + * @param string $extension File extension + * + * @return string|null + */ + public function fromExtension($extension) + { + $extension = strtolower($extension); + + return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null; + } + + /** + * Get a mimetype from a filename + * + * @param string $filename Filename to generate a mimetype from + * + * @return string|null + */ + public function fromFilename($filename) + { + return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/Collection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/Collection.php new file mode 100644 index 0000000000000000000000000000000000000000..cac010ba35f0d8a743634dfa5d8e366a7998eed0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/Collection.php @@ -0,0 +1,242 @@ +items = $items; + } + + /** + * Gets the value of a field from the Graph node. + * + * @param string $name The field to retrieve. + * @param mixed $default The default to return if the field doesn't exist. + * + * @return mixed + */ + public function getField($name, $default = null) + { + if (isset($this->items[$name])) { + return $this->items[$name]; + } + + return $default ?: null; + } + + /** + * Gets the value of the named property for this graph object. + * + * @param string $name The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + * + * @deprecated 5.0.0 getProperty() has been renamed to getField() + * @todo v6: Remove this method + */ + public function getProperty($name, $default = null) + { + return $this->getField($name, $default); + } + + /** + * Returns a list of all fields set on the object. + * + * @return array + */ + public function getFieldNames() + { + return array_keys($this->items); + } + + /** + * Returns a list of all properties set on the object. + * + * @return array + * + * @deprecated 5.0.0 getPropertyNames() has been renamed to getFieldNames() + * @todo v6: Remove this method + */ + public function getPropertyNames() + { + return $this->getFieldNames(); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function asArray() + { + return array_map(function ($value) { + return $value instanceof Collection ? $value->asArray() : $value; + }, $this->items); + } + + /** + * Run a map over each of the items. + * + * @param \Closure $callback + * + * @return static + */ + public function map(\Closure $callback) + { + return new static(array_map($callback, $this->items, array_keys($this->items))); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * + * @return string + */ + public function asJson($options = 0) + { + return json_encode($this->asArray(), $options); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Get an iterator for the items. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->items); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * + * @return mixed + */ + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * + * @return void + */ + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * + * @return void + */ + public function offsetUnset($key) + { + unset($this->items[$key]); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->asJson(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphAchievement.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphAchievement.php new file mode 100644 index 0000000000000000000000000000000000000000..3fba815c9148b8a3adecb1c2d120516c726e29a5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphAchievement.php @@ -0,0 +1,113 @@ + '\Facebook\GraphNodes\GraphUser', + 'application' => '\Facebook\GraphNodes\GraphApplication', + ]; + + /** + * Returns the ID for the achievement. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the user who achieved this. + * + * @return GraphUser|null + */ + public function getFrom() + { + return $this->getField('from'); + } + + /** + * Returns the time at which this was achieved. + * + * @return \DateTime|null + */ + public function getPublishTime() + { + return $this->getField('publish_time'); + } + + /** + * Returns the app in which the user achieved this. + * + * @return GraphApplication|null + */ + public function getApplication() + { + return $this->getField('application'); + } + + /** + * Returns information about the achievement type this instance is connected with. + * + * @return array|null + */ + public function getData() + { + return $this->getField('data'); + } + + /** + * Returns the type of achievement. + * + * @see https://developers.facebook.com/docs/graph-api/reference/v2.2/achievement + * + * @return string + */ + public function getType() + { + return 'game.achievement'; + } + + /** + * Indicates whether gaining the achievement published a feed story for the user. + * + * @return boolean|null + */ + public function isNoFeedStory() + { + return $this->getField('no_feed_story'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphAlbum.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphAlbum.php new file mode 100644 index 0000000000000000000000000000000000000000..50d1f2c378603f9a1043d1f60778367fdd8d03aa --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphAlbum.php @@ -0,0 +1,183 @@ + '\Facebook\GraphNodes\GraphUser', + 'place' => '\Facebook\GraphNodes\GraphPage', + ]; + + /** + * Returns the ID for the album. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns whether the viewer can upload photos to this album. + * + * @return boolean|null + */ + public function getCanUpload() + { + return $this->getField('can_upload'); + } + + /** + * Returns the number of photos in this album. + * + * @return int|null + */ + public function getCount() + { + return $this->getField('count'); + } + + /** + * Returns the ID of the album's cover photo. + * + * @return string|null + */ + public function getCoverPhoto() + { + return $this->getField('cover_photo'); + } + + /** + * Returns the time the album was initially created. + * + * @return \DateTime|null + */ + public function getCreatedTime() + { + return $this->getField('created_time'); + } + + /** + * Returns the time the album was updated. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + return $this->getField('updated_time'); + } + + /** + * Returns the description of the album. + * + * @return string|null + */ + public function getDescription() + { + return $this->getField('description'); + } + + /** + * Returns profile that created the album. + * + * @return GraphUser|null + */ + public function getFrom() + { + return $this->getField('from'); + } + + /** + * Returns profile that created the album. + * + * @return GraphPage|null + */ + public function getPlace() + { + return $this->getField('place'); + } + + /** + * Returns a link to this album on Facebook. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the textual location of the album. + * + * @return string|null + */ + public function getLocation() + { + return $this->getField('location'); + } + + /** + * Returns the title of the album. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the privacy settings for the album. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getField('privacy'); + } + + /** + * Returns the type of the album. + * + * enum{ profile, mobile, wall, normal, album } + * + * @return string|null + */ + public function getType() + { + return $this->getField('type'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphApplication.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphApplication.php new file mode 100644 index 0000000000000000000000000000000000000000..69b09bb57c693a5fa3a97be8508563ac850cfd71 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphApplication.php @@ -0,0 +1,43 @@ +getField('id'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphCoverPhoto.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphCoverPhoto.php new file mode 100644 index 0000000000000000000000000000000000000000..ee6075075db32d6a17cde78236e7fc015d844000 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphCoverPhoto.php @@ -0,0 +1,72 @@ +getField('id'); + } + + /** + * Returns the source of cover if it exists + * + * @return string|null + */ + public function getSource() + { + return $this->getField('source'); + } + + /** + * Returns the offset_x of cover if it exists + * + * @return int|null + */ + public function getOffsetX() + { + return $this->getField('offset_x'); + } + + /** + * Returns the offset_y of cover if it exists + * + * @return int|null + */ + public function getOffsetY() + { + return $this->getField('offset_y'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphEdge.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphEdge.php new file mode 100644 index 0000000000000000000000000000000000000000..95f32849a854e032d2436a93e6039420fbc3b8ea --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphEdge.php @@ -0,0 +1,260 @@ +request = $request; + $this->metaData = $metaData; + $this->parentEdgeEndpoint = $parentEdgeEndpoint; + $this->subclassName = $subclassName; + + parent::__construct($data); + } + + /** + * Gets the parent Graph edge endpoint that generated the list. + * + * @return string|null + */ + public function getParentGraphEdge() + { + return $this->parentEdgeEndpoint; + } + + /** + * Gets the subclass name that the child GraphNode's are cast as. + * + * @return string|null + */ + public function getSubClassName() + { + return $this->subclassName; + } + + /** + * Returns the raw meta data associated with this GraphEdge. + * + * @return array + */ + public function getMetaData() + { + return $this->metaData; + } + + /** + * Returns the next cursor if it exists. + * + * @return string|null + */ + public function getNextCursor() + { + return $this->getCursor('after'); + } + + /** + * Returns the previous cursor if it exists. + * + * @return string|null + */ + public function getPreviousCursor() + { + return $this->getCursor('before'); + } + + /** + * Returns the cursor for a specific direction if it exists. + * + * @param string $direction The direction of the page: after|before + * + * @return string|null + */ + public function getCursor($direction) + { + if (isset($this->metaData['paging']['cursors'][$direction])) { + return $this->metaData['paging']['cursors'][$direction]; + } + + return null; + } + + /** + * Generates a pagination URL based on a cursor. + * + * @param string $direction The direction of the page: next|previous + * + * @return string|null + * + * @throws FacebookSDKException + */ + public function getPaginationUrl($direction) + { + $this->validateForPagination(); + + // Do we have a paging URL? + if (isset($this->metaData['paging'][$direction])) { + // Graph returns the full URL with all the original params. + // We just want the endpoint though. + $pageUrl = $this->metaData['paging'][$direction]; + + return FacebookUrlManipulator::baseGraphUrlEndpoint($pageUrl); + } + + // Do we have a cursor to work with? + $cursorDirection = $direction === 'next' ? 'after' : 'before'; + $cursor = $this->getCursor($cursorDirection); + if (!$cursor) { + return null; + } + + // If we don't know the ID of the parent node, this ain't gonna work. + if (!$this->parentEdgeEndpoint) { + return null; + } + + // We have the parent node ID, paging cursor & original request. + // These were the ingredients chosen to create the perfect little URL. + $pageUrl = $this->parentEdgeEndpoint . '?' . $cursorDirection . '=' . urlencode($cursor); + + // Pull in the original params + $originalUrl = $this->request->getUrl(); + $pageUrl = FacebookUrlManipulator::mergeUrlParams($originalUrl, $pageUrl); + + return FacebookUrlManipulator::forceSlashPrefix($pageUrl); + } + + /** + * Validates whether or not we can paginate on this request. + * + * @throws FacebookSDKException + */ + public function validateForPagination() + { + if ($this->request->getMethod() !== 'GET') { + throw new FacebookSDKException('You can only paginate on a GET request.', 720); + } + } + + /** + * Gets the request object needed to make a next|previous page request. + * + * @param string $direction The direction of the page: next|previous + * + * @return FacebookRequest|null + * + * @throws FacebookSDKException + */ + public function getPaginationRequest($direction) + { + $pageUrl = $this->getPaginationUrl($direction); + if (!$pageUrl) { + return null; + } + + $newRequest = clone $this->request; + $newRequest->setEndpoint($pageUrl); + + return $newRequest; + } + + /** + * Gets the request object needed to make a "next" page request. + * + * @return FacebookRequest|null + * + * @throws FacebookSDKException + */ + public function getNextPageRequest() + { + return $this->getPaginationRequest('next'); + } + + /** + * Gets the request object needed to make a "previous" page request. + * + * @return FacebookRequest|null + * + * @throws FacebookSDKException + */ + public function getPreviousPageRequest() + { + return $this->getPaginationRequest('previous'); + } + + /** + * The total number of results according to Graph if it exists. + * + * This will be returned if the summary=true modifier is present in the request. + * + * @return int|null + */ + public function getTotalCount() + { + if (isset($this->metaData['summary']['total_count'])) { + return $this->metaData['summary']['total_count']; + } + + return null; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphEvent.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..19ff2fb4aa17194bdeee7a6f9a847c2bfe45b39f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphEvent.php @@ -0,0 +1,242 @@ + '\Facebook\GraphNodes\GraphCoverPhoto', + 'place' => '\Facebook\GraphNodes\GraphPage', + 'picture' => '\Facebook\GraphNodes\GraphPicture', + 'parent_group' => '\Facebook\GraphNodes\GraphGroup', + ]; + + /** + * Returns the `id` (The event ID) as string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the `cover` (Cover picture) as GraphCoverPhoto if present. + * + * @return GraphCoverPhoto|null + */ + public function getCover() + { + return $this->getField('cover'); + } + + /** + * Returns the `description` (Long-form description) as string if present. + * + * @return string|null + */ + public function getDescription() + { + return $this->getField('description'); + } + + /** + * Returns the `end_time` (End time, if one has been set) as DateTime if present. + * + * @return \DateTime|null + */ + public function getEndTime() + { + return $this->getField('end_time'); + } + + /** + * Returns the `is_date_only` (Whether the event only has a date specified, but no time) as bool if present. + * + * @return bool|null + */ + public function getIsDateOnly() + { + return $this->getField('is_date_only'); + } + + /** + * Returns the `name` (Event name) as string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the `owner` (The profile that created the event) as GraphNode if present. + * + * @return GraphNode|null + */ + public function getOwner() + { + return $this->getField('owner'); + } + + /** + * Returns the `parent_group` (The group the event belongs to) as GraphGroup if present. + * + * @return GraphGroup|null + */ + public function getParentGroup() + { + return $this->getField('parent_group'); + } + + /** + * Returns the `place` (Event Place information) as GraphPage if present. + * + * @return GraphPage|null + */ + public function getPlace() + { + return $this->getField('place'); + } + + /** + * Returns the `privacy` (Who can see the event) as string if present. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getField('privacy'); + } + + /** + * Returns the `start_time` (Start time) as DateTime if present. + * + * @return \DateTime|null + */ + public function getStartTime() + { + return $this->getField('start_time'); + } + + /** + * Returns the `ticket_uri` (The link users can visit to buy a ticket to this event) as string if present. + * + * @return string|null + */ + public function getTicketUri() + { + return $this->getField('ticket_uri'); + } + + /** + * Returns the `timezone` (Timezone) as string if present. + * + * @return string|null + */ + public function getTimezone() + { + return $this->getField('timezone'); + } + + /** + * Returns the `updated_time` (Last update time) as DateTime if present. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + return $this->getField('updated_time'); + } + + /** + * Returns the `picture` (Event picture) as GraphPicture if present. + * + * @return GraphPicture|null + */ + public function getPicture() + { + return $this->getField('picture'); + } + + /** + * Returns the `attending_count` (Number of people attending the event) as int if present. + * + * @return int|null + */ + public function getAttendingCount() + { + return $this->getField('attending_count'); + } + + /** + * Returns the `declined_count` (Number of people who declined the event) as int if present. + * + * @return int|null + */ + public function getDeclinedCount() + { + return $this->getField('declined_count'); + } + + /** + * Returns the `maybe_count` (Number of people who maybe going to the event) as int if present. + * + * @return int|null + */ + public function getMaybeCount() + { + return $this->getField('maybe_count'); + } + + /** + * Returns the `noreply_count` (Number of people who did not reply to the event) as int if present. + * + * @return int|null + */ + public function getNoreplyCount() + { + return $this->getField('noreply_count'); + } + + /** + * Returns the `invited_count` (Number of people invited to the event) as int if present. + * + * @return int|null + */ + public function getInvitedCount() + { + return $this->getField('invited_count'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphGroup.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphGroup.php new file mode 100644 index 0000000000000000000000000000000000000000..07a4dbd75e6f474b38c7c9ce7dfea40218ec9592 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphGroup.php @@ -0,0 +1,171 @@ + '\Facebook\GraphNodes\GraphCoverPhoto', + 'venue' => '\Facebook\GraphNodes\GraphLocation', + ]; + + /** + * Returns the `id` (The Group ID) as string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the `cover` (The cover photo of the Group) as GraphCoverPhoto if present. + * + * @return GraphCoverPhoto|null + */ + public function getCover() + { + return $this->getField('cover'); + } + + /** + * Returns the `description` (A brief description of the Group) as string if present. + * + * @return string|null + */ + public function getDescription() + { + return $this->getField('description'); + } + + /** + * Returns the `email` (The email address to upload content to the Group. Only current members of the Group can use this) as string if present. + * + * @return string|null + */ + public function getEmail() + { + return $this->getField('email'); + } + + /** + * Returns the `icon` (The URL for the Group's icon) as string if present. + * + * @return string|null + */ + public function getIcon() + { + return $this->getField('icon'); + } + + /** + * Returns the `link` (The Group's website) as string if present. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the `name` (The name of the Group) as string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the `member_request_count` (Number of people asking to join the group.) as int if present. + * + * @return int|null + */ + public function getMemberRequestCount() + { + return $this->getField('member_request_count'); + } + + /** + * Returns the `owner` (The profile that created this Group) as GraphNode if present. + * + * @return GraphNode|null + */ + public function getOwner() + { + return $this->getField('owner'); + } + + /** + * Returns the `parent` (The parent Group of this Group, if it exists) as GraphNode if present. + * + * @return GraphNode|null + */ + public function getParent() + { + return $this->getField('parent'); + } + + /** + * Returns the `privacy` (The privacy setting of the Group) as string if present. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getField('privacy'); + } + + /** + * Returns the `updated_time` (The last time the Group was updated (this includes changes in the Group's properties and changes in posts and comments if user can see them)) as \DateTime if present. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + return $this->getField('updated_time'); + } + + /** + * Returns the `venue` (The location for the Group) as GraphLocation if present. + * + * @return GraphLocation|null + */ + public function getVenue() + { + return $this->getField('venue'); + } + +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphList.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphList.php new file mode 100644 index 0000000000000000000000000000000000000000..a60a07a7e56d3c5e3ccb52465b480b91c607d4a2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphList.php @@ -0,0 +1,36 @@ +getField('street'); + } + + /** + * Returns the city component of the location + * + * @return string|null + */ + public function getCity() + { + return $this->getField('city'); + } + + /** + * Returns the state component of the location + * + * @return string|null + */ + public function getState() + { + return $this->getField('state'); + } + + /** + * Returns the country component of the location + * + * @return string|null + */ + public function getCountry() + { + return $this->getField('country'); + } + + /** + * Returns the zipcode component of the location + * + * @return string|null + */ + public function getZip() + { + return $this->getField('zip'); + } + + /** + * Returns the latitude component of the location + * + * @return float|null + */ + public function getLatitude() + { + return $this->getField('latitude'); + } + + /** + * Returns the street component of the location + * + * @return float|null + */ + public function getLongitude() + { + return $this->getField('longitude'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphNode.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphNode.php new file mode 100644 index 0000000000000000000000000000000000000000..0d2f504c1ef677f5079cede5783c9e40270b71ff --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphNode.php @@ -0,0 +1,185 @@ +castItems($data)); + } + + /** + * Iterates over an array and detects the types each node + * should be cast to and returns all the items as an array. + * + * @TODO Add auto-casting to AccessToken entities. + * + * @param array $data The array to iterate over. + * + * @return array + */ + public function castItems(array $data) + { + $items = []; + + foreach ($data as $k => $v) { + if ($this->shouldCastAsDateTime($k) + && (is_numeric($v) + || $k === 'birthday' + || $this->isIso8601DateString($v)) + ) { + $items[$k] = $this->castToDateTime($v); + } else { + $items[$k] = $v; + } + } + + return $items; + } + + /** + * Uncasts any auto-casted datatypes. + * Basically the reverse of castItems(). + * + * @return array + */ + public function uncastItems() + { + $items = $this->asArray(); + + return array_map(function ($v) { + if ($v instanceof \DateTime) { + return $v->format(\DateTime::ISO8601); + } + + return $v; + }, $items); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * + * @return string + */ + public function asJson($options = 0) + { + return json_encode($this->uncastItems(), $options); + } + + /** + * Detects an ISO 8601 formatted string. + * + * @param string $string + * + * @return boolean + * + * @see https://developers.facebook.com/docs/graph-api/using-graph-api/#readmodifiers + * @see http://www.cl.cam.ac.uk/~mgk25/iso-time.html + * @see http://en.wikipedia.org/wiki/ISO_8601 + */ + public function isIso8601DateString($string) + { + // This insane regex was yoinked from here: + // http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ + // ...and I'm all like: + // http://thecodinglove.com/post/95378251969/when-code-works-and-i-dont-know-why + $crazyInsaneRegexThatSomehowDetectsIso8601 = '/^([\+-]?\d{4}(?!\d{2}\b))' + . '((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?' + . '|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d' + . '|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])' + . '((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d' + . '([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/'; + + return preg_match($crazyInsaneRegexThatSomehowDetectsIso8601, $string) === 1; + } + + /** + * Determines if a value from Graph should be cast to DateTime. + * + * @param string $key + * + * @return boolean + */ + public function shouldCastAsDateTime($key) + { + return in_array($key, [ + 'created_time', + 'updated_time', + 'start_time', + 'end_time', + 'backdated_time', + 'issued_at', + 'expires_at', + 'birthday', + 'publish_time' + ], true); + } + + /** + * Casts a date value from Graph to DateTime. + * + * @param int|string $value + * + * @return \DateTime + */ + public function castToDateTime($value) + { + if (is_int($value)) { + $dt = new \DateTime(); + $dt->setTimestamp($value); + } else { + $dt = new \DateTime($value); + } + + return $dt; + } + + /** + * Getter for $graphObjectMap. + * + * @return array + */ + public static function getObjectMap() + { + return static::$graphObjectMap; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphNodeFactory.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphNodeFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..e1bedd910b8c2af886b82f5a7eac3f2b266f123b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphNodeFactory.php @@ -0,0 +1,392 @@ +response = $response; + $this->decodedBody = $response->getDecodedBody(); + } + + /** + * Tries to convert a FacebookResponse entity into a GraphNode. + * + * @param string|null $subclassName The GraphNode sub class to cast to. + * + * @return GraphNode + * + * @throws FacebookSDKException + */ + public function makeGraphNode($subclassName = null) + { + $this->validateResponseAsArray(); + $this->validateResponseCastableAsGraphNode(); + + return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName); + } + + /** + * Convenience method for creating a GraphAchievement collection. + * + * @return GraphAchievement + * + * @throws FacebookSDKException + */ + public function makeGraphAchievement() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAchievement'); + } + + /** + * Convenience method for creating a GraphAlbum collection. + * + * @return GraphAlbum + * + * @throws FacebookSDKException + */ + public function makeGraphAlbum() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAlbum'); + } + + /** + * Convenience method for creating a GraphPage collection. + * + * @return GraphPage + * + * @throws FacebookSDKException + */ + public function makeGraphPage() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphPage'); + } + + /** + * Convenience method for creating a GraphSessionInfo collection. + * + * @return GraphSessionInfo + * + * @throws FacebookSDKException + */ + public function makeGraphSessionInfo() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphSessionInfo'); + } + + /** + * Convenience method for creating a GraphUser collection. + * + * @return GraphUser + * + * @throws FacebookSDKException + */ + public function makeGraphUser() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphUser'); + } + + /** + * Convenience method for creating a GraphEvent collection. + * + * @return GraphEvent + * + * @throws FacebookSDKException + */ + public function makeGraphEvent() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent'); + } + + /** + * Convenience method for creating a GraphGroup collection. + * + * @return GraphGroup + * + * @throws FacebookSDKException + */ + public function makeGraphGroup() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphGroup'); + } + + /** + * Tries to convert a FacebookResponse entity into a GraphEdge. + * + * @param string|null $subclassName The GraphNode sub class to cast the list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return GraphEdge + * + * @throws FacebookSDKException + */ + public function makeGraphEdge($subclassName = null, $auto_prefix = true) + { + $this->validateResponseAsArray(); + $this->validateResponseCastableAsGraphEdge(); + + if ($subclassName && $auto_prefix) { + $subclassName = static::BASE_GRAPH_OBJECT_PREFIX . $subclassName; + } + + return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName); + } + + /** + * Validates the decoded body. + * + * @throws FacebookSDKException + */ + public function validateResponseAsArray() + { + if (!is_array($this->decodedBody)) { + throw new FacebookSDKException('Unable to get response from Graph as array.', 620); + } + } + + /** + * Validates that the return data can be cast as a GraphNode. + * + * @throws FacebookSDKException + */ + public function validateResponseCastableAsGraphNode() + { + if (isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data'])) { + throw new FacebookSDKException( + 'Unable to convert response from Graph to a GraphNode because the response looks like a GraphEdge. Try using GraphNodeFactory::makeGraphEdge() instead.', + 620 + ); + } + } + + /** + * Validates that the return data can be cast as a GraphEdge. + * + * @throws FacebookSDKException + */ + public function validateResponseCastableAsGraphEdge() + { + if (!(isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data']))) { + throw new FacebookSDKException( + 'Unable to convert response from Graph to a GraphEdge because the response does not look like a GraphEdge. Try using GraphNodeFactory::makeGraphNode() instead.', + 620 + ); + } + } + + /** + * Safely instantiates a GraphNode of $subclassName. + * + * @param array $data The array of data to iterate over. + * @param string|null $subclassName The subclass to cast this collection to. + * + * @return GraphNode + * + * @throws FacebookSDKException + */ + public function safelyMakeGraphNode(array $data, $subclassName = null) + { + $subclassName = $subclassName ?: static::BASE_GRAPH_NODE_CLASS; + static::validateSubclass($subclassName); + + // Remember the parent node ID + $parentNodeId = isset($data['id']) ? $data['id'] : null; + + $items = []; + + foreach ($data as $k => $v) { + // Array means could be recurable + if (is_array($v)) { + // Detect any smart-casting from the $graphObjectMap array. + // This is always empty on the GraphNode collection, but subclasses can define + // their own array of smart-casting types. + $graphObjectMap = $subclassName::getObjectMap(); + $objectSubClass = isset($graphObjectMap[$k]) + ? $graphObjectMap[$k] + : null; + + // Could be a GraphEdge or GraphNode + $items[$k] = $this->castAsGraphNodeOrGraphEdge($v, $objectSubClass, $k, $parentNodeId); + } else { + $items[$k] = $v; + } + } + + return new $subclassName($items); + } + + /** + * Takes an array of values and determines how to cast each node. + * + * @param array $data The array of data to iterate over. + * @param string|null $subclassName The subclass to cast this collection to. + * @param string|null $parentKey The key of this data (Graph edge). + * @param string|null $parentNodeId The parent Graph node ID. + * + * @return GraphNode|GraphEdge + * + * @throws FacebookSDKException + */ + public function castAsGraphNodeOrGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null) + { + if (isset($data['data'])) { + // Create GraphEdge + if (static::isCastableAsGraphEdge($data['data'])) { + return $this->safelyMakeGraphEdge($data, $subclassName, $parentKey, $parentNodeId); + } + // Sometimes Graph is a weirdo and returns a GraphNode under the "data" key + $data = $data['data']; + } + + // Create GraphNode + return $this->safelyMakeGraphNode($data, $subclassName); + } + + /** + * Return an array of GraphNode's. + * + * @param array $data The array of data to iterate over. + * @param string|null $subclassName The GraphNode subclass to cast each item in the list to. + * @param string|null $parentKey The key of this data (Graph edge). + * @param string|null $parentNodeId The parent Graph node ID. + * + * @return GraphEdge + * + * @throws FacebookSDKException + */ + public function safelyMakeGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null) + { + if (!isset($data['data'])) { + throw new FacebookSDKException('Cannot cast data to GraphEdge. Expected a "data" key.', 620); + } + + $dataList = []; + foreach ($data['data'] as $graphNode) { + $dataList[] = $this->safelyMakeGraphNode($graphNode, $subclassName, $parentKey, $parentNodeId); + } + + $metaData = $this->getMetaData($data); + + // We'll need to make an edge endpoint for this in case it's a GraphEdge (for cursor pagination) + $parentGraphEdgeEndpoint = $parentNodeId && $parentKey ? '/' . $parentNodeId . '/' . $parentKey : null; + $className = static::BASE_GRAPH_EDGE_CLASS; + + return new $className($this->response->getRequest(), $dataList, $metaData, $parentGraphEdgeEndpoint, $subclassName); + } + + /** + * Get the meta data from a list in a Graph response. + * + * @param array $data The Graph response. + * + * @return array + */ + public function getMetaData(array $data) + { + unset($data['data']); + + return $data; + } + + /** + * Determines whether or not the data should be cast as a GraphEdge. + * + * @param array $data + * + * @return boolean + */ + public static function isCastableAsGraphEdge(array $data) + { + if ($data === []) { + return true; + } + + // Checks for a sequential numeric array which would be a GraphEdge + return array_keys($data) === range(0, count($data) - 1); + } + + /** + * Ensures that the subclass in question is valid. + * + * @param string $subclassName The GraphNode subclass to validate. + * + * @throws FacebookSDKException + */ + public static function validateSubclass($subclassName) + { + if ($subclassName == static::BASE_GRAPH_NODE_CLASS || is_subclass_of($subclassName, static::BASE_GRAPH_NODE_CLASS)) { + return; + } + + throw new FacebookSDKException('The given subclass "' . $subclassName . '" is not valid. Cannot cast to an object that is not a GraphNode subclass.', 620); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphObject.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphObject.php new file mode 100644 index 0000000000000000000000000000000000000000..bb8f8e40c1c243a3e2d1cd08c75c29b3ab156901 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphObject.php @@ -0,0 +1,36 @@ +makeGraphNode($subclassName); + } + + /** + * Convenience method for creating a GraphEvent collection. + * + * @return GraphEvent + * + * @throws FacebookSDKException + */ + public function makeGraphEvent() + { + return $this->makeGraphObject(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent'); + } + + /** + * Tries to convert a FacebookResponse entity into a GraphEdge. + * + * @param string|null $subclassName The GraphNode sub class to cast the list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return GraphEdge + * + * @deprecated 5.0.0 GraphObjectFactory has been renamed to GraphNodeFactory + */ + public function makeGraphList($subclassName = null, $auto_prefix = true) + { + return $this->makeGraphEdge($subclassName, $auto_prefix); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphPage.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphPage.php new file mode 100644 index 0000000000000000000000000000000000000000..ab8e31a6c4858f7cc3e777d7cb6026ec95f0e51d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphPage.php @@ -0,0 +1,125 @@ + '\Facebook\GraphNodes\GraphPage', + 'global_brand_parent_page' => '\Facebook\GraphNodes\GraphPage', + 'location' => '\Facebook\GraphNodes\GraphLocation', + ]; + + /** + * Returns the ID for the user's page as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the Category for the user's page as a string if present. + * + * @return string|null + */ + public function getCategory() + { + return $this->getField('category'); + } + + /** + * Returns the Name of the user's page as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the best available Page on Facebook. + * + * @return GraphPage|null + */ + public function getBestPage() + { + return $this->getField('best_page'); + } + + /** + * Returns the brand's global (parent) Page. + * + * @return GraphPage|null + */ + public function getGlobalBrandParentPage() + { + return $this->getField('global_brand_parent_page'); + } + + /** + * Returns the location of this place. + * + * @return GraphLocation|null + */ + public function getLocation() + { + return $this->getField('location'); + } + + /** + * Returns the page access token for the admin user. + * + * Only available in the `/me/accounts` context. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->getField('access_token'); + } + + /** + * Returns the roles of the page admin user. + * + * Only available in the `/me/accounts` context. + * + * @return array|null + */ + public function getPerms() + { + return $this->getField('perms'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphPicture.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphPicture.php new file mode 100644 index 0000000000000000000000000000000000000000..bfd37fabce7e351f280c80ee8bf77ece5e5a859f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphPicture.php @@ -0,0 +1,72 @@ +getField('is_silhouette'); + } + + /** + * Returns the url of user picture if it exists + * + * @return string|null + */ + public function getUrl() + { + return $this->getField('url'); + } + + /** + * Returns the width of user picture if it exists + * + * @return int|null + */ + public function getWidth() + { + return $this->getField('width'); + } + + /** + * Returns the height of user picture if it exists + * + * @return int|null + */ + public function getHeight() + { + return $this->getField('height'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphSessionInfo.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphSessionInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..3c9e2ff485336910c7126e873f795e48dae91742 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphSessionInfo.php @@ -0,0 +1,102 @@ +getField('app_id'); + } + + /** + * Returns the application name the token was issued for. + * + * @return string|null + */ + public function getApplication() + { + return $this->getField('application'); + } + + /** + * Returns the date & time that the token expires. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->getField('expires_at'); + } + + /** + * Returns whether the token is valid. + * + * @return boolean + */ + public function getIsValid() + { + return $this->getField('is_valid'); + } + + /** + * Returns the date & time the token was issued at. + * + * @return \DateTime|null + */ + public function getIssuedAt() + { + return $this->getField('issued_at'); + } + + /** + * Returns the scope permissions associated with the token. + * + * @return array + */ + public function getScopes() + { + return $this->getField('scopes'); + } + + /** + * Returns the login id of the user associated with the token. + * + * @return string|null + */ + public function getUserId() + { + return $this->getField('user_id'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphUser.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphUser.php new file mode 100644 index 0000000000000000000000000000000000000000..cb9ddbb5d9c2685cccba4d4c05983102609425cb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/GraphNodes/GraphUser.php @@ -0,0 +1,162 @@ + '\Facebook\GraphNodes\GraphPage', + 'location' => '\Facebook\GraphNodes\GraphPage', + 'significant_other' => '\Facebook\GraphNodes\GraphUser', + 'picture' => '\Facebook\GraphNodes\GraphPicture', + ]; + + /** + * Returns the ID for the user as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the name for the user as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the first name for the user as a string if present. + * + * @return string|null + */ + public function getFirstName() + { + return $this->getField('first_name'); + } + + /** + * Returns the middle name for the user as a string if present. + * + * @return string|null + */ + public function getMiddleName() + { + return $this->getField('middle_name'); + } + + /** + * Returns the last name for the user as a string if present. + * + * @return string|null + */ + public function getLastName() + { + return $this->getField('last_name'); + } + + /** + * Returns the gender for the user as a string if present. + * + * @return string|null + */ + public function getGender() + { + return $this->getField('gender'); + } + + /** + * Returns the Facebook URL for the user as a string if available. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the users birthday, if available. + * + * @return \DateTime|null + */ + public function getBirthday() + { + return $this->getField('birthday'); + } + + /** + * Returns the current location of the user as a GraphPage. + * + * @return GraphPage|null + */ + public function getLocation() + { + return $this->getField('location'); + } + + /** + * Returns the current location of the user as a GraphPage. + * + * @return GraphPage|null + */ + public function getHometown() + { + return $this->getField('hometown'); + } + + /** + * Returns the current location of the user as a GraphUser. + * + * @return GraphUser|null + */ + public function getSignificantOther() + { + return $this->getField('significant_other'); + } + + /** + * Returns the picture of the user as a GraphPicture + * + * @return GraphPicture|null + */ + public function getPicture() + { + return $this->getField('picture'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookCanvasHelper.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookCanvasHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..8068526ad32e2f4a953ab3f696886a223d143560 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookCanvasHelper.php @@ -0,0 +1,52 @@ +signedRequest ? $this->signedRequest->get('app_data') : null; + } + + /** + * Get raw signed request from POST. + * + * @return string|null + */ + public function getRawSignedRequest() + { + return $this->getRawSignedRequestFromPost() ?: null; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookJavaScriptHelper.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookJavaScriptHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..5d406b53949f073f28f7792aa86f2b83e2f17b63 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookJavaScriptHelper.php @@ -0,0 +1,42 @@ +getRawSignedRequestFromCookie(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookPageTabHelper.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookPageTabHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..ee43f5e8970ae34f0e91241943bc7368d2104e03 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookPageTabHelper.php @@ -0,0 +1,95 @@ +signedRequest) { + return; + } + + $this->pageData = $this->signedRequest->get('page'); + } + + /** + * Returns a value from the page data. + * + * @param string $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function getPageData($key, $default = null) + { + if (isset($this->pageData[$key])) { + return $this->pageData[$key]; + } + + return $default; + } + + /** + * Returns true if the user is an admin. + * + * @return boolean + */ + public function isAdmin() + { + return $this->getPageData('admin') === true; + } + + /** + * Returns the page id if available. + * + * @return string|null + */ + public function getPageId() + { + return $this->getPageData('id'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookRedirectLoginHelper.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookRedirectLoginHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..144a5b4546cd99183a6d38eb1408021fda6bd0ce --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookRedirectLoginHelper.php @@ -0,0 +1,360 @@ +oAuth2Client = $oAuth2Client; + $this->persistentDataHandler = $persistentDataHandler ?: new FacebookSessionPersistentDataHandler(); + $this->urlDetectionHandler = $urlHandler ?: new FacebookUrlDetectionHandler(); + $this->pseudoRandomStringGenerator = $prsg ?: $this->detectPseudoRandomStringGenerator(); + } + + /** + * Returns the persistent data handler. + * + * @return PersistentDataInterface + */ + public function getPersistentDataHandler() + { + return $this->persistentDataHandler; + } + + /** + * Returns the URL detection handler. + * + * @return UrlDetectionInterface + */ + public function getUrlDetectionHandler() + { + return $this->urlDetectionHandler; + } + + /** + * Returns the cryptographically secure pseudo-random string generator. + * + * @return PseudoRandomStringGeneratorInterface + */ + public function getPseudoRandomStringGenerator() + { + return $this->pseudoRandomStringGenerator; + } + + /** + * Detects which pseudo-random string generator to use. + * + * @return PseudoRandomStringGeneratorInterface + * + * @throws FacebookSDKException + */ + public function detectPseudoRandomStringGenerator() + { + // Since openssl_random_pseudo_bytes() can sometimes return non-cryptographically + // secure pseudo-random strings (in rare cases), we check for mcrypt_create_iv() first. + if (function_exists('mcrypt_create_iv')) { + return new McryptPseudoRandomStringGenerator(); + } + + if (function_exists('openssl_random_pseudo_bytes')) { + return new OpenSslPseudoRandomStringGenerator(); + } + + if (!ini_get('open_basedir') && is_readable('/dev/urandom')) { + return new UrandomPseudoRandomStringGenerator(); + } + + throw new FacebookSDKException('Unable to detect a cryptographically secure pseudo-random string generator.'); + } + + /** + * Stores CSRF state and returns a URL to which the user should be sent to in order to continue the login process with Facebook. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param array $params An array of parameters to generate URL. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + private function makeUrl($redirectUrl, array $scope, array $params = [], $separator = '&') + { + $state = $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH); + $this->persistentDataHandler->set('state', $state); + + return $this->oAuth2Client->getAuthorizationUrl($redirectUrl, $state, $scope, $params, $separator); + } + + /** + * Returns the URL to send the user in order to login to Facebook. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getLoginUrl($redirectUrl, array $scope = [], $separator = '&') + { + return $this->makeUrl($redirectUrl, $scope, [], $separator); + } + + /** + * Returns the URL to send the user in order to log out of Facebook. + * + * @param AccessToken|string $accessToken The access token that will be logged out. + * @param string $next The url Facebook should redirect the user to after a successful logout. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + * + * @throws FacebookSDKException + */ + public function getLogoutUrl($accessToken, $next, $separator = '&') + { + if (!$accessToken instanceof AccessToken) { + $accessToken = new AccessToken($accessToken); + } + + if ($accessToken->isAppAccessToken()) { + throw new FacebookSDKException('Cannot generate a logout URL with an app access token.', 722); + } + + $params = [ + 'next' => $next, + 'access_token' => $accessToken->getValue(), + ]; + + return 'https://www.facebook.com/logout.php?' . http_build_query($params, null, $separator); + } + + /** + * Returns the URL to send the user in order to login to Facebook with permission(s) to be re-asked. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getReRequestUrl($redirectUrl, array $scope = [], $separator = '&') + { + $params = ['auth_type' => 'rerequest']; + + return $this->makeUrl($redirectUrl, $scope, $params, $separator); + } + + /** + * Returns the URL to send the user in order to login to Facebook with user to be re-authenticated. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getReAuthenticationUrl($redirectUrl, array $scope = [], $separator = '&') + { + $params = ['auth_type' => 'reauthenticate']; + + return $this->makeUrl($redirectUrl, $scope, $params, $separator); + } + + /** + * Takes a valid code from a login redirect, and returns an AccessToken entity. + * + * @param string|null $redirectUrl The redirect URL. + * + * @return AccessToken|null + * + * @throws FacebookSDKException + */ + public function getAccessToken($redirectUrl = null) + { + if (!$code = $this->getCode()) { + return null; + } + + $this->validateCsrf(); + + $redirectUrl = $redirectUrl ?: $this->urlDetectionHandler->getCurrentUrl(); + // At minimum we need to remove the state param + $redirectUrl = FacebookUrlManipulator::removeParamsFromUrl($redirectUrl, ['state']); + + return $this->oAuth2Client->getAccessTokenFromCode($code, $redirectUrl); + } + + /** + * Validate the request against a cross-site request forgery. + * + * @throws FacebookSDKException + */ + protected function validateCsrf() + { + $state = $this->getState(); + $savedState = $this->persistentDataHandler->get('state'); + + if (!$state || !$savedState) { + throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing.'); + } + + $savedLen = strlen($savedState); + $givenLen = strlen($state); + + if ($savedLen !== $givenLen) { + throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.'); + } + + $result = 0; + for ($i = 0; $i < $savedLen; $i++) { + $result |= ord($state[$i]) ^ ord($savedState[$i]); + } + + if ($result !== 0) { + throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.'); + } + } + + /** + * Return the code. + * + * @return string|null + */ + protected function getCode() + { + return $this->getInput('code'); + } + + /** + * Return the state. + * + * @return string|null + */ + protected function getState() + { + return $this->getInput('state'); + } + + /** + * Return the error code. + * + * @return string|null + */ + public function getErrorCode() + { + return $this->getInput('error_code'); + } + + /** + * Returns the error. + * + * @return string|null + */ + public function getError() + { + return $this->getInput('error'); + } + + /** + * Returns the error reason. + * + * @return string|null + */ + public function getErrorReason() + { + return $this->getInput('error_reason'); + } + + /** + * Returns the error description. + * + * @return string|null + */ + public function getErrorDescription() + { + return $this->getInput('error_description'); + } + + /** + * Returns a value from a GET param. + * + * @param string $key + * + * @return string|null + */ + private function getInput($key) + { + return isset($_GET[$key]) ? $_GET[$key] : null; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..aafa24645fe58bae647a7da7d5de5c4ee03862bb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php @@ -0,0 +1,166 @@ +app = $app; + $graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; + $this->oAuth2Client = new OAuth2Client($this->app, $client, $graphVersion); + + $this->instantiateSignedRequest(); + } + + /** + * Instantiates a new SignedRequest entity. + * + * @param string|null + */ + public function instantiateSignedRequest($rawSignedRequest = null) + { + $rawSignedRequest = $rawSignedRequest ?: $this->getRawSignedRequest(); + + if (!$rawSignedRequest) { + return; + } + + $this->signedRequest = new SignedRequest($this->app, $rawSignedRequest); + } + + /** + * Returns an AccessToken entity from the signed request. + * + * @return AccessToken|null + * + * @throws \Facebook\Exceptions\FacebookSDKException + */ + public function getAccessToken() + { + if ($this->signedRequest && $this->signedRequest->hasOAuthData()) { + $code = $this->signedRequest->get('code'); + $accessToken = $this->signedRequest->get('oauth_token'); + + if ($code && !$accessToken) { + return $this->oAuth2Client->getAccessTokenFromCode($code); + } + + $expiresAt = $this->signedRequest->get('expires', 0); + + return new AccessToken($accessToken, $expiresAt); + } + + return null; + } + + /** + * Returns the SignedRequest entity. + * + * @return SignedRequest|null + */ + public function getSignedRequest() + { + return $this->signedRequest; + } + + /** + * Returns the user_id if available. + * + * @return string|null + */ + public function getUserId() + { + return $this->signedRequest ? $this->signedRequest->getUserId() : null; + } + + /** + * Get raw signed request from input. + * + * @return string|null + */ + abstract public function getRawSignedRequest(); + + /** + * Get raw signed request from POST input. + * + * @return string|null + */ + public function getRawSignedRequestFromPost() + { + if (isset($_POST['signed_request'])) { + return $_POST['signed_request']; + } + + return null; + } + + /** + * Get raw signed request from cookie set from the Javascript SDK. + * + * @return string|null + */ + public function getRawSignedRequestFromCookie() + { + if (isset($_COOKIE['fbsr_' . $this->app->getId()])) { + return $_COOKIE['fbsr_' . $this->app->getId()]; + } + + return null; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/GraphRawResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/GraphRawResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..583d3033c5be911736bc7ab43f11d18f2ec341c7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/GraphRawResponse.php @@ -0,0 +1,137 @@ +httpResponseCode = (int)$httpStatusCode; + } + + if (is_array($headers)) { + $this->headers = $headers; + } else { + $this->setHeadersFromString($headers); + } + + $this->body = $body; + } + + /** + * Return the response headers. + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Return the body of the response. + * + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * Return the HTTP response code. + * + * @return int + */ + public function getHttpResponseCode() + { + return $this->httpResponseCode; + } + + /** + * Sets the HTTP response code from a raw header. + * + * @param string $rawResponseHeader + */ + public function setHttpResponseCodeFromHeader($rawResponseHeader) + { + preg_match('|HTTP/\d\.\d\s+(\d+)\s+.*|', $rawResponseHeader, $match); + $this->httpResponseCode = (int)$match[1]; + } + + /** + * Parse the raw headers and set as an array. + * + * @param string $rawHeaders The raw headers from the response. + */ + protected function setHeadersFromString($rawHeaders) + { + // Normalize line breaks + $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); + + // There will be multiple headers if a 301 was followed + // or a proxy was followed, etc + $headerCollection = explode("\n\n", trim($rawHeaders)); + // We just want the last response (at the end) + $rawHeader = array_pop($headerCollection); + + $headerComponents = explode("\n", $rawHeader); + foreach ($headerComponents as $line) { + if (strpos($line, ': ') === false) { + $this->setHttpResponseCodeFromHeader($line); + } else { + list($key, $value) = explode(': ', $line); + $this->headers[$key] = $value; + } + } + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/RequestBodyInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/RequestBodyInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..97e0a2e876a66717077e7795369fb8f63947a3cd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/RequestBodyInterface.php @@ -0,0 +1,39 @@ +params = $params; + $this->files = $files; + $this->boundary = $boundary ?: uniqid(); + } + + /** + * @inheritdoc + */ + public function getBody() + { + $body = ''; + + // Compile normal params + $params = $this->getNestedParams($this->params); + foreach ($params as $k => $v) { + $body .= $this->getParamString($k, $v); + } + + // Compile files + foreach ($this->files as $k => $v) { + $body .= $this->getFileString($k, $v); + } + + // Peace out + $body .= "--{$this->boundary}--\r\n"; + + return $body; + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + /** + * Get the string needed to transfer a file. + * + * @param string $name + * @param FacebookFile $file + * + * @return string + */ + private function getFileString($name, FacebookFile $file) + { + return sprintf( + "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s\r\n\r\n%s\r\n", + $this->boundary, + $name, + $file->getFileName(), + $this->getFileHeaders($file), + $file->getContents() + ); + } + + /** + * Get the string needed to transfer a POST field. + * + * @param string $name + * @param string $value + * + * @return string + */ + private function getParamString($name, $value) + { + return sprintf( + "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", + $this->boundary, + $name, + $value + ); + } + + /** + * Returns the params as an array of nested params. + * + * @param array $params + * + * @return array + */ + private function getNestedParams(array $params) + { + $query = http_build_query($params, null, '&'); + $params = explode('&', $query); + $result = []; + + foreach ($params as $param) { + list($key, $value) = explode('=', $param, 2); + $result[urldecode($key)] = urldecode($value); + } + + return $result; + } + + /** + * Get the headers needed before transferring the content of a POST file. + * + * @param FacebookFile $file + * + * @return string + */ + protected function getFileHeaders(FacebookFile $file) + { + return "\r\nContent-Type: {$file->getMimetype()}"; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/RequestBodyUrlEncoded.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/RequestBodyUrlEncoded.php new file mode 100644 index 0000000000000000000000000000000000000000..77c2b649282136c843b101525ac183fcfc8212ba --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Http/RequestBodyUrlEncoded.php @@ -0,0 +1,55 @@ +params = $params; + } + + /** + * @inheritdoc + */ + public function getBody() + { + return http_build_query($this->params, null, '&'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookCurl.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookCurl.php new file mode 100644 index 0000000000000000000000000000000000000000..e5d124ac428f041f907319c83cb4ed1159912557 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookCurl.php @@ -0,0 +1,129 @@ +curl = curl_init(); + } + + /** + * Set a curl option + * + * @param $key + * @param $value + */ + public function setopt($key, $value) + { + curl_setopt($this->curl, $key, $value); + } + + /** + * Set an array of options to a curl resource + * + * @param array $options + */ + public function setoptArray(array $options) + { + curl_setopt_array($this->curl, $options); + } + + /** + * Send a curl request + * + * @return mixed + */ + public function exec() + { + return curl_exec($this->curl); + } + + /** + * Return the curl error number + * + * @return int + */ + public function errno() + { + return curl_errno($this->curl); + } + + /** + * Return the curl error message + * + * @return string + */ + public function error() + { + return curl_error($this->curl); + } + + /** + * Get info from a curl reference + * + * @param $type + * + * @return mixed + */ + public function getinfo($type) + { + return curl_getinfo($this->curl, $type); + } + + /** + * Get the currently installed curl version + * + * @return array + */ + public function version() + { + return curl_version(); + } + + /** + * Close the resource connection to curl + */ + public function close() + { + curl_close($this->curl); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookCurlHttpClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookCurlHttpClient.php new file mode 100644 index 0000000000000000000000000000000000000000..955ac062299546c64addc415ed430bac8bb5e799 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookCurlHttpClient.php @@ -0,0 +1,210 @@ +facebookCurl = $facebookCurl ?: new FacebookCurl(); + } + + /** + * @inheritdoc + */ + public function send($url, $method, $body, array $headers, $timeOut) + { + $this->openConnection($url, $method, $body, $headers, $timeOut); + $this->sendRequest(); + + if ($curlErrorCode = $this->facebookCurl->errno()) { + throw new FacebookSDKException($this->facebookCurl->error(), $curlErrorCode); + } + + // Separate the raw headers from the raw body + list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); + + $this->closeConnection(); + + return new GraphRawResponse($rawHeaders, $rawBody); + } + + /** + * Opens a new curl connection. + * + * @param string $url The endpoint to send the request to. + * @param string $method The request method. + * @param string $body The body of the request. + * @param array $headers The request headers. + * @param int $timeOut The timeout in seconds for the request. + */ + public function openConnection($url, $method, $body, array $headers, $timeOut) + { + $options = [ + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_HTTPHEADER => $this->compileRequestHeaders($headers), + CURLOPT_URL => $url, + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_TIMEOUT => $timeOut, + CURLOPT_RETURNTRANSFER => true, // Follow 301 redirects + CURLOPT_HEADER => true, // Enable header processing + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_CAINFO => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ]; + + if ($method !== "GET") { + $options[CURLOPT_POSTFIELDS] = $body; + } + + $this->facebookCurl->init(); + $this->facebookCurl->setoptArray($options); + } + + /** + * Closes an existing curl connection + */ + public function closeConnection() + { + $this->facebookCurl->close(); + } + + /** + * Send the request and get the raw response from curl + */ + public function sendRequest() + { + $this->rawResponse = $this->facebookCurl->exec(); + } + + /** + * Compiles the request headers into a curl-friendly format. + * + * @param array $headers The request headers. + * + * @return array + */ + public function compileRequestHeaders(array $headers) + { + $return = []; + + foreach ($headers as $key => $value) { + $return[] = $key . ': ' . $value; + } + + return $return; + } + + /** + * Extracts the headers and the body into a two-part array + * + * @return array + */ + public function extractResponseHeadersAndBody() + { + $headerSize = $this->getHeaderSize(); + + $rawHeaders = mb_substr($this->rawResponse, 0, $headerSize); + $rawBody = mb_substr($this->rawResponse, $headerSize); + + return [trim($rawHeaders), trim($rawBody)]; + } + + /** + * Return proper header size + * + * @return integer + */ + private function getHeaderSize() + { + $headerSize = $this->facebookCurl->getinfo(CURLINFO_HEADER_SIZE); + // This corrects a Curl bug where header size does not account + // for additional Proxy headers. + if ($this->needsCurlProxyFix()) { + // Additional way to calculate the request body size. + if (preg_match('/Content-Length: (\d+)/', $this->rawResponse, $m)) { + $headerSize = mb_strlen($this->rawResponse) - $m[1]; + } elseif (stripos($this->rawResponse, self::CONNECTION_ESTABLISHED) !== false) { + $headerSize += mb_strlen(self::CONNECTION_ESTABLISHED); + } + } + + return $headerSize; + } + + /** + * Detect versions of Curl which report incorrect header lengths when + * using Proxies. + * + * @return boolean + */ + private function needsCurlProxyFix() + { + $ver = $this->facebookCurl->version(); + $version = $ver['version_number']; + + return $version < self::CURL_PROXY_QUIRK_VER; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookGuzzleHttpClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookGuzzleHttpClient.php new file mode 100644 index 0000000000000000000000000000000000000000..6f2a1c6f2293445c477ecc22576a3b8e3e37a448 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookGuzzleHttpClient.php @@ -0,0 +1,97 @@ +guzzleClient = $guzzleClient ?: new Client(); + } + + /** + * @inheritdoc + */ + public function send($url, $method, $body, array $headers, $timeOut) + { + $options = [ + 'headers' => $headers, + 'body' => $body, + 'timeout' => $timeOut, + 'connect_timeout' => 10, + 'verify' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ]; + $request = $this->guzzleClient->createRequest($method, $url, $options); + + try { + $rawResponse = $this->guzzleClient->send($request); + } catch (RequestException $e) { + $rawResponse = $e->getResponse(); + + if ($e->getPrevious() instanceof RingException || !$rawResponse instanceof ResponseInterface) { + throw new FacebookSDKException($e->getMessage(), $e->getCode()); + } + } + + $rawHeaders = $this->getHeadersAsString($rawResponse); + $rawBody = $rawResponse->getBody(); + $httpStatusCode = $rawResponse->getStatusCode(); + + return new GraphRawResponse($rawHeaders, $rawBody, $httpStatusCode); + } + + /** + * Returns the Guzzle array of headers as a string. + * + * @param ResponseInterface $response The Guzzle response. + * + * @return string + */ + public function getHeadersAsString(ResponseInterface $response) + { + $headers = $response->getHeaders(); + $rawHeaders = []; + foreach ($headers as $name => $values) { + $rawHeaders[] = $name . ": " . implode(", ", $values); + } + + return implode("\r\n", $rawHeaders); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookHttpClientInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookHttpClientInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0029bc022ec431694bc7b50d275cbe08d2e9392b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookHttpClientInterface.php @@ -0,0 +1,47 @@ +stream = stream_context_create($options); + } + + /** + * The response headers from the stream wrapper + * + * @return array|null + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + } + + /** + * Send a stream wrapped request + * + * @param string $url + * + * @return mixed + */ + public function fileGetContents($url) + { + $rawResponse = file_get_contents($url, false, $this->stream); + $this->responseHeaders = $http_response_header; + + return $rawResponse; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookStreamHttpClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookStreamHttpClient.php new file mode 100644 index 0000000000000000000000000000000000000000..b157514310c561c4810f8fbb2e763d4589dbcfc8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/FacebookStreamHttpClient.php @@ -0,0 +1,94 @@ +facebookStream = $facebookStream ?: new FacebookStream(); + } + + /** + * @inheritdoc + */ + public function send($url, $method, $body, array $headers, $timeOut) + { + $options = [ + 'http' => [ + 'method' => $method, + 'header' => $this->compileHeader($headers), + 'content' => $body, + 'timeout' => $timeOut, + 'ignore_errors' => true + ], + 'ssl' => [ + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => true, // All root certificates are self-signed + 'cafile' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ], + ]; + + $this->facebookStream->streamContextCreate($options); + $rawBody = $this->facebookStream->fileGetContents($url); + $rawHeaders = $this->facebookStream->getResponseHeaders(); + + if ($rawBody === false || !$rawHeaders) { + throw new FacebookSDKException('Stream returned an empty response', 660); + } + + $rawHeaders = implode("\r\n", $rawHeaders); + + return new GraphRawResponse($rawHeaders, $rawBody); + } + + /** + * Formats the headers for use in the stream wrapper. + * + * @param array $headers The request headers. + * + * @return string + */ + public function compileHeader(array $headers) + { + $header = []; + foreach ($headers as $k => $v) { + $header[] = $k . ': ' . $v; + } + + return implode("\r\n", $header); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem new file mode 100644 index 0000000000000000000000000000000000000000..9e6810ab70cfa112fe066ec8b2122fbf76586317 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..93a66861531a7b1ea68a229ce3b1bc472449c740 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php @@ -0,0 +1,53 @@ +sessionData[$key]) ? $this->sessionData[$key] : null; + } + + /** + * @inheritdoc + */ + public function set($key, $value) + { + $this->sessionData[$key] = $value; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..698bfd046c1b55ed7231371fa10c0ed5937ee8a6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php @@ -0,0 +1,76 @@ +sessionPrefix . $key])) { + return $_SESSION[$this->sessionPrefix . $key]; + } + + return null; + } + + /** + * @inheritdoc + */ + public function set($key, $value) + { + $_SESSION[$this->sessionPrefix . $key] = $value; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/PersistentDataInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/PersistentDataInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bd7e072a837bc91d6d25a75978142335880dc335 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PersistentData/PersistentDataInterface.php @@ -0,0 +1,49 @@ +validateLength($length); + + $binaryString = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + + if ($binaryString === false) { + throw new FacebookSDKException( + static::ERROR_MESSAGE . + 'mcrypt_create_iv() returned an error.' + ); + } + + return $this->binToHex($binaryString, $length); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..f4ea6b870708ec6d018aa82ddb6fc19ccd839304 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php @@ -0,0 +1,67 @@ +validateLength($length); + + $wasCryptographicallyStrong = false; + $binaryString = openssl_random_pseudo_bytes($length, $wasCryptographicallyStrong); + + if ($binaryString === false) { + throw new FacebookSDKException(static::ERROR_MESSAGE . 'openssl_random_pseudo_bytes() returned an unknown error.'); + } + + if ($wasCryptographicallyStrong !== true) { + throw new FacebookSDKException(static::ERROR_MESSAGE . 'openssl_random_pseudo_bytes() returned a pseudo-random string but it was not cryptographically secure and cannot be used.'); + } + + return $this->binToHex($binaryString, $length); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..970330c856ab69a236dd5beef65a561dc8c85bdd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php @@ -0,0 +1,45 @@ +validateLength($length); + + $stream = fopen('/dev/urandom', 'rb'); + if (!is_resource($stream)) { + throw new FacebookSDKException( + static::ERROR_MESSAGE . + 'Unable to open stream to /dev/urandom.' + ); + } + + if (!defined('HHVM_VERSION')) { + stream_set_read_buffer($stream, 0); + } + + $binaryString = fread($stream, $length); + fclose($stream); + + if (!$binaryString) { + throw new FacebookSDKException( + static::ERROR_MESSAGE . + 'Stream to /dev/urandom returned no data.' + ); + } + + return $this->binToHex($binaryString, $length); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/SignedRequest.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/SignedRequest.php new file mode 100644 index 0000000000000000000000000000000000000000..77099a346d348ffc0b1b2f7485d6cd7a02ebaad3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/SignedRequest.php @@ -0,0 +1,332 @@ +app = $facebookApp; + + if (!$rawSignedRequest) { + return; + } + + $this->rawSignedRequest = $rawSignedRequest; + + $this->parse(); + } + + /** + * Returns the raw signed request data. + * + * @return string|null + */ + public function getRawSignedRequest() + { + return $this->rawSignedRequest; + } + + /** + * Returns the parsed signed request data. + * + * @return array|null + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Returns a property from the signed request data if available. + * + * @param string $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function get($key, $default = null) + { + if (isset($this->payload[$key])) { + return $this->payload[$key]; + } + + return $default; + } + + /** + * Returns user_id from signed request data if available. + * + * @return string|null + */ + public function getUserId() + { + return $this->get('user_id'); + } + + /** + * Checks for OAuth data in the payload. + * + * @return boolean + */ + public function hasOAuthData() + { + return $this->get('oauth_token') || $this->get('code'); + } + + /** + * Creates a signed request from an array of data. + * + * @param array $payload + * + * @return string + */ + public function make(array $payload) + { + $payload['algorithm'] = isset($payload['algorithm']) ? $payload['algorithm'] : 'HMAC-SHA256'; + $payload['issued_at'] = isset($payload['issued_at']) ? $payload['issued_at'] : time(); + $encodedPayload = $this->base64UrlEncode(json_encode($payload)); + + $hashedSig = $this->hashSignature($encodedPayload); + $encodedSig = $this->base64UrlEncode($hashedSig); + + return $encodedSig . '.' . $encodedPayload; + } + + /** + * Validates and decodes a signed request and saves + * the payload to an array. + */ + protected function parse() + { + list($encodedSig, $encodedPayload) = $this->split(); + + // Signature validation + $sig = $this->decodeSignature($encodedSig); + $hashedSig = $this->hashSignature($encodedPayload); + $this->validateSignature($hashedSig, $sig); + + $this->payload = $this->decodePayload($encodedPayload); + + // Payload validation + $this->validateAlgorithm(); + } + + /** + * Splits a raw signed request into signature and payload. + * + * @returns array + * + * @throws FacebookSDKException + */ + protected function split() + { + if (strpos($this->rawSignedRequest, '.') === false) { + throw new FacebookSDKException('Malformed signed request.', 606); + } + + return explode('.', $this->rawSignedRequest, 2); + } + + /** + * Decodes the raw signature from a signed request. + * + * @param string $encodedSig + * + * @returns string + * + * @throws FacebookSDKException + */ + protected function decodeSignature($encodedSig) + { + $sig = $this->base64UrlDecode($encodedSig); + + if (!$sig) { + throw new FacebookSDKException('Signed request has malformed encoded signature data.', 607); + } + + return $sig; + } + + /** + * Decodes the raw payload from a signed request. + * + * @param string $encodedPayload + * + * @returns array + * + * @throws FacebookSDKException + */ + protected function decodePayload($encodedPayload) + { + $payload = $this->base64UrlDecode($encodedPayload); + + if ($payload) { + $payload = json_decode($payload, true); + } + + if (!is_array($payload)) { + throw new FacebookSDKException('Signed request has malformed encoded payload data.', 607); + } + + return $payload; + } + + /** + * Validates the algorithm used in a signed request. + * + * @throws FacebookSDKException + */ + protected function validateAlgorithm() + { + if ($this->get('algorithm') !== 'HMAC-SHA256') { + throw new FacebookSDKException('Signed request is using the wrong algorithm.', 605); + } + } + + /** + * Hashes the signature used in a signed request. + * + * @param string $encodedData + * + * @return string + * + * @throws FacebookSDKException + */ + protected function hashSignature($encodedData) + { + $hashedSig = hash_hmac( + 'sha256', + $encodedData, + $this->app->getSecret(), + $raw_output = true + ); + + if (!$hashedSig) { + throw new FacebookSDKException('Unable to hash signature from encoded payload data.', 602); + } + + return $hashedSig; + } + + /** + * Validates the signature used in a signed request. + * + * @param string $hashedSig + * @param string $sig + * + * @throws FacebookSDKException + */ + protected function validateSignature($hashedSig, $sig) + { + if (mb_strlen($hashedSig) === mb_strlen($sig)) { + $validate = 0; + for ($i = 0; $i < mb_strlen($sig); $i++) { + $validate |= ord($hashedSig[$i]) ^ ord($sig[$i]); + } + if ($validate === 0) { + return; + } + } + + throw new FacebookSDKException('Signed request has an invalid signature.', 602); + } + + /** + * Base64 decoding which replaces characters: + * + instead of - + * / instead of _ + * + * @link http://en.wikipedia.org/wiki/Base64#URL_applications + * + * @param string $input base64 url encoded input + * + * @return string decoded string + */ + public function base64UrlDecode($input) + { + $urlDecodedBase64 = strtr($input, '-_', '+/'); + $this->validateBase64($urlDecodedBase64); + + return base64_decode($urlDecodedBase64); + } + + /** + * Base64 encoding which replaces characters: + * + instead of - + * / instead of _ + * + * @link http://en.wikipedia.org/wiki/Base64#URL_applications + * + * @param string $input string to encode + * + * @return string base64 url encoded input + */ + public function base64UrlEncode($input) + { + return strtr(base64_encode($input), '+/', '-_'); + } + + /** + * Validates a base64 string. + * + * @param string $input base64 value to validate + * + * @throws FacebookSDKException + */ + protected function validateBase64($input) + { + if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $input)) { + throw new FacebookSDKException('Signed request contains malformed base64 encoding.', 608); + } + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/FacebookUrlDetectionHandler.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/FacebookUrlDetectionHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..5fbb9ce478c2ff8d98e69b9931f70b0976aa0fce --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/FacebookUrlDetectionHandler.php @@ -0,0 +1,163 @@ +getHttpScheme() . '://' . $this->getHostName() . $this->getServerVar('REQUEST_URI'); + } + + /** + * Get the currently active URL scheme. + * + * @return string + */ + protected function getHttpScheme() + { + return $this->isBehindSsl() ? 'https' : 'http'; + } + + /** + * Tries to detect if the server is running behind an SSL. + * + * @return boolean + */ + protected function isBehindSsl() + { + // Check for proxy first + $protocol = $this->getHeader('X_FORWARDED_PROTO'); + if ($protocol) { + return $this->protocolWithActiveSsl($protocol); + } + + $protocol = $this->getServerVar('HTTPS'); + if ($protocol) { + return $this->protocolWithActiveSsl($protocol); + } + + return (string)$this->getServerVar('SERVER_PORT') === '443'; + } + + /** + * Detects an active SSL protocol value. + * + * @param string $protocol + * + * @return boolean + */ + protected function protocolWithActiveSsl($protocol) + { + $protocol = strtolower((string)$protocol); + + return in_array($protocol, ['on', '1', 'https', 'ssl'], true); + } + + /** + * Tries to detect the host name of the server. + * + * Some elements adapted from + * + * @see https://github.com/symfony/HttpFoundation/blob/master/Request.php + * + * @return string + */ + protected function getHostName() + { + // Check for proxy first + if ($host = $this->getHeader('X_FORWARDED_HOST')) { + $elements = explode(',', $host); + $host = $elements[count($elements) - 1]; + } elseif (!$host = $this->getHeader('HOST')) { + if (!$host = $this->getServerVar('SERVER_NAME')) { + $host = $this->getServerVar('SERVER_ADDR'); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // Port number + $scheme = $this->getHttpScheme(); + $port = $this->getCurrentPort(); + $appendPort = ':' . $port; + + // Don't append port number if a normal port. + if (($scheme == 'http' && $port == '80') || ($scheme == 'https' && $port == '443')) { + $appendPort = ''; + } + + return $host . $appendPort; + } + + protected function getCurrentPort() + { + // Check for proxy first + $port = $this->getHeader('X_FORWARDED_PORT'); + if ($port) { + return (string)$port; + } + + $protocol = (string)$this->getHeader('X_FORWARDED_PROTO'); + if ($protocol === 'https') { + return '443'; + } + + return (string)$this->getServerVar('SERVER_PORT'); + } + + /** + * Returns the a value from the $_SERVER super global. + * + * @param string $key + * + * @return string + */ + protected function getServerVar($key) + { + return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; + } + + /** + * Gets a value from the HTTP request headers. + * + * @param string $key + * + * @return string + */ + protected function getHeader($key) + { + return $this->getServerVar('HTTP_' . $key); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/FacebookUrlManipulator.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/FacebookUrlManipulator.php new file mode 100644 index 0000000000000000000000000000000000000000..20a0299e0d8c951c3bd725b1debc8ed05a9b85e3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/FacebookUrlManipulator.php @@ -0,0 +1,167 @@ + 0) { + $query = '?' . http_build_query($params, null, '&'); + } + } + + $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : ''; + $host = isset($parts['host']) ? $parts['host'] : ''; + $port = isset($parts['port']) ? ':' . $parts['port'] : ''; + $path = isset($parts['path']) ? $parts['path'] : ''; + $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; + + return $scheme . $host . $port . $path . $query . $fragment; + } + + /** + * Gracefully appends params to the URL. + * + * @param string $url The URL that will receive the params. + * @param array $newParams The params to append to the URL. + * + * @return string + */ + public static function appendParamsToUrl($url, array $newParams = []) + { + if (!$newParams) { + return $url; + } + + if (strpos($url, '?') === false) { + return $url . '?' . http_build_query($newParams, null, '&'); + } + + list($path, $query) = explode('?', $url, 2); + $existingParams = []; + parse_str($query, $existingParams); + + // Favor params from the original URL over $newParams + $newParams = array_merge($newParams, $existingParams); + + // Sort for a predicable order + ksort($newParams); + + return $path . '?' . http_build_query($newParams, null, '&'); + } + + /** + * Returns the params from a URL in the form of an array. + * + * @param string $url The URL to parse the params from. + * + * @return array + */ + public static function getParamsAsArray($url) + { + $query = parse_url($url, PHP_URL_QUERY); + if (!$query) { + return []; + } + $params = []; + parse_str($query, $params); + + return $params; + } + + /** + * Adds the params of the first URL to the second URL. + * + * Any params that already exist in the second URL will go untouched. + * + * @param string $urlToStealFrom The URL harvest the params from. + * @param string $urlToAddTo The URL that will receive the new params. + * + * @return string The $urlToAddTo with any new params from $urlToStealFrom. + */ + public static function mergeUrlParams($urlToStealFrom, $urlToAddTo) + { + $newParams = static::getParamsAsArray($urlToStealFrom); + // Nothing new to add, return as-is + if (!$newParams) { + return $urlToAddTo; + } + + return static::appendParamsToUrl($urlToAddTo, $newParams); + } + + /** + * Check for a "/" prefix and prepend it if not exists. + * + * @param string|null $string + * + * @return string|null + */ + public static function forceSlashPrefix($string) + { + if (!$string) { + return $string; + } + + return strpos($string, '/') === 0 ? $string : '/' . $string; + } + + /** + * Trims off the hostname and Graph version from a URL. + * + * @param string $urlToTrim The URL the needs the surgery. + * + * @return string The $urlToTrim with the hostname and Graph version removed. + */ + public static function baseGraphUrlEndpoint($urlToTrim) + { + return '/' . preg_replace('/^https:\/\/.+\.facebook\.com(\/v.+?)?\//', '', $urlToTrim); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/UrlDetectionInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/UrlDetectionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..764a606e09c75133a42eb0af4c8d207afc134e97 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Facebook/Url/UrlDetectionInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Draw; + +use Imagine\Image\AbstractFont; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\PointInterface; +use Imagine\Exception\RuntimeException; + +/** + * Interface for the drawer + */ +interface DrawerInterface +{ + /** + * Draws an arc on a starting at a given x, y coordinates under a given + * start and end angles + * + * @param PointInterface $center + * @param BoxInterface $size + * @param integer $start + * @param integer $end + * @param ColorInterface $color + * @param integer $thickness + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1); + + /** + * Same as arc, but also connects end points with a straight line + * + * @param PointInterface $center + * @param BoxInterface $size + * @param integer $start + * @param integer $end + * @param ColorInterface $color + * @param Boolean $fill + * @param integer $thickness + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1); + + /** + * Draws and ellipse with center at the given x, y coordinates, and given + * width and height + * + * @param PointInterface $center + * @param BoxInterface $size + * @param ColorInterface $color + * @param Boolean $fill + * @param integer $thickness + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1); + + /** + * Draws a line from start(x, y) to end(x, y) coordinates + * + * @param PointInterface $start + * @param PointInterface $end + * @param ColorInterface $outline + * @param integer $thickness + * + * @return DrawerInterface + */ + public function line(PointInterface $start, PointInterface $end, ColorInterface $outline, $thickness = 1); + + /** + * Same as arc, but connects end points and the center + * + * @param PointInterface $center + * @param BoxInterface $size + * @param integer $start + * @param integer $end + * @param ColorInterface $color + * @param Boolean $fill + * @param integer $thickness + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1); + + /** + * Places a one pixel point at specific coordinates and fills it with + * specified color + * + * @param PointInterface $position + * @param ColorInterface $color + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function dot(PointInterface $position, ColorInterface $color); + + /** + * Draws a polygon using array of x, y coordinates. Must contain at least + * three coordinates + * + * @param array $coordinates + * @param ColorInterface $color + * @param Boolean $fill + * @param integer $thickness + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1); + + /** + * Annotates image with specified text at a given position starting on the + * top left of the final text box + * + * The rotation is done CW + * + * @param string $string + * @param AbstractFont $font + * @param PointInterface $position + * @param integer $angle + * @param integer $width + * + * @throws RuntimeException + * + * @return DrawerInterface + */ + public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Effects/EffectsInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Effects/EffectsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..3593d75da64c03e8ceefe583df52d329b8411741 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Effects/EffectsInterface.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 Imagine\Effects; + +use Imagine\Exception\RuntimeException; +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * Interface for the effects + */ +interface EffectsInterface +{ + /** + * Apply gamma correction + * + * @param float $correction + * @return EffectsInterface + * + * @throws RuntimeException + */ + public function gamma($correction); + + /** + * Invert the colors of the image + * + * @return EffectsInterface + * + * @throws RuntimeException + */ + public function negative(); + + /** + * Grayscale the image + * + * @return EffectsInterface + * + * @throws RuntimeException + */ + public function grayscale(); + + /** + * Colorize the image + * + * @param ColorInterface $color + * + * @return EffectsInterface + * + * @throws RuntimeException + */ + public function colorize(ColorInterface $color); + + /** + * Sharpens the image + * + * @return EffectsInterface + * + * @throws RuntimeException + */ + public function sharpen(); + + /** + * Blur the image + * + * @param float|int $sigma + * + * @return EffectsInterface + * + * @throws RuntimeException + */ + public function blur($sigma); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/Exception.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..39a67afed77f4c7685dc16cfc049aa38a7182f59 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Exception; + +/** + * Imagine-specific exception + */ +interface Exception +{ +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/InvalidArgumentException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..5ac1396728bc08d451c89014163503ee1717b842 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Exception; + +/** + * Imagine-specific invalid argument exception + */ +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/NotSupportedException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/NotSupportedException.php new file mode 100644 index 0000000000000000000000000000000000000000..fd68ce7c060cf06096ad2f4d44a1c3f996545388 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/NotSupportedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Exception; + +/** + * Should be used when a driver does not support an operation. + */ +class NotSupportedException extends RuntimeException implements Exception +{ +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/OutOfBoundsException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/OutOfBoundsException.php new file mode 100644 index 0000000000000000000000000000000000000000..f51cc9b4f2d8bfce76ddacae150196873aa80db4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/OutOfBoundsException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Exception; + +/** + * Imagine-specific out of bounds exception + */ +class OutOfBoundsException extends \OutOfBoundsException implements Exception +{ +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/RuntimeException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..205ad85e840e8d01f40b74eb32372c9ac4cbd51b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Exception; + +/** + * Imagine-specific runtime exception + */ +class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Border.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Border.php new file mode 100644 index 0000000000000000000000000000000000000000..47a3ceb1ffbf20e3d7f3b96ae5d79cfe9b4ec5d4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Border.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Filter\Advanced; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Point; + +/** + * A border filter + */ +class Border implements FilterInterface +{ + /** + * @var ColorInterface + */ + private $color; + + /** + * @var integer + */ + private $width; + + /** + * @var integer + */ + private $height; + + /** + * Constructs Border filter with given color, width and height + * + * @param ColorInterface $color + * @param integer $width Width of the border on the left and right sides of the image + * @param integer $height Height of the border on the top and bottom sides of the image + */ + public function __construct(ColorInterface $color, $width = 1, $height = 1) + { + $this->color = $color; + $this->width = $width; + $this->height = $height; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + $size = $image->getSize(); + $width = $size->getWidth(); + $height = $size->getHeight(); + + $draw = $image->draw(); + + // Draw top and bottom lines + $draw + ->line( + new Point(0, 0), + new Point($width - 1, 0), + $this->color, + $this->height + ) + ->line( + new Point($width - 1, $height - 1), + new Point(0, $height - 1), + $this->color, + $this->height + ) + ; + + // Draw sides + $draw + ->line( + new Point(0, 0), + new Point(0, $height - 1), + $this->color, + $this->width + ) + ->line( + new Point($width - 1, 0), + new Point($width - 1, $height - 1), + $this->color, + $this->width + ) + ; + + return $image; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Canvas.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Canvas.php new file mode 100644 index 0000000000000000000000000000000000000000..685b8ae73e65746ab9e141b949dd8679de16ded5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Canvas.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 Imagine\Filter\Advanced; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\BoxInterface; +use Imagine\Image\Point; +use Imagine\Image\PointInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\ImagineInterface; + +/** + * A canvas filter + */ +class Canvas implements FilterInterface +{ + /** + * @var BoxInterface + */ + private $size; + + /** + * @var PointInterface + */ + private $placement; + + /** + * @var ColorInterface + */ + private $background; + + /** + * @var ImagineInterface + */ + private $imagine; + + /** + * Constructs Canvas filter with given width and height and the placement of the current image + * inside the new canvas + * + * @param ImagineInterface $imagine + * @param BoxInterface $size + * @param PointInterface $placement + * @param ColorInterface $background + */ + public function __construct(ImagineInterface $imagine, BoxInterface $size, PointInterface $placement = null, ColorInterface $background = null) + { + $this->imagine = $imagine; + $this->size = $size; + $this->placement = $placement ?: new Point(0, 0); + $this->background = $background; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + $canvas = $this->imagine->create($this->size, $this->background); + $canvas->paste($image, $this->placement); + + return $canvas; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Grayscale.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Grayscale.php new file mode 100644 index 0000000000000000000000000000000000000000..b3a08b403c21d642bd039effdd38fcfb2c756df0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/Grayscale.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 Imagine\Filter\Advanced; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\Point; + +/** + * The Grayscale filter calculates the gray-value based on RGB. + */ +class Grayscale extends OnPixelBased implements FilterInterface +{ + public function __construct() + { + parent::__construct(function (ImageInterface $image, Point $point) { + $color = $image->getColorAt($point); + $image->draw()->dot($point, $color->grayscale()); + }); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/OnPixelBased.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/OnPixelBased.php new file mode 100644 index 0000000000000000000000000000000000000000..ef297eaf6b7a403cbf8925c63485471921c701d5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/OnPixelBased.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 Imagine\Filter\Advanced; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\Point; + +/** + * The OnPixelBased takes a callable, and for each pixel, this callable is called with the + * image (\Imagine\Image\ImageInterface) and the current point (\Imagine\Image\Point) + */ +class OnPixelBased implements FilterInterface +{ + protected $callback; + + public function __construct($callback) + { + if (!is_callable($callback)) { + throw new InvalidArgumentException('$callback has to be callable'); + } + + $this->callback = $callback; + } + + /** + * Applies scheduled transformation to ImageInterface instance + * Returns processed ImageInterface instance + * + * @param ImageInterface $image + * + * @return ImageInterface + */ + public function apply(ImageInterface $image) + { + $w = $image->getSize()->getWidth(); + $h = $image->getSize()->getHeight(); + + for ($x = 0; $x < $w; $x++) { + for ($y = 0; $y < $h; $y++) { + call_user_func($this->callback, $image, new Point($x, $y)); + } + } + + return $image; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/RelativeResize.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/RelativeResize.php new file mode 100644 index 0000000000000000000000000000000000000000..954587ad245a3a1a39ec6650b4633f8273657f94 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Advanced/RelativeResize.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 Imagine\Filter\Advanced; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; + +/** + * The RelativeResize filter allows images to be resized relative to their + * existing dimensions. + */ +class RelativeResize implements FilterInterface +{ + private $method; + private $parameter; + + /** + * Constructs a RelativeResize filter with the given method and argument. + * + * @param string $method BoxInterface method + * @param mixed $parameter Parameter for BoxInterface method + */ + public function __construct($method, $parameter) + { + if (!in_array($method, array('heighten', 'increase', 'scale', 'widen'))) { + throw new InvalidArgumentException(sprintf('Unsupported method: ', $method)); + } + + $this->method = $method; + $this->parameter = $parameter; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->resize(call_user_func(array($image->getSize(), $this->method), $this->parameter)); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/ApplyMask.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/ApplyMask.php new file mode 100644 index 0000000000000000000000000000000000000000..2be2786f99d3b0166db853e09e47a02e7072b16e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/ApplyMask.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 Imagine\Filter\Basic; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; + +/** + * An apply mask filter + */ +class ApplyMask implements FilterInterface +{ + /** + * @var ImageInterface + */ + private $mask; + + /** + * @param ImageInterface $mask + */ + public function __construct(ImageInterface $mask) + { + $this->mask = $mask; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->applyMask($this->mask); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Autorotate.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Autorotate.php new file mode 100644 index 0000000000000000000000000000000000000000..3ef7cbec728d103a01ca3337fb5def152bd97e75 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Autorotate.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Filter\Basic; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * Rotates an image automatically based on exif information. + * + * Your attention please: This filter requires the use of the + * ExifMetadataReader to work. + * + * @see https://imagine.readthedocs.org/en/latest/usage/metadata.html + */ +class Autorotate implements FilterInterface +{ + private $color; + + /** + * @param string|array|ColorInterface $color A color + */ + public function __construct($color = '000000') + { + $this->color = $color; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + $metadata = $image->metadata(); + + switch (isset($metadata['ifd0.Orientation']) ? $metadata['ifd0.Orientation'] : null) { + case 1: // top-left + break; + case 2: // top-right + $image->flipHorizontally(); + break; + case 3: // bottom-right + $image->rotate(180, $this->getColor($image)); + break; + case 4: // bottom-left + $image->flipHorizontally(); + $image->rotate(180, $this->getColor($image)); + break; + case 5: // left-top + $image->flipHorizontally(); + $image->rotate(-90, $this->getColor($image)); + break; + case 6: // right-top + $image->rotate(90, $this->getColor($image)); + break; + case 7: // right-bottom + $image->flipHorizontally(); + $image->rotate(90, $this->getColor($image)); + break; + case 8: // left-bottom + $image->rotate(-90, $this->getColor($image)); + break; + default: // Invalid orientation + break; + } + + return $image; + } + + private function getColor(ImageInterface $image) + { + if ($this->color instanceof ColorInterface) { + return $this->color; + } + + return $image->palette()->color($this->color); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Copy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Copy.php new file mode 100644 index 0000000000000000000000000000000000000000..781b23a7ef015be530d86f69a2ee40194f06b45d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Copy.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 Imagine\Filter\Basic; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; + +/** + * A copy filter + */ +class Copy implements FilterInterface +{ + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->copy(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Crop.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Crop.php new file mode 100644 index 0000000000000000000000000000000000000000..6559e2298dd0f561ddaa6bc4b8f2d5c1fac6d14d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Crop.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Image\BoxInterface; +use Imagine\Image\PointInterface; +use Imagine\Filter\FilterInterface; + +/** + * A crop filter + */ +class Crop implements FilterInterface +{ + /** + * @var PointInterface + */ + private $start; + + /** + * @var BoxInterface + */ + private $size; + + /** + * Constructs a Crop filter with given x, y, coordinates and crop width and + * height values + * + * @param PointInterface $start + * @param BoxInterface $size + */ + public function __construct(PointInterface $start, BoxInterface $size) + { + $this->start = $start; + $this->size = $size; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->crop($this->start, $this->size); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Fill.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Fill.php new file mode 100644 index 0000000000000000000000000000000000000000..4be0d0f2baa0e908c8de411909db040d3c053a2e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Fill.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 Imagine\Filter\Basic; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\Fill\FillInterface; +use Imagine\Image\ImageInterface; + +/** + * A fill filter + */ +class Fill implements FilterInterface +{ + /** + * @var FillInterface + */ + private $fill; + + /** + * @param FillInterface $fill + */ + public function __construct(FillInterface $fill) + { + $this->fill = $fill; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->fill($this->fill); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/FlipHorizontally.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/FlipHorizontally.php new file mode 100644 index 0000000000000000000000000000000000000000..50203748d06b8305df0cf19498ab9c714c7b30fa --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/FlipHorizontally.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Filter\FilterInterface; + +/** + * A "flip horizontally" filter + */ +class FlipHorizontally implements FilterInterface +{ + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->flipHorizontally(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/FlipVertically.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/FlipVertically.php new file mode 100644 index 0000000000000000000000000000000000000000..684c31b3de46e2a3c97c085523e4dfb8d540f65f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/FlipVertically.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Filter\FilterInterface; + +/** + * A "flip vertically" filter + */ +class FlipVertically implements FilterInterface +{ + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->flipVertically(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Paste.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Paste.php new file mode 100644 index 0000000000000000000000000000000000000000..bd274a1791cee336d11a3604457c4180b8fd8add --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Paste.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Image\PointInterface; +use Imagine\Filter\FilterInterface; + +/** + * A paste filter + */ +class Paste implements FilterInterface +{ + /** + * @var ImageInterface + */ + private $image; + + /** + * @var PointInterface + */ + private $start; + + /** + * Constructs a Paste filter with given ImageInterface to paste and x, y + * coordinates of target position + * + * @param ImageInterface $image + * @param PointInterface $start + */ + public function __construct(ImageInterface $image, PointInterface $start) + { + $this->image = $image; + $this->start = $start; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->paste($this->image, $this->start); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Resize.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Resize.php new file mode 100644 index 0000000000000000000000000000000000000000..934cfe2fd4d38271a23b6abf084e08f23dc6a640 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Resize.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 Imagine\Filter\Basic; + +use Imagine\Filter\FilterInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\BoxInterface; + +/** + * A resize filter + */ +class Resize implements FilterInterface +{ + /** + * @var BoxInterface + */ + private $size; + private $filter; + + /** + * Constructs Resize filter with given width and height + * + * @param BoxInterface $size + * @param string $filter + */ + public function __construct(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) + { + $this->size = $size; + $this->filter = $filter; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->resize($this->size, $this->filter); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Rotate.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Rotate.php new file mode 100644 index 0000000000000000000000000000000000000000..7b5553ff92c3b7a2915a9e0ee2c729d42769dcba --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Rotate.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Filter\FilterInterface; + +/** + * A rotate filter + */ +class Rotate implements FilterInterface +{ + /** + * @var integer + */ + private $angle; + + /** + * @var ColorInterface + */ + private $background; + + /** + * Constructs Rotate filter with given angle and background color + * + * @param integer $angle + * @param ColorInterface $background + */ + public function __construct($angle, ColorInterface $background = null) + { + $this->angle = $angle; + $this->background = $background; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->rotate($this->angle, $this->background); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Save.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Save.php new file mode 100644 index 0000000000000000000000000000000000000000..0f76625a1acefe7d557a6c7ed986e4deeb531312 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Save.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Filter\FilterInterface; + +/** + * A save filter + */ +class Save implements FilterInterface +{ + /** + * @var string + */ + private $path; + + /** + * @var array + */ + private $options; + + /** + * Constructs Save filter with given path and options + * + * @param string $path + * @param array $options + */ + public function __construct($path = null, array $options = array()) + { + $this->path = $path; + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->save($this->path, $this->options); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Show.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Show.php new file mode 100644 index 0000000000000000000000000000000000000000..a7d4cbc44469d103d1b9f7521bdea1f3982c523e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Show.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Filter\FilterInterface; + +/** + * A show filter + */ +class Show implements FilterInterface +{ + /** + * @var string + */ + private $format; + + /** + * @var array + */ + private $options; + + /** + * Constructs the Show filter with given format and options + * + * @param string $format + * @param array $options + */ + public function __construct($format, array $options = array()) + { + $this->format = $format; + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->show($this->format, $this->options); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Strip.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Strip.php new file mode 100644 index 0000000000000000000000000000000000000000..c096a51bcda4e2a38b685e8813044832a4f0b74f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Strip.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Filter\FilterInterface; + +/** + * A strip filter + */ +class Strip implements FilterInterface +{ + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->strip(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Thumbnail.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Thumbnail.php new file mode 100644 index 0000000000000000000000000000000000000000..3176f1c395e3d59c954aa6b9ba67dd266ca22d42 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/Thumbnail.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Image\BoxInterface; +use Imagine\Filter\FilterInterface; + +/** + * A thumbnail filter + */ +class Thumbnail implements FilterInterface +{ + /** + * @var BoxInterface + */ + private $size; + + /** + * @var string + */ + private $mode; + + /** + * @var string + */ + private $filter; + + /** + * Constructs the Thumbnail filter with given width, height and mode + * + * @param BoxInterface $size + * @param string $mode + * @param string $filter + */ + public function __construct(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED) + { + $this->size = $size; + $this->mode = $mode; + $this->filter = $filter; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return $image->thumbnail($this->size, $this->mode, $this->filter); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/WebOptimization.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/WebOptimization.php new file mode 100644 index 0000000000000000000000000000000000000000..37f5f3f1ae5343eb0268f4c87dc200dfcc4358e8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Basic/WebOptimization.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 Imagine\Filter\Basic; + +use Imagine\Image\ImageInterface; +use Imagine\Image\Palette\RGB; +use Imagine\Filter\FilterInterface; + +/** + * A filter to render web-optimized images + */ +class WebOptimization implements FilterInterface +{ + private $palette; + private $path; + private $options; + + public function __construct($path = null, array $options = array()) + { + $this->path = $path; + $this->options = array_replace(array( + 'resolution-units' => ImageInterface::RESOLUTION_PIXELSPERINCH, + 'resolution-y' => 72, + 'resolution-x' => 72, + ), $options); + $this->palette = new RGB(); + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + $image + ->usePalette($this->palette) + ->strip(); + + if (is_callable($this->path)) { + $path = call_user_func($this->path, $image); + } elseif (null !== $this->path) { + $path = $this->path; + } else { + return $image; + } + + return $image->save($path, $this->options); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/FilterInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/FilterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e23ab70e0984417d2d248442439ed4a5bb09ece1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/FilterInterface.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 Imagine\Filter; + +use Imagine\Image\ImageInterface; + +/** + * Interface for imagine filters + */ +interface FilterInterface +{ + /** + * Applies scheduled transformation to ImageInterface instance + * Returns processed ImageInterface instance + * + * @param ImageInterface $image + * + * @return ImageInterface + */ + public function apply(ImageInterface $image); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/ImagineAware.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/ImagineAware.php new file mode 100644 index 0000000000000000000000000000000000000000..606c5254c0484a0cdd4edec3e1b22a9a47c9b5cd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/ImagineAware.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 Imagine\Filter; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Image\ImagineInterface; + +/** + * ImagineAware base class + */ +abstract class ImagineAware implements FilterInterface +{ + /** + * An ImagineInterface instance. + * + * @var ImagineInterface + */ + private $imagine; + + /** + * Set ImagineInterface instance. + * + * @param ImagineInterface $imagine An ImagineInterface instance + */ + public function setImagine(ImagineInterface $imagine) + { + $this->imagine = $imagine; + } + + /** + * Get ImagineInterface instance. + * + * @return ImagineInterface + * + * @throws InvalidArgumentException + */ + public function getImagine() + { + if (!$this->imagine instanceof ImagineInterface) { + throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to filter constructor', get_class($this))); + } + + return $this->imagine; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Transformation.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Transformation.php new file mode 100644 index 0000000000000000000000000000000000000000..26c023a0f3b0895ac1b2cc463610b711f8d94b59 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Filter/Transformation.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Filter; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Filter\Basic\ApplyMask; +use Imagine\Filter\Basic\Copy; +use Imagine\Filter\Basic\Crop; +use Imagine\Filter\Basic\Fill; +use Imagine\Filter\Basic\FlipVertically; +use Imagine\Filter\Basic\FlipHorizontally; +use Imagine\Filter\Basic\Paste; +use Imagine\Filter\Basic\Resize; +use Imagine\Filter\Basic\Rotate; +use Imagine\Filter\Basic\Save; +use Imagine\Filter\Basic\Show; +use Imagine\Filter\Basic\Strip; +use Imagine\Filter\Basic\Thumbnail; +use Imagine\Image\ImageInterface; +use Imagine\Image\ImagineInterface; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Fill\FillInterface; +use Imagine\Image\ManipulatorInterface; +use Imagine\Image\PointInterface; + +/** + * A transformation filter + */ +final class Transformation implements FilterInterface, ManipulatorInterface +{ + /** + * @var array + */ + private $filters = array(); + + /** + * @var array + */ + private $sorted; + + /** + * An ImagineInterface instance. + * + * @var ImagineInterface + */ + private $imagine; + + /** + * Class constructor. + * + * @param ImagineInterface $imagine An ImagineInterface instance + */ + public function __construct(ImagineInterface $imagine = null) + { + $this->imagine = $imagine; + } + + /** + * Applies a given FilterInterface onto given ImageInterface and returns + * modified ImageInterface + * + * @param ImageInterface $image + * @param FilterInterface $filter + * + * @return ImageInterface + * @throws InvalidArgumentException + */ + public function applyFilter(ImageInterface $image, FilterInterface $filter) + { + if ($filter instanceof ImagineAware) { + if ($this->imagine === null) { + throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to Transformation constructor', get_class($filter))); + } + $filter->setImagine($this->imagine); + } + + return $filter->apply($image); + } + + /** + * Returns a list of filters sorted by their priority. Filters with same priority will be returned in the order they were added. + * + * @return array + */ + public function getFilters() + { + if (null === $this->sorted) { + if (!empty($this->filters)) { + ksort($this->filters); + $this->sorted = call_user_func_array('array_merge', $this->filters); + } else { + $this->sorted = array(); + } + } + + return $this->sorted; + } + + /** + * {@inheritdoc} + */ + public function apply(ImageInterface $image) + { + return array_reduce( + $this->getFilters(), + array($this, 'applyFilter'), + $image + ); + } + + /** + * {@inheritdoc} + */ + public function copy() + { + return $this->add(new Copy()); + } + + /** + * {@inheritdoc} + */ + public function crop(PointInterface $start, BoxInterface $size) + { + return $this->add(new Crop($start, $size)); + } + + /** + * {@inheritdoc} + */ + public function flipHorizontally() + { + return $this->add(new FlipHorizontally()); + } + + /** + * {@inheritdoc} + */ + public function flipVertically() + { + return $this->add(new FlipVertically()); + } + + /** + * {@inheritdoc} + */ + public function strip() + { + return $this->add(new Strip()); + } + + /** + * {@inheritdoc} + */ + public function paste(ImageInterface $image, PointInterface $start) + { + return $this->add(new Paste($image, $start)); + } + + /** + * {@inheritdoc} + */ + public function applyMask(ImageInterface $mask) + { + return $this->add(new ApplyMask($mask)); + } + + /** + * {@inheritdoc} + */ + public function fill(FillInterface $fill) + { + return $this->add(new Fill($fill)); + } + + /** + * {@inheritdoc} + */ + public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) + { + return $this->add(new Resize($size, $filter)); + } + + /** + * {@inheritdoc} + */ + public function rotate($angle, ColorInterface $background = null) + { + return $this->add(new Rotate($angle, $background)); + } + + /** + * {@inheritdoc} + */ + public function save($path = null, array $options = array()) + { + return $this->add(new Save($path, $options)); + } + + /** + * {@inheritdoc} + */ + public function show($format, array $options = array()) + { + return $this->add(new Show($format, $options)); + } + + /** + * {@inheritdoc} + */ + public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED) + { + return $this->add(new Thumbnail($size, $mode, $filter)); + } + + /** + * Registers a given FilterInterface in an internal array of filters for + * later application to an instance of ImageInterface + * + * @param FilterInterface $filter + * @param int $priority + * @return Transformation + */ + public function add(FilterInterface $filter, $priority = 0) + { + $this->filters[$priority][] = $filter; + $this->sorted = null; + + return $this; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Drawer.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Drawer.php new file mode 100644 index 0000000000000000000000000000000000000000..e5d74e0d94a0cf042553427d83cbbbd17295bfbe --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Drawer.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gd; + +use Imagine\Draw\DrawerInterface; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\AbstractFont; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Palette\Color\RGB as RGBColor; +use Imagine\Image\PointInterface; + +/** + * Drawer implementation using the GD library + */ +final class Drawer implements DrawerInterface +{ + /** + * @var resource + */ + private $resource; + + /** + * @var array + */ + private $info; + + /** + * Constructs Drawer with a given gd image resource + * + * @param resource $resource + */ + public function __construct($resource) + { + $this->loadGdInfo(); + $this->resource = $resource; + } + + /** + * {@inheritdoc} + */ + public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1) + { + imagesetthickness($this->resource, max(1, (int) $thickness)); + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw arc operation failed'); + } + + if (false === imagearc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color))) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw arc operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw arc operation failed'); + } + + return $this; + } + + /** + * This function does not work properly because of a bug in GD + * + * {@inheritdoc} + */ + public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) + { + imagesetthickness($this->resource, max(1, (int) $thickness)); + + if ($fill) { + $style = IMG_ARC_CHORD; + } else { + $style = IMG_ARC_CHORD | IMG_ARC_NOFILL; + } + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw chord operation failed'); + } + + if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw chord operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw chord operation failed'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1) + { + imagesetthickness($this->resource, max(1, (int) $thickness)); + + if ($fill) { + $callback = 'imagefilledellipse'; + } else { + $callback = 'imageellipse'; + } + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw ellipse operation failed'); + } + + if (false === $callback($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $this->getColor($color))) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw ellipse operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw ellipse operation failed'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1) + { + imagesetthickness($this->resource, max(1, (int) $thickness)); + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw line operation failed'); + } + + if (false === imageline($this->resource, $start->getX(), $start->getY(), $end->getX(), $end->getY(), $this->getColor($color))) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw line operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw line operation failed'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) + { + imagesetthickness($this->resource, max(1, (int) $thickness)); + + if ($fill) { + $style = IMG_ARC_EDGED; + } else { + $style = IMG_ARC_EDGED | IMG_ARC_NOFILL; + } + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw chord operation failed'); + } + + if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw chord operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw chord operation failed'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function dot(PointInterface $position, ColorInterface $color) + { + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw point operation failed'); + } + + if (false === imagesetpixel($this->resource, $position->getX(), $position->getY(), $this->getColor($color))) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw point operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw point operation failed'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1) + { + imagesetthickness($this->resource, max(1, (int) $thickness)); + + if (count($coordinates) < 3) { + throw new InvalidArgumentException(sprintf('A polygon must consist of at least 3 points, %d given', count($coordinates))); + } + + $points = call_user_func_array('array_merge', array_map(function (PointInterface $p) { + return array($p->getX(), $p->getY()); + }, $coordinates)); + + if ($fill) { + $callback = 'imagefilledpolygon'; + } else { + $callback = 'imagepolygon'; + } + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Draw polygon operation failed'); + } + + if (false === $callback($this->resource, $points, count($coordinates), $this->getColor($color))) { + imagealphablending($this->resource, false); + throw new RuntimeException('Draw polygon operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Draw polygon operation failed'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null) + { + if (!$this->info['FreeType Support']) { + throw new RuntimeException('GD is not compiled with FreeType support'); + } + + $angle = -1 * $angle; + $fontsize = $font->getSize(); + $fontfile = $font->getFile(); + $x = $position->getX(); + $y = $position->getY() + $fontsize; + + if ($width !== null) { + $string = $this->wrapText($string, $font, $angle, $width); + } + + if (false === imagealphablending($this->resource, true)) { + throw new RuntimeException('Font mask operation failed'); + } + + if (false === imagefttext($this->resource, $fontsize, $angle, $x, $y, $this->getColor($font->getColor()), $fontfile, $string)) { + imagealphablending($this->resource, false); + throw new RuntimeException('Font mask operation failed'); + } + + if (false === imagealphablending($this->resource, false)) { + throw new RuntimeException('Font mask operation failed'); + } + + return $this; + } + + /** + * Internal + * + * Generates a GD color from Color instance + * + * @param ColorInterface $color + * + * @return resource + * + * @throws RuntimeException + * @throws InvalidArgumentException + */ + private function getColor(ColorInterface $color) + { + if (!$color instanceof RGBColor) { + throw new InvalidArgumentException('GD driver only supports RGB colors'); + } + + $gdColor = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), (100 - $color->getAlpha()) * 127 / 100); + if (false === $gdColor) { + throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha())); + } + + return $gdColor; + } + + private function loadGdInfo() + { + if (!function_exists('gd_info')) { + throw new RuntimeException('Gd not installed'); + } + + $this->info = gd_info(); + } + + /** + * Internal + * + * Fits a string into box with given width + */ + private function wrapText($string, AbstractFont $font, $angle, $width) + { + $result = ''; + $words = explode(' ', $string); + foreach ($words as $word) { + $teststring = $result . ' ' . $word; + $testbox = imagettfbbox($font->getSize(), $angle, $font->getFile(), $teststring); + if ($testbox[2] > $width) { + $result .= ($result == '' ? '' : "\n") . $word; + } else { + $result .= ($result == '' ? '' : ' ') . $word; + } + } + + return $result; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Effects.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Effects.php new file mode 100644 index 0000000000000000000000000000000000000000..d86c4f8a27dae7612faf7eea735872e2ca0d98a3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Effects.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gd; + +use Imagine\Effects\EffectsInterface; +use Imagine\Exception\RuntimeException; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Palette\Color\RGB as RGBColor; + +/** + * Effects implementation using the GD library + */ +class Effects implements EffectsInterface +{ + private $resource; + + public function __construct($resource) + { + $this->resource = $resource; + } + + /** + * {@inheritdoc} + */ + public function gamma($correction) + { + if (false === imagegammacorrect($this->resource, 1.0, $correction)) { + throw new RuntimeException('Failed to apply gamma correction to the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function negative() + { + if (false === imagefilter($this->resource, IMG_FILTER_NEGATE)) { + throw new RuntimeException('Failed to negate the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function grayscale() + { + if (false === imagefilter($this->resource, IMG_FILTER_GRAYSCALE)) { + throw new RuntimeException('Failed to grayscale the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function colorize(ColorInterface $color) + { + if (!$color instanceof RGBColor) { + throw new RuntimeException('Colorize effects only accepts RGB color in GD context'); + } + + if (false === imagefilter($this->resource, IMG_FILTER_COLORIZE, $color->getRed(), $color->getGreen(), $color->getBlue())) { + throw new RuntimeException('Failed to colorize the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function sharpen() + { + $sharpenMatrix = array(array(-1,-1,-1), array(-1,16,-1), array(-1,-1,-1)); + $divisor = array_sum(array_map('array_sum', $sharpenMatrix)); + + if (false === imageconvolution($this->resource, $sharpenMatrix, $divisor, 0)) { + throw new RuntimeException('Failed to sharpen the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function blur($sigma = 1) + { + if (false === imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR)) { + throw new RuntimeException('Failed to blur the image'); + } + + return $this; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Font.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Font.php new file mode 100644 index 0000000000000000000000000000000000000000..8bc2b04d43a6f09ce31e883983e6176b228898e6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Font.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 Imagine\Gd; + +use Imagine\Exception\RuntimeException; +use Imagine\Image\AbstractFont; +use Imagine\Image\Box; + +/** + * Font implementation using the GD library + */ +final class Font extends AbstractFont +{ + /** + * {@inheritdoc} + */ + public function box($string, $angle = 0) + { + if (!function_exists('imageftbbox')) { + throw new RuntimeException('GD must have been compiled with `--with-freetype-dir` option to use the Font feature.'); + } + + $angle = -1 * $angle; + $info = imageftbbox($this->size, $angle, $this->file, $string); + $xs = array($info[0], $info[2], $info[4], $info[6]); + $ys = array($info[1], $info[3], $info[5], $info[7]); + $width = abs(max($xs) - min($xs)); + $height = abs(max($ys) - min($ys)); + + return new Box($width, $height); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Image.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Image.php new file mode 100644 index 0000000000000000000000000000000000000000..73d7b9506f062a5bafce5011685c675480442cd0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Image.php @@ -0,0 +1,735 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gd; + +use Imagine\Image\AbstractImage; +use Imagine\Image\ImageInterface; +use Imagine\Image\Box; +use Imagine\Image\BoxInterface; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Fill\FillInterface; +use Imagine\Image\Point; +use Imagine\Image\PointInterface; +use Imagine\Image\Palette\PaletteInterface; +use Imagine\Image\Palette\Color\RGB as RGBColor; +use Imagine\Image\ProfileInterface; +use Imagine\Image\Palette\RGB; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\OutOfBoundsException; +use Imagine\Exception\RuntimeException; + +/** + * Image implementation using the GD library + */ +final class Image extends AbstractImage +{ + /** + * @var resource + */ + private $resource; + + /** + * @var Layers|null + */ + private $layers; + + /** + * @var PaletteInterface + */ + private $palette; + + /** + * Constructs a new Image instance + * + * @param resource $resource + * @param PaletteInterface $palette + * @param MetadataBag $metadata + */ + public function __construct($resource, PaletteInterface $palette, MetadataBag $metadata) + { + $this->metadata = $metadata; + $this->palette = $palette; + $this->resource = $resource; + } + + /** + * Makes sure the current image resource is destroyed + */ + public function __destruct() + { + if (is_resource($this->resource) && 'gd' === get_resource_type($this->resource)) { + imagedestroy($this->resource); + } + } + + /** + * Returns Gd resource + * + * @return resource + */ + public function getGdResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function copy() + { + $size = $this->getSize(); + $copy = $this->createImage($size, 'copy'); + + if (false === imagecopy($copy, $this->resource, 0, 0, 0, 0, $size->getWidth(), $size->getHeight())) { + throw new RuntimeException('Image copy operation failed'); + } + + return new Image($copy, $this->palette, $this->metadata); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function crop(PointInterface $start, BoxInterface $size) + { + if (!$start->in($this->getSize())) { + throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders'); + } + + $width = $size->getWidth(); + $height = $size->getHeight(); + + $dest = $this->createImage($size, 'crop'); + + if (false === imagecopy($dest, $this->resource, 0, 0, $start->getX(), $start->getY(), $width, $height)) { + throw new RuntimeException('Image crop operation failed'); + } + + imagedestroy($this->resource); + + $this->resource = $dest; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function paste(ImageInterface $image, PointInterface $start) + { + if (!$image instanceof self) { + throw new InvalidArgumentException(sprintf('Gd\Image can only paste() Gd\Image instances, %s given', get_class($image))); + } + + $size = $image->getSize(); + if (!$this->getSize()->contains($size, $start)) { + throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box'); + } + + imagealphablending($this->resource, true); + imagealphablending($image->resource, true); + + if (false === imagecopy($this->resource, $image->resource, $start->getX(), $start->getY(), 0, 0, $size->getWidth(), $size->getHeight())) { + throw new RuntimeException('Image paste operation failed'); + } + + imagealphablending($this->resource, false); + imagealphablending($image->resource, false); + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) + { + if (ImageInterface::FILTER_UNDEFINED !== $filter) { + throw new InvalidArgumentException('Unsupported filter type, GD only supports ImageInterface::FILTER_UNDEFINED filter'); + } + + $width = $size->getWidth(); + $height = $size->getHeight(); + + $dest = $this->createImage($size, 'resize'); + + imagealphablending($this->resource, true); + imagealphablending($dest, true); + + if (false === imagecopyresampled($dest, $this->resource, 0, 0, 0, 0, $width, $height, imagesx($this->resource), imagesy($this->resource))) { + throw new RuntimeException('Image resize operation failed'); + } + + imagealphablending($this->resource, false); + imagealphablending($dest, false); + + imagedestroy($this->resource); + + $this->resource = $dest; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function rotate($angle, ColorInterface $background = null) + { + $color = $background ? $background : $this->palette->color('fff'); + $resource = imagerotate($this->resource, -1 * $angle, $this->getColor($color)); + + if (false === $resource) { + throw new RuntimeException('Image rotate operation failed'); + } + + imagedestroy($this->resource); + $this->resource = $resource; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function save($path = null, array $options = array()) + { + $path = null === $path ? (isset($this->metadata['filepath']) ? $this->metadata['filepath'] : $path) : $path; + + if (null === $path) { + throw new RuntimeException('You can omit save path only if image has been open from a file'); + } + + if (isset($options['format'])) { + $format = $options['format']; + } elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) { + $format = $extension; + } else { + $originalPath = isset($this->metadata['filepath']) ? $this->metadata['filepath'] : null; + $format = pathinfo($originalPath, \PATHINFO_EXTENSION); + } + + $this->saveOrOutput($format, $options, $path); + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function show($format, array $options = array()) + { + header('Content-type: '.$this->getMimeType($format)); + + $this->saveOrOutput($format, $options); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get($format, array $options = array()) + { + ob_start(); + $this->saveOrOutput($format, $options); + + return ob_get_clean(); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->get('png'); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function flipHorizontally() + { + $size = $this->getSize(); + $width = $size->getWidth(); + $height = $size->getHeight(); + $dest = $this->createImage($size, 'flip'); + + for ($i = 0; $i < $width; $i++) { + if (false === imagecopy($dest, $this->resource, $i, 0, ($width - 1) - $i, 0, 1, $height)) { + throw new RuntimeException('Horizontal flip operation failed'); + } + } + + imagedestroy($this->resource); + + $this->resource = $dest; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function flipVertically() + { + $size = $this->getSize(); + $width = $size->getWidth(); + $height = $size->getHeight(); + $dest = $this->createImage($size, 'flip'); + + for ($i = 0; $i < $height; $i++) { + if (false === imagecopy($dest, $this->resource, 0, $i, 0, ($height - 1) - $i, $width, 1)) { + throw new RuntimeException('Vertical flip operation failed'); + } + } + + imagedestroy($this->resource); + + $this->resource = $dest; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + final public function strip() + { + // GD strips profiles and comment, so there's nothing to do here + return $this; + } + + /** + * {@inheritdoc} + */ + public function draw() + { + return new Drawer($this->resource); + } + + /** + * {@inheritdoc} + */ + public function effects() + { + return new Effects($this->resource); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + return new Box(imagesx($this->resource), imagesy($this->resource)); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function applyMask(ImageInterface $mask) + { + if (!$mask instanceof self) { + throw new InvalidArgumentException('Cannot mask non-gd images'); + } + + $size = $this->getSize(); + $maskSize = $mask->getSize(); + + if ($size != $maskSize) { + throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size)); + } + + for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) { + for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) { + $position = new Point($x, $y); + $color = $this->getColorAt($position); + $maskColor = $mask->getColorAt($position); + $round = (int) round(max($color->getAlpha(), (100 - $color->getAlpha()) * $maskColor->getRed() / 255)); + + if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($color->dissolve($round - $color->getAlpha())))) { + throw new RuntimeException('Apply mask operation failed'); + } + } + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function fill(FillInterface $fill) + { + $size = $this->getSize(); + + for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) { + for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) { + if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($fill->getColor(new Point($x, $y))))) { + throw new RuntimeException('Fill operation failed'); + } + } + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function mask() + { + $mask = $this->copy(); + + if (false === imagefilter($mask->resource, IMG_FILTER_GRAYSCALE)) { + throw new RuntimeException('Mask operation failed'); + } + + return $mask; + } + + /** + * {@inheritdoc} + */ + public function histogram() + { + $size = $this->getSize(); + $colors = array(); + + for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) { + for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) { + $colors[] = $this->getColorAt(new Point($x, $y)); + } + } + + return array_unique($colors); + } + + /** + * {@inheritdoc} + */ + public function getColorAt(PointInterface $point) + { + if (!$point->in($this->getSize())) { + throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight())); + } + + $index = imagecolorat($this->resource, $point->getX(), $point->getY()); + $info = imagecolorsforindex($this->resource, $index); + + return $this->palette->color(array($info['red'], $info['green'], $info['blue']), max(min(100 - (int) round($info['alpha'] / 127 * 100), 100), 0)); + } + + /** + * {@inheritdoc} + */ + public function layers() + { + if (null === $this->layers) { + $this->layers = new Layers($this, $this->palette, $this->resource); + } + + return $this->layers; + } + + /** + * {@inheritdoc} + **/ + public function interlace($scheme) + { + static $supportedInterlaceSchemes = array( + ImageInterface::INTERLACE_NONE => 0, + ImageInterface::INTERLACE_LINE => 1, + ImageInterface::INTERLACE_PLANE => 1, + ImageInterface::INTERLACE_PARTITION => 1, + ); + + if (!array_key_exists($scheme, $supportedInterlaceSchemes)) { + throw new InvalidArgumentException('Unsupported interlace type'); + } + + imageinterlace($this->resource, $supportedInterlaceSchemes[$scheme]); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function palette() + { + return $this->palette; + } + + /** + * {@inheritdoc} + */ + public function profile(ProfileInterface $profile) + { + throw new RuntimeException('GD driver does not support color profiles'); + } + + /** + * {@inheritdoc} + */ + public function usePalette(PaletteInterface $palette) + { + if (!$palette instanceof RGB) { + throw new RuntimeException('GD driver only supports RGB palette'); + } + + $this->palette = $palette; + + return $this; + } + + /** + * Internal + * + * Performs save or show operation using one of GD's image... functions + * + * @param string $format + * @param array $options + * @param string $filename + * + * @throws InvalidArgumentException + * @throws RuntimeException + */ + private function saveOrOutput($format, array $options, $filename = null) + { + $format = $this->normalizeFormat($format); + + if (!$this->supported($format)) { + throw new InvalidArgumentException(sprintf('Saving image in "%s" format is not supported, please use one of the following extensions: "%s"', $format, implode('", "', $this->supported()))); + } + + $save = 'image'.$format; + $args = array(&$this->resource, $filename); + + // Preserve BC until version 1.0 + if (isset($options['quality']) && !isset($options['png_compression_level'])) { + $options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100); + } + if (isset($options['filters']) && !isset($options['png_compression_filter'])) { + $options['png_compression_filter'] = $options['filters']; + } + + $options = $this->updateSaveOptions($options); + + if ($format === 'jpeg' && isset($options['jpeg_quality'])) { + $args[] = $options['jpeg_quality']; + } + + if ($format === 'png') { + if (isset($options['png_compression_level'])) { + if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) { + throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9'); + } + $args[] = $options['png_compression_level']; + } else { + $args[] = -1; // use default level + } + + if (isset($options['png_compression_filter'])) { + if (~PNG_ALL_FILTERS & $options['png_compression_filter']) { + throw new InvalidArgumentException('png_compression_filter option should be a combination of the PNG_FILTER_XXX constants'); + } + $args[] = $options['png_compression_filter']; + } + } + + if (($format === 'wbmp' || $format === 'xbm') && isset($options['foreground'])) { + $args[] = $options['foreground']; + } + + $this->setExceptionHandler(); + + if (false === call_user_func_array($save, $args)) { + throw new RuntimeException('Save operation failed'); + } + + $this->resetExceptionHandler(); + } + + /** + * Internal + * + * Generates a GD image + * + * @param BoxInterface $size + * @param string the operation initiating the creation + * + * @return resource + * + * @throws RuntimeException + * + */ + private function createImage(BoxInterface $size, $operation) + { + $resource = imagecreatetruecolor($size->getWidth(), $size->getHeight()); + + if (false === $resource) { + throw new RuntimeException('Image '.$operation.' failed'); + } + + if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) { + throw new RuntimeException('Image '.$operation.' failed'); + } + + if (function_exists('imageantialias')) { + imageantialias($resource, true); + } + + $transparent = imagecolorallocatealpha($resource, 255, 255, 255, 127); + imagefill($resource, 0, 0, $transparent); + imagecolortransparent($resource, $transparent); + + return $resource; + } + + /** + * Internal + * + * Generates a GD color from Color instance + * + * @param ColorInterface $color + * + * @return integer A color identifier + * + * @throws RuntimeException + * @throws InvalidArgumentException + */ + private function getColor(ColorInterface $color) + { + if (!$color instanceof RGBColor) { + throw new InvalidArgumentException('GD driver only supports RGB colors'); + } + + $index = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100)); + + if (false === $index) { + throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha())); + } + + return $index; + } + + /** + * Internal + * + * Normalizes a given format name + * + * @param string $format + * + * @return string + */ + private function normalizeFormat($format) + { + $format = strtolower($format); + + if ('jpg' === $format || 'pjpeg' === $format) { + $format = 'jpeg'; + } + + return $format; + } + + /** + * Internal + * + * Checks whether a given format is supported by GD library + * + * @param string $format + * + * @return Boolean + */ + private function supported($format = null) + { + $formats = array('gif', 'jpeg', 'png', 'wbmp', 'xbm'); + + if (null === $format) { + return $formats; + } + + return in_array($format, $formats); + } + + private function setExceptionHandler() + { + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + if (0 === error_reporting()) { + return; + } + + throw new RuntimeException($errstr, $errno, new \ErrorException($errstr, 0, $errno, $errfile, $errline)); + }, E_WARNING | E_NOTICE); + } + + private function resetExceptionHandler() + { + restore_error_handler(); + } + + /** + * Internal + * + * Get the mime type based on format. + * + * @param string $format + * + * @return string mime-type + * + * @throws RuntimeException + */ + private function getMimeType($format) + { + $format = $this->normalizeFormat($format); + + if (!$this->supported($format)) { + throw new RuntimeException('Invalid format'); + } + + static $mimeTypes = array( + 'jpeg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'wbmp' => 'image/vnd.wap.wbmp', + 'xbm' => 'image/xbm', + ); + + return $mimeTypes[$format]; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Imagine.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Imagine.php new file mode 100644 index 0000000000000000000000000000000000000000..f9e9508761a8589948eb53eb87d1873dbc8c9f57 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Imagine.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gd; + +use Imagine\Image\AbstractImagine; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Palette\RGB; +use Imagine\Image\Palette\PaletteInterface; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\RGB as RGBColor; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; + +/** + * Imagine implementation using the GD library + */ +final class Imagine extends AbstractImagine +{ + /** + * @var array + */ + private $info; + + /** + * @throws RuntimeException + */ + public function __construct() + { + $this->loadGdInfo(); + $this->requireGdVersion('2.0.1'); + } + + /** + * {@inheritdoc} + */ + public function create(BoxInterface $size, ColorInterface $color = null) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + $resource = imagecreatetruecolor($width, $height); + + if (false === $resource) { + throw new RuntimeException('Create operation failed'); + } + + $palette = null !== $color ? $color->getPalette() : new RGB(); + $color = $color ? $color : $palette->color('fff'); + + if (!$color instanceof RGBColor) { + throw new InvalidArgumentException('GD driver only supports RGB colors'); + } + + $index = imagecolorallocatealpha($resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100)); + + if (false === $index) { + throw new RuntimeException('Unable to allocate color'); + } + + if (false === imagefill($resource, 0, 0, $index)) { + throw new RuntimeException('Could not set background color fill'); + } + + if ($color->getAlpha() >= 95) { + imagecolortransparent($resource, $index); + } + + return $this->wrap($resource, $palette, new MetadataBag()); + } + + /** + * {@inheritdoc} + */ + public function open($path) + { + $path = $this->checkPath($path); + $data = @file_get_contents($path); + + if (false === $data) { + throw new RuntimeException(sprintf('Failed to open file %s', $path)); + } + + $resource = @imagecreatefromstring($data); + + if (!is_resource($resource)) { + throw new RuntimeException(sprintf('Unable to open image %s', $path)); + } + + return $this->wrap($resource, new RGB(), $this->getMetadataReader()->readFile($path)); + } + + /** + * {@inheritdoc} + */ + public function load($string) + { + return $this->doLoad($string, $this->getMetadataReader()->readData($string)); + } + + /** + * {@inheritdoc} + */ + public function read($resource) + { + if (!is_resource($resource)) { + throw new InvalidArgumentException('Variable does not contain a stream resource'); + } + + $content = stream_get_contents($resource); + + if (false === $content) { + throw new InvalidArgumentException('Cannot read resource content'); + } + + return $this->doLoad($content, $this->getMetadataReader()->readStream($resource)); + } + + /** + * {@inheritdoc} + */ + public function font($file, $size, ColorInterface $color) + { + if (!$this->info['FreeType Support']) { + throw new RuntimeException('GD is not compiled with FreeType support'); + } + + return new Font($file, $size, $color); + } + + private function wrap($resource, PaletteInterface $palette, MetadataBag $metadata) + { + if (!imageistruecolor($resource)) { + list($width, $height) = array(imagesx($resource), imagesy($resource)); + + // create transparent truecolor canvas + $truecolor = imagecreatetruecolor($width, $height); + $transparent = imagecolorallocatealpha($truecolor, 255, 255, 255, 127); + + imagefill($truecolor, 0, 0, $transparent); + imagecolortransparent($truecolor, $transparent); + + imagecopymerge($truecolor, $resource, 0, 0, 0, 0, $width, $height, 100); + + imagedestroy($resource); + $resource = $truecolor; + } + + if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) { + throw new RuntimeException('Could not set alphablending, savealpha and antialias values'); + } + + if (function_exists('imageantialias')) { + imageantialias($resource, true); + } + + return new Image($resource, $palette, $metadata); + } + + private function loadGdInfo() + { + if (!function_exists('gd_info')) { + throw new RuntimeException('Gd not installed'); + } + + $this->info = gd_info(); + } + + private function requireGdVersion($version) + { + if (version_compare(GD_VERSION, $version, '<')) { + throw new RuntimeException(sprintf('GD2 version %s or higher is required', $version)); + } + } + + private function doLoad($string, MetadataBag $metadata) + { + $resource = @imagecreatefromstring($string); + + if (!is_resource($resource)) { + throw new RuntimeException('An image could not be created from the given input'); + } + + return $this->wrap($resource, new RGB(), $metadata); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Layers.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Layers.php new file mode 100644 index 0000000000000000000000000000000000000000..93c7a26958eee7d07360f06efe45cbc1691c1fbe --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gd/Layers.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gd; + +use Imagine\Image\AbstractLayers; +use Imagine\Exception\RuntimeException; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\PaletteInterface; +use Imagine\Exception\NotSupportedException; + +class Layers extends AbstractLayers +{ + private $image; + private $offset; + private $resource; + private $palette; + + public function __construct(Image $image, PaletteInterface $palette, $resource) + { + if (!is_resource($resource)) { + throw new RuntimeException('Invalid Gd resource provided'); + } + + $this->image = $image; + $this->resource = $resource; + $this->offset = 0; + $this->palette = $palette; + } + + /** + * {@inheritdoc} + */ + public function merge() + { + } + + /** + * {@inheritdoc} + */ + public function coalesce() + { + } + + /** + * {@inheritdoc} + */ + public function animate($format, $delay, $loops) + { + return $this; + } + + /** + * {@inheritdoc} + */ + public function current() + { + return new Image($this->resource, $this->palette, new MetadataBag()); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function next() + { + ++$this->offset; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->offset = 0; + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->offset < 1; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return 0 === $offset; + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + if (0 === $offset) { + return new Image($this->resource, $this->palette, new MetadataBag()); + } + + throw new RuntimeException('GD only supports one layer at offset 0'); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + throw new NotSupportedException('GD does not support layer set'); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + throw new NotSupportedException('GD does not support layer unset'); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Drawer.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Drawer.php new file mode 100644 index 0000000000000000000000000000000000000000..de6cc904858b1dbf7adb2c439c5b894e258df8c7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Drawer.php @@ -0,0 +1,356 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gmagick; + +use Imagine\Draw\DrawerInterface; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\NotSupportedException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\AbstractFont; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Point; +use Imagine\Image\PointInterface; + +/** + * Drawer implementation using the Gmagick PHP extension + */ +final class Drawer implements DrawerInterface +{ + /** + * @var \Gmagick + */ + private $gmagick; + + /** + * @param \Gmagick $gmagick + */ + public function __construct(\Gmagick $gmagick) + { + $this->gmagick = $gmagick; + } + + /** + * {@inheritdoc} + */ + public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1) + { + $x = $center->getX(); + $y = $center->getY(); + $width = $size->getWidth(); + $height = $size->getHeight(); + + try { + $pixel = $this->getColor($color); + $arc = new \GmagickDraw(); + + $arc->setstrokecolor($pixel); + $arc->setstrokewidth(max(1, (int) $thickness)); + $arc->setfillcolor('transparent'); + $arc->arc( + $x - $width / 2, + $y - $height / 2, + $x + $width / 2, + $y + $height / 2, + $start, + $end + ); + + $this->gmagick->drawImage($arc); + + $pixel = null; + + $arc = null; + } catch (\GmagickException $e) { + throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) + { + $x = $center->getX(); + $y = $center->getY(); + $width = $size->getWidth(); + $height = $size->getHeight(); + + try { + $pixel = $this->getColor($color); + $chord = new \GmagickDraw(); + + $chord->setstrokecolor($pixel); + $chord->setstrokewidth(max(1, (int) $thickness)); + + if ($fill) { + $chord->setfillcolor($pixel); + } else { + $x1 = round($x + $width / 2 * cos(deg2rad($start))); + $y1 = round($y + $height / 2 * sin(deg2rad($start))); + $x2 = round($x + $width / 2 * cos(deg2rad($end))); + $y2 = round($y + $height / 2 * sin(deg2rad($end))); + + $this->line(new Point($x1, $y1), new Point($x2, $y2), $color); + + $chord->setfillcolor('transparent'); + } + + $chord->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end); + + $this->gmagick->drawImage($chord); + + $pixel = null; + + $chord = null; + } catch (\GmagickException $e) { + throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + try { + $pixel = $this->getColor($color); + $ellipse = new \GmagickDraw(); + + $ellipse->setstrokecolor($pixel); + $ellipse->setstrokewidth(max(1, (int) $thickness)); + + if ($fill) { + $ellipse->setfillcolor($pixel); + } else { + $ellipse->setfillcolor('transparent'); + } + + $ellipse->ellipse( + $center->getX(), + $center->getY(), + $width / 2, + $height / 2, + 0, 360 + ); + + $this->gmagick->drawImage($ellipse); + + $pixel = null; + + $ellipse = null; + } catch (\GmagickException $e) { + throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1) + { + try { + $pixel = $this->getColor($color); + $line = new \GmagickDraw(); + + $line->setstrokecolor($pixel); + $line->setstrokewidth(max(1, (int) $thickness)); + $line->setfillcolor($pixel); + $line->line( + $start->getX(), + $start->getY(), + $end->getX(), + $end->getY() + ); + + $this->gmagick->drawImage($line); + + $pixel = null; + + $line = null; + } catch (\GmagickException $e) { + throw new RuntimeException('Draw line operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + $x1 = round($center->getX() + $width / 2 * cos(deg2rad($start))); + $y1 = round($center->getY() + $height / 2 * sin(deg2rad($start))); + $x2 = round($center->getX() + $width / 2 * cos(deg2rad($end))); + $y2 = round($center->getY() + $height / 2 * sin(deg2rad($end))); + + if ($fill) { + $this->chord($center, $size, $start, $end, $color, true, $thickness); + $this->polygon( + array( + $center, + new Point($x1, $y1), + new Point($x2, $y2), + ), + $color, + true, + $thickness + ); + } else { + $this->arc($center, $size, $start, $end, $color, $thickness); + $this->line($center, new Point($x1, $y1), $color, $thickness); + $this->line($center, new Point($x2, $y2), $color, $thickness); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function dot(PointInterface $position, ColorInterface $color) + { + $x = $position->getX(); + $y = $position->getY(); + + try { + $pixel = $this->getColor($color); + $point = new \GmagickDraw(); + + $point->setfillcolor($pixel); + $point->point($x, $y); + + $this->gmagick->drawimage($point); + + $pixel = null; + $point = null; + } catch (\GmagickException $e) { + throw new RuntimeException('Draw point operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1) + { + if (count($coordinates) < 3) { + throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates))); + } + + $points = array_map(function (PointInterface $p) { + return array('x' => $p->getX(), 'y' => $p->getY()); + }, $coordinates); + + try { + $pixel = $this->getColor($color); + $polygon = new \GmagickDraw(); + + $polygon->setstrokecolor($pixel); + $polygon->setstrokewidth(max(1, (int) $thickness)); + + if ($fill) { + $polygon->setfillcolor($pixel); + } else { + $polygon->setfillcolor('transparent'); + } + + $polygon->polygon($points); + + $this->gmagick->drawImage($polygon); + + unset($pixel, $polygon); + } catch (\GmagickException $e) { + throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null) + { + try { + $pixel = $this->getColor($font->getColor()); + $text = new \GmagickDraw(); + + $text->setfont($font->getFile()); + /** + * @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 + * + * ensure font resolution is the same as GD's hard-coded 96 + */ + $text->setfontsize((int) ($font->getSize() * (96 / 72))); + $text->setfillcolor($pixel); + + $info = $this->gmagick->queryfontmetrics($text, $string); + $rad = deg2rad($angle); + $cos = cos($rad); + $sin = sin($rad); + + $x1 = round(0 * $cos - 0 * $sin); + $x2 = round($info['textWidth'] * $cos - $info['textHeight'] * $sin); + $y1 = round(0 * $sin + 0 * $cos); + $y2 = round($info['textWidth'] * $sin + $info['textHeight'] * $cos); + + $xdiff = 0 - min($x1, $x2); + $ydiff = 0 - min($y1, $y2); + + if ($width !== null) { + throw new NotSupportedException('Gmagick doesn\'t support queryfontmetrics function for multiline text', 1); + } + + $this->gmagick->annotateimage($text, $position->getX() + $x1 + $xdiff, $position->getY() + $y2 + $ydiff, $angle, $string); + + unset($pixel, $text); + } catch (\GmagickException $e) { + throw new RuntimeException('Draw text operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * Gets specifically formatted color string from Color instance + * + * @param ColorInterface $color + * + * @return \GmagickPixel + * + * @throws InvalidArgumentException In case a non-opaque color is passed + */ + private function getColor(ColorInterface $color) + { + if (!$color->isOpaque()) { + throw new InvalidArgumentException('Gmagick doesn\'t support transparency'); + } + + return new \GmagickPixel((string) $color); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Effects.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Effects.php new file mode 100644 index 0000000000000000000000000000000000000000..3f9c5cd08d1d75ceb77529525ce25a2ddba3f728 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Effects.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gmagick; + +use Imagine\Effects\EffectsInterface; +use Imagine\Exception\RuntimeException; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Exception\NotSupportedException; + +/** + * Effects implementation using the Gmagick PHP extension + */ +class Effects implements EffectsInterface +{ + private $gmagick; + + public function __construct(\Gmagick $gmagick) + { + $this->gmagick = $gmagick; + } + + /** + * {@inheritdoc} + */ + public function gamma($correction) + { + try { + $this->gmagick->gammaimage($correction); + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to apply gamma correction to the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function negative() + { + if (!method_exists($this->gmagick, 'negateimage')) { + throw new NotSupportedException('Gmagick version 1.1.0 RC3 is required for negative effect'); + } + + try { + $this->gmagick->negateimage(false, \Gmagick::CHANNEL_ALL); + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to negate the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function grayscale() + { + try { + $this->gmagick->setImageType(2); + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to grayscale the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function colorize(ColorInterface $color) + { + throw new NotSupportedException('Gmagick does not support colorize'); + } + + /** + * {@inheritdoc} + */ + public function sharpen() + { + throw new NotSupportedException('Gmagick does not support sharpen yet'); + } + + /** + * {@inheritdoc} + */ + public function blur($sigma = 1) + { + try { + $this->gmagick->blurImage(0, $sigma); + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to blur the image', $e->getCode(), $e); + } + + return $this; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Font.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Font.php new file mode 100644 index 0000000000000000000000000000000000000000..ad67c56471a8205abdc38deecbcd84d99b325d54 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Font.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gmagick; + +use Imagine\Image\AbstractFont; +use Imagine\Image\Box; +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * Font implementation using the Gmagick PHP extension + */ +final class Font extends AbstractFont +{ + /** + * @var \Gmagick + */ + private $gmagick; + + /** + * @param \Gmagick $gmagick + * @param string $file + * @param integer $size + * @param ColorInterface $color + */ + public function __construct(\Gmagick $gmagick, $file, $size, ColorInterface $color) + { + $this->gmagick = $gmagick; + + parent::__construct($file, $size, $color); + } + + /** + * {@inheritdoc} + */ + public function box($string, $angle = 0) + { + $text = new \GmagickDraw(); + + $text->setfont($this->file); + /** + * @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 + * + * ensure font resolution is the same as GD's hard-coded 96 + */ + $text->setfontsize((int) ($this->size * (96 / 72))); + $text->setfontstyle(\Gmagick::STYLE_OBLIQUE); + + $info = $this->gmagick->queryfontmetrics($text, $string); + + $box = new Box($info['textWidth'], $info['textHeight']); + + return $box; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Image.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Image.php new file mode 100644 index 0000000000000000000000000000000000000000..61ef858c1d194b2abeb1b8001eb36c1e4e5177e6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Image.php @@ -0,0 +1,786 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gmagick; + +use Imagine\Exception\OutOfBoundsException; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\AbstractImage; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\PaletteInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\Box; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Fill\FillInterface; +use Imagine\Image\Point; +use Imagine\Image\PointInterface; +use Imagine\Image\ProfileInterface; + +/** + * Image implementation using the Gmagick PHP extension + */ +final class Image extends AbstractImage +{ + /** + * @var \Gmagick + */ + private $gmagick; + /** + * @var Layers + */ + private $layers; + + /** + * @var PaletteInterface + */ + private $palette; + + private static $colorspaceMapping = array( + PaletteInterface::PALETTE_CMYK => \Gmagick::COLORSPACE_CMYK, + PaletteInterface::PALETTE_RGB => \Gmagick::COLORSPACE_RGB, + ); + + /** + * Constructs a new Image instance + * + * @param \Gmagick $gmagick + * @param PaletteInterface $palette + * @param MetadataBag $metadata + */ + public function __construct(\Gmagick $gmagick, PaletteInterface $palette, MetadataBag $metadata) + { + $this->metadata = $metadata; + $this->gmagick = $gmagick; + $this->setColorspace($palette); + $this->layers = new Layers($this, $this->palette, $this->gmagick); + } + + /** + * Destroys allocated gmagick resources + */ + public function __destruct() + { + if ($this->gmagick instanceof \Gmagick) { + $this->gmagick->clear(); + $this->gmagick->destroy(); + } + } + + /** + * Returns gmagick instance + * + * @return Gmagick + */ + public function getGmagick() + { + return $this->gmagick; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function copy() + { + return new self(clone $this->gmagick, $this->palette, clone $this->metadata); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function crop(PointInterface $start, BoxInterface $size) + { + if (!$start->in($this->getSize())) { + throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders'); + } + + try { + $this->gmagick->cropimage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY()); + } catch (\GmagickException $e) { + throw new RuntimeException('Crop operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function flipHorizontally() + { + try { + $this->gmagick->flopimage(); + } catch (\GmagickException $e) { + throw new RuntimeException('Horizontal flip operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function flipVertically() + { + try { + $this->gmagick->flipimage(); + } catch (\GmagickException $e) { + throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function strip() + { + try { + try { + $this->profile($this->palette->profile()); + } catch (\Exception $e) { + // here we discard setting the profile as the previous incorporated profile + // is corrupted, let's now strip the image + } + $this->gmagick->stripimage(); + } catch (\GmagickException $e) { + throw new RuntimeException('Strip operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function paste(ImageInterface $image, PointInterface $start) + { + if (!$image instanceof self) { + throw new InvalidArgumentException(sprintf('Gmagick\Image can only paste() Gmagick\Image instances, %s given', get_class($image))); + } + + if (!$this->getSize()->contains($image->getSize(), $start)) { + throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box'); + } + + try { + $this->gmagick->compositeimage($image->gmagick, \Gmagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY()); + } catch (\GmagickException $e) { + throw new RuntimeException('Paste operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) + { + static $supportedFilters = array( + ImageInterface::FILTER_UNDEFINED => \Gmagick::FILTER_UNDEFINED, + ImageInterface::FILTER_BESSEL => \Gmagick::FILTER_BESSEL, + ImageInterface::FILTER_BLACKMAN => \Gmagick::FILTER_BLACKMAN, + ImageInterface::FILTER_BOX => \Gmagick::FILTER_BOX, + ImageInterface::FILTER_CATROM => \Gmagick::FILTER_CATROM, + ImageInterface::FILTER_CUBIC => \Gmagick::FILTER_CUBIC, + ImageInterface::FILTER_GAUSSIAN => \Gmagick::FILTER_GAUSSIAN, + ImageInterface::FILTER_HANNING => \Gmagick::FILTER_HANNING, + ImageInterface::FILTER_HAMMING => \Gmagick::FILTER_HAMMING, + ImageInterface::FILTER_HERMITE => \Gmagick::FILTER_HERMITE, + ImageInterface::FILTER_LANCZOS => \Gmagick::FILTER_LANCZOS, + ImageInterface::FILTER_MITCHELL => \Gmagick::FILTER_MITCHELL, + ImageInterface::FILTER_POINT => \Gmagick::FILTER_POINT, + ImageInterface::FILTER_QUADRATIC => \Gmagick::FILTER_QUADRATIC, + ImageInterface::FILTER_SINC => \Gmagick::FILTER_SINC, + ImageInterface::FILTER_TRIANGLE => \Gmagick::FILTER_TRIANGLE + ); + + if (!array_key_exists($filter, $supportedFilters)) { + throw new InvalidArgumentException('Unsupported filter type'); + } + + try { + $this->gmagick->resizeimage($size->getWidth(), $size->getHeight(), $supportedFilters[$filter], 1); + } catch (\GmagickException $e) { + throw new RuntimeException('Resize operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function rotate($angle, ColorInterface $background = null) + { + try { + $background = $background ?: $this->palette->color('fff'); + $pixel = $this->getColor($background); + + $this->gmagick->rotateimage($pixel, $angle); + + unset($pixel); + } catch (\GmagickException $e) { + throw new RuntimeException('Rotate operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * Internal + * + * Applies options before save or output + * + * @param \Gmagick $image + * @param array $options + * @param string $path + * + * @throws InvalidArgumentException + */ + private function applyImageOptions(\Gmagick $image, array $options, $path) + { + if (isset($options['format'])) { + $format = $options['format']; + } elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) { + $format = $extension; + } else { + $format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION); + } + + $format = strtolower($format); + + $options = $this->updateSaveOptions($options); + + if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) { + $image->setCompressionQuality($options['jpeg_quality']); + } + + if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') { + // first digit: compression level (default: 7) + if (isset($options['png_compression_level'])) { + if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) { + throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9'); + } + $compression = $options['png_compression_level'] * 10; + } else { + $compression = 70; + } + + // second digit: compression filter (default: 5) + if (isset($options['png_compression_filter'])) { + if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) { + throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9'); + } + $compression += $options['png_compression_filter']; + } else { + $compression += 5; + } + + $image->setCompressionQuality($compression); + } + + if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) { + if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) { + $image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERCENTIMETER); + } elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) { + $image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERINCH); + } else { + throw new InvalidArgumentException('Unsupported image unit format'); + } + + $image->setimageresolution($options['resolution-x'], $options['resolution-y']); + } + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function save($path = null, array $options = array()) + { + $path = null === $path ? $this->gmagick->getImageFilename() : $path; + + if ('' === trim($path)) { + throw new RuntimeException('You can omit save path only if image has been open from a file'); + } + + try { + $this->prepareOutput($options, $path); + $allFrames = !isset($options['animated']) || false === $options['animated']; + $this->gmagick->writeimage($path, $allFrames); + } catch (\GmagickException $e) { + throw new RuntimeException('Save operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function show($format, array $options = array()) + { + header('Content-type: '.$this->getMimeType($format)); + echo $this->get($format, $options); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get($format, array $options = array()) + { + try { + $options['format'] = $format; + $this->prepareOutput($options); + } catch (\GmagickException $e) { + throw new RuntimeException('Get operation failed', $e->getCode(), $e); + } + + return $this->gmagick->getimagesblob(); + } + + /** + * @param array $options + * @param string $path + */ + private function prepareOutput(array $options, $path = null) + { + if (isset($options['format'])) { + $this->gmagick->setimageformat($options['format']); + } + + if (isset($options['animated']) && true === $options['animated']) { + $format = isset($options['format']) ? $options['format'] : 'gif'; + $delay = isset($options['animated.delay']) ? $options['animated.delay'] : null; + $loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0; + + $options['flatten'] = false; + + $this->layers->animate($format, $delay, $loops); + } else { + $this->layers->merge(); + } + $this->applyImageOptions($this->gmagick, $options, $path); + + // flatten only if image has multiple layers + if ((!isset($options['flatten']) || $options['flatten'] === true) && count($this->layers) > 1) { + $this->flatten(); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->get('png'); + } + + /** + * {@inheritdoc} + */ + public function draw() + { + return new Drawer($this->gmagick); + } + + /** + * {@inheritdoc} + */ + public function effects() + { + return new Effects($this->gmagick); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + try { + $i = $this->gmagick->getimageindex(); + $this->gmagick->setimageindex(0); //rewind + $width = $this->gmagick->getimagewidth(); + $height = $this->gmagick->getimageheight(); + $this->gmagick->setimageindex($i); + } catch (\GmagickException $e) { + throw new RuntimeException('Get size operation failed', $e->getCode(), $e); + } + + return new Box($width, $height); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function applyMask(ImageInterface $mask) + { + if (!$mask instanceof self) { + throw new InvalidArgumentException('Can only apply instances of Imagine\Gmagick\Image as masks'); + } + + $size = $this->getSize(); + $maskSize = $mask->getSize(); + + if ($size != $maskSize) { + throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size)); + } + + try { + $mask = $mask->copy(); + $this->gmagick->compositeimage($mask->gmagick, \Gmagick::COMPOSITE_DEFAULT, 0, 0); + } catch (\GmagickException $e) { + throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function mask() + { + $mask = $this->copy(); + + try { + $mask->gmagick->modulateimage(100, 0, 100); + } catch (\GmagickException $e) { + throw new RuntimeException('Mask operation failed', $e->getCode(), $e); + } + + return $mask; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function fill(FillInterface $fill) + { + try { + $draw = new \GmagickDraw(); + $size = $this->getSize(); + + $w = $size->getWidth(); + $h = $size->getHeight(); + + for ($x = 0; $x <= $w; $x++) { + for ($y = 0; $y <= $h; $y++) { + $pixel = $this->getColor($fill->getColor(new Point($x, $y))); + + $draw->setfillcolor($pixel); + $draw->point($x, $y); + + $pixel = null; + } + } + + $this->gmagick->drawimage($draw); + + $draw = null; + } catch (\GmagickException $e) { + throw new RuntimeException('Fill operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function histogram() + { + try { + $pixels = $this->gmagick->getimagehistogram(); + } catch (\GmagickException $e) { + throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e); + } + + $image = $this; + + return array_map(function (\GmagickPixel $pixel) use ($image) { + return $image->pixelToColor($pixel); + }, $pixels); + } + + /** + * {@inheritdoc} + */ + public function getColorAt(PointInterface $point) + { + if (!$point->in($this->getSize())) { + throw new InvalidArgumentException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight())); + } + + try { + $cropped = clone $this->gmagick; + $histogram = $cropped + ->cropImage(1, 1, $point->getX(), $point->getY()) + ->getImageHistogram(); + } catch (\GmagickException $e) { + throw new RuntimeException('Unable to get the pixel'); + } + + $pixel = array_shift($histogram); + + unset($histogram, $cropped); + + return $this->pixelToColor($pixel); + } + + /** + * Returns a color given a pixel, depending the Palette context + * + * Note : this method is public for PHP 5.3 compatibility + * + * @param \GmagickPixel $pixel + * + * @return ColorInterface + * + * @throws InvalidArgumentException In case a unknown color is requested + */ + public function pixelToColor(\GmagickPixel $pixel) + { + static $colorMapping = array( + ColorInterface::COLOR_RED => \Gmagick::COLOR_RED, + ColorInterface::COLOR_GREEN => \Gmagick::COLOR_GREEN, + ColorInterface::COLOR_BLUE => \Gmagick::COLOR_BLUE, + ColorInterface::COLOR_CYAN => \Gmagick::COLOR_CYAN, + ColorInterface::COLOR_MAGENTA => \Gmagick::COLOR_MAGENTA, + ColorInterface::COLOR_YELLOW => \Gmagick::COLOR_YELLOW, + ColorInterface::COLOR_KEYLINE => \Gmagick::COLOR_BLACK, + // There is no gray component in \Gmagick, let's use one of the RGB comp + ColorInterface::COLOR_GRAY => \Gmagick::COLOR_RED, + ); + + if ($this->palette->supportsAlpha()) { + try { + $alpha = (int) round($pixel->getcolorvalue(\Gmagick::COLOR_ALPHA) * 100); + } catch (\GmagickPixelException $e) { + $alpha = null; + } + } else { + $alpha = null; + } + + $palette = $this->palette(); + + return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) { + if (!isset($colorMapping[$color])) { + throw new InvalidArgumentException(sprintf('Color %s is not mapped in Gmagick', $color)); + } + $multiplier = 255; + if ($palette->name() === PaletteInterface::PALETTE_CMYK) { + $multiplier = 100; + } + + return $pixel->getcolorvalue($colorMapping[$color]) * $multiplier; + }, $this->palette->pixelDefinition()), $alpha); + } + + /** + * {@inheritdoc} + */ + public function layers() + { + return $this->layers; + } + + /** + * {@inheritdoc} + */ + public function interlace($scheme) + { + static $supportedInterlaceSchemes = array( + ImageInterface::INTERLACE_NONE => \Gmagick::INTERLACE_NO, + ImageInterface::INTERLACE_LINE => \Gmagick::INTERLACE_LINE, + ImageInterface::INTERLACE_PLANE => \Gmagick::INTERLACE_PLANE, + ImageInterface::INTERLACE_PARTITION => \Gmagick::INTERLACE_PARTITION, + ); + + if (!array_key_exists($scheme, $supportedInterlaceSchemes)) { + throw new InvalidArgumentException('Unsupported interlace type'); + } + + $this->gmagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function usePalette(PaletteInterface $palette) + { + if (!isset(static::$colorspaceMapping[$palette->name()])) { + throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver',$palette->name())); + } + + if ($this->palette->name() === $palette->name()) { + return $this; + } + + try { + try { + $hasICCProfile = (Boolean) $this->gmagick->getimageprofile('ICM'); + } catch (\GmagickException $e) { + $hasICCProfile = false; + } + + if (!$hasICCProfile) { + $this->profile($this->palette->profile()); + } + + $this->profile($palette->profile()); + + $this->setColorspace($palette); + $this->palette = $palette; + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function palette() + { + return $this->palette; + } + + /** + * {@inheritdoc} + */ + public function profile(ProfileInterface $profile) + { + try { + $this->gmagick->profileimage('ICM', $profile->data()); + } catch (\GmagickException $e) { + throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e); + } + + return $this; + } + + /** + * Internal + * + * Flatten the image. + */ + private function flatten() + { + /** + * @see http://pecl.php.net/bugs/bug.php?id=22435 + */ + if (method_exists($this->gmagick, 'flattenImages')) { + try { + $this->gmagick = $this->gmagick->flattenImages(); + } catch (\GmagickException $e) { + throw new RuntimeException('Flatten operation failed', $e->getCode(), $e); + } + } + } + + /** + * Gets specifically formatted color string from Color instance + * + * @param ColorInterface $color + * + * @return \GmagickPixel + * + * @throws InvalidArgumentException + */ + private function getColor(ColorInterface $color) + { + if (!$color->isOpaque()) { + throw new InvalidArgumentException('Gmagick doesn\'t support transparency'); + } + + return new \GmagickPixel((string) $color); + } + + /** + * Internal + * + * Get the mime type based on format. + * + * @param string $format + * + * @return string mime-type + * + * @throws InvalidArgumentException + */ + private function getMimeType($format) + { + static $mimeTypes = array( + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'wbmp' => 'image/vnd.wap.wbmp', + 'xbm' => 'image/xbm', + ); + + if (!isset($mimeTypes[$format])) { + throw new InvalidArgumentException(sprintf('Unsupported format given. Only %s are supported, %s given', implode(", ", array_keys($mimeTypes)), $format)); + } + + return $mimeTypes[$format]; + } + + /** + * Sets colorspace and image type, assigns the palette. + * + * @param PaletteInterface $palette + * + * @throws InvalidArgumentException + */ + private function setColorspace(PaletteInterface $palette) + { + if (!isset(static::$colorspaceMapping[$palette->name()])) { + throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver', $palette->name())); + } + + $this->gmagick->setimagecolorspace(static::$colorspaceMapping[$palette->name()]); + $this->palette = $palette; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Imagine.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Imagine.php new file mode 100644 index 0000000000000000000000000000000000000000..ce4d4ed6c22c070a47903c39f8e1e0ab65c63e52 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Imagine.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gmagick; + +use Imagine\Image\AbstractImagine; +use Imagine\Exception\NotSupportedException; +use Imagine\Image\BoxInterface; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Palette\Grayscale; +use Imagine\Image\Palette\CMYK; +use Imagine\Image\Palette\RGB; +use Imagine\Image\Palette\Color\CMYK as CMYKColor; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; + +/** + * Imagine implementation using the Gmagick PHP extension + */ +class Imagine extends AbstractImagine +{ + /** + * @throws RuntimeException + */ + public function __construct() + { + if (!class_exists('Gmagick')) { + throw new RuntimeException('Gmagick not installed'); + } + } + + /** + * {@inheritdoc} + */ + public function open($path) + { + $path = $this->checkPath($path); + + try { + $gmagick = new \Gmagick($path); + $image = new Image($gmagick, $this->createPalette($gmagick), $this->getMetadataReader()->readFile($path)); + } catch (\GmagickException $e) { + throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e); + } + + return $image; + } + + /** + * {@inheritdoc} + */ + public function create(BoxInterface $size, ColorInterface $color = null) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + $palette = null !== $color ? $color->getPalette() : new RGB(); + $color = null !== $color ? $color : $palette->color('fff'); + + try { + $gmagick = new \Gmagick(); + // Gmagick does not support creation of CMYK GmagickPixel + // see https://bugs.php.net/bug.php?id=64466 + if ($color instanceof CMYKColor) { + $switchPalette = $palette; + $palette = new RGB(); + $pixel = new \GmagickPixel($palette->color((string) $color)); + } else { + $switchPalette = null; + $pixel = new \GmagickPixel((string) $color); + } + + if ($color->getPalette()->supportsAlpha() && $color->getAlpha() < 100) { + throw new NotSupportedException('alpha transparency is not supported'); + } + + $gmagick->newimage($width, $height, $pixel->getcolor(false)); + $gmagick->setimagecolorspace(\Gmagick::COLORSPACE_TRANSPARENT); + $gmagick->setimagebackgroundcolor($pixel); + + $image = new Image($gmagick, $palette, new MetadataBag()); + + if ($switchPalette) { + $image->usePalette($switchPalette); + } + + return $image; + } catch (\GmagickException $e) { + throw new RuntimeException('Could not create empty image', $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function load($string) + { + return $this->doLoad($string, $this->getMetadataReader()->readData($string)); + } + + /** + * {@inheritdoc} + */ + public function read($resource) + { + if (!is_resource($resource)) { + throw new InvalidArgumentException('Variable does not contain a stream resource'); + } + + $content = stream_get_contents($resource); + + if (false === $content) { + throw new InvalidArgumentException('Couldn\'t read given resource'); + } + + return $this->doLoad($content, $this->getMetadataReader()->readStream($resource)); + } + + /** + * {@inheritdoc} + */ + public function font($file, $size, ColorInterface $color) + { + $gmagick = new \Gmagick(); + $gmagick->newimage(1, 1, 'transparent'); + + return new Font($gmagick, $file, $size, $color); + } + + private function createPalette(\Gmagick $gmagick) + { + switch ($gmagick->getimagecolorspace()) { + case \Gmagick::COLORSPACE_SRGB: + case \Gmagick::COLORSPACE_RGB: + return new RGB(); + case \Gmagick::COLORSPACE_CMYK: + return new CMYK(); + case \Gmagick::COLORSPACE_GRAY: + return new Grayscale(); + default: + throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported'); + } + } + + private function doLoad($content, MetadataBag $metadata) + { + try { + $gmagick = new \Gmagick(); + $gmagick->readimageblob($content); + } catch (\GmagickException $e) { + throw new RuntimeException( + 'Could not load image from string', $e->getCode(), $e + ); + } + + return new Image($gmagick, $this->createPalette($gmagick), $metadata); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Layers.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Layers.php new file mode 100644 index 0000000000000000000000000000000000000000..d708812a14d410132fc7874ed0ae22a276e577a9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Gmagick/Layers.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Gmagick; + +use Imagine\Image\AbstractLayers; +use Imagine\Exception\RuntimeException; +use Imagine\Exception\NotSupportedException; +use Imagine\Exception\OutOfBoundsException; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\PaletteInterface; + +class Layers extends AbstractLayers +{ + /** + * @var Image + */ + private $image; + + /** + * @var \Gmagick + */ + private $resource; + + /** + * @var integer + */ + private $offset = 0; + + /** + * @var array + */ + private $layers = array(); + + /** + * @var PaletteInterface + */ + private $palette; + + public function __construct(Image $image, PaletteInterface $palette, \Gmagick $resource) + { + $this->image = $image; + $this->resource = $resource; + $this->palette = $palette; + } + + /** + * {@inheritdoc} + */ + public function merge() + { + foreach ($this->layers as $offset => $image) { + try { + $this->resource->setimageindex($offset); + $this->resource->setimage($image->getGmagick()); + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e); + } + } + } + + /** + * {@inheritdoc} + */ + public function coalesce() + { + throw new NotSupportedException('Gmagick does not support coalescing'); + } + + /** + * {@inheritdoc} + */ + public function animate($format, $delay, $loops) + { + if ('gif' !== strtolower($format)) { + throw new NotSupportedException('Animated picture is currently only supported on gif'); + } + + if (!is_int($loops) || $loops < 0) { + throw new InvalidArgumentException('Loops must be a positive integer.'); + } + + if (null !== $delay && (!is_int($delay) || $delay < 0)) { + throw new InvalidArgumentException('Delay must be either null or a positive integer.'); + } + + try { + foreach ($this as $offset => $layer) { + $this->resource->setimageindex($offset); + $this->resource->setimageformat($format); + + if (null !== $delay) { + $this->resource->setimagedelay($delay / 10); + } + + $this->resource->setimageiterations($loops); + } + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to animate layers', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->extractAt($this->offset); + } + + /** + * Tries to extract layer at given offset + * + * @param integer $offset + * @return Image + * @throws RuntimeException + */ + private function extractAt($offset) + { + if (!isset($this->layers[$offset])) { + try { + $this->resource->setimageindex($offset); + $this->layers[$offset] = new Image($this->resource->getimage(), $this->palette, new MetadataBag()); + } catch (\GmagickException $e) { + throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e); + } + } + + return $this->layers[$offset]; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function next() + { + ++$this->offset; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->offset = 0; + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->offset < count($this); + } + + /** + * {@inheritdoc} + */ + public function count() + { + try { + return $this->resource->getnumberimages(); + } catch (\GmagickException $e) { + throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return is_int($offset) && $offset >= 0 && $offset < count($this); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->extractAt($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $image) + { + if (!$image instanceof Image) { + throw new InvalidArgumentException('Only a Gmagick Image can be used as layer'); + } + + if (null === $offset) { + $offset = count($this) - 1; + } else { + if (!is_int($offset)) { + throw new InvalidArgumentException('Invalid offset for layer, it must be an integer'); + } + + if (count($this) < $offset || 0 > $offset) { + throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset)); + } + + if (isset($this[$offset])) { + unset($this[$offset]); + $offset = $offset - 1; + } + } + + $frame = $image->getGmagick(); + + try { + if (count($this) > 0) { + $this->resource->setimageindex($offset); + $this->resource->nextimage(); + } + $this->resource->addimage($frame); + + /** + * ugly hack to bypass issue https://bugs.php.net/bug.php?id=64623 + */ + if (count($this) == 2) { + $this->resource->setimageindex($offset+1); + $this->resource->nextimage(); + $this->resource->addimage($frame); + unset($this[0]); + } + } catch (\GmagickException $e) { + throw new RuntimeException('Unable to set the layer', $e->getCode(), $e); + } + + $this->layers = array(); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + try { + $this->extractAt($offset); + } catch (RuntimeException $e) { + return; + } + + try { + $this->resource->setimageindex($offset); + $this->resource->removeimage(); + } catch (\GmagickException $e) { + throw new RuntimeException('Unable to remove layer', $e->getCode(), $e); + } + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractFont.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractFont.php new file mode 100644 index 0000000000000000000000000000000000000000..59e9a45ba23cd55eeed375d5020c3df86835a96e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractFont.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image; + +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * Abstract font base class + */ +abstract class AbstractFont implements FontInterface +{ + /** + * @var string + */ + protected $file; + + /** + * @var integer + */ + protected $size; + + /** + * @var ColorInterface + */ + protected $color; + + /** + * Constructs a font with specified $file, $size and $color + * + * The font size is to be specified in points (e.g. 10pt means 10) + * + * @param string $file + * @param integer $size + * @param ColorInterface $color + */ + public function __construct($file, $size, ColorInterface $color) + { + $this->file = $file; + $this->size = $size; + $this->color = $color; + } + + /** + * {@inheritdoc} + */ + final public function getFile() + { + return $this->file; + } + + /** + * {@inheritdoc} + */ + final public function getSize() + { + return $this->size; + } + + /** + * {@inheritdoc} + */ + final public function getColor() + { + return $this->color; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractImage.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractImage.php new file mode 100644 index 0000000000000000000000000000000000000000..5c85769fd11ceb6947f0cfa0d1f25d1a3d6406f0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractImage.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Image\Metadata\MetadataBag; + +abstract class AbstractImage implements ImageInterface +{ + /** + * @var MetadataBag + */ + protected $metadata; + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED) + { + if ($mode !== ImageInterface::THUMBNAIL_INSET && + $mode !== ImageInterface::THUMBNAIL_OUTBOUND) { + throw new InvalidArgumentException('Invalid mode specified'); + } + + $imageSize = $this->getSize(); + $ratios = array( + $size->getWidth() / $imageSize->getWidth(), + $size->getHeight() / $imageSize->getHeight() + ); + + $thumbnail = $this->copy(); + + $thumbnail->usePalette($this->palette()); + $thumbnail->strip(); + // if target width is larger than image width + // AND target height is longer than image height + if ($size->contains($imageSize)) { + return $thumbnail; + } + + if ($mode === ImageInterface::THUMBNAIL_INSET) { + $ratio = min($ratios); + } else { + $ratio = max($ratios); + } + + if ($mode === ImageInterface::THUMBNAIL_OUTBOUND) { + if (!$imageSize->contains($size)) { + $size = new Box( + min($imageSize->getWidth(), $size->getWidth()), + min($imageSize->getHeight(), $size->getHeight()) + ); + } else { + $imageSize = $thumbnail->getSize()->scale($ratio); + $thumbnail->resize($imageSize, $filter); + } + $thumbnail->crop(new Point( + max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2)), + max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2)) + ), $size); + } else { + if (!$imageSize->contains($size)) { + $imageSize = $imageSize->scale($ratio); + $thumbnail->resize($imageSize, $filter); + } else { + $imageSize = $thumbnail->getSize()->scale($ratio); + $thumbnail->resize($imageSize, $filter); + } + } + + return $thumbnail; + } + + /** + * Updates a given array of save options for backward compatibility with legacy names + * + * @param array $options + * + * @return array + */ + protected function updateSaveOptions(array $options) + { + // Preserve BC until version 1.0 + if (isset($options['quality']) && !isset($options['jpeg_quality'])) { + $options['jpeg_quality'] = $options['quality']; + } + + return $options; + } + + /** + * {@inheritdoc} + */ + public function metadata() + { + return $this->metadata; + } + + /** + * Assures the metadata instance will be cloned, too + */ + public function __clone() + { + if ($this->metadata !== null) { + $this->metadata = clone $this->metadata; + } + } + +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractImagine.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractImagine.php new file mode 100644 index 0000000000000000000000000000000000000000..fcb1a3d678e1b269459dd4666c1be3ba1cdbb15a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractImagine.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 Imagine\Image; + +use Imagine\Image\Metadata\DefaultMetadataReader; +use Imagine\Image\Metadata\ExifMetadataReader; +use Imagine\Image\Metadata\MetadataReaderInterface; +use Imagine\Exception\InvalidArgumentException; + +abstract class AbstractImagine implements ImagineInterface +{ + /** @var MetadataReaderInterface */ + private $metadataReader; + + /** + * @param MetadataReaderInterface $metadataReader + * + * @return ImagineInterface + */ + public function setMetadataReader(MetadataReaderInterface $metadataReader) + { + $this->metadataReader = $metadataReader; + + return $this; + } + + /** + * @return MetadataReaderInterface + */ + public function getMetadataReader() + { + if (null === $this->metadataReader) { + if (ExifMetadataReader::isSupported()) { + $this->metadataReader = new ExifMetadataReader(); + } else { + $this->metadataReader = new DefaultMetadataReader(); + } + } + + return $this->metadataReader; + } + + /** + * Checks a path that could be used with ImagineInterface::open and returns + * a proper string. + * + * @param string|object $path + * + * @return string + * + * @throws InvalidArgumentException In case the given path is invalid. + */ + protected function checkPath($path) + { + // provide compatibility with objects such as \SplFileInfo + if (is_object($path) && method_exists($path, '__toString')) { + $path = (string) $path; + } + + $handle = @fopen($path, 'r'); + + if (false === $handle) { + throw new InvalidArgumentException(sprintf('File %s does not exist.', $path)); + } + + fclose($handle); + + return $path; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractLayers.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractLayers.php new file mode 100644 index 0000000000000000000000000000000000000000..486a722283507f826d9e8f33dedb8ea7fb9b897e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/AbstractLayers.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 Imagine\Image; + +abstract class AbstractLayers implements LayersInterface +{ + /** + * {@inheritdoc} + */ + public function add(ImageInterface $image) + { + $this[] = $image; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function set($offset, ImageInterface $image) + { + $this[$offset] = $image; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function remove($offset) + { + unset($this[$offset]); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get($offset) + { + return $this[$offset]; + } + + /** + * {@inheritdoc} + */ + public function has($offset) + { + return isset($this[$offset]); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Box.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Box.php new file mode 100644 index 0000000000000000000000000000000000000000..37b85135a9e9e4e61aba310478d745fe698ce3fc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Box.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 Imagine\Image; + +use Imagine\Exception\InvalidArgumentException; + +/** + * A box implementation + */ +final class Box implements BoxInterface +{ + /** + * @var integer + */ + private $width; + + /** + * @var integer + */ + private $height; + + /** + * Constructs the Size with given width and height + * + * @param integer $width + * @param integer $height + * + * @throws InvalidArgumentException + */ + public function __construct($width, $height) + { + if ($height < 1 || $width < 1) { + throw new InvalidArgumentException(sprintf('Length of either side cannot be 0 or negative, current size is %sx%s', $width, $height)); + } + + $this->width = (int) $width; + $this->height = (int) $height; + } + + /** + * {@inheritdoc} + */ + public function getWidth() + { + return $this->width; + } + + /** + * {@inheritdoc} + */ + public function getHeight() + { + return $this->height; + } + + /** + * {@inheritdoc} + */ + public function scale($ratio) + { + return new Box(round($ratio * $this->width), round($ratio * $this->height)); + } + + /** + * {@inheritdoc} + */ + public function increase($size) + { + return new Box((int) $size + $this->width, (int) $size + $this->height); + } + + /** + * {@inheritdoc} + */ + public function contains(BoxInterface $box, PointInterface $start = null) + { + $start = $start ? $start : new Point(0, 0); + + return $start->in($this) && $this->width >= $box->getWidth() + $start->getX() && $this->height >= $box->getHeight() + $start->getY(); + } + + /** + * {@inheritdoc} + */ + public function square() + { + return $this->width * $this->height; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%dx%d px', $this->width, $this->height); + } + + /** + * {@inheritdoc} + */ + public function widen($width) + { + return $this->scale($width / $this->width); + } + + /** + * {@inheritdoc} + */ + public function heighten($height) + { + return $this->scale($height / $this->height); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/BoxInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/BoxInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..4a086c39ff9fe841e4aff6aeb8eb010d9362ce52 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/BoxInterface.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image; + +/** + * Interface for a box + */ +interface BoxInterface +{ + /** + * Gets current image height + * + * @return integer + */ + public function getHeight(); + + /** + * Gets current image width + * + * @return integer + */ + public function getWidth(); + + /** + * Creates new BoxInterface instance with ratios applied to both sides + * + * @param float $ratio + * + * @return BoxInterface + */ + public function scale($ratio); + + /** + * Creates new BoxInterface, adding given size to both sides + * + * @param integer $size + * + * @return BoxInterface + */ + public function increase($size); + + /** + * Checks whether current box can fit given box at a given start position, + * start position defaults to top left corner xy(0,0) + * + * @param BoxInterface $box + * @param PointInterface $start + * + * @return Boolean + */ + public function contains(BoxInterface $box, PointInterface $start = null); + + /** + * Gets current box square, useful for getting total number of pixels in a + * given box + * + * @return integer + */ + public function square(); + + /** + * Returns a string representation of the current box + * + * @return string + */ + public function __toString(); + + /** + * Resizes box to given width, constraining proportions and returns the new box + * + * @param integer $width + * + * @return BoxInterface + */ + public function widen($width); + + /** + * Resizes box to given height, constraining proportions and returns the new box + * + * @param integer $height + * + * @return BoxInterface + */ + public function heighten($height); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/FillInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/FillInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c8d69e5d4d17cb30e202c519c45838e306042ced --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/FillInterface.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 Imagine\Image\Fill; + +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\PointInterface; + +/** + * Interface for the fill + */ +interface FillInterface +{ + /** + * Gets color of the fill for the given position + * + * @param PointInterface $position + * + * @return ColorInterface + */ + public function getColor(PointInterface $position); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.php new file mode 100644 index 0000000000000000000000000000000000000000..4a4230671e53e73e18e83cd1326181e0db3e0834 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.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 Imagine\Image\Fill\Gradient; + +use Imagine\Image\PointInterface; + +/** + * Horizontal gradient fill + */ +final class Horizontal extends Linear +{ + /** + * {@inheritdoc} + */ + public function getDistance(PointInterface $position) + { + return $position->getX(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Linear.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Linear.php new file mode 100644 index 0000000000000000000000000000000000000000..9bf2cf6ccaa1cef55c48059ae6d3d88e4cf3ebe5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Linear.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Fill\Gradient; + +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Fill\FillInterface; +use Imagine\Image\PointInterface; + +/** + * Linear gradient fill + */ +abstract class Linear implements FillInterface +{ + /** + * @var integer + */ + private $length; + + /** + * @var ColorInterface + */ + private $start; + + /** + * @var ColorInterface + */ + private $end; + + /** + * Constructs a linear gradient with overall gradient length, and start and + * end shades, which default to 0 and 255 accordingly + * + * @param integer $length + * @param ColorInterface $start + * @param ColorInterface $end + */ + final public function __construct($length, ColorInterface $start, ColorInterface $end) + { + $this->length = $length; + $this->start = $start; + $this->end = $end; + } + + /** + * {@inheritdoc} + */ + final public function getColor(PointInterface $position) + { + $l = $this->getDistance($position); + + if ($l >= $this->length) { + return $this->end; + } + + if ($l < 0) { + return $this->start; + } + + return $this->start->getPalette()->blend($this->start, $this->end, $l / $this->length); + } + + /** + * @return ColorInterface + */ + final public function getStart() + { + return $this->start; + } + + /** + * @return ColorInterface + */ + final public function getEnd() + { + return $this->end; + } + + /** + * Get the distance of the position relative to the beginning of the gradient + * + * @param PointInterface $position + * + * @return integer + */ + abstract protected function getDistance(PointInterface $position); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Vertical.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Vertical.php new file mode 100644 index 0000000000000000000000000000000000000000..9a26b778927c9930f1b772d22e30c16ab2426b8f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Fill/Gradient/Vertical.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 Imagine\Image\Fill\Gradient; + +use Imagine\Image\PointInterface; + +/** + * Vertical gradient fill + */ +final class Vertical extends Linear +{ + /** + * {@inheritdoc} + */ + public function getDistance(PointInterface $position) + { + return $position->getY(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/FontInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/FontInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..59d4a265af0f188207c192e4a71bca3bf468d1cb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/FontInterface.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 Imagine\Image; + +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * The font interface + */ +interface FontInterface +{ + /** + * Gets the fontfile for current font + * + * @return string + */ + public function getFile(); + + /** + * Gets font's integer point size + * + * @return integer + */ + public function getSize(); + + /** + * Gets font's color + * + * @return ColorInterface + */ + public function getColor(); + + /** + * Gets BoxInterface of font size on the image based on string and angle + * + * @param string $string + * @param integer $angle + * + * @return BoxInterface + */ + public function box($string, $angle = 0); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Histogram/Bucket.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Histogram/Bucket.php new file mode 100644 index 0000000000000000000000000000000000000000..34dad5eedcc4caf8fa902b7663691c583217bda2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Histogram/Bucket.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 Imagine\Image\Histogram; + +/** + * Bucket histogram + */ +final class Bucket implements \Countable +{ + /** + * @var Range + */ + private $range; + + /** + * @var integer + */ + private $count; + + /** + * @param Range $range + * @param integer $count + */ + public function __construct(Range $range, $count = 0) + { + $this->range = $range; + $this->count = $count; + } + + /** + * @param integer $value + */ + public function add($value) + { + if ($this->range->contains($value)) { + $this->count++; + } + } + + /** + * @return integer The number of elements in the bucket. + */ + public function count() + { + return $this->count; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Histogram/Range.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Histogram/Range.php new file mode 100644 index 0000000000000000000000000000000000000000..9dc9f9928e0af28bc0e7f1f6ea296363de0b482d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Histogram/Range.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 Imagine\Image\Histogram; + +use Imagine\Exception\OutOfBoundsException; + +/** + * Range histogram + */ +final class Range +{ + /** + * @var integer + */ + private $start; + + /** + * @var integer + */ + private $end; + + /** + * @param integer $start + * @param integer $end + * + * @throws OutOfBoundsException + */ + public function __construct($start, $end) + { + if ($end <= $start) { + throw new OutOfBoundsException(sprintf('Range end cannot be bigger than start, %d %d given accordingly', $this->start, $this->end)); + } + + $this->start = $start; + $this->end = $end; + } + + /** + * @param integer $value + * + * @return Boolean + */ + public function contains($value) + { + return $value >= $this->start && $value < $this->end; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ImageInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ImageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6cf1dd87d623d439ff4793e5cad6571e36e76a23 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ImageInterface.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 Imagine\Image; + +use Imagine\Draw\DrawerInterface; +use Imagine\Effects\EffectsInterface; +use Imagine\Image\Palette\PaletteInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Exception\RuntimeException; +use Imagine\Exception\OutOfBoundsException; + +/** + * The image interface + */ +interface ImageInterface extends ManipulatorInterface +{ + const RESOLUTION_PIXELSPERINCH = 'ppi'; + const RESOLUTION_PIXELSPERCENTIMETER = 'ppc'; + + const INTERLACE_NONE = 'none'; + const INTERLACE_LINE = 'line'; + const INTERLACE_PLANE = 'plane'; + const INTERLACE_PARTITION = 'partition'; + + const FILTER_UNDEFINED = 'undefined'; + const FILTER_POINT = 'point'; + const FILTER_BOX = 'box'; + const FILTER_TRIANGLE = 'triangle'; + const FILTER_HERMITE = 'hermite'; + const FILTER_HANNING = 'hanning'; + const FILTER_HAMMING = 'hamming'; + const FILTER_BLACKMAN = 'blackman'; + const FILTER_GAUSSIAN = 'gaussian'; + const FILTER_QUADRATIC = 'quadratic'; + const FILTER_CUBIC = 'cubic'; + const FILTER_CATROM = 'catrom'; + const FILTER_MITCHELL = 'mitchell'; + const FILTER_LANCZOS = 'lanczos'; + const FILTER_BESSEL = 'bessel'; + const FILTER_SINC = 'sinc'; + + /** + * Returns the image content as a binary string + * + * @param string $format + * @param array $options + * + * @throws RuntimeException + * + * @return string binary + */ + public function get($format, array $options = array()); + + /** + * Returns the image content as a PNG binary string + * + * @throws RuntimeException + * + * @return string binary + */ + public function __toString(); + + /** + * Instantiates and returns a DrawerInterface instance for image drawing + * + * @return DrawerInterface + */ + public function draw(); + + /** + * @return EffectsInterface + */ + public function effects(); + + /** + * Returns current image size + * + * @return BoxInterface + */ + public function getSize(); + + /** + * Transforms creates a grayscale mask from current image, returns a new + * image, while keeping the existing image unmodified + * + * @return ImageInterface + */ + public function mask(); + + /** + * Returns array of image colors as Imagine\Image\Palette\Color\ColorInterface instances + * + * @return array + */ + public function histogram(); + + /** + * Returns color at specified positions of current image + * + * @param PointInterface $point + * + * @throws RuntimeException + * + * @return ColorInterface + */ + public function getColorAt(PointInterface $point); + + /** + * Returns the image layers when applicable. + * + * @throws RuntimeException In case the layer can not be returned + * @throws OutOfBoundsException In case the index is not a valid value + * + * @return LayersInterface + */ + public function layers(); + + /** + * Enables or disables interlacing + * + * @param string $scheme + * + * @throws InvalidArgumentException When an unsupported Interface type is supplied + * + * @return ImageInterface + */ + public function interlace($scheme); + + /** + * Return the current color palette + * + * @return PaletteInterface + */ + public function palette(); + + /** + * Set a palette for the image. Useful to change colorspace. + * + * @param PaletteInterface $palette + * + * @return ImageInterface + * + * @throws RuntimeException + */ + public function usePalette(PaletteInterface $palette); + + /** + * Applies a color profile on the Image + * + * @param ProfileInterface $profile + * + * @return ImageInterface + * + * @throws RuntimeException + */ + public function profile(ProfileInterface $profile); + + /** + * Returns the Image's meta data + * + * @return Metadata\MetadataInterface + */ + public function metadata(); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ImagineInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ImagineInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b5c942c73dc423a35d304dff65ef94c82b229d95 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ImagineInterface.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 Imagine\Image; + +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; + +/** + * The imagine interface + */ +interface ImagineInterface +{ + const VERSION = '0.7-dev'; + + /** + * Creates a new empty image with an optional background color + * + * @param BoxInterface $size + * @param ColorInterface $color + * + * @throws InvalidArgumentException + * @throws RuntimeException + * + * @return ImageInterface + */ + public function create(BoxInterface $size, ColorInterface $color = null); + + /** + * Opens an existing image from $path + * + * @param string $path + * + * @throws RuntimeException + * + * @return ImageInterface + */ + public function open($path); + + /** + * Loads an image from a binary $string + * + * @param string $string + * + * @throws RuntimeException + * + * @return ImageInterface + */ + public function load($string); + + /** + * Loads an image from a resource $resource + * + * @param resource $resource + * + * @throws RuntimeException + * + * @return ImageInterface + */ + public function read($resource); + + /** + * Constructs a font with specified $file, $size and $color + * + * The font size is to be specified in points (e.g. 10pt means 10) + * + * @param string $file + * @param integer $size + * @param ColorInterface $color + * + * @return FontInterface + */ + public function font($file, $size, ColorInterface $color); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/LayersInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/LayersInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..44df423697adfaa0cbb6c66554d50daf26813e40 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/LayersInterface.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 Imagine\Image; + +use Imagine\Exception\RuntimeException; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\OutOfBoundsException; + +/** + * The layers interface + */ +interface LayersInterface extends \Iterator, \Countable, \ArrayAccess +{ + /** + * Merge layers into the original objects + * + * @throws RuntimeException + */ + public function merge(); + + /** + * Animates layers + * + * @param string $format The output output format + * @param integer $delay The delay in milliseconds between two frames + * @param integer $loops The number of loops, 0 means infinite + * + * @return LayersInterface + * + * @throws InvalidArgumentException In case an invalid argument is provided + * @throws RuntimeException In case the driver fails to animate + */ + public function animate($format, $delay, $loops); + + /** + * Coalesce layers. Each layer in the sequence is the same size as the first and composited with the next layer in + * the sequence. + */ + public function coalesce(); + + /** + * Adds an image at the end of the layers stack + * + * @param ImageInterface $image + * + * @return LayersInterface + * + * @throws RuntimeException + */ + public function add(ImageInterface $image); + + /** + * Set an image at offset + * + * @param integer $offset + * @param ImageInterface $image + * + * @return LayersInterface + * + * @throws RuntimeException + * @throws InvalidArgumentException + * @throws OutOfBoundsException + */ + public function set($offset, ImageInterface $image); + + /** + * Removes the image at offset + * + * @param integer $offset + * + * @return LayersInterface + * + * @throws RuntimeException + * @throws InvalidArgumentException + */ + public function remove($offset); + + /** + * Returns the image at offset + * + * @param integer $offset + * + * @return ImageInterface + * + * @throws RuntimeException + * @throws InvalidArgumentException + */ + public function get($offset); + + /** + * Returns true if a layer at offset is preset + * + * @param integer $offset + * + * @return Boolean + */ + public function has($offset); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ManipulatorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ManipulatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..392b90828cc3de23d424fb700f9ff84369ec907d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ManipulatorInterface.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\OutOfBoundsException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Fill\FillInterface; + +/** + * The manipulator interface + */ +interface ManipulatorInterface +{ + const THUMBNAIL_INSET = 'inset'; + const THUMBNAIL_OUTBOUND = 'outbound'; + + /** + * Copies current source image into a new ImageInterface instance + * + * @throws RuntimeException + * + * @return static + */ + public function copy(); + + /** + * Crops a specified box out of the source image (modifies the source image) + * Returns cropped self + * + * @param PointInterface $start + * @param BoxInterface $size + * + * @throws OutOfBoundsException + * @throws RuntimeException + * + * @return static + */ + public function crop(PointInterface $start, BoxInterface $size); + + /** + * Resizes current image and returns self + * + * @param BoxInterface $size + * @param string $filter + * + * @throws RuntimeException + * + * @return static + */ + public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED); + + /** + * Rotates an image at the given angle. + * Optional $background can be used to specify the fill color of the empty + * area of rotated image. + * + * @param integer $angle + * @param ColorInterface $background + * + * @throws RuntimeException + * + * @return static + */ + public function rotate($angle, ColorInterface $background = null); + + /** + * Pastes an image into a parent image + * Throws exceptions if image exceeds parent image borders or if paste + * operation fails + * + * Returns source image + * + * @param ImageInterface $image + * @param PointInterface $start + * + * @throws InvalidArgumentException + * @throws OutOfBoundsException + * @throws RuntimeException + * + * @return static + */ + public function paste(ImageInterface $image, PointInterface $start); + + /** + * Saves the image at a specified path, the target file extension is used + * to determine file format, only jpg, jpeg, gif, png, wbmp and xbm are + * supported + * + * @param string $path + * @param array $options + * + * @throws RuntimeException + * + * @return static + */ + public function save($path = null, array $options = array()); + + /** + * Outputs the image content + * + * @param string $format + * @param array $options + * + * @throws RuntimeException + * + * @return static + */ + public function show($format, array $options = array()); + + /** + * Flips current image using horizontal axis + * + * @throws RuntimeException + * + * @return static + */ + public function flipHorizontally(); + + /** + * Flips current image using vertical axis + * + * @throws RuntimeException + * + * @return static + */ + public function flipVertically(); + + /** + * Remove all profiles and comments + * + * @throws RuntimeException + * + * @return static + */ + public function strip(); + + /** + * Generates a thumbnail from a current image + * Returns it as a new image, doesn't modify the current image + * + * @param BoxInterface $size + * @param string $mode + * @param string $filter The filter to use for resizing, one of ImageInterface::FILTER_* + * + * @throws RuntimeException + * + * @return static + */ + public function thumbnail(BoxInterface $size, $mode = self::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED); + + /** + * Applies a given mask to current image's alpha channel + * + * @param ImageInterface $mask + * + * @return static + */ + public function applyMask(ImageInterface $mask); + + /** + * Fills image with provided filling, by replacing each pixel's color in + * the current image with corresponding color from FillInterface, and + * returns modified image + * + * @param FillInterface $fill + * + * @return static + */ + public function fill(FillInterface $fill); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php new file mode 100644 index 0000000000000000000000000000000000000000..a709655bef190dcd7cbe5a09c9a3526230c4bb22 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Metadata; + +use Imagine\Exception\InvalidArgumentException; + +abstract class AbstractMetadataReader implements MetadataReaderInterface +{ + /** + * {@inheritdoc} + */ + public function readFile($file) + { + if (stream_is_local($file)) { + if (!is_file($file)) { + throw new InvalidArgumentException(sprintf('File %s does not exist.', $file)); + } + + return new MetadataBag(array_merge(array('filepath' => realpath($file), 'uri' => $file), $this->extractFromFile($file))); + } + + return new MetadataBag(array_merge(array('uri' => $file), $this->extractFromFile($file))); + } + + /** + * {@inheritdoc} + */ + public function readData($data) + { + return new MetadataBag($this->extractFromData($data)); + } + + /** + * {@inheritdoc} + */ + public function readStream($resource) + { + if (!is_resource($resource)) { + throw new InvalidArgumentException('Invalid resource provided.'); + } + + return new MetadataBag(array_merge($this->getStreamMetadata($resource), $this->extractFromStream($resource))); + } + + /** + * Gets the URI from a stream resource + * + * @param resource $resource + * + * @return string|null The URI f ava + */ + private function getStreamMetadata($resource) + { + $metadata = array(); + + if (false !== $data = @stream_get_meta_data($resource)) { + $metadata['uri'] = $data['uri']; + if (stream_is_local($resource)) { + $metadata['filepath'] = realpath($data['uri']); + } + } + + return $metadata; + } + + /** + * Extracts metadata from a file + * + * @param $file + * + * @return array An associative array of metadata + */ + abstract protected function extractFromFile($file); + + /** + * Extracts metadata from raw data + * + * @param $data + * + * @return array An associative array of metadata + */ + abstract protected function extractFromData($data); + + /** + * Extracts metadata from a stream + * + * @param $resource + * + * @return array An associative array of metadata + */ + abstract protected function extractFromStream($resource); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.php new file mode 100644 index 0000000000000000000000000000000000000000..349366e06b885badfa17e5b8b9da21c210a0cc53 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.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 Imagine\Image\Metadata; + +/** + * Default metadata reader + */ +class DefaultMetadataReader extends AbstractMetadataReader +{ + /** + * {@inheritdoc} + */ + protected function extractFromFile($file) + { + return array(); + } + + /** + * {@inheritdoc} + */ + protected function extractFromData($data) + { + return array(); + } + + /** + * {@inheritdoc} + */ + protected function extractFromStream($resource) + { + return array(); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php new file mode 100644 index 0000000000000000000000000000000000000000..dd8e7d09348e3872992fe1fc2aa7420e26d9662c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Metadata; + +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\NotSupportedException; + +/** + * Metadata driven by Exif information + */ +class ExifMetadataReader extends AbstractMetadataReader +{ + public function __construct() + { + if (!self::isSupported()) { + throw new NotSupportedException('PHP exif extension is required to use the ExifMetadataReader'); + } + } + + public static function isSupported() + { + return function_exists('exif_read_data'); + } + + /** + * {@inheritdoc} + */ + protected function extractFromFile($file) + { + if (false === $data = @file_get_contents($file)) { + throw new InvalidArgumentException(sprintf('File %s is not readable.', $file)); + } + + return $this->doReadData($data); + } + + /** + * {@inheritdoc} + */ + protected function extractFromData($data) + { + return $this->doReadData($data); + } + + /** + * {@inheritdoc} + */ + protected function extractFromStream($resource) + { + if (0 < ftell($resource)) { + $metadata = stream_get_meta_data($resource); + if ($metadata['seekable']) { + rewind($resource); + } + } + + return $this->doReadData(stream_get_contents($resource)); + } + + /** + * Extracts metadata from raw data, merges with existing metadata + * + * @param string $data + * + * @return MetadataBag + */ + private function doReadData($data) + { + if (substr($data, 0, 2) === 'II') { + $mime = 'image/tiff'; + } else { + $mime = 'image/jpeg'; + } + + return $this->extract('data://' . $mime . ';base64,' . base64_encode($data)); + } + + /** + * Performs the exif data extraction given a path or data-URI representation. + * + * @param string $path The path to the file or the data-URI representation. + * + * @return MetadataBag + */ + private function extract($path) + { + if (false === $exifData = @exif_read_data($path, null, true, false)) { + return array(); + } + + $metadata = array(); + $sources = array('EXIF' => 'exif', 'IFD0' => 'ifd0'); + + foreach ($sources as $name => $prefix) { + if (!isset($exifData[$name])) { + continue; + } + foreach ($exifData[$name] as $prop => $value) { + $metadata[$prefix.'.'.$prop] = $value; + } + } + + return $metadata; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/MetadataBag.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/MetadataBag.php new file mode 100644 index 0000000000000000000000000000000000000000..b7e917a387c6d05118fd113f4e0b7d68d9bf2d7c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/MetadataBag.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Metadata; + +/** + * An interface for Image Metadata + */ +class MetadataBag implements \ArrayAccess, \IteratorAggregate, \Countable +{ + /** @var array */ + private $data; + + public function __construct(array $data = array()) + { + $this->data = $data; + } + + /** + * Returns the metadata key, default value if it does not exist + * + * @param string $key + * @param mixed|null $default + * + * @return mixed + */ + public function get($key, $default = null) + { + return array_key_exists($key, $this->data) ? $this->data[$key] : $default; + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->data); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->data); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Returns metadata as an array + * + * @return array An associative array + */ + public function toArray() + { + return $this->data; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..62fcc88174a5a16449c186a6b9c76bbaf143a4a6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.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 Imagine\Image\Metadata; + +use Imagine\Exception\InvalidArgumentException; + +interface MetadataReaderInterface +{ + /** + * Reads metadata from a file. + * + * @param $file The path to the file where to read metadata. + * + * @throws InvalidArgumentException In case the file does not exist. + * + * @return MetadataBag + */ + public function readFile($file); + + /** + * Reads metadata from a binary string. + * + * @param $data The binary string to read. + * + * @return MetadataBag + */ + public function readData($data); + + /** + * Reads metadata from a stream. + * + * @param $resource The stream to read. + * + * @throws InvalidArgumentException In case the resource is not valid. + * + * @return MetadataBag + */ + public function readStream($resource); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/CMYK.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/CMYK.php new file mode 100644 index 0000000000000000000000000000000000000000..2beecf28a92dd3671c7288cbf6ec8dc79e928c16 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/CMYK.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette; + +use Imagine\Image\Palette\Color\CMYK as CMYKColor; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Exception\RuntimeException; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Image\Profile; +use Imagine\Image\ProfileInterface; + +class CMYK implements PaletteInterface +{ + private $parser; + private $profile; + private static $colors = array(); + + public function __construct() + { + $this->parser = new ColorParser(); + } + + /** + * {@inheritdoc} + */ + public function name() + { + return PaletteInterface::PALETTE_CMYK; + } + + /** + * {@inheritdoc} + */ + public function pixelDefinition() + { + return array( + ColorInterface::COLOR_CYAN, + ColorInterface::COLOR_MAGENTA, + ColorInterface::COLOR_YELLOW, + ColorInterface::COLOR_KEYLINE, + ); + } + + /** + * {@inheritdoc} + */ + public function supportsAlpha() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function color($color, $alpha = null) + { + if (null !== $alpha) { + throw new InvalidArgumentException('CMYK palette does not support alpha'); + } + + $color = $this->parser->parseToCMYK($color); + $index = sprintf('cmyk(%d, %d, %d, %d)', $color[0], $color[1], $color[2], $color[3]); + + if (false === array_key_exists($index, self::$colors)) { + self::$colors[$index] = new CMYKColor($this, $color); + } + + return self::$colors[$index]; + } + + /** + * {@inheritdoc} + */ + public function blend(ColorInterface $color1, ColorInterface $color2, $amount) + { + if (!$color1 instanceof CMYKColor || ! $color2 instanceof CMYKColor) { + throw new RuntimeException('CMYK palette can only blend CMYK colors'); + } + + return $this->color(array( + min(100, $color1->getCyan() + $color2->getCyan() * $amount), + min(100, $color1->getMagenta() + $color2->getMagenta() * $amount), + min(100, $color1->getYellow() + $color2->getYellow() * $amount), + min(100, $color1->getKeyline() + $color2->getKeyline() * $amount), + )); + } + + /** + * {@inheritdoc} + */ + public function useProfile(ProfileInterface $profile) + { + $this->profile = $profile; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function profile() + { + if (!$this->profile) { + $this->profile = Profile::fromPath(__DIR__ . '/../../resources/Adobe/CMYK/USWebUncoated.icc'); + } + + return $this->profile; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/CMYK.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/CMYK.php new file mode 100644 index 0000000000000000000000000000000000000000..316543355129fa521238431cc75ea11962c30471 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/CMYK.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette\Color; + +use Imagine\Image\Palette\CMYK as CMYKPalette; +use Imagine\Exception\RuntimeException; +use Imagine\Exception\InvalidArgumentException; + +final class CMYK implements ColorInterface +{ + /** + * @var integer + */ + private $c; + + /** + * @var integer + */ + private $m; + + /** + * @var integer + */ + private $y; + + /** + * @var integer + */ + private $k; + + /** + * + * @var CMYK + */ + private $palette; + + public function __construct(CMYKPalette $palette, array $color) + { + $this->palette = $palette; + $this->setColor($color); + } + + /** + * {@inheritdoc} + */ + public function getValue($component) + { + switch ($component) { + case ColorInterface::COLOR_CYAN: + return $this->getCyan(); + case ColorInterface::COLOR_MAGENTA: + return $this->getMagenta(); + case ColorInterface::COLOR_YELLOW: + return $this->getYellow(); + case ColorInterface::COLOR_KEYLINE: + return $this->getKeyline(); + default: + throw new InvalidArgumentException(sprintf('Color component %s is not valid', $component)); + } + } + + /** + * Returns Cyan value of the color + * + * @return integer + */ + public function getCyan() + { + return $this->c; + } + + /** + * Returns Magenta value of the color + * + * @return integer + */ + public function getMagenta() + { + return $this->m; + } + + /** + * Returns Yellow value of the color + * + * @return integer + */ + public function getYellow() + { + return $this->y; + } + + /** + * Returns Key value of the color + * + * @return integer + */ + public function getKeyline() + { + return $this->k; + } + + /** + * {@inheritdoc} + */ + public function getPalette() + { + return $this->palette; + } + + /** + * {@inheritdoc} + */ + public function getAlpha() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function dissolve($alpha) + { + throw new RuntimeException('CMYK does not support dissolution'); + } + + /** + * {@inheritdoc} + */ + public function lighten($shade) + { + return $this->palette->color( + array( + $this->c, + $this->m, + $this->y, + max(0, $this->k - $shade), + ) + ); + } + + /** + * {@inheritdoc} + */ + public function darken($shade) + { + return $this->palette->color( + array( + $this->c, + $this->m, + $this->y, + min(100, $this->k + $shade), + ) + ); + } + + /** + * {@inheritdoc} + */ + public function grayscale() + { + $color = array( + $this->c * (1 - $this->k / 100) + $this->k, + $this->m * (1 - $this->k / 100) + $this->k, + $this->y * (1 - $this->k / 100) + $this->k, + ); + + $gray = min(100, round(0.299 * $color[0] + 0.587 * $color[1] + 0.114 * $color[2])); + + return $this->palette->color(array($gray, $gray, $gray, $this->k)); + } + + /** + * {@inheritdoc} + */ + public function isOpaque() + { + return true; + } + + /** + * Returns hex representation of the color + * + * @return string + */ + public function __toString() + { + return sprintf('cmyk(%d%%, %d%%, %d%%, %d%%)', $this->c, $this->m, $this->y, $this->k); + } + + /** + * Internal, Performs checks for color validity (an of array(C, M, Y, K)) + * + * @param array $color + * + * @throws InvalidArgumentException + */ + private function setColor(array $color) + { + if (count($color) !== 4) { + throw new InvalidArgumentException('Color argument must look like array(C, M, Y, K), where C, M, Y, K are the integer values between 0 and 255 for cyan, magenta, yellow and black color indexes accordingly'); + } + + $colors = array_values($color); + array_walk($colors, function ($color) { + return max(0, min(100, $color)); + }); + + list($this->c, $this->m, $this->y, $this->k) = $colors; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8784c4ed3ea3eb6d35a02c4a17c63bddfa0c9fd0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette\Color; + +use Imagine\Image\Palette\PaletteInterface; + +interface ColorInterface +{ + const COLOR_RED = 'red'; + const COLOR_GREEN = 'green'; + const COLOR_BLUE = 'blue'; + + const COLOR_CYAN = 'cyan'; + const COLOR_MAGENTA = 'magenta'; + const COLOR_YELLOW = 'yellow'; + const COLOR_KEYLINE = 'keyline'; + + const COLOR_GRAY = 'gray'; + + /** + * Return the value of one of the component. + * + * @param string $component One of the ColorInterface::COLOR_* component + * + * @return Integer + */ + public function getValue($component); + + /** + * Returns percentage of transparency of the color + * + * @return integer + */ + public function getAlpha(); + + /** + * Returns the palette attached to the current color + * + * @return PaletteInterface + */ + public function getPalette(); + + /** + * Returns a copy of current color, incrementing the alpha channel by the + * given amount + * + * @param integer $alpha + * + * @return ColorInterface + */ + public function dissolve($alpha); + + /** + * Returns a copy of the current color, lightened by the specified number + * of shades + * + * @param integer $shade + * + * @return ColorInterface + */ + public function lighten($shade); + + /** + * Returns a copy of the current color, darkened by the specified number of + * shades + * + * @param integer $shade + * + * @return ColorInterface + */ + public function darken($shade); + + /** + * Returns a gray related to the current color + * + * @return ColorInterface + */ + public function grayscale(); + + /** + * Checks if the current color is opaque + * + * @return Boolean + */ + public function isOpaque(); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/Gray.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/Gray.php new file mode 100644 index 0000000000000000000000000000000000000000..c8096450fc786a9aa1ba950e62f08844f0e692ba --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/Gray.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette\Color; + +use Imagine\Image\Palette\Grayscale; +use Imagine\Exception\InvalidArgumentException; + +final class Gray implements ColorInterface +{ + /** + * @var integer + */ + private $gray; + + /** + * @var integer + */ + private $alpha; + + /** + * + * @var Grayscale + */ + private $palette; + + public function __construct(Grayscale $palette, array $color, $alpha) + { + $this->palette = $palette; + $this->setColor($color); + $this->setAlpha($alpha); + } + + /** + * {@inheritdoc} + */ + public function getValue($component) + { + switch ($component) { + case ColorInterface::COLOR_GRAY: + return $this->getGray(); + default: + throw new InvalidArgumentException(sprintf('Color component %s is not valid', $component)); + } + } + + /** + * Returns Gray value of the color + * + * @return integer + */ + public function getGray() + { + return $this->gray; + } + + /** + * {@inheritdoc} + */ + public function getPalette() + { + return $this->palette; + } + + /** + * {@inheritdoc} + */ + public function getAlpha() + { + return $this->alpha; + } + + /** + * {@inheritdoc} + */ + public function dissolve($alpha) + { + return $this->palette->color( + array($this->gray), $this->alpha + $alpha + ); + } + + /** + * {@inheritdoc} + */ + public function lighten($shade) + { + return $this->palette->color(array(min(255, $this->gray + $shade)), $this->alpha); + } + + /** + * {@inheritdoc} + */ + public function darken($shade) + { + return $this->palette->color(array(max(0, $this->gray - $shade)), $this->alpha); + } + + /** + * {@inheritdoc} + */ + public function grayscale() + { + return $this; + } + + /** + * {@inheritdoc} + */ + public function isOpaque() + { + return 100 === $this->alpha; + } + + /** + * Returns hex representation of the color + * + * @return string + */ + public function __toString() + { + return sprintf('#%02x%02x%02x', $this->gray, $this->gray, $this->gray); + } + + /** + * Performs checks for validity of given alpha value and sets it + * + * @param integer $alpha + * + * @throws InvalidArgumentException + */ + private function setAlpha($alpha) + { + if (!is_int($alpha) || $alpha < 0 || $alpha > 100) { + throw new InvalidArgumentException(sprintf('Alpha must be an integer between 0 and 100, %s given', $alpha)); + } + + $this->alpha = $alpha; + } + + /** + * Performs checks for color validity (array of array(gray)) + * + * @param array $color + * + * @throws InvalidArgumentException + */ + private function setColor(array $color) + { + if (count($color) !== 1) { + throw new InvalidArgumentException('Color argument must look like array(gray), where gray is the integer value between 0 and 255 for the grayscale'); + } + + list($this->gray) = array_values($color); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/RGB.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/RGB.php new file mode 100644 index 0000000000000000000000000000000000000000..a0b4f0d017473db271843023c0f201e7804fd985 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Color/RGB.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette\Color; + +use Imagine\Image\Palette\RGB as RGBPalette; +use Imagine\Exception\InvalidArgumentException; + +final class RGB implements ColorInterface +{ + /** + * @var integer + */ + private $r; + + /** + * @var integer + */ + private $g; + + /** + * @var integer + */ + private $b; + + /** + * @var integer + */ + private $alpha; + + /** + * + * @var RGBPalette + */ + private $palette; + + public function __construct(RGBPalette $palette, array $color, $alpha) + { + $this->palette = $palette; + $this->setColor($color); + $this->setAlpha($alpha); + } + + /** + * {@inheritdoc} + */ + public function getValue($component) + { + switch ($component) { + case ColorInterface::COLOR_RED: + return $this->getRed(); + case ColorInterface::COLOR_GREEN: + return $this->getGreen(); + case ColorInterface::COLOR_BLUE: + return $this->getBlue(); + default: + throw new InvalidArgumentException(sprintf('Color component %s is not valid', $component)); + } + } + + /** + * Returns RED value of the color + * + * @return integer + */ + public function getRed() + { + return $this->r; + } + + /** + * Returns GREEN value of the color + * + * @return integer + */ + public function getGreen() + { + return $this->g; + } + + /** + * Returns BLUE value of the color + * + * @return integer + */ + public function getBlue() + { + return $this->b; + } + + /** + * {@inheritdoc} + */ + public function getPalette() + { + return $this->palette; + } + + /** + * {@inheritdoc} + */ + public function getAlpha() + { + return $this->alpha; + } + + /** + * {@inheritdoc} + */ + public function dissolve($alpha) + { + return $this->palette->color(array($this->r, $this->g, $this->b), $this->alpha + $alpha); + } + + /** + * {@inheritdoc} + */ + public function lighten($shade) + { + return $this->palette->color( + array( + min(255, $this->r + $shade), + min(255, $this->g + $shade), + min(255, $this->b + $shade), + ), $this->alpha + ); + } + + /** + * {@inheritdoc} + */ + public function darken($shade) + { + return $this->palette->color( + array( + max(0, $this->r - $shade), + max(0, $this->g - $shade), + max(0, $this->b - $shade), + ), $this->alpha + ); + } + + /** + * {@inheritdoc} + */ + public function grayscale() + { + $gray = min(255, round(0.299 * $this->getRed() + 0.114 * $this->getBlue() + 0.587 * $this->getGreen())); + + return $this->palette->color(array($gray, $gray, $gray), $this->alpha); + } + + /** + * {@inheritdoc} + */ + public function isOpaque() + { + return 100 === $this->alpha; + } + + /** + * Returns hex representation of the color + * + * @return string + */ + public function __toString() + { + return sprintf('#%02x%02x%02x', $this->r, $this->g, $this->b); + } + + /** + * Internal + * + * Performs checks for validity of given alpha value and sets it + * + * @param integer $alpha + * + * @throws InvalidArgumentException + */ + private function setAlpha($alpha) + { + if (!is_int($alpha) || $alpha < 0 || $alpha > 100) { + throw new InvalidArgumentException(sprintf('Alpha must be an integer between 0 and 100, %s given', $alpha)); + } + + $this->alpha = $alpha; + } + + /** + * Internal + * + * Performs checks for color validity (array of array(R, G, B)) + * + * @param array $color + * + * @throws InvalidArgumentException + */ + private function setColor(array $color) + { + if (count($color) !== 3) { + throw new InvalidArgumentException('Color argument must look like array(R, G, B), where R, G, B are the integer values between 0 and 255 for red, green and blue color indexes accordingly'); + } + + list($this->r, $this->g, $this->b) = array_values($color); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/ColorParser.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/ColorParser.php new file mode 100644 index 0000000000000000000000000000000000000000..35cf4e952314fd2bc848ce4c47f81d4ecb4d1515 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/ColorParser.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette; + +use Imagine\Exception\InvalidArgumentException; + +class ColorParser +{ + /** + * Parses a color to a RGB tuple + * + * @param string|array|integer $color + * + * @return array + * + * @throws InvalidArgumentException + */ + public function parseToRGB($color) + { + $color = $this->parse($color); + + if (4 === count($color)) { + $color = array( + 255 * (1 - $color[0] / 100) * (1 - $color[3] / 100), + 255 * (1 - $color[1] / 100) * (1 - $color[3] / 100), + 255 * (1 - $color[2] / 100) * (1 - $color[3] / 100), + ); + } + + return $color; + } + + /** + * Parses a color to a CMYK tuple + * + * @param string|array|integer $color + * + * @return array + * + * @throws InvalidArgumentException + */ + public function parseToCMYK($color) + { + $color = $this->parse($color); + + if (3 === count($color)) { + $r = $color[0] / 255; + $g = $color[1] / 255; + $b = $color[2] / 255; + + $k = 1 - max($r, $g, $b); + + $color = array( + 1 === $k ? 0 : round((1 - $r - $k) / (1- $k) * 100), + 1 === $k ? 0 : round((1 - $g - $k) / (1- $k) * 100), + 1 === $k ? 0 : round((1 - $b - $k) / (1- $k) * 100), + round($k * 100) + ); + } + + return $color; + } + + /** + * Parses a color to a grayscale value + * + * @param string|array|integer $color + * + * @return array + * + * @throws InvalidArgumentException + */ + public function parseToGrayscale($color) + { + if (is_array($color) && 1 === count($color)) { + return array_values($color); + } + + $color = array_unique($this->parse($color)); + + if (1 !== count($color)) { + throw new InvalidArgumentException('The provided color has different values of red, green and blue components. Grayscale colors must have the same values for these.'); + } + + return $color; + } + + /** + * Parses a color + * + * @param string|array|integer $color + * + * @return array + * + * @throws InvalidArgumentException + */ + private function parse($color) + { + if (!is_string($color) && !is_array($color) && !is_int($color)) { + throw new InvalidArgumentException(sprintf('Color must be specified as a hexadecimal string, array or integer, %s given', gettype($color))); + } + + if (is_array($color)) { + if (3 === count($color) || 4 === count($color)) { + return array_values($color); + } + throw new InvalidArgumentException('Color argument if array, must look like array(R, G, B), or array(C, M, Y, K) where R, G, B are the integer values between 0 and 255 for red, green and blue or cyan, magenta, yellow and black color indexes accordingly'); + } + + if (is_string($color)) { + if (0 === strpos($color, 'cmyk(')) { + $substrColor = substr($color, 5, strlen($color) - 6); + + $components = array_map(function ($component) { + return round(trim($component, ' %')); + }, explode(',', $substrColor)); + + if (count($components) !== 4) { + throw new InvalidArgumentException(sprintf('Unable to parse color %s', $color)); + } + + return $components; + } else { + $color = ltrim($color, '#'); + + if (strlen($color) !== 3 && strlen($color) !== 6) { + throw new InvalidArgumentException(sprintf('Color must be a hex value in regular (6 characters) or short (3 characters) notation, "%s" given', $color)); + } + + if (strlen($color) === 3) { + $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2]; + } + + $color = array_map('hexdec', str_split($color, 2)); + } + } + + if (is_int($color)) { + $color = array(255 & ($color >> 16), 255 & ($color >> 8), 255 & $color); + } + + return $color; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Grayscale.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Grayscale.php new file mode 100644 index 0000000000000000000000000000000000000000..088b7909af8c06d63fc5c1634d852662d010d2c2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/Grayscale.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette; + +use Imagine\Image\Palette\Color\Gray as GrayColor; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\ProfileInterface; +use Imagine\Image\Profile; +use Imagine\Exception\RuntimeException; + +class Grayscale implements PaletteInterface +{ + /** + * @var ColorParser + */ + private $parser; + + /** + * @var ProfileInterface + */ + private $profile; + + /** + * @var array + */ + protected static $colors = array(); + + public function __construct() + { + $this->parser = new ColorParser(); + } + + /** + * {@inheritdoc} + */ + public function name() + { + return PaletteInterface::PALETTE_GRAYSCALE; + } + + /** + * {@inheritdoc} + */ + public function pixelDefinition() + { + return array(ColorInterface::COLOR_GRAY); + } + + /** + * {@inheritdoc} + */ + public function supportsAlpha() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function useProfile(ProfileInterface $profile) + { + $this->profile = $profile; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function profile() + { + if (!$this->profile) { + $this->profile = Profile::fromPath(__DIR__ . '/../../resources/colormanagement.org/ISOcoated_v2_grey1c_bas.ICC'); + } + + return $this->profile; + } + + /** + * {@inheritdoc} + */ + public function color($color, $alpha = null) + { + if (null === $alpha) { + $alpha = 0; + } + + $color = $this->parser->parseToGrayscale($color); + $index = sprintf('#%02x%02x%02x-%d', $color[0], $color[0], $color[0], $alpha); + + if (false === array_key_exists($index, static::$colors)) { + static::$colors[$index] = new GrayColor($this, $color, $alpha); + } + + return static::$colors[$index]; + } + + /** + * {@inheritdoc} + */ + public function blend(ColorInterface $color1, ColorInterface $color2, $amount) + { + if (!$color1 instanceof GrayColor || ! $color2 instanceof GrayColor) { + throw new RuntimeException('Grayscale palette can only blend Grayscale colors'); + } + + return $this->color( + array( + (int) min(255, min($color1->getGray(), $color2->getGray()) + round(abs($color2->getGray() - $color1->getGray()) * $amount)), + ), + (int) min(100, min($color1->getAlpha(), $color2->getAlpha()) + round(abs($color2->getAlpha() - $color1->getAlpha()) * $amount)) + ); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/PaletteInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/PaletteInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..855c244c41fa0792c5cbcb221db94e97a6e1af04 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/PaletteInterface.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image\Palette; + +use Imagine\Image\ProfileInterface; +use Imagine\Image\Palette\Color\ColorInterface; + +interface PaletteInterface +{ + const PALETTE_GRAYSCALE = 'gray'; + const PALETTE_RGB = 'rgb'; + const PALETTE_CMYK = 'cmyk'; + + /** + * Returns a color given some values + * + * @param string|array|integer $color A color + * @param integer|null $alpha Set alpha to null to disable it + * + * @return ColorInterface + * + * @throws InvalidArgumentException In case you pass an alpha value to a + * Palette that does not support alpha + */ + public function color($color, $alpha = null); + + /** + * Blend two colors given an amount + * + * @param ColorInterface $color1 + * @param ColorInterface $color2 + * @param float $amount The amount of color2 in color1 + * + * @return ColorInterface + */ + public function blend(ColorInterface $color1, ColorInterface $color2, $amount); + + /** + * Attachs an ICC profile to this Palette. + * + * (A default profile is provided by default) + * + * @param ProfileInterface $profile + * + * @return PaletteInterface + */ + public function useProfile(ProfileInterface $profile); + + /** + * Returns the ICC profile attached to this Palette. + * + * @return ProfileInterface + */ + public function profile(); + + /** + * Returns the name of this Palette, one of PaletteInterface::PALETTE_* + * constants + * + * @return String + */ + public function name(); + + /** + * Returns an array containing ColorInterface::COLOR_* constants that + * define the structure of colors for a pixel. + * + * @return array + */ + public function pixelDefinition(); + + /** + * Tells if alpha channel is supported in this palette + * + * @return Boolean + */ + public function supportsAlpha(); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/RGB.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/RGB.php new file mode 100644 index 0000000000000000000000000000000000000000..0462ca4daa67ed8f943a31c04230540faa2dceb9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Palette/RGB.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 Imagine\Image\Palette; + +use Imagine\Image\Palette\Color\RGB as RGBColor; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\ProfileInterface; +use Imagine\Image\Profile; +use Imagine\Exception\RuntimeException; + +class RGB implements PaletteInterface +{ + /** + * @var ColorParser + */ + private $parser; + + /** + * @var ProfileInterface + */ + private $profile; + + /** + * @var array + */ + protected static $colors = array(); + + public function __construct() + { + $this->parser = new ColorParser(); + } + + /** + * {@inheritdoc} + */ + public function name() + { + return PaletteInterface::PALETTE_RGB; + } + + /** + * {@inheritdoc} + */ + public function pixelDefinition() + { + return array( + ColorInterface::COLOR_RED, + ColorInterface::COLOR_GREEN, + ColorInterface::COLOR_BLUE, + ); + } + + /** + * {@inheritdoc} + */ + public function supportsAlpha() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function useProfile(ProfileInterface $profile) + { + $this->profile = $profile; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function profile() + { + if (!$this->profile) { + $this->profile = Profile::fromPath(__DIR__ . '/../../resources/color.org/sRGB_IEC61966-2-1_black_scaled.icc'); + } + + return $this->profile; + } + + /** + * {@inheritdoc} + */ + public function color($color, $alpha = null) + { + if (null === $alpha) { + $alpha = 100; + } + + $color = $this->parser->parseToRGB($color); + $index = sprintf('#%02x%02x%02x-%d', $color[0], $color[1], $color[2], $alpha); + + if (false === array_key_exists($index, static::$colors)) { + static::$colors[$index] = new RGBColor($this, $color, $alpha); + } + + return static::$colors[$index]; + } + + /** + * {@inheritdoc} + */ + public function blend(ColorInterface $color1, ColorInterface $color2, $amount) + { + if (!$color1 instanceof RGBColor || ! $color2 instanceof RGBColor) { + throw new RuntimeException('RGB palette can only blend RGB colors'); + } + + return $this->color( + array( + (int) min(255, min($color1->getRed(), $color2->getRed()) + round(abs($color2->getRed() - $color1->getRed()) * $amount)), + (int) min(255, min($color1->getGreen(), $color2->getGreen()) + round(abs($color2->getGreen() - $color1->getGreen()) * $amount)), + (int) min(255, min($color1->getBlue(), $color2->getBlue()) + round(abs($color2->getBlue() - $color1->getBlue()) * $amount)), + ), + (int) min(100, min($color1->getAlpha(), $color2->getAlpha()) + round(abs($color2->getAlpha() - $color1->getAlpha()) * $amount)) + ); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Point.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Point.php new file mode 100644 index 0000000000000000000000000000000000000000..abfc7c3a8cc10aa0274a89a7dfa2e61abf14ca2c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Point.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Image; + +use Imagine\Exception\InvalidArgumentException; + +/** + * The point class + */ +final class Point implements PointInterface +{ + /** + * @var integer + */ + private $x; + + /** + * @var integer + */ + private $y; + + /** + * Constructs a point of coordinates + * + * @param integer $x + * @param integer $y + * + * @throws InvalidArgumentException + */ + public function __construct($x, $y) + { + if ($x < 0 || $y < 0) { + throw new InvalidArgumentException(sprintf('A coordinate cannot be positioned outside of a bounding box (x: %s, y: %s given)', $x, $y)); + } + + $this->x = $x; + $this->y = $y; + } + + /** + * {@inheritdoc} + */ + public function getX() + { + return $this->x; + } + + /** + * {@inheritdoc} + */ + public function getY() + { + return $this->y; + } + + /** + * {@inheritdoc} + */ + public function in(BoxInterface $box) + { + return $this->x < $box->getWidth() && $this->y < $box->getHeight(); + } + + /** + * {@inheritdoc} + */ + public function move($amount) + { + return new Point($this->x + $amount, $this->y + $amount); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('(%d, %d)', $this->x, $this->y); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Point/Center.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Point/Center.php new file mode 100644 index 0000000000000000000000000000000000000000..0e60349e1a4c40f0e80870951d898cd82ec63a80 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Point/Center.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 Imagine\Image\Point; + +use Imagine\Image\BoxInterface; +use Imagine\Image\Point as OriginalPoint; +use Imagine\Image\PointInterface; + +/** + * Point center + */ +final class Center implements PointInterface +{ + /** + * @var BoxInterface + */ + private $box; + + /** + * Constructs coordinate with size instance, it needs to be relative to + * + * @param BoxInterface $box + */ + public function __construct(BoxInterface $box) + { + $this->box = $box; + } + + /** + * {@inheritdoc} + */ + public function getX() + { + return ceil($this->box->getWidth() / 2); + } + + /** + * {@inheritdoc} + */ + public function getY() + { + return ceil($this->box->getHeight() / 2); + } + + /** + * {@inheritdoc} + */ + public function in(BoxInterface $box) + { + return $this->getX() < $box->getWidth() && $this->getY() < $box->getHeight(); + } + + /** + * {@inheritdoc} + */ + public function move($amount) + { + return new OriginalPoint($this->getX() + $amount, $this->getY() + $amount); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('(%d, %d)', $this->getX(), $this->getY()); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/PointInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/PointInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f35fa80da23adcb8dee1cd8bca016ff7c8aab64c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/PointInterface.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 Imagine\Image; + +/** + * The point interface + */ +interface PointInterface +{ + /** + * Gets points x coordinate + * + * @return integer + */ + public function getX(); + + /** + * Gets points y coordinate + * + * @return integer + */ + public function getY(); + + /** + * Checks if current coordinate is inside a given bo + * + * @param BoxInterface $box + * + * @return Boolean + */ + public function in(BoxInterface $box); + + /** + * Returns another point, moved by a given amount from current coordinates + * + * @param integer $amount + * @return ImageInterface + */ + public function move($amount); + + /** + * Gets a string representation for the current point + * + * @return string + */ + public function __toString(); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Profile.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Profile.php new file mode 100644 index 0000000000000000000000000000000000000000..fda5415a6e2da43d40210934596c946370e54bb8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/Profile.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 Imagine\Image; + +use Imagine\Exception\InvalidArgumentException; + +class Profile implements ProfileInterface +{ + private $data; + private $name; + + public function __construct($name, $data) + { + $this->name = $name; + $this->data = $data; + } + + /** + * {@inheritdoc} + */ + public function name() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function data() + { + return $this->data; + } + + /** + * Creates a profile from a path to a file + * + * @param String $path + * + * @return Profile + * + * @throws InvalidArgumentException In case the provided path is not valid + */ + public static function fromPath($path) + { + if (!file_exists($path) || !is_file($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('Path %s is an invalid profile file or is not readable', $path)); + } + + return new static(basename($path), file_get_contents($path)); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ProfileInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ProfileInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b2caa9c9a7c8db5e51430db734b2f7ea1f003ace --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Image/ProfileInterface.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 Imagine\Image; + +interface ProfileInterface +{ + /** + * Returns the name of the profile + * + * @return String + */ + public function name(); + + /** + * Returns the profile data + * + * @return String + */ + public function data(); +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Drawer.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Drawer.php new file mode 100644 index 0000000000000000000000000000000000000000..2f86364912b68ecaaec386697c6876ed57364c5c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Drawer.php @@ -0,0 +1,404 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Imagick; + +use Imagine\Draw\DrawerInterface; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\AbstractFont; +use Imagine\Image\BoxInterface; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Point; +use Imagine\Image\PointInterface; + +/** + * Drawer implementation using the Imagick PHP extension + */ +final class Drawer implements DrawerInterface +{ + /** + * @var Imagick + */ + private $imagick; + + /** + * @param \Imagick $imagick + */ + public function __construct(\Imagick $imagick) + { + $this->imagick = $imagick; + } + + /** + * {@inheritdoc} + */ + public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1) + { + $x = $center->getX(); + $y = $center->getY(); + $width = $size->getWidth(); + $height = $size->getHeight(); + + try { + $pixel = $this->getColor($color); + $arc = new \ImagickDraw(); + + $arc->setStrokeColor($pixel); + $arc->setStrokeWidth(max(1, (int) $thickness)); + $arc->setFillColor('transparent'); + $arc->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end); + + $this->imagick->drawImage($arc); + + $pixel->clear(); + $pixel->destroy(); + + $arc->clear(); + $arc->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) + { + $x = $center->getX(); + $y = $center->getY(); + $width = $size->getWidth(); + $height = $size->getHeight(); + + try { + $pixel = $this->getColor($color); + $chord = new \ImagickDraw(); + + $chord->setStrokeColor($pixel); + $chord->setStrokeWidth(max(1, (int) $thickness)); + + if ($fill) { + $chord->setFillColor($pixel); + } else { + $this->line( + new Point(round($x + $width / 2 * cos(deg2rad($start))), round($y + $height / 2 * sin(deg2rad($start)))), + new Point(round($x + $width / 2 * cos(deg2rad($end))), round($y + $height / 2 * sin(deg2rad($end)))), + $color + ); + + $chord->setFillColor('transparent'); + } + + $chord->arc( + $x - $width / 2, + $y - $height / 2, + $x + $width / 2, + $y + $height / 2, + $start, + $end + ); + + $this->imagick->drawImage($chord); + + $pixel->clear(); + $pixel->destroy(); + + $chord->clear(); + $chord->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + try { + $pixel = $this->getColor($color); + $ellipse = new \ImagickDraw(); + + $ellipse->setStrokeColor($pixel); + $ellipse->setStrokeWidth(max(1, (int) $thickness)); + + if ($fill) { + $ellipse->setFillColor($pixel); + } else { + $ellipse->setFillColor('transparent'); + } + + $ellipse->ellipse( + $center->getX(), + $center->getY(), + $width / 2, + $height / 2, + 0, 360 + ); + + if (false === $this->imagick->drawImage($ellipse)) { + throw new RuntimeException('Ellipse operation failed'); + } + + $pixel->clear(); + $pixel->destroy(); + + $ellipse->clear(); + $ellipse->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1) + { + try { + $pixel = $this->getColor($color); + $line = new \ImagickDraw(); + + $line->setStrokeColor($pixel); + $line->setStrokeWidth(max(1, (int) $thickness)); + $line->setFillColor($pixel); + $line->line( + $start->getX(), + $start->getY(), + $end->getX(), + $end->getY() + ); + + $this->imagick->drawImage($line); + + $pixel->clear(); + $pixel->destroy(); + + $line->clear(); + $line->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw line operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + $x1 = round($center->getX() + $width / 2 * cos(deg2rad($start))); + $y1 = round($center->getY() + $height / 2 * sin(deg2rad($start))); + $x2 = round($center->getX() + $width / 2 * cos(deg2rad($end))); + $y2 = round($center->getY() + $height / 2 * sin(deg2rad($end))); + + if ($fill) { + $this->chord($center, $size, $start, $end, $color, true, $thickness); + $this->polygon( + array( + $center, + new Point($x1, $y1), + new Point($x2, $y2), + ), + $color, + true, + $thickness + ); + } else { + $this->arc($center, $size, $start, $end, $color, $thickness); + $this->line($center, new Point($x1, $y1), $color, $thickness); + $this->line($center, new Point($x2, $y2), $color, $thickness); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function dot(PointInterface $position, ColorInterface $color) + { + $x = $position->getX(); + $y = $position->getY(); + + try { + $pixel = $this->getColor($color); + $point = new \ImagickDraw(); + + $point->setFillColor($pixel); + $point->point($x, $y); + + $this->imagick->drawimage($point); + + $pixel->clear(); + $pixel->destroy(); + + $point->clear(); + $point->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw point operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1) + { + if (count($coordinates) < 3) { + throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates))); + } + + $points = array_map(function (PointInterface $p) { + return array('x' => $p->getX(), 'y' => $p->getY()); + }, $coordinates); + + try { + $pixel = $this->getColor($color); + $polygon = new \ImagickDraw(); + + $polygon->setStrokeColor($pixel); + $polygon->setStrokeWidth(max(1, (int) $thickness)); + + if ($fill) { + $polygon->setFillColor($pixel); + } else { + $polygon->setFillColor('transparent'); + } + + $polygon->polygon($points); + $this->imagick->drawImage($polygon); + + $pixel->clear(); + $pixel->destroy(); + + $polygon->clear(); + $polygon->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null) + { + try { + $pixel = $this->getColor($font->getColor()); + $text = new \ImagickDraw(); + + $text->setFont($font->getFile()); + /** + * @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 + * + * ensure font resolution is the same as GD's hard-coded 96 + */ + if (version_compare(phpversion("imagick"), "3.0.2", ">=")) { + $text->setResolution(96, 96); + $text->setFontSize($font->getSize()); + } else { + $text->setFontSize((int) ($font->getSize() * (96 / 72))); + } + $text->setFillColor($pixel); + $text->setTextAntialias(true); + + $info = $this->imagick->queryFontMetrics($text, $string); + $rad = deg2rad($angle); + $cos = cos($rad); + $sin = sin($rad); + + // round(0 * $cos - 0 * $sin) + $x1 = 0; + $x2 = round($info['characterWidth'] * $cos - $info['characterHeight'] * $sin); + // round(0 * $sin + 0 * $cos) + $y1 = 0; + $y2 = round($info['characterWidth'] * $sin + $info['characterHeight'] * $cos); + + $xdiff = 0 - min($x1, $x2); + $ydiff = 0 - min($y1, $y2); + + if ($width !== null) { + $string = $this->wrapText($string, $text, $angle, $width); + } + + $this->imagick->annotateImage( + $text, $position->getX() + $x1 + $xdiff, + $position->getY() + $y2 + $ydiff, $angle, $string + ); + + $pixel->clear(); + $pixel->destroy(); + + $text->clear(); + $text->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Draw text operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * Gets specifically formatted color string from ColorInterface instance + * + * @param ColorInterface $color + * + * @return string + */ + private function getColor(ColorInterface $color) + { + $pixel = new \ImagickPixel((string) $color); + $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); + + return $pixel; + } + + /** + * Internal + * + * Fits a string into box with given width + */ + private function wrapText($string, $text, $angle, $width) + { + $result = ''; + $words = explode(' ', $string); + foreach ($words as $word) { + $teststring = $result . ' ' . $word; + $testbox = $this->imagick->queryFontMetrics($text, $teststring, true); + if ($testbox['textWidth'] > $width) { + $result .= ($result == '' ? '' : "\n") . $word; + } else { + $result .= ($result == '' ? '' : ' ') . $word; + } + } + + return $result; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Effects.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Effects.php new file mode 100644 index 0000000000000000000000000000000000000000..debe32a609072638b18c32e2fb82d92b73b8dd6a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Effects.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Imagick; + +use Imagine\Effects\EffectsInterface; +use Imagine\Exception\RuntimeException; +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * Effects implementation using the Imagick PHP extension + */ +class Effects implements EffectsInterface +{ + private $imagick; + + public function __construct(\Imagick $imagick) + { + $this->imagick = $imagick; + } + + /** + * {@inheritdoc} + */ + public function gamma($correction) + { + try { + $this->imagick->gammaImage($correction, \Imagick::CHANNEL_ALL); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to apply gamma correction to the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function negative() + { + try { + $this->imagick->negateImage(false, \Imagick::CHANNEL_ALL); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to negate the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function grayscale() + { + try { + $this->imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to grayscale the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function colorize(ColorInterface $color) + { + try { + $this->imagick->colorizeImage((string) $color, 1); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to colorize the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function sharpen() + { + try { + $this->imagick->sharpenImage(2, 1); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to sharpen the image'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function blur($sigma = 1) + { + try { + $this->imagick->gaussianBlurImage(0, $sigma); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to blur the image', $e->getCode(), $e); + } + + return $this; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Font.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Font.php new file mode 100644 index 0000000000000000000000000000000000000000..3fc41dd2f2be7892071e741e519b56d4ad7d32c5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Font.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 Imagine\Imagick; + +use Imagine\Image\AbstractFont; +use Imagine\Image\Box; +use Imagine\Image\Palette\Color\ColorInterface; + +/** + * Font implementation using the Imagick PHP extension + */ +final class Font extends AbstractFont +{ + /** + * @var \Imagick + */ + private $imagick; + + /** + * @param \Imagick $imagick + * @param string $file + * @param integer $size + * @param ColorInterface $color + */ + public function __construct(\Imagick $imagick, $file, $size, ColorInterface $color) + { + $this->imagick = $imagick; + + parent::__construct($file, $size, $color); + } + + /** + * {@inheritdoc} + */ + public function box($string, $angle = 0) + { + $text = new \ImagickDraw(); + + $text->setFont($this->file); + + /** + * @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 + * + * ensure font resolution is the same as GD's hard-coded 96 + */ + if (version_compare(phpversion("imagick"), "3.0.2", ">=")) { + $text->setResolution(96, 96); + $text->setFontSize($this->size); + } else { + $text->setFontSize((int) ($this->size * (96 / 72))); + } + + $info = $this->imagick->queryFontMetrics($text, $string); + + $box = new Box($info['textWidth'], $info['textHeight']); + + return $box; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Image.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Image.php new file mode 100644 index 0000000000000000000000000000000000000000..c94f605d570e86e66f7e1333953df27408634a6a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Image.php @@ -0,0 +1,880 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Imagick; + +use Imagine\Exception\OutOfBoundsException; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\AbstractImage; +use Imagine\Image\Box; +use Imagine\Image\BoxInterface; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Image\Fill\FillInterface; +use Imagine\Image\Fill\Gradient\Horizontal; +use Imagine\Image\Fill\Gradient\Linear; +use Imagine\Image\Point; +use Imagine\Image\PointInterface; +use Imagine\Image\ProfileInterface; +use Imagine\Image\ImageInterface; +use Imagine\Image\Palette\PaletteInterface; + +/** + * Image implementation using the Imagick PHP extension + */ +final class Image extends AbstractImage +{ + /** + * @var \Imagick + */ + private $imagick; + /** + * @var Layers + */ + private $layers; + /** + * @var PaletteInterface + */ + private $palette; + + /** + * @var Boolean + */ + private static $supportsColorspaceConversion; + + private static $colorspaceMapping = array( + PaletteInterface::PALETTE_CMYK => \Imagick::COLORSPACE_CMYK, + PaletteInterface::PALETTE_RGB => \Imagick::COLORSPACE_RGB, + PaletteInterface::PALETTE_GRAYSCALE => \Imagick::COLORSPACE_GRAY, + ); + + /** + * Constructs a new Image instance + * + * @param \Imagick $imagick + * @param PaletteInterface $palette + * @param MetadataBag $metadata + */ + public function __construct(\Imagick $imagick, PaletteInterface $palette, MetadataBag $metadata) + { + $this->metadata = $metadata; + $this->detectColorspaceConversionSupport(); + $this->imagick = $imagick; + if (static::$supportsColorspaceConversion) { + $this->setColorspace($palette); + } + $this->palette = $palette; + $this->layers = new Layers($this, $this->palette, $this->imagick); + } + + /** + * Destroys allocated imagick resources + */ + public function __destruct() + { + if ($this->imagick instanceof \Imagick) { + $this->imagick->clear(); + $this->imagick->destroy(); + } + } + + /** + * Returns the underlying \Imagick instance + * + * @return \Imagick + */ + public function getImagick() + { + return $this->imagick; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function copy() + { + try { + if (version_compare(phpversion("imagick"), "3.1.0b1", ">=") || defined("HHVM_VERSION")) { + $clone = clone $this->imagick; + } else { + $clone = $this->imagick->clone(); + } + } catch (\ImagickException $e) { + throw new RuntimeException('Copy operation failed', $e->getCode(), $e); + } + + return new self($clone, $this->palette, clone $this->metadata); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function crop(PointInterface $start, BoxInterface $size) + { + if (!$start->in($this->getSize())) { + throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders'); + } + + try { + $this->imagick->cropImage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY()); + // Reset canvas for gif format + $this->imagick->setImagePage(0, 0, 0, 0); + } catch (\ImagickException $e) { + throw new RuntimeException('Crop operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function flipHorizontally() + { + try { + $this->imagick->flopImage(); + } catch (\ImagickException $e) { + throw new RuntimeException('Horizontal Flip operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function flipVertically() + { + try { + $this->imagick->flipImage(); + } catch (\ImagickException $e) { + throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function strip() + { + try { + try { + $this->profile($this->palette->profile()); + } catch (\Exception $e) { + // here we discard setting the profile as the previous incorporated profile + // is corrupted, let's now strip the image + } + $this->imagick->stripImage(); + } catch (\ImagickException $e) { + throw new RuntimeException('Strip operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function paste(ImageInterface $image, PointInterface $start) + { + if (!$image instanceof self) { + throw new InvalidArgumentException(sprintf('Imagick\Image can only paste() Imagick\Image instances, %s given', get_class($image))); + } + + if (!$this->getSize()->contains($image->getSize(), $start)) { + throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box'); + } + + try { + $this->imagick->compositeImage($image->imagick, \Imagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY()); + } catch (\ImagickException $e) { + throw new RuntimeException('Paste operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) + { + try { + $this->imagick->resizeImage($size->getWidth(), $size->getHeight(), $this->getFilter($filter), 1); + } catch (\ImagickException $e) { + throw new RuntimeException('Resize operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function rotate($angle, ColorInterface $background = null) + { + $color = $background ? $background : $this->palette->color('fff'); + + try { + $pixel = $this->getColor($color); + + $this->imagick->rotateimage($pixel, $angle); + + $pixel->clear(); + $pixel->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Rotate operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function save($path = null, array $options = array()) + { + $path = null === $path ? $this->imagick->getImageFilename() : $path; + if (null === $path) { + throw new RuntimeException('You can omit save path only if image has been open from a file'); + } + + try { + $this->prepareOutput($options, $path); + $this->imagick->writeImages($path, true); + } catch (\ImagickException $e) { + throw new RuntimeException('Save operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function show($format, array $options = array()) + { + header('Content-type: '.$this->getMimeType($format)); + echo $this->get($format, $options); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get($format, array $options = array()) + { + try { + $options['format'] = $format; + $this->prepareOutput($options); + } catch (\ImagickException $e) { + throw new RuntimeException('Get operation failed', $e->getCode(), $e); + } + + return $this->imagick->getImagesBlob(); + } + + /** + * {@inheritdoc} + */ + public function interlace($scheme) + { + static $supportedInterlaceSchemes = array( + ImageInterface::INTERLACE_NONE => \Imagick::INTERLACE_NO, + ImageInterface::INTERLACE_LINE => \Imagick::INTERLACE_LINE, + ImageInterface::INTERLACE_PLANE => \Imagick::INTERLACE_PLANE, + ImageInterface::INTERLACE_PARTITION => \Imagick::INTERLACE_PARTITION, + ); + + if (!array_key_exists($scheme, $supportedInterlaceSchemes)) { + throw new InvalidArgumentException('Unsupported interlace type'); + } + + $this->imagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]); + + return $this; + } + + /** + * @param array $options + * @param string $path + */ + private function prepareOutput(array $options, $path = null) + { + if (isset($options['format'])) { + $this->imagick->setImageFormat($options['format']); + } + + if (isset($options['animated']) && true === $options['animated']) { + $format = isset($options['format']) ? $options['format'] : 'gif'; + $delay = isset($options['animated.delay']) ? $options['animated.delay'] : null; + $loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0; + + $options['flatten'] = false; + + $this->layers->animate($format, $delay, $loops); + } else { + $this->layers->merge(); + } + $this->applyImageOptions($this->imagick, $options, $path); + + // flatten only if image has multiple layers + if ((!isset($options['flatten']) || $options['flatten'] === true) && count($this->layers) > 1) { + $this->flatten(); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->get('png'); + } + + /** + * {@inheritdoc} + */ + public function draw() + { + return new Drawer($this->imagick); + } + + /** + * {@inheritdoc} + */ + public function effects() + { + return new Effects($this->imagick); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + try { + $i = $this->imagick->getIteratorIndex(); + $this->imagick->rewind(); + $width = $this->imagick->getImageWidth(); + $height = $this->imagick->getImageHeight(); + $this->imagick->setIteratorIndex($i); + } catch (\ImagickException $e) { + throw new RuntimeException('Could not get size', $e->getCode(), $e); + } + + return new Box($width, $height); + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function applyMask(ImageInterface $mask) + { + if (!$mask instanceof self) { + throw new InvalidArgumentException('Can only apply instances of Imagine\Imagick\Image as masks'); + } + + $size = $this->getSize(); + $maskSize = $mask->getSize(); + + if ($size != $maskSize) { + throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size)); + } + + $mask = $mask->mask(); + $mask->imagick->negateImage(true); + + try { + // remove transparent areas of the original from the mask + $mask->imagick->compositeImage($this->imagick, \Imagick::COMPOSITE_DSTIN, 0, 0); + $this->imagick->compositeImage($mask->imagick, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + $mask->imagick->clear(); + $mask->imagick->destroy(); + } catch (\ImagickException $e) { + throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function mask() + { + $mask = $this->copy(); + + try { + $mask->imagick->modulateImage(100, 0, 100); + $mask->imagick->setImageMatte(false); + } catch (\ImagickException $e) { + throw new RuntimeException('Mask operation failed', $e->getCode(), $e); + } + + return $mask; + } + + /** + * {@inheritdoc} + * + * @return ImageInterface + */ + public function fill(FillInterface $fill) + { + try { + if ($this->isLinearOpaque($fill)) { + $this->applyFastLinear($fill); + } else { + $iterator = $this->imagick->getPixelIterator(); + + foreach ($iterator as $y => $pixels) { + foreach ($pixels as $x => $pixel) { + $color = $fill->getColor(new Point($x, $y)); + + $pixel->setColor((string) $color); + $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); + } + + $iterator->syncIterator(); + } + } + } catch (\ImagickException $e) { + throw new RuntimeException('Fill operation failed', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function histogram() + { + try { + $pixels = $this->imagick->getImageHistogram(); + } catch (\ImagickException $e) { + throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e); + } + + $image = $this; + + return array_map(function (\ImagickPixel $pixel) use ($image) { + return $image->pixelToColor($pixel); + },$pixels); + } + + /** + * {@inheritdoc} + */ + public function getColorAt(PointInterface $point) + { + if (!$point->in($this->getSize())) { + throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight())); + } + + try { + $pixel = $this->imagick->getImagePixelColor($point->getX(), $point->getY()); + } catch (\ImagickException $e) { + throw new RuntimeException('Error while getting image pixel color', $e->getCode(), $e); + } + + return $this->pixelToColor($pixel); + } + + /** + * Returns a color given a pixel, depending the Palette context + * + * Note : this method is public for PHP 5.3 compatibility + * + * @param \ImagickPixel $pixel + * + * @return ColorInterface + * + * @throws InvalidArgumentException In case a unknown color is requested + */ + public function pixelToColor(\ImagickPixel $pixel) + { + static $colorMapping = array( + ColorInterface::COLOR_RED => \Imagick::COLOR_RED, + ColorInterface::COLOR_GREEN => \Imagick::COLOR_GREEN, + ColorInterface::COLOR_BLUE => \Imagick::COLOR_BLUE, + ColorInterface::COLOR_CYAN => \Imagick::COLOR_CYAN, + ColorInterface::COLOR_MAGENTA => \Imagick::COLOR_MAGENTA, + ColorInterface::COLOR_YELLOW => \Imagick::COLOR_YELLOW, + ColorInterface::COLOR_KEYLINE => \Imagick::COLOR_BLACK, + // There is no gray component in \Imagick, let's use one of the RGB comp + ColorInterface::COLOR_GRAY => \Imagick::COLOR_RED, + ); + + $alpha = $this->palette->supportsAlpha() ? (int) round($pixel->getColorValue(\Imagick::COLOR_ALPHA) * 100) : null; + $palette = $this->palette(); + + return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) { + if (!isset($colorMapping[$color])) { + throw new InvalidArgumentException(sprintf('Color %s is not mapped in Imagick', $color)); + } + $multiplier = 255; + if ($palette->name() === PaletteInterface::PALETTE_CMYK) { + $multiplier = 100; + } + + return $pixel->getColorValue($colorMapping[$color]) * $multiplier; + }, $this->palette->pixelDefinition()), $alpha); + } + + /** + * {@inheritdoc} + */ + public function layers() + { + return $this->layers; + } + + /** + * {@inheritdoc} + */ + public function usePalette(PaletteInterface $palette) + { + if (!isset(static::$colorspaceMapping[$palette->name()])) { + throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name())); + } + + if ($this->palette->name() === $palette->name()) { + return $this; + } + + if (!static::$supportsColorspaceConversion) { + throw new RuntimeException('Your version of Imagick does not support colorspace conversions.'); + } + + try { + try { + $hasICCProfile = (Boolean) $this->imagick->getImageProfile('icc'); + } catch (\ImagickException $e) { + $hasICCProfile = false; + } + + if (!$hasICCProfile) { + $this->profile($this->palette->profile()); + } + + $this->profile($palette->profile()); + $this->setColorspace($palette); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function palette() + { + return $this->palette; + } + + /** + * {@inheritdoc} + */ + public function profile(ProfileInterface $profile) + { + try { + $this->imagick->profileImage('icc', $profile->data()); + } catch (\ImagickException $e) { + throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e); + } + + return $this; + } + + /** + * Internal + * + * Flatten the image. + */ + private function flatten() + { + /** + * @see https://github.com/mkoppanen/imagick/issues/45 + */ + try { + if (method_exists($this->imagick, 'mergeImageLayers') && defined('Imagick::LAYERMETHOD_UNDEFINED')) { + $this->imagick = $this->imagick->mergeImageLayers(\Imagick::LAYERMETHOD_UNDEFINED); + } elseif (method_exists($this->imagick, 'flattenImages')) { + $this->imagick = $this->imagick->flattenImages(); + } + } catch (\ImagickException $e) { + throw new RuntimeException('Flatten operation failed', $e->getCode(), $e); + } + } + + /** + * Internal + * + * Applies options before save or output + * + * @param \Imagick $image + * @param array $options + * @param string $path + * + * @throws InvalidArgumentException + * @throws RuntimeException + */ + private function applyImageOptions(\Imagick $image, array $options, $path) + { + if (isset($options['format'])) { + $format = $options['format']; + } elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) { + $format = $extension; + } else { + $format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION); + } + + $format = strtolower($format); + + $options = $this->updateSaveOptions($options); + + if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) { + $image->setImageCompressionQuality($options['jpeg_quality']); + } + + if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') { + // first digit: compression level (default: 7) + if (isset($options['png_compression_level'])) { + if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) { + throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9'); + } + $compression = $options['png_compression_level'] * 10; + } else { + $compression = 70; + } + + // second digit: compression filter (default: 5) + if (isset($options['png_compression_filter'])) { + if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) { + throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9'); + } + $compression += $options['png_compression_filter']; + } else { + $compression += 5; + } + + $image->setImageCompressionQuality($compression); + } + + if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) { + if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) { + $image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERCENTIMETER); + } elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) { + $image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH); + } else { + throw new RuntimeException('Unsupported image unit format'); + } + + $filter = ImageInterface::FILTER_UNDEFINED; + if (!empty($options['resampling-filter'])) { + $filter = $options['resampling-filter']; + } + + $image->setImageResolution($options['resolution-x'], $options['resolution-y']); + $image->resampleImage($options['resolution-x'], $options['resolution-y'], $this->getFilter($filter), 0); + } + } + + /** + * Gets specifically formatted color string from Color instance + * + * @param ColorInterface $color + * + * @return \ImagickPixel + */ + private function getColor(ColorInterface $color) + { + $pixel = new \ImagickPixel((string) $color); + $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); + + return $pixel; + } + + /** + * Checks whether given $fill is linear and opaque + * + * @param FillInterface $fill + * + * @return Boolean + */ + private function isLinearOpaque(FillInterface $fill) + { + return $fill instanceof Linear && $fill->getStart()->isOpaque() && $fill->getEnd()->isOpaque(); + } + + /** + * Performs optimized gradient fill for non-opaque linear gradients + * + * @param Linear $fill + */ + private function applyFastLinear(Linear $fill) + { + $gradient = new \Imagick(); + $size = $this->getSize(); + $color = sprintf('gradient:%s-%s', (string) $fill->getStart(), (string) $fill->getEnd()); + + if ($fill instanceof Horizontal) { + $gradient->newPseudoImage($size->getHeight(), $size->getWidth(), $color); + $gradient->rotateImage(new \ImagickPixel(), 90); + } else { + $gradient->newPseudoImage($size->getWidth(), $size->getHeight(), $color); + } + + $this->imagick->compositeImage($gradient, \Imagick::COMPOSITE_OVER, 0, 0); + $gradient->clear(); + $gradient->destroy(); + } + + /** + * Internal + * + * Get the mime type based on format. + * + * @param string $format + * + * @return string mime-type + * + * @throws RuntimeException + */ + private function getMimeType($format) + { + static $mimeTypes = array( + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'wbmp' => 'image/vnd.wap.wbmp', + 'xbm' => 'image/xbm', + ); + + if (!isset($mimeTypes[$format])) { + throw new RuntimeException(sprintf('Unsupported format given. Only %s are supported, %s given', implode(", ", array_keys($mimeTypes)), $format)); + } + + return $mimeTypes[$format]; + } + + /** + * Sets colorspace and image type, assigns the palette. + * + * @param PaletteInterface $palette + * + * @throws InvalidArgumentException + */ + private function setColorspace(PaletteInterface $palette) + { + static $typeMapping = array( + // We use Matte variants to preserve alpha + PaletteInterface::PALETTE_CMYK => \Imagick::IMGTYPE_TRUECOLORMATTE, + PaletteInterface::PALETTE_RGB => \Imagick::IMGTYPE_TRUECOLORMATTE, + PaletteInterface::PALETTE_GRAYSCALE => \Imagick::IMGTYPE_GRAYSCALEMATTE, + ); + + if (!isset(static::$colorspaceMapping[$palette->name()])) { + throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name())); + } + + $this->imagick->setType($typeMapping[$palette->name()]); + $this->imagick->setColorspace(static::$colorspaceMapping[$palette->name()]); + $this->palette = $palette; + } + + /** + * Older imagemagick versions does not support colorspace conversions. + * Let's detect if it is supported. + * + * @return Boolean + */ + private function detectColorspaceConversionSupport() + { + if (null !== static::$supportsColorspaceConversion) { + return static::$supportsColorspaceConversion; + } + + return static::$supportsColorspaceConversion = method_exists('Imagick', 'setColorspace'); + } + + /** + * Returns the filter if it's supported. + * + * @param string $filter + * + * @return string + * + * @throws InvalidArgumentException If the filter is unsupported. + */ + private function getFilter($filter = ImageInterface::FILTER_UNDEFINED) + { + static $supportedFilters = array( + ImageInterface::FILTER_UNDEFINED => \Imagick::FILTER_UNDEFINED, + ImageInterface::FILTER_BESSEL => \Imagick::FILTER_BESSEL, + ImageInterface::FILTER_BLACKMAN => \Imagick::FILTER_BLACKMAN, + ImageInterface::FILTER_BOX => \Imagick::FILTER_BOX, + ImageInterface::FILTER_CATROM => \Imagick::FILTER_CATROM, + ImageInterface::FILTER_CUBIC => \Imagick::FILTER_CUBIC, + ImageInterface::FILTER_GAUSSIAN => \Imagick::FILTER_GAUSSIAN, + ImageInterface::FILTER_HANNING => \Imagick::FILTER_HANNING, + ImageInterface::FILTER_HAMMING => \Imagick::FILTER_HAMMING, + ImageInterface::FILTER_HERMITE => \Imagick::FILTER_HERMITE, + ImageInterface::FILTER_LANCZOS => \Imagick::FILTER_LANCZOS, + ImageInterface::FILTER_MITCHELL => \Imagick::FILTER_MITCHELL, + ImageInterface::FILTER_POINT => \Imagick::FILTER_POINT, + ImageInterface::FILTER_QUADRATIC => \Imagick::FILTER_QUADRATIC, + ImageInterface::FILTER_SINC => \Imagick::FILTER_SINC, + ImageInterface::FILTER_TRIANGLE => \Imagick::FILTER_TRIANGLE + ); + + if (!array_key_exists($filter, $supportedFilters)) { + throw new InvalidArgumentException(sprintf( + 'The resampling filter "%s" is not supported by Imagick driver.', + $filter + )); + } + + return $supportedFilters[$filter]; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Imagine.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Imagine.php new file mode 100644 index 0000000000000000000000000000000000000000..aa6e926463fc9dd55960e6e13b2b81ca7121e58a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Imagine.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 Imagine\Imagick; + +use Imagine\Exception\NotSupportedException; +use Imagine\Image\AbstractImagine; +use Imagine\Image\BoxInterface; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Image\Palette\Color\ColorInterface; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Exception\RuntimeException; +use Imagine\Image\Palette\CMYK; +use Imagine\Image\Palette\RGB; +use Imagine\Image\Palette\Grayscale; + +/** + * Imagine implementation using the Imagick PHP extension + */ +final class Imagine extends AbstractImagine +{ + /** + * @throws RuntimeException + */ + public function __construct() + { + if (!class_exists('Imagick')) { + throw new RuntimeException('Imagick not installed'); + } + + if (version_compare('6.2.9', $this->getVersion(new \Imagick())) > 0) { + throw new RuntimeException('ImageMagick version 6.2.9 or higher is required'); + } + } + + /** + * {@inheritdoc} + */ + public function open($path) + { + $path = $this->checkPath($path); + + try { + $imagick = new \Imagick($path); + $image = new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readFile($path)); + } catch (\Exception $e) { + throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e); + } + + return $image; + } + + /** + * {@inheritdoc} + */ + public function create(BoxInterface $size, ColorInterface $color = null) + { + $width = $size->getWidth(); + $height = $size->getHeight(); + + $palette = null !== $color ? $color->getPalette() : new RGB(); + $color = null !== $color ? $color : $palette->color('fff'); + + try { + $pixel = new \ImagickPixel((string) $color); + $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); + + $imagick = new \Imagick(); + $imagick->newImage($width, $height, $pixel); + $imagick->setImageMatte(true); + $imagick->setImageBackgroundColor($pixel); + + if (version_compare('6.3.1', $this->getVersion($imagick)) < 0) { + $imagick->setImageOpacity($pixel->getColorValue(\Imagick::COLOR_ALPHA)); + } + + $pixel->clear(); + $pixel->destroy(); + + return new Image($imagick, $palette, new MetadataBag()); + } catch (\ImagickException $e) { + throw new RuntimeException('Could not create empty image', $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function load($string) + { + try { + $imagick = new \Imagick(); + + $imagick->readImageBlob($string); + $imagick->setImageMatte(true); + + return new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readData($string)); + } catch (\ImagickException $e) { + throw new RuntimeException('Could not load image from string', $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function read($resource) + { + if (!is_resource($resource)) { + throw new InvalidArgumentException('Variable does not contain a stream resource'); + } + + try { + $imagick = new \Imagick(); + $imagick->readImageFile($resource); + } catch (\ImagickException $e) { + throw new RuntimeException('Could not read image from resource', $e->getCode(), $e); + } + + return new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readStream($resource)); + } + + /** + * {@inheritdoc} + */ + public function font($file, $size, ColorInterface $color) + { + return new Font(new \Imagick(), $file, $size, $color); + } + + /** + * Returns the palette corresponding to an \Imagick resource colorspace + * + * @param \Imagick $imagick + * + * @return CMYK|Grayscale|RGB + * + * @throws NotSupportedException + */ + private function createPalette(\Imagick $imagick) + { + switch ($imagick->getImageColorspace()) { + case \Imagick::COLORSPACE_RGB: + case \Imagick::COLORSPACE_SRGB: + return new RGB(); + case \Imagick::COLORSPACE_CMYK: + return new CMYK(); + case \Imagick::COLORSPACE_GRAY: + return new Grayscale(); + default: + throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported'); + } + } + + /** + * Returns ImageMagick version + * + * @param \Imagick $imagick + * + * @return string + */ + private function getVersion(\Imagick $imagick) + { + $v = $imagick->getVersion(); + list($version) = sscanf($v['versionString'], 'ImageMagick %s %04d-%02d-%02d %s %s'); + + return $version; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Layers.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Layers.php new file mode 100644 index 0000000000000000000000000000000000000000..7809447ad69fe3d467b6b3180faf10094c922abb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Imagine/Imagick/Layers.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Imagine\Imagick; + +use Imagine\Image\AbstractLayers; +use Imagine\Image\Metadata\MetadataBag; +use Imagine\Exception\RuntimeException; +use Imagine\Exception\OutOfBoundsException; +use Imagine\Exception\InvalidArgumentException; +use Imagine\Image\Palette\PaletteInterface; + +class Layers extends AbstractLayers +{ + /** + * @var Image + */ + private $image; + /** + * @var \Imagick + */ + private $resource; + /** + * @var integer + */ + private $offset = 0; + /** + * @var array + */ + private $layers = array(); + + private $palette; + + public function __construct(Image $image, PaletteInterface $palette, \Imagick $resource) + { + $this->image = $image; + $this->resource = $resource; + $this->palette = $palette; + } + + /** + * {@inheritdoc} + */ + public function merge() + { + foreach ($this->layers as $offset => $image) { + try { + $this->resource->setIteratorIndex($offset); + $this->resource->setImage($image->getImagick()); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e); + } + } + } + + /** + * {@inheritdoc} + */ + public function animate($format, $delay, $loops) + { + if ('gif' !== strtolower($format)) { + throw new InvalidArgumentException('Animated picture is currently only supported on gif'); + } + + if (!is_int($loops) || $loops < 0) { + throw new InvalidArgumentException('Loops must be a positive integer.'); + } + + if (null !== $delay && (!is_int($delay) || $delay < 0)) { + throw new InvalidArgumentException('Delay must be either null or a positive integer.'); + } + + try { + foreach ($this as $offset => $layer) { + $this->resource->setIteratorIndex($offset); + $this->resource->setFormat($format); + + if (null !== $delay) { + $layer->getImagick()->setImageDelay($delay / 10); + $layer->getImagick()->setImageTicksPerSecond(100); + } + $layer->getImagick()->setImageIterations($loops); + + $this->resource->setImage($layer->getImagick()); + } + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to animate layers', $e->getCode(), $e); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function coalesce() + { + try { + $coalescedResource = $this->resource->coalesceImages(); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to coalesce layers', $e->getCode(), $e); + } + + $count = $coalescedResource->getNumberImages(); + for ($offset = 0; $offset < $count; $offset++) { + try { + $coalescedResource->setIteratorIndex($offset); + $this->layers[$offset] = new Image($coalescedResource->getImage(), $this->palette, new MetadataBag()); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to retrieve layer', $e->getCode(), $e); + } + } + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->extractAt($this->offset); + } + + /** + * Tries to extract layer at given offset + * + * @param integer $offset + * + * @return Image + * @throws RuntimeException + */ + private function extractAt($offset) + { + if (!isset($this->layers[$offset])) { + try { + $this->resource->setIteratorIndex($offset); + $this->layers[$offset] = new Image($this->resource->getImage(), $this->palette, new MetadataBag()); + } catch (\ImagickException $e) { + throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e); + } + } + + return $this->layers[$offset]; + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function next() + { + ++$this->offset; + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->offset = 0; + } + + /** + * {@inheritdoc} + */ + public function valid() + { + return $this->offset < count($this); + } + + /** + * {@inheritdoc} + */ + public function count() + { + try { + return $this->resource->getNumberImages(); + } catch (\ImagickException $e) { + throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return is_int($offset) && $offset >= 0 && $offset < count($this); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->extractAt($offset); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $image) + { + if (!$image instanceof Image) { + throw new InvalidArgumentException('Only an Imagick Image can be used as layer'); + } + + if (null === $offset) { + $offset = count($this) - 1; + } else { + if (!is_int($offset)) { + throw new InvalidArgumentException('Invalid offset for layer, it must be an integer'); + } + + if (count($this) < $offset || 0 > $offset) { + throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset)); + } + + if (isset($this[$offset])) { + unset($this[$offset]); + $offset = $offset - 1; + } + } + + $frame = $image->getImagick(); + + try { + if (count($this) > 0) { + $this->resource->setIteratorIndex($offset); + } + $this->resource->addImage($frame); + } catch (\ImagickException $e) { + throw new RuntimeException('Unable to set the layer', $e->getCode(), $e); + } + + $this->layers = array(); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + try { + $this->extractAt($offset); + } catch (RuntimeException $e) { + return; + } + + try { + $this->resource->setIteratorIndex($offset); + $this->resource->removeImage(); + } catch (\ImagickException $e) { + throw new RuntimeException('Unable to remove layer', $e->getCode(), $e); + } + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Collection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Collection.php new file mode 100644 index 0000000000000000000000000000000000000000..61dcbeb1943d1a4fd3a6176e25d26aa6bd9ac7fe --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Collection.php @@ -0,0 +1,192 @@ +aItems = array(); + } + + /** + * @param mixed $mItem + * @param bool $bToTop = false + * @return self + */ + public function Add($mItem, $bToTop = false) + { + if ($bToTop) + { + \array_unshift($this->aItems, $mItem); + } + else + { + \array_push($this->aItems, $mItem); + } + + return $this; + } + + /** + * @param array $aItems + * @return self + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function AddArray($aItems) + { + if (!\is_array($aItems)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + foreach ($aItems as $mItem) + { + $this->Add($mItem); + } + + return $this; + } + + /** + * @return self + */ + public function Clear() + { + $this->aItems = array(); + + return $this; + } + + /** + * @return array + */ + public function CloneAsArray() + { + return $this->aItems; + } + + /** + * @return int + */ + public function Count() + { + return \count($this->aItems); + } + + /** + * @return array + */ + public function &GetAsArray() + { + return $this->aItems; + } + + /** + * @param mixed $mCallback + */ + public function MapList($mCallback) + { + $aResult = array(); + if (\is_callable($mCallback)) + { + foreach ($this->aItems as $oItem) + { + $aResult[] = \call_user_func($mCallback, $oItem); + } + } + + return $aResult; + } + + /** + * @param mixed $mCallback + * @return array + */ + public function FilterList($mCallback) + { + $aResult = array(); + if (\is_callable($mCallback)) + { + foreach ($this->aItems as $oItem) + { + if (\call_user_func($mCallback, $oItem)) + { + $aResult[] = $oItem; + } + } + } + + return $aResult; + } + + /** + * @param mixed $mCallback + * @return void + */ + public function ForeachList($mCallback) + { + if (\is_callable($mCallback)) + { + foreach ($this->aItems as $oItem) + { + \call_user_func($mCallback, $oItem); + } + } + } + + /** + * @return mixed | null + * @return mixed + */ + public function &GetByIndex($iIndex) + { + $mResult = null; + if (\key_exists($iIndex, $this->aItems)) + { + $mResult = $this->aItems[$iIndex]; + } + + return $mResult; + } + + /** + * @param array $aItems + * @return self + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetAsArray($aItems) + { + if (!\is_array($aItems)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->aItems = $aItems; + + return $this; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Crypt.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Crypt.php new file mode 100644 index 0000000000000000000000000000000000000000..3ef6fc5f8171e7edd9bd8db9a245569f11e12ccd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Crypt.php @@ -0,0 +1,189 @@ +> 2 & 3; + for ($iPIndex = 0; $iPIndex < $iN; $iPIndex++) + { + $iY = $aV[$iPIndex + 1]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iZ = $aV[$iPIndex] = self::int32($aV[$iPIndex] + $iMx); + } + $iY = $aV[0]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iZ = $aV[$iN] = self::int32($aV[$iN] + $iMx); + } + + return self::long2str($aV, false); + } + + /** + * @param string $sEncriptedString + * @param string $sKey + * + * @return string + */ + public static function XxteaDecrypt($sEncriptedString, $sKey) + { + if (0 === \strlen($sEncriptedString)) + { + return ''; + } + + $aV = self::str2long($sEncriptedString, false); + $aK = self::str2long($sKey, false); + + if (\count($aK) < 4) + { + for ($iIndex = \count($aK); $iIndex < 4; $iIndex++) + { + $aK[$iIndex] = 0; + } + } + + $iN = \count($aV) - 1; + + $iZ = $aV[$iN]; + $iY = $aV[0]; + $iDelta = 0x9E3779B9; + $iQ = \floor(6 + 52 / ($iN + 1)); + $iSum = self::int32($iQ * $iDelta); + while ($iSum != 0) + { + $iE = $iSum >> 2 & 3; + for ($iPIndex = $iN; $iPIndex > 0; $iPIndex--) + { + $iZ = $aV[$iPIndex - 1]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iY = $aV[$iPIndex] = self::int32($aV[$iPIndex] - $iMx); + } + $iZ = $aV[$iN]; + $iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) + + (($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ)); + $iY = $aV[0] = self::int32($aV[0] - $iMx); + $iSum = self::int32($iSum - $iDelta); + } + + return self::long2str($aV, true); + } + + /** + * @param array $aV + * @param array $aW + * + * @return string + */ + private static function long2str($aV, $aW) + { + $iLen = \count($aV); + $iN = ($iLen - 1) << 2; + if ($aW) + { + $iM = $aV[$iLen - 1]; + if (($iM < $iN - 3) || ($iM > $iN)) + { + return false; + } + $iN = $iM; + } + $aS = array(); + for ($iIndex = 0; $iIndex < $iLen; $iIndex++) + { + $aS[$iIndex] = \pack('V', $aV[$iIndex]); + } + if ($aW) + { + return \substr(\join('', $aS), 0, $iN); + } + else + { + return \join('', $aS); + } + } + + /** + * @param string $sS + * @param string $sW + * + * @return array + */ + private static function str2long($sS, $sW) + { + $aV = \unpack('V*', $sS . \str_repeat("\0", (4 - \strlen($sS) % 4) & 3)); + $aV = \array_values($aV); + if ($sW) + { + $aV[\count($aV)] = \strlen($sS); + } + return $aV; + } + + /** + * @param int $iN + * + * @return int + */ + private static function int32($iN) + { + while ($iN >= 2147483648) + { + $iN -= 4294967296; + } + while ($iN <= -2147483649) + { + $iN += 4294967296; + } + return (int) $iN; + } +} \ No newline at end of file diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/DateTimeHelper.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/DateTimeHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..fb37b49ce62b30d878531ff6f85094b96c76da07 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/DateTimeHelper.php @@ -0,0 +1,154 @@ +getTimestamp() : 0; + } + + /** + * Parse date string formated as "10-Jan-2012 01:58:17 -0800" + * IMAP INTERNALDATE Format + * + * @param string $sDateTime + * + * @return int + */ + public static function ParseInternalDateString($sDateTime) + { + $sDateTime = \trim($sDateTime); + if (empty($sDateTime)) + { + return 0; + } + + if (\preg_match('/^[a-z]{2,4}, /i', $sDateTime)) // RFC2822 ~ "Thu, 10 Jun 2010 08:58:33 -0700 (PDT)" + { + return \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sDateTime); + } + + $oDateTime = \DateTime::createFromFormat('d-M-Y H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); + return $oDateTime ? $oDateTime->getTimestamp() : 0; + } + + /** + * Parse date string formated as "2011-06-14 23:59:59 +0400" + * + * @param string $sDateTime + * + * @return int + */ + public static function ParseDateStringType1($sDateTime) + { + $sDateTime = \trim($sDateTime); + if (empty($sDateTime)) + { + return 0; + } + + $oDateTime = \DateTime::createFromFormat('Y-m-d H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject()); + return $oDateTime ? $oDateTime->getTimestamp() : 0; + } + + /** + * Parse date string formated as "2015-05-08T14:32:18.483-07:00" + * + * @param string $sDateTime + * + * @return int + */ + public static function TryToParseSpecEtagFormat($sDateTime) + { + $sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', \trim($sDateTime))); + $sDateTime = \trim(\preg_replace('/(:[\d]{2})\.[\d]{3}/', '$1', \trim($sDateTime))); + $sDateTime = \trim(\preg_replace('/(-[\d]{2})T([\d]{2}:)/', '$1 $2', \trim($sDateTime))); + $sDateTime = \trim(\preg_replace('/([\-+][\d]{2}):([\d]{2})$/', ' $1$2', \trim($sDateTime))); + + return \MailSo\Base\DateTimeHelper::ParseDateStringType1($sDateTime); + } + + /** + * @param string $sTime + * + * @return int + */ + public static function TimeToSec($sTime) + { + $iMod = 1; + $sTime = \trim($sTime); + if ('-' === \substr($sTime, 0, 1)) + { + $iMod = -1; + $sTime = \substr($sTime, 1); + } + + $aParts = \preg_split('/[:.,]/', (string) $sTime); + + $iResult = 0; + if (isset($aParts[0]) && \is_numeric($aParts[0])) + { + $iResult += 3600 * ((int) $aParts[0]); + } + + if (isset($aParts[1]) && \is_numeric($aParts[1])) + { + $iResult += 60 * ((int) $aParts[1]); + } + + return $iResult * $iMod; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Enumerations/Charset.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Enumerations/Charset.php new file mode 100644 index 0000000000000000000000000000000000000000..85132e6eac236dfe3b3beb81c56d7633cc976767 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Enumerations/Charset.php @@ -0,0 +1,38 @@ +getFile()).' ~ '.$this->getLine().')' : $sMessage; + + parent::__construct($sMessage, $iCode, $oPrevious); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Exceptions/InvalidArgumentException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..a366b6395a7212b20b87050f1925445f637f5bbf --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Exceptions/InvalidArgumentException.php @@ -0,0 +1,19 @@ +hasAttributes() && isset($oElement->attributes) && $oElement->attributes) + { + foreach ($oElement->attributes as $oAttr) + { + if ($oAttr && !empty($oAttr->nodeName)) + { + $sAttrName = \trim(\strtolower($oAttr->nodeName)); + $aResult[$sAttrName] = $oAttr->nodeValue; + } + } + } + } + + return $aResult; + } + + /** + * @param string $sText + * + * @return \DOMDocument|bool + */ + public static function GetDomFromText($sText) + { + $bState = true; + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_use_internal_errors')) + { + $bState = \libxml_use_internal_errors(true); + } + + $sHtmlAttrs = $sBodyAttrs = ''; + + $sText = \MailSo\Base\HtmlUtils::FixSchemas($sText); + $sText = \MailSo\Base\HtmlUtils::ClearFastTags($sText); + $sText = \MailSo\Base\HtmlUtils::ClearBodyAndHtmlTag($sText, $sHtmlAttrs, $sBodyAttrs); + + $oDom = self::createDOMDocument(); + @$oDom->loadHTML('<'.'?xml version="1.0" encoding="utf-8"?'.'>'. + '
'. + ''. + ''.\MailSo\Base\Utils::Utf8Clear($sText).''); + + @$oDom->normalizeDocument(); + + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_clear_errors')) + { + @\libxml_clear_errors(); + } + + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('libxml_use_internal_errors')) + { + \libxml_use_internal_errors($bState); + } + + return $oDom; + } + + /** + * @return \DOMDocument + */ + private static function createDOMDocument() + { + $oDoc = new \DOMDocument('1.0', 'UTF-8'); + $oDoc->encoding = 'UTF-8'; + $oDoc->strictErrorChecking = false; + $oDoc->formatOutput = false; + $oDoc->preserveWhiteSpace = false; + + return $oDoc; + } + + /** + * @return boolean + */ + private static function comparedVersion() + { + return \version_compare(PHP_VERSION, '5.3.6') >= 0; + } + + /** + * @param \DOMDocument|\DOMElement $oElem + * + * @return string + */ + private static function domToString($oElem, $oDom = null) + { + $sResult = ''; + if ($oElem instanceof \DOMDocument) + { + if (isset($oElem->documentElement) && self::comparedVersion()) + { + $sResult = $oElem->saveHTML($oElem->documentElement); + } + else + { + $sResult = $oElem->saveHTML(); + } + } + else if ($oElem) + { + if ($oDom && self::comparedVersion()) + { + $sResult = $oDom->saveHTML($oElem); + } + else + { + $oTempDoc = self::createDOMDocument(); + $oTempDoc->appendChild($oTempDoc->importNode($oElem->cloneNode(true), true)); + $sResult = $oTempDoc->saveHTML(); + } + } + + return \trim($sResult); + } + + /** + * @param \DOMDocument $oDom + * @param bool $bWrapByFakeHtmlAndBodyDiv = true + * + * @return string + */ + public static function GetTextFromDom_($oDom, $bWrapByFakeHtmlAndBodyDiv = true) + { + $sResult = ''; + + $aHtmlAttrs = $aBodylAttrs = array(); + if ($bWrapByFakeHtmlAndBodyDiv) + { + $oHtml = $oDom->getElementsByTagName('html')->item(0); + $oBody = $oDom->getElementsByTagName('body')->item(0); + + $aHtmlAttrs = \MailSo\Base\HtmlUtils::GetElementAttributesAsArray($oHtml); + $aBodylAttrs = \MailSo\Base\HtmlUtils::GetElementAttributesAsArray($oBody); + } + + $oDiv = $oDom->getElementsByTagName('div')->item(0); + if ($oDiv && $oDiv->hasAttribute('data-wrp') && 'rainloop' === $oDiv->getAttribute('data-wrp')) + { + $oDiv->removeAttribute('data-wrp'); + if ($bWrapByFakeHtmlAndBodyDiv) + { + $oWrap = $oDom->createElement('div'); + + $oWrap->setAttribute('data-x-div-type', 'html'); + foreach ($aHtmlAttrs as $sKey => $sValue) + { + $oWrap->setAttribute($sKey, $sValue); + } + + $oDiv->setAttribute('data-x-div-type', 'body'); + foreach ($aBodylAttrs as $sKey => $sValue) + { + $oDiv->setAttribute($sKey, $sValue); + } + + $oWrap->appendChild($oDiv); + $sResult = self::domToString($oWrap, $oDom); + } + else + { + $sResult = self::domToString($oDiv, $oDom); + } + } + else + { + $sResult = self::domToString($oDom); + } + + $sResult = \str_replace(\MailSo\Base\HtmlUtils::$KOS, ':', $sResult); + $sResult = \MailSo\Base\Utils::StripSpaces($sResult); + + return $sResult; + } + + /** + * @param \DOMDocument $oDom + * @param bool $bWrapByFakeHtmlAndBodyDiv = true + * + * @return string + */ + public static function GetTextFromDom($oDom, $bWrapByFakeHtmlAndBodyDiv = true) + { + $sResult = ''; + + $oHtml = $oDom->getElementsByTagName('html')->item(0); + $oBody = $oDom->getElementsByTagName('body')->item(0); + + foreach ($oBody->childNodes as $oChild) + { + $sResult .= $oDom->saveHTML($oChild); + } + + if ($bWrapByFakeHtmlAndBodyDiv) + { + $aHtmlAttrs = \MailSo\Base\HtmlUtils::GetElementAttributesAsArray($oHtml); + $aBodylAttrs = \MailSo\Base\HtmlUtils::GetElementAttributesAsArray($oBody); + + $oWrapHtml = $oDom->createElement('div'); + $oWrapHtml->setAttribute('data-x-div-type', 'html'); + foreach ($aHtmlAttrs as $sKey => $sValue) + { + $oWrapHtml->setAttribute($sKey, $sValue); + } + + $oWrapDom = $oDom->createElement('div', '___xxx___'); + $oWrapDom->setAttribute('data-x-div-type', 'body'); + foreach ($aBodylAttrs as $sKey => $sValue) + { + $oWrapDom->setAttribute($sKey, $sValue); + } + + $oWrapHtml->appendChild($oWrapDom); + + $sWrp = $oDom->saveHTML($oWrapHtml); + + $sResult = \str_replace('___xxx___', $sResult, $sWrp); + } + + $sResult = \str_replace(\MailSo\Base\HtmlUtils::$KOS, ':', $sResult); + $sResult = \MailSo\Base\Utils::StripSpaces($sResult); + + return $sResult; + } + + /** + * @param string $sHtml + * @param string $sHtmlAttrs = '' + * @param string $sBodyAttrs = '' + * + * @return string + */ + public static function ClearBodyAndHtmlTag($sHtml, &$sHtmlAttrs = '', &$sBodyAttrs = '') + { + $aMatch = array(); + if (\preg_match('/]+)>/im', $sHtml, $aMatch) && !empty($aMatch[1])) + { + $sHtmlAttrs = $aMatch[1]; + } + + $aMatch = array(); + if (\preg_match('/]+)>/im', $sHtml, $aMatch) && !empty($aMatch[1])) + { + $sBodyAttrs = $aMatch[1]; + } + + $sHtml = \preg_replace('/]*)>/si', '', $sHtml); + $sHtml = \preg_replace('/]*)>/si', '', $sHtml); + $sHtml = \preg_replace('/<\/body>/i', '', $sHtml); + $sHtml = \preg_replace('/]*)>/i', '', $sHtml); + $sHtml = \preg_replace('/<\/html>/i', '', $sHtml); + + $sHtmlAttrs = \preg_replace('/xmlns:[a-z]="[^"]*"/i', '', $sHtmlAttrs); + $sHtmlAttrs = \preg_replace('/xmlns:[a-z]=\'[^\']*\'/i', '', $sHtmlAttrs); + $sHtmlAttrs = \preg_replace('/xmlns="[^"]*"/i', '', $sHtmlAttrs); + $sHtmlAttrs = \preg_replace('/xmlns=\'[^\']*\'/i', '', $sHtmlAttrs); + $sBodyAttrs = \preg_replace('/xmlns:[a-z]="[^"]*"/i', '', $sBodyAttrs); + $sBodyAttrs = \preg_replace('/xmlns:[a-z]=\'[^\']*\'/i', '', $sBodyAttrs); + + $sHtmlAttrs = trim($sHtmlAttrs); + $sBodyAttrs = trim($sBodyAttrs); + + return $sHtml; + } + + /** + * @param string $sHtml + * @param bool $bClearEmpty = true + * + * @return string + */ + public static function FixSchemas($sHtml, $bClearEmpty = true) + { + if ($bClearEmpty) + { + $sHtml = \str_replace(']*><\/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'); + } + } + + 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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Validator.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Base/Validator.php new file mode 100644 index 0000000000000000000000000000000000000000..dbaf2ffdb824c23ba1001328299274ba41949227 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/CacheClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/CacheClient.php new file mode 100644 index 0000000000000000000000000000000000000000..e72f56eaadfe7ecafe4b7972c659b54c9a12a094 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/DriverInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/DriverInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..8751ba1db31ac9b11f9f64c3f26c6104cde75272 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/Drivers/File.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/Drivers/File.php new file mode 100644 index 0000000000000000000000000000000000000000..bee1c51b852c474938bd65a558c2c1c14a35164e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/Drivers/Memcache.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/Drivers/Memcache.php new file mode 100644 index 0000000000000000000000000000000000000000..35094b4880245983bd3b924c6ad4ef3b96b51567 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/Drivers/Redis.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Cache/Drivers/Redis.php new file mode 100644 index 0000000000000000000000000000000000000000..638f86886145779607a7ed3fc214df4565da4ad0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Config.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..696def77e39b45df897148e1a9ff3b6def17aa1a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Enumerations/FetchType.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Enumerations/FetchType.php new file mode 100644 index 0000000000000000000000000000000000000000..fe6a36e18b37e8ab3870cace776b6cbbaaa1d5cc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Exceptions/ResponseException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Exceptions/ResponseException.php new file mode 100644 index 0000000000000000000000000000000000000000..24f0d8ca3029f2401e362fe30a45520e052626db --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Exceptions/ResponseNotFoundException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Exceptions/ResponseNotFoundException.php new file mode 100644 index 0000000000000000000000000000000000000000..2d4d029646be20409571a88681ab1fc600c49548 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Folder.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Folder.php new file mode 100644 index 0000000000000000000000000000000000000000..50fd423373bd243539a741faafc97af2888e0a1c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/FolderInformation.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/FolderInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..d8fe5a0e2c0f09692202d903f9436447bd6cbe1b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/ImapClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/ImapClient.php new file mode 100644 index 0000000000000000000000000000000000000000..19aa9a51e93b0377c0b7dad2cf6e4867ff094a3c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/ImapClient.php @@ -0,0 +1,2667 @@ +iTagCount = 0; + $this->aCapabilityItems = null; + $this->oCurrentFolderInfo = null; + $this->aFetchCallbacks = null; + $this->iResponseBufParsedPos = 0; + + $this->aLastResponse = array(); + $this->bNeedNext = true; + $this->aPartialResponses = array(); + + $this->aTagTimeouts = array(); + + $this->bIsLoggined = false; + $this->bIsSelected = false; + $this->sLogginedUser = ''; + + $this->__FORCE_SELECT_ON_EXAMINE__ = false; + + @\ini_set('xdebug.max_nesting_level', 500); + } + + /** + * @return \MailSo\Imap\ImapClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @return string + */ + public function GetLogginedUser() + { + return $this->sLogginedUser; + } + + /** + * @param string $sServerName + * @param int $iPort = 143 + * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Connect($sServerName, $iPort = 143, + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) + { + $this->aTagTimeouts['*'] = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + + $this->parseResponseWithValidation('*', true); + + if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( + $this->IsSupported('STARTTLS'), $this->iSecurityType)) + { + $this->SendRequestWithCheck('STARTTLS'); + $this->EnableCrypto(); + + $this->aCapabilityItems = null; + } + else if (\MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS === $this->iSecurityType) + { + $this->writeLogException( + new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $this; + } + + protected function _xor($string, $string2) + { + $result = ''; + $size = strlen($string); + for ($i=0; $i<$size; $i++) { + $result .= chr(ord($string[$i]) ^ ord($string2[$i])); + } + return $result; + } + + /** + * @param string $sLogin + * @param string $sPassword + * @param string $sProxyAuthUser = '' + * @param bool $bUseAuthPlainIfSupported = true + * @param bool $bUseAuthCramMd5IfSupported = true + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Login($sLogin, $sPassword, $sProxyAuthUser = '', + $bUseAuthPlainIfSupported = true, $bUseAuthCramMd5IfSupported = true) + { + if (!\MailSo\Base\Validator::NotEmptyString($sLogin, true) || + !\MailSo\Base\Validator::NotEmptyString($sPassword, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); + + $sPassword = $sPassword; + + $this->sLogginedUser = $sLogin; + + try + { + if ($bUseAuthCramMd5IfSupported && $this->IsSupported('AUTH=CRAM-MD5')) + { + $this->SendRequest('AUTHENTICATE', array('CRAM-MD5')); + + $aResponse = $this->parseResponseWithValidation(); + if ($aResponse && \is_array($aResponse) && 0 < \count($aResponse) && + \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $aResponse[\count($aResponse) - 1]->ResponseType) + { + $oContinuationResponse = null; + foreach ($aResponse as $oResponse) + { + if ($oResponse && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oResponse->ResponseType) + { + $oContinuationResponse = $oResponse; + } + } + + if ($oContinuationResponse && !empty($oContinuationResponse->ResponseList[1])) + { + $sTicket = @\base64_decode($oContinuationResponse->ResponseList[1]); + $this->oLogger->Write('ticket: '.$sTicket); + + $sToken = \base64_encode($sLogin.' '.\MailSo\Base\Utils::Hmac($sTicket, $sPassword)); + + if ($this->oLogger) + { + $this->oLogger->AddSecret($sToken); + } + + $this->sendRaw($sToken, true, '*******'); + $this->parseResponseWithValidation(); + } + else + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + } + else if ($bUseAuthPlainIfSupported && $this->IsSupported('AUTH=PLAIN')) + { + $sToken = \base64_encode("\0".$sLogin."\0".$sPassword); + if ($this->oLogger) + { + $this->oLogger->AddSecret($sToken); + } + + if ($this->IsSupported('AUTH=SASL-IR') && false) + { + $this->SendRequestWithCheck('AUTHENTICATE', array('PLAIN', $sToken)); + } + else + { + $this->SendRequest('AUTHENTICATE', array('PLAIN')); + $this->parseResponseWithValidation(); + + $this->sendRaw($sToken, true, '*******'); + $this->parseResponseWithValidation(); + } + } + else + { + if ($this->oLogger) + { + $this->oLogger->AddSecret($this->EscapeString($sPassword)); + } + + $this->SendRequestWithCheck('LOGIN', + array( + $this->EscapeString($sLogin), + $this->EscapeString($sPassword) + )); + } +// else +// { +// $this->writeLogException( +// new \MailSo\Imap\Exceptions\LoginBadMethodException(), +// \MailSo\Log\Enumerations\Type::NOTICE, true); +// } + + if (0 < \strlen($sProxyAuthUser)) + { + $this->SendRequestWithCheck('PROXYAUTH', array($this->EscapeString($sProxyAuthUser))); + } + } + catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + $this->aCapabilityItems = null; + + return $this; + } + + /** + * @param string $sXOAuth2Token + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function LoginWithXOauth2($sXOAuth2Token) + { + if (!\MailSo\Base\Validator::NotEmptyString($sXOAuth2Token, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$this->IsSupported('AUTH=XOAUTH2')) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginBadMethodException(), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + try + { + $this->SendRequest('AUTHENTICATE', array('XOAUTH2', \trim($sXOAuth2Token))); + $aR = $this->parseResponseWithValidation(); + + if (\is_array($aR) && 0 < \count($aR) && isset($aR[\count($aR) - 1])) + { + $oR = $aR[\count($aR) - 1]; + if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oR->ResponseType) + { + if (!empty($oR->ResponseList[1]) && preg_match('/^[a-zA-Z0-9=+\/]+$/', $oR->ResponseList[1])) + { + $this->Logger()->Write(\base64_decode($oR->ResponseList[1]), + \MailSo\Log\Enumerations\Type::WARNING); + } + + $this->sendRaw(''); + $this->parseResponseWithValidation(); + } + } + } + catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\LoginBadCredentialsException( + $oException->GetResponses(), '', 0, $oException), + \MailSo\Log\Enumerations\Type::NOTICE, true); + } + + $this->bIsLoggined = true; + $this->aCapabilityItems = null; + + return $this; + } + + /** + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + */ + public function Logout() + { + if ($this->bIsLoggined) + { + $this->bIsLoggined = false; + $this->SendRequestWithCheck('LOGOUT', array()); + } + + return $this; + } + + /** + * @return \MailSo\Imap\ImapClient + */ + public function ForceCloseConnection() + { + $this->Disconnect(); + + return $this; + } + + /** + * @return bool + */ + public function IsLoggined() + { + return $this->IsConnected() && $this->bIsLoggined; + } + + /** + * @return bool + */ + public function IsSelected() + { + return $this->IsLoggined() && $this->bIsSelected; + } + + /** + * @return array|null + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Capability() + { + $this->SendRequestWithCheck('CAPABILITY', array(), true); + return $this->aCapabilityItems; + } + + /** + * @param string $sExtentionName + * @return bool + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function IsSupported($sExtentionName) + { + $bResult = \MailSo\Base\Validator::NotEmptyString($sExtentionName, true); + if ($bResult && null === $this->aCapabilityItems) + { + $this->aCapabilityItems = $this->Capability(); + } + + return $bResult && \is_array($this->aCapabilityItems) && + \in_array(\strtoupper($sExtentionName), $this->aCapabilityItems); + } + + /** + * @return \MailSo\Imap\NamespaceResult|null + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function GetNamespace() + { + if (!$this->IsSupported('NAMESPACE')) + { + return null; + } + + $oReturn = false; + + $this->SendRequest('NAMESPACE'); + $aResult = $this->parseResponseWithValidation(); + + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + 'NAMESPACE' === $oImapResponse->StatusOrIndex) + { + $oReturn = NamespaceResult::NewInstance(); + $oReturn->InitByImapResponse($oImapResponse); + break; + } + } + + if (false === $oReturn) + { + $this->writeLogException( + new \MailSo\Imap\Exceptions\ResponseException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + return $oReturn; + } + + /** + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Noop() + { + return $this->SendRequestWithCheck('NOOP'); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderCreate($sFolderName) + { + return $this->SendRequestWithCheck('CREATE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderDelete($sFolderName) + { + return $this->SendRequestWithCheck('DELETE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderSubscribe($sFolderName) + { + return $this->SendRequestWithCheck('SUBSCRIBE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderUnSubscribe($sFolderName) + { + return $this->SendRequestWithCheck('UNSUBSCRIBE', + array($this->EscapeString($sFolderName))); + } + + /** + * @param string $sOldFolderName + * @param string $sNewFolderName + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderRename($sOldFolderName, $sNewFolderName) + { + return $this->SendRequestWithCheck('RENAME', array( + $this->EscapeString($sOldFolderName), + $this->EscapeString($sNewFolderName))); + } + + /** + * @param array $aResult + * + * @return array + */ + protected function getStatusFolderInformation($aResult) + { + $aReturn = array(); + + if (\is_array($aResult)) + { + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + 'STATUS' === $oImapResponse->StatusOrIndex && isset($oImapResponse->ResponseList[3]) && + \is_array($oImapResponse->ResponseList[3])) + { + $sName = null; + foreach ($oImapResponse->ResponseList[3] as $sArrayItem) + { + if (null === $sName) + { + $sName = $sArrayItem; + } + else + { + $aReturn[$sName] = $sArrayItem; + $sName = null; + } + } + } + } + } + + return $aReturn; + } + + /** + * @param string $sFolderName + * @param array $aStatusItems + * + * @return array|bool + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderStatus($sFolderName, array $aStatusItems) + { + $aResult = false; + if (\count($aStatusItems) > 0) + { + $this->SendRequest('STATUS', + array($this->EscapeString($sFolderName), $aStatusItems)); + + $aResult = $this->getStatusFolderInformation( + $this->parseResponseWithValidation()); + } + + return $aResult; + } + + /** + * @param array $aResult + * @param string $sStatus + * @param bool $bUseListStatus = false + * + * @return array + */ + private function getFoldersFromResult(array $aResult, $sStatus, $bUseListStatus = false) + { + $aReturn = array(); + + $sDelimiter = ''; + $bInbox = false; + + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + $sStatus === $oImapResponse->StatusOrIndex && 5 === count($oImapResponse->ResponseList)) + { + try + { + $oFolder = Folder::NewInstance($oImapResponse->ResponseList[4], + $oImapResponse->ResponseList[3], $oImapResponse->ResponseList[2]); + + if ($oFolder->IsInbox()) + { + $bInbox = true; + } + + if (empty($sDelimiter)) + { + $sDelimiter = $oFolder->Delimiter(); + } + + $aReturn[] = $oFolder; + } + catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) + { + $this->writeLogException($oException, \MailSo\Log\Enumerations\Type::WARNING, false); + } + } + } + + if (!$bInbox && !empty($sDelimiter)) + { + $aReturn[] = Folder::NewInstance('INBOX', $sDelimiter); + } + + if ($bUseListStatus) + { + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && + 'STATUS' === $oImapResponse->StatusOrIndex && + isset($oImapResponse->ResponseList[2]) && + isset($oImapResponse->ResponseList[3]) && + \is_array($oImapResponse->ResponseList[3])) + { + $sFolderNameRaw = $oImapResponse->ResponseList[2]; + + $oCurrentFolder = null; + foreach ($aReturn as &$oFolder) + { + if ($oFolder && $sFolderNameRaw === $oFolder->FullNameRaw()) + { + $oCurrentFolder =& $oFolder; + break; + } + } + + if (null !== $oCurrentFolder) + { + $sName = null; + $aStatus = array(); + + foreach ($oImapResponse->ResponseList[3] as $sArrayItem) + { + if (null === $sName) + { + $sName = $sArrayItem; + } + else + { + $aStatus[$sName] = $sArrayItem; + $sName = null; + } + } + + if (0 < count($aStatus)) + { + $oCurrentFolder->SetExtended('STATUS', $aStatus); + } + } + + unset($oCurrentFolder); + } + } + } + + return $aReturn; + } + + /** + * @param bool $bIsSubscribeList + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * @param bool $bUseListStatus = false + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + private function specificFolderList($bIsSubscribeList, $sParentFolderName = '', $sListPattern = '*', $bUseListStatus = false) + { + $sCmd = 'LSUB'; + if (!$bIsSubscribeList) + { + $sCmd = 'LIST'; + } + + $sListPattern = 0 === strlen(trim($sListPattern)) ? '*' : $sListPattern; + + $aParameters = array( + $this->EscapeString($sParentFolderName), + $this->EscapeString($sListPattern) + ); + + if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS')) + { + $aL = array( + \MailSo\Imap\Enumerations\FolderStatus::MESSAGES, + \MailSo\Imap\Enumerations\FolderStatus::UNSEEN, + \MailSo\Imap\Enumerations\FolderStatus::UIDNEXT + ); + +// if ($this->IsSupported('CONDSTORE')) +// { +// $aL[] = \MailSo\Imap\Enumerations\FolderStatus::HIGHESTMODSEQ; +// } + + $aParameters[] = 'RETURN'; + $aParameters[] = array('STATUS', $aL); + } + else + { + $bUseListStatus = false; + } + + $this->SendRequest($sCmd, $aParameters); + + return $this->getFoldersFromResult( + $this->parseResponseWithValidation(), $sCmd, $bUseListStatus); + } + + /** + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderList($sParentFolderName = '', $sListPattern = '*') + { + return $this->specificFolderList(false, $sParentFolderName, $sListPattern); + } + + /** + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderSubscribeList($sParentFolderName = '', $sListPattern = '*') + { + return $this->specificFolderList(true, $sParentFolderName, $sListPattern); + } + + /** + * @param string $sParentFolderName = '' + * @param string $sListPattern = '*' + * + * @return array + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderStatusList($sParentFolderName = '', $sListPattern = '*') + { + return $this->specificFolderList(false, $sParentFolderName, $sListPattern, true); + } + + /** + * @param array $aResult + * @param string $sFolderName + * @param bool $bIsWritable + * + * @return void + */ + protected function initCurrentFolderInformation($aResult, $sFolderName, $bIsWritable) + { + if (\is_array($aResult)) + { + $oImapResponse = null; + $oResult = FolderInformation::NewInstance($sFolderName, $bIsWritable); + + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType) + { + if (\count($oImapResponse->ResponseList) > 2 && + 'FLAGS' === $oImapResponse->ResponseList[1] && \is_array($oImapResponse->ResponseList[2])) + { + $oResult->Flags = $oImapResponse->ResponseList[2]; + } + + if (is_array($oImapResponse->OptionalResponse) && \count($oImapResponse->OptionalResponse) > 1) + { + if ('PERMANENTFLAGS' === $oImapResponse->OptionalResponse[0] && + is_array($oImapResponse->OptionalResponse[1])) + { + $oResult->PermanentFlags = $oImapResponse->OptionalResponse[1]; + } + else if ('UIDVALIDITY' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1])) + { + $oResult->Uidvalidity = $oImapResponse->OptionalResponse[1]; + } + else if ('UNSEEN' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1]) && + is_numeric($oImapResponse->OptionalResponse[1])) + { + $oResult->Unread = (int) $oImapResponse->OptionalResponse[1]; + } + else if ('UIDNEXT' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1])) + { + $oResult->Uidnext = $oImapResponse->OptionalResponse[1]; + } + else if ('HIGHESTMODSEQ' === $oImapResponse->OptionalResponse[0] && + isset($oImapResponse->OptionalResponse[1]) && + \is_numeric($oImapResponse->OptionalResponse[1])) + { + $oResult->HighestModSeq = \trim($oImapResponse->OptionalResponse[1]); + } + } + + if (\count($oImapResponse->ResponseList) > 2 && + \is_string($oImapResponse->ResponseList[2]) && + \is_numeric($oImapResponse->ResponseList[1])) + { + switch($oImapResponse->ResponseList[2]) + { + case 'EXISTS': + $oResult->Exists = (int) $oImapResponse->ResponseList[1]; + break; + case 'RECENT': + $oResult->Recent = (int) $oImapResponse->ResponseList[1]; + break; + } + } + } + } + + $this->oCurrentFolderInfo = $oResult; + } + } + + /** + * @param string $sFolderName + * @param bool $bIsWritable + * @param bool $bReSelectSameFolders + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + protected function selectOrExamineFolder($sFolderName, $bIsWritable, $bReSelectSameFolders) + { + if (!$bReSelectSameFolders) + { + if ($this->oCurrentFolderInfo && + $sFolderName === $this->oCurrentFolderInfo->FolderName && + $bIsWritable === $this->oCurrentFolderInfo->IsWritable) + { + return $this; + } + } + + if (!\MailSo\Base\Validator::NotEmptyString($sFolderName, true)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->SendRequest(($bIsWritable) ? 'SELECT' : 'EXAMINE', + array($this->EscapeString($sFolderName))); + + $this->initCurrentFolderInformation( + $this->parseResponseWithValidation(), $sFolderName, $bIsWritable); + + $this->bIsSelected = true; + + return $this; + } + + /** + * @param string $sFolderName + * @param bool $bReSelectSameFolders = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderSelect($sFolderName, $bReSelectSameFolders = false) + { + return $this->selectOrExamineFolder($sFolderName, true, $bReSelectSameFolders); + } + + /** + * @param string $sFolderName + * @param bool $bReSelectSameFolders = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderExamine($sFolderName, $bReSelectSameFolders = false) + { + return $this->selectOrExamineFolder($sFolderName, $this->__FORCE_SELECT_ON_EXAMINE__, $bReSelectSameFolders); + } + + /** + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function FolderUnSelect() + { + if ($this->IsSelected() && $this->IsSupported('UNSELECT')) + { + $this->SendRequestWithCheck('UNSELECT'); + $this->bIsSelected = false; + } + + return $this; + } + + /** + * @param array $aInputFetchItems + * @param string $sIndexRange + * @param bool $bIndexIsUid + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Fetch(array $aInputFetchItems, $sIndexRange, $bIndexIsUid) + { + $sIndexRange = (string) $sIndexRange; + if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $aFetchItems = \MailSo\Imap\Enumerations\FetchType::ChangeFetchItemsBefourRequest($aInputFetchItems); + foreach ($aFetchItems as $sName => $mItem) + { + if (0 < \strlen($sName) && '' !== $mItem) + { + if (null === $this->aFetchCallbacks) + { + $this->aFetchCallbacks = array(); + } + + $this->aFetchCallbacks[$sName] = $mItem; + } + } + + $this->SendRequest((($bIndexIsUid) ? 'UID ' : '').'FETCH', array($sIndexRange, \array_keys($aFetchItems))); + $aResult = $this->validateResponse($this->parseResponse()); + $this->aFetchCallbacks = null; + + $aReturn = array(); + $oImapResponse = null; + foreach ($aResult as $oImapResponse) + { + if (FetchResponse::IsValidFetchImapResponse($oImapResponse)) + { + if (FetchResponse::IsNotEmptyFetchImapResponse($oImapResponse)) + { + $aReturn[] = FetchResponse::NewInstance($oImapResponse); + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Skipped Imap Response! ['.$oImapResponse->ToLine().']', \MailSo\Log\Enumerations\Type::NOTICE); + } + } + } + } + + return $aReturn; + } + + + /** + * @return array|false + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function Quota() + { + $aReturn = false; + if ($this->IsSupported('QUOTA')) + { + $this->SendRequest('GETQUOTAROOT "INBOX"'); + $aResult = $this->parseResponseWithValidation(); + + $aReturn = array(0, 0); + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && 'QUOTA' === $oImapResponse->StatusOrIndex + && \is_array($oImapResponse->ResponseList) + && isset($oImapResponse->ResponseList[3]) + && \is_array($oImapResponse->ResponseList[3]) + && 2 < \count($oImapResponse->ResponseList[3]) + && 'STORAGE' === \strtoupper($oImapResponse->ResponseList[3][0]) + && \is_numeric($oImapResponse->ResponseList[3][1]) + && \is_numeric($oImapResponse->ResponseList[3][2]) + ) + { + $aReturn = array( + (int) $oImapResponse->ResponseList[3][1], + (int) $oImapResponse->ResponseList[3][2], + 0, + 0 + ); + + if (5 < \count($oImapResponse->ResponseList[3]) + && 'MESSAGE' === \strtoupper($oImapResponse->ResponseList[3][3]) + && \is_numeric($oImapResponse->ResponseList[3][4]) + && \is_numeric($oImapResponse->ResponseList[3][5]) + ) + { + $aReturn[2] = (int) $oImapResponse->ResponseList[3][4]; + $aReturn[3] = (int) $oImapResponse->ResponseList[3][5]; + } + } + } + } + + return $aReturn; + } + + /** + * @param array $aSortTypes + * @param string $sSearchCriterias + * @param bool $bReturnUid + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleSort($aSortTypes, $sSearchCriterias = 'ALL', $bReturnUid = true) + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + if (!\is_array($aSortTypes) || 0 === \count($aSortTypes)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + else if (!$this->IsSupported('SORT')) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $aRequest = array(); + $aRequest[] = $aSortTypes; + $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; + $aRequest[] = $sSearchCriterias; + + $sCmd = 'SORT'; + + $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); + $aResult = $this->parseResponseWithValidation(); + + $aReturn = array(); + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ($sCmd === $oImapResponse->StatusOrIndex || + ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + && \is_array($oImapResponse->ResponseList) + && 2 < \count($oImapResponse->ResponseList)) + { + $iStart = 2; + if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && + !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + { + $iStart = 3; + } + + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) + { + $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; + } + } + } + + return $aReturn; + } + + /** + * @param bool $bSort = false + * @param string $sSearchCriterias = 'ALL' + * @param array $aSearchOrSortReturn = null + * @param bool $bReturnUid = true + * @param string $sLimit = '' + * @param string $sCharset = '' + * @param array $aSortTypes = null + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + private function simpleESearchOrESortHelper($bSort = false, $sSearchCriterias = 'ALL', $aSearchOrSortReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '', $aSortTypes = null) + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + $sCmd = $bSort ? 'SORT': 'SEARCH'; + if ($bSort && (!\is_array($aSortTypes) || 0 === \count($aSortTypes) || !$this->IsSupported('SORT'))) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$this->IsSupported($bSort ? 'ESORT' : 'ESEARCH')) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!\is_array($aSearchOrSortReturn) || 0 === \count($aSearchOrSortReturn)) + { + $aSearchOrSortReturn = array('ALL'); + } + + $aRequest = array(); + if ($bSort) + { + $aRequest[] = 'RETURN'; + $aRequest[] = $aSearchOrSortReturn; + + $aRequest[] = $aSortTypes; + $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; + } + else + { + if (0 < \strlen($sCharset)) + { + $aRequest[] = 'CHARSET'; + $aRequest[] = \strtoupper($sCharset); + } + + $aRequest[] = 'RETURN'; + $aRequest[] = $aSearchOrSortReturn; + } + + $aRequest[] = $sSearchCriterias; + + if (0 < \strlen($sLimit)) + { + $aRequest[] = $sLimit; + } + + $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); + $sRequestTag = $this->getCurrentTag(); + + $aResult = array(); + $aResponse = $this->parseResponseWithValidation(); + + if (\is_array($aResponse)) + { + $oImapResponse = null; + foreach ($aResponse as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ('ESEARCH' === $oImapResponse->StatusOrIndex || 'ESORT' === $oImapResponse->StatusOrIndex) + && \is_array($oImapResponse->ResponseList) + && isset($oImapResponse->ResponseList[2], $oImapResponse->ResponseList[2][0], $oImapResponse->ResponseList[2][1]) + && 'TAG' === $oImapResponse->ResponseList[2][0] && $sRequestTag === $oImapResponse->ResponseList[2][1] + && (!$bReturnUid || ($bReturnUid && !empty($oImapResponse->ResponseList[3]) && 'UID' === $oImapResponse->ResponseList[3])) + ) + { + $iStart = 3; + foreach ($oImapResponse->ResponseList as $iIndex => $mItem) + { + if ($iIndex >= $iStart) + { + switch ($mItem) + { + case 'ALL': + case 'MAX': + case 'MIN': + case 'COUNT': + if (isset($oImapResponse->ResponseList[$iIndex + 1])) + { + $aResult[$mItem] = $oImapResponse->ResponseList[$iIndex + 1]; + } + break; + } + } + } + } + } + } + + return $aResult; + } + + /** + * @param string $sSearchCriterias = 'ALL' + * @param array $aSearchReturn = null + * @param bool $bReturnUid = true + * @param string $sLimit = '' + * @param string $sCharset = '' + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleESearch($sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '', $sCharset = '') + { + return $this->simpleESearchOrESortHelper(false, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, $sCharset); + } + + /** + * @param array $aSortTypes + * @param string $sSearchCriterias = 'ALL' + * @param array $aSearchReturn = null + * @param bool $bReturnUid = true + * @param string $sLimit = '' + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleESort($aSortTypes, $sSearchCriterias = 'ALL', $aSearchReturn = null, $bReturnUid = true, $sLimit = '') + { + return $this->simpleESearchOrESortHelper(true, $sSearchCriterias, $aSearchReturn, $bReturnUid, $sLimit, '', $aSortTypes); + } + + /** + * @param array $aResult + * @return \MailSo\Imap\Response + */ + private function findLastResponse($aResult) + { + $oResult = null; + if (\is_array($aResult) && 0 < \count($aResult)) + { + $oResult = $aResult[\count($aResult) - 1]; + if (!($oResult instanceof \MailSo\Imap\Response)) + { + $oResult = null; + } + } + + return $oResult; + } + + /** + * @param string $sSearchCriterias + * @param bool $bReturnUid = true + * @param string $sCharset = '' + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleSearch($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = '') + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = 0 === \strlen($sSearchCriterias) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + $aRequest = array(); + if (0 < \strlen($sCharset)) + { + $aRequest[] = 'CHARSET'; + $aRequest[] = \strtoupper($sCharset); + } + + $aRequest[] = $sSearchCriterias; + + $sCmd = 'SEARCH'; + + $sCont = $this->SendRequest($sCommandPrefix.$sCmd, $aRequest, true); + if ('' !== $sCont) + { + $aResult = $this->parseResponseWithValidation(); + $oItem = $this->findLastResponse($aResult); + + if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) + { + $aParts = explode("\r\n", $sCont); + foreach ($aParts as $sLine) + { + $this->sendRaw($sLine); + + $aResult = $this->parseResponseWithValidation(); + $oItem = $this->findLastResponse($aResult); + if ($oItem && \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oItem->ResponseType) + { + continue; + } + } + } + } + else + { + $aResult = $this->parseResponseWithValidation(); + } + + $aReturn = array(); + $oImapResponse = null; + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ($sCmd === $oImapResponse->StatusOrIndex || + ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + && \is_array($oImapResponse->ResponseList) + && 2 < count($oImapResponse->ResponseList)) + { + $iStart = 2; + if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && + !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + { + $iStart = 3; + } + + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) + { + $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; + } + } + } + + $aReturn = \array_reverse($aReturn); + return $aReturn; + } + + /** + * @param mixed $aValue + * + * @return mixed + */ + private function validateThreadItem($aValue) + { + $mResult = false; + if (\is_numeric($aValue)) + { + $mResult = (int) $aValue; + if (0 >= $mResult) + { + $mResult = false; + } + } + else if (\is_array($aValue)) + { + if (1 === \count($aValue) && \is_numeric($aValue[0])) + { + $mResult = (int) $aValue[0]; + if (0 >= $mResult) + { + $mResult = false; + } + } + else + { + $mResult = array(); + foreach ($aValue as $aValueItem) + { + $mTemp = $this->validateThreadItem($aValueItem); + if (false !== $mTemp) + { + $mResult[] = $mTemp; + } + } + } + } + + return $mResult; + } + + /** + * @param string $sSearchCriterias = 'ALL' + * @param bool $bReturnUid = true + * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageSimpleThread($sSearchCriterias = 'ALL', $bReturnUid = true, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) + { + $sCommandPrefix = ($bReturnUid) ? 'UID ' : ''; + $sSearchCriterias = !\MailSo\Base\Validator::NotEmptyString($sSearchCriterias, true) || '*' === $sSearchCriterias + ? 'ALL' : $sSearchCriterias; + + $sThreadType = ''; + switch (true) + { + case $this->IsSupported('THREAD=REFS'): + $sThreadType = 'REFS'; + break; + case $this->IsSupported('THREAD=REFERENCES'): + $sThreadType = 'REFERENCES'; + break; + case $this->IsSupported('THREAD=ORDEREDSUBJECT'): + $sThreadType = 'ORDEREDSUBJECT'; + break; + default: + $this->writeLogException( + new Exceptions\RuntimeException('Thread is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + break; + } + + $aRequest = array(); + $aRequest[] = $sThreadType; + $aRequest[] = \strtoupper($sCharset); + $aRequest[] = $sSearchCriterias; + + $sCmd = 'THREAD'; + + $this->SendRequest($sCommandPrefix.$sCmd, $aRequest); + $aResult = $this->parseResponseWithValidation(); + + $aReturn = array(); + $oImapResponse = null; + + foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && ($sCmd === $oImapResponse->StatusOrIndex || + ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex) && !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + && \is_array($oImapResponse->ResponseList) + && 2 < \count($oImapResponse->ResponseList)) + { + $iStart = 2; + if ($bReturnUid && 'UID' === $oImapResponse->StatusOrIndex && + !empty($oImapResponse->ResponseList[2]) && + $sCmd === $oImapResponse->ResponseList[2]) + { + $iStart = 3; + } + + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) + { + $aNewValue = $this->validateThreadItem($oImapResponse->ResponseList[$iIndex]); + if (false !== $aNewValue) + { + $aReturn[] = $aNewValue; + } + } + } + } + + return $aReturn; + } + + /** + * @param string $sToFolder + * @param string $sIndexRange + * @param bool $bIndexIsUid + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageCopy($sToFolder, $sIndexRange, $bIndexIsUid) + { + if (0 === \strlen($sIndexRange)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; + return $this->SendRequestWithCheck($sCommandPrefix.'COPY', + array($sIndexRange, $this->EscapeString($sToFolder))); + } + + /** + * @param string $sToFolder + * @param string $sIndexRange + * @param bool $bIndexIsUid + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageMove($sToFolder, $sIndexRange, $bIndexIsUid) + { + if (0 === \strlen($sIndexRange)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + if (!$this->IsSupported('MOVE')) + { + $this->writeLogException( + new Exceptions\RuntimeException('Move is not supported'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $sCommandPrefix = ($bIndexIsUid) ? 'UID ' : ''; + return $this->SendRequestWithCheck($sCommandPrefix.'MOVE', + array($sIndexRange, $this->EscapeString($sToFolder))); + } + + /** + * @param string $sUidRangeIfSupported = '' + * @param bool $bForceUidExpunge = false + * @param bool $bExpungeAll = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageExpunge($sUidRangeIfSupported = '', $bForceUidExpunge = false, $bExpungeAll = false) + { + $sUidRangeIfSupported = \trim($sUidRangeIfSupported); + + $sCmd = 'EXPUNGE'; + $aArguments = array(); + + if (!$bExpungeAll && $bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS')) + { + $sCmd = 'UID '.$sCmd; + $aArguments = array($sUidRangeIfSupported); + } + + return $this->SendRequestWithCheck($sCmd, $aArguments); + } + + /** + * @param string $sIndexRange + * @param bool $bIndexIsUid + * @param array $aInputStoreItems + * @param string $sStoreAction + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageStoreFlag($sIndexRange, $bIndexIsUid, $aInputStoreItems, $sStoreAction) + { + if (!\MailSo\Base\Validator::NotEmptyString($sIndexRange, true) || + !\MailSo\Base\Validator::NotEmptyString($sStoreAction, true) || + 0 === \count($aInputStoreItems)) + { + return false; + } + + $sCmd = ($bIndexIsUid) ? 'UID STORE' : 'STORE'; + return $this->SendRequestWithCheck($sCmd, array($sIndexRange, $sStoreAction, $aInputStoreItems)); + } + + /** + * @param string $sFolderName + * @param resource $rMessageAppendStream + * @param int $iStreamSize + * @param array $aAppendFlags = null + * @param int $iUid = null + * @param int $sDateTime = 0 + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageAppendStream($sFolderName, $rMessageAppendStream, $iStreamSize, $aAppendFlags = null, &$iUid = null, $sDateTime = 0) + { + $aData = array($this->EscapeString($sFolderName), $aAppendFlags); + if (0 < $sDateTime) + { + $aData[] = $this->EscapeString(\gmdate('d-M-Y H:i:s', $sDateTime).' +0000'); + } + + $aData[] = '{'.$iStreamSize.'}'; + + $this->SendRequest('APPEND', $aData); + $this->parseResponseWithValidation(); + + $this->writeLog('Write to connection stream', \MailSo\Log\Enumerations\Type::NOTE); + + \MailSo\Base\Utils::MultipleStreamWriter($rMessageAppendStream, array($this->rConnect)); + + $this->sendRaw(''); + $this->parseResponseWithValidation(); + + if (null !== $iUid) + { + $aLastResponse = $this->GetLastResponse(); + if (\is_array($aLastResponse) && 0 < \count($aLastResponse) && $aLastResponse[\count($aLastResponse) - 1]) + { + $oLast = $aLastResponse[count($aLastResponse) - 1]; + if ($oLast && \MailSo\Imap\Enumerations\ResponseType::TAGGED === $oLast->ResponseType && \is_array($oLast->OptionalResponse)) + { + if (0 < \strlen($oLast->OptionalResponse[0]) && + 0 < \strlen($oLast->OptionalResponse[2]) && + 'APPENDUID' === strtoupper($oLast->OptionalResponse[0]) && + \is_numeric($oLast->OptionalResponse[2]) + ) + { + $iUid = (int) $oLast->OptionalResponse[2]; + } + } + } + } + + return $this; + } + + /** + * @return \MailSo\Imap\FolderInformation + */ + public function FolderCurrentInformation() + { + return $this->oCurrentFolderInfo; + } + + /** + * @param string $sCommand + * @param array $aParams = array() + * @param bool $bBreakOnLiteral = false + * + * @return string + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + */ + public function SendRequest($sCommand, $aParams = array(), $bBreakOnLiteral = false) + { + if (!\MailSo\Base\Validator::NotEmptyString($sCommand, true) || !\is_array($aParams)) + { + $this->writeLogException( + new \MailSo\Base\Exceptions\InvalidArgumentException(), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + + $this->IsConnected(true); + + $sTag = $this->getNewTag(); + + $sCommand = \trim($sCommand); + $sRealCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aParams); + + $sFakeCommand = ''; + $aFakeParams = $this->secureRequestParams($sCommand, $aParams); + if (null !== $aFakeParams) + { + $sFakeCommand = $sTag.' '.$sCommand.$this->prepearParamLine($aFakeParams); + } + + $this->aTagTimeouts[$sTag] = \microtime(true); + + if ($bBreakOnLiteral && !\preg_match('/\d\+\}\r\n/', $sRealCommand)) + { + $iPos = \strpos($sRealCommand, "}\r\n"); + if (false !== $iPos) + { + $iFakePos = \strpos($sFakeCommand, "}\r\n"); + + $this->sendRaw(\substr($sRealCommand, 0, $iPos + 1), true, + false !== $iFakePos ? \substr($sFakeCommand, 0, $iFakePos + 3) : ''); + + return \substr($sRealCommand, $iPos + 3); + } + } + + $this->sendRaw($sRealCommand, true, $sFakeCommand); + return ''; + } + + /** + * @param string $sCommand + * @param array $aParams + * + * @return array|null + */ + private function secureRequestParams($sCommand, $aParams) + { + $aResult = null; + switch ($sCommand) + { + case 'LOGIN': + $aResult = $aParams; + if (\is_array($aResult) && 2 === count($aResult)) + { + $aResult[1] = '"********"'; + } + break; + } + + return $aResult; + } + + /** + * @param string $sCommand + * @param array $aParams = array() + * @param bool $bFindCapa = false + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function SendRequestWithCheck($sCommand, $aParams = array(), $bFindCapa = false) + { + $this->SendRequest($sCommand, $aParams); + $this->parseResponseWithValidation(null, $bFindCapa); + + return $this; + } + + /** + * @return array + */ + public function GetLastResponse() + { + return $this->aLastResponse; + } + + /** + * @param mixed $aResult + * + * @return array + * + * @throws \MailSo\Imap\Exceptions\ResponseNotFoundException + * @throws \MailSo\Imap\Exceptions\InvalidResponseException + * @throws \MailSo\Imap\Exceptions\NegativeResponseException + */ + private function validateResponse($aResult) + { + if (!\is_array($aResult) || 0 === $iCnt = \count($aResult)) + { + $this->writeLogException( + new Exceptions\ResponseNotFoundException(), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + if ($aResult[$iCnt - 1]->ResponseType !== \MailSo\Imap\Enumerations\ResponseType::CONTINUATION) + { + if (!$aResult[$iCnt - 1]->IsStatusResponse) + { + $this->writeLogException( + new Exceptions\InvalidResponseException($aResult), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + + if (\MailSo\Imap\Enumerations\ResponseStatus::OK !== $aResult[$iCnt - 1]->StatusOrIndex) + { + $this->writeLogException( + new Exceptions\NegativeResponseException($aResult), + \MailSo\Log\Enumerations\Type::WARNING, true); + } + } + + return $aResult; + } + + /** + * @param string $sEndTag = null + * @param bool $bFindCapa = false + * + * @return array|bool + */ + protected function parseResponse($sEndTag = null, $bFindCapa = false) + { + if (\is_resource($this->rConnect)) + { + $oImapResponse = null; + $sEndTag = (null === $sEndTag) ? $this->getCurrentTag() : $sEndTag; + + while (true) + { + $oImapResponse = Response::NewInstance(); + + $this->partialParseResponseBranch($oImapResponse); + + if ($oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNKNOWN === $oImapResponse->ResponseType) + { + return false; + } + + if ($bFindCapa) + { + $this->initCapabilityImapResponse($oImapResponse); + } + + $this->aPartialResponses[] = $oImapResponse; + if ($sEndTag === $oImapResponse->Tag || \MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) + { + if (isset($this->aTagTimeouts[$sEndTag])) + { + $this->writeLog((\microtime(true) - $this->aTagTimeouts[$sEndTag]).' ('.$sEndTag.')', + \MailSo\Log\Enumerations\Type::TIME); + + unset($this->aTagTimeouts[$sEndTag]); + } + + break; + } + } + else + { + return false; + } + + unset($oImapResponse); + } + } + + $this->iResponseBufParsedPos = 0; + $this->aLastResponse = $this->aPartialResponses; + $this->aPartialResponses = array(); + + return $this->aLastResponse; + } + + /** + * @param string $sEndTag = null + * @param bool $bFindCapa = false + * + * @return array + */ + private function parseResponseWithValidation($sEndTag = null, $bFindCapa = false) + { + return $this->validateResponse($this->parseResponse($sEndTag, $bFindCapa)); + } + + /** + * @param \MailSo\Imap\Response $oImapResponse + * + * @return void + */ + private function initCapabilityImapResponse($oImapResponse) + { + if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType + && \is_array($oImapResponse->ResponseList)) + { + $aList = null; + if (isset($oImapResponse->ResponseList[1]) && \is_string($oImapResponse->ResponseList[1]) && + 'CAPABILITY' === \strtoupper($oImapResponse->ResponseList[1])) + { + $aList = \array_slice($oImapResponse->ResponseList, 2); + } + else if ($oImapResponse->OptionalResponse && \is_array($oImapResponse->OptionalResponse) && + 1 < \count($oImapResponse->OptionalResponse) && \is_string($oImapResponse->OptionalResponse[0]) && + 'CAPABILITY' === \strtoupper($oImapResponse->OptionalResponse[0])) + { + $aList = \array_slice($oImapResponse->OptionalResponse, 1); + } + + if (\is_array($aList) && 0 < \count($aList)) + { + $this->aCapabilityItems = \array_map('strtoupper', $aList); + } + } + } + + /** + * @return array|string + * + * @throws \MailSo\Net\Exceptions\Exception + */ + private function partialParseResponseBranch(&$oImapResponse, $iStackIndex = -1, + $bTreatAsAtom = false, $sParentToken = '', $sOpenBracket = '') + { + $mNull = null; + + $iStackIndex++; + $iPos = $this->iResponseBufParsedPos; + + $sPreviousAtomUpperCase = null; + $bIsEndOfList = false; + $bIsClosingBracketSquare = false; + $iLiteralLen = 0; + $iBufferEndIndex = 0; + $iDebugCount = 0; + + $rImapLiteralStream = null; + + $bIsGotoDefault = false; + $bIsGotoLiteral = false; + $bIsGotoLiteralEnd = false; + $bIsGotoAtomBracket = false; + $bIsGotoNotAtomBracket = false; + + $bCountOneInited = false; + $bCountTwoInited = false; + + $sAtomBuilder = $bTreatAsAtom ? '' : null; + $aList = array(); + if (null !== $oImapResponse) + { + $aList =& $oImapResponse->ResponseList; + } + + while (!$bIsEndOfList) + { + $iDebugCount++; + if (100000 === $iDebugCount) + { + $this->Logger()->Write('PartialParseOver: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); + } + + if ($this->bNeedNext) + { + $iPos = 0; + $this->getNextBuffer(); + $this->iResponseBufParsedPos = $iPos; + $this->bNeedNext = false; + } + + $sChar = null; + if ($bIsGotoDefault) + { + $sChar = 'GOTO_DEFAULT'; + $bIsGotoDefault = false; + } + else if ($bIsGotoLiteral) + { + $bIsGotoLiteral = false; + $bIsGotoLiteralEnd = true; + + if ($this->partialResponseLiteralCallbackCallable( + $sParentToken, null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $this->rConnect, $iLiteralLen)) + { + if (!$bTreatAsAtom) + { + $aList[] = ''; + } + } + else + { + $sLiteral = ''; + $iRead = $iLiteralLen; + + while (0 < $iRead) + { + $sAddRead = \fread($this->rConnect, $iRead); + if (false === $sAddRead) + { + $sLiteral = false; + break; + } + + $sLiteral .= $sAddRead; + $iRead -= \strlen($sAddRead); + + \MailSo\Base\Utils::ResetTimeLimit(); + } + + if (false !== $sLiteral) + { + $iLiteralSize = \strlen($sLiteral); + \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralSize); + if ($iLiteralLen !== $iLiteralSize) + { + $this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '. + $iLiteralLen.'" bytes', \MailSo\Log\Enumerations\Type::WARNING); + } + + if (!$bTreatAsAtom) + { + $aList[] = $sLiteral; + + if (\MailSo\Config::$LogSimpleLiterals) + { + $this->writeLog('{'.\strlen($sLiteral).'} '.$sLiteral, \MailSo\Log\Enumerations\Type::INFO); + } + } + } + else + { + $this->writeLog('Can\'t read imap stream', \MailSo\Log\Enumerations\Type::NOTE); + } + + unset($sLiteral); + } + + continue; + } + else if ($bIsGotoLiteralEnd) + { + $rImapLiteralStream = null; + $sPreviousAtomUpperCase = null; + $this->bNeedNext = true; + $bIsGotoLiteralEnd = false; + + continue; + } + else if ($bIsGotoAtomBracket) + { + if ($bTreatAsAtom) + { + $sAtomBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, + null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); + + $sAtomBuilder .= $sAtomBlock; + $iPos = $this->iResponseBufParsedPos; + $sAtomBuilder .= ($bIsClosingBracketSquare) ? ']' : ')'; + } + + $sPreviousAtomUpperCase = null; + $bIsGotoAtomBracket = false; + + continue; + } + else if ($bIsGotoNotAtomBracket) + { + $aSubItems = $this->partialParseResponseBranch($mNull, $iStackIndex, false, + null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), $sOpenBracket); + + $aList[] = $aSubItems; + $iPos = $this->iResponseBufParsedPos; + $sPreviousAtomUpperCase = null; + if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) + { + $oImapResponse->OptionalResponse = $aSubItems; + + $bIsGotoDefault = true; + $bIsGotoNotAtomBracket = false; + continue; + } + $bIsGotoNotAtomBracket = false; + + continue; + } + else + { + $iBufferEndIndex = \strlen($this->sResponseBuffer) - 3; + $this->bResponseBufferChanged = false; + + if ($iPos > $iBufferEndIndex) + { + break; + } + + $sChar = $this->sResponseBuffer[$iPos]; + } + + switch (true) + { + case ']' === $sChar: + $iPos++; + $sPreviousAtomUpperCase = null; + $bIsEndOfList = true; + break; + case ')' === $sChar: + $iPos++; + $sPreviousAtomUpperCase = null; + $bIsEndOfList = true; + break; + case ' ' === $sChar: + if ($bTreatAsAtom) + { + $sAtomBuilder .= ' '; + } + $iPos++; + break; + case '[' === $sChar: + $bIsClosingBracketSquare = true; + case '(' === $sChar: + if ('(' === $sChar) + { + $bIsClosingBracketSquare = false; + } + + if ($bTreatAsAtom) + { + $sAtomBuilder .= $bIsClosingBracketSquare ? '[' : '('; + } + $iPos++; + + $this->iResponseBufParsedPos = $iPos; + if ($bTreatAsAtom) + { + $bIsGotoAtomBracket = true; + $sOpenBracket = $bIsClosingBracketSquare ? '[' : '('; + } + else + { + $bIsGotoNotAtomBracket = true; + $sOpenBracket = $bIsClosingBracketSquare ? '[' : '('; + } + break; + case '{' === $sChar: + $bIsLiteralParsed = false; + $mLiteralEndPos = \strpos($this->sResponseBuffer, '}', $iPos); + if (false !== $mLiteralEndPos && $mLiteralEndPos > $iPos) + { + $sLiteralLenAsString = \substr($this->sResponseBuffer, $iPos + 1, $mLiteralEndPos - $iPos - 1); + if (\is_numeric($sLiteralLenAsString)) + { + $iLiteralLen = (int) $sLiteralLenAsString; + $bIsLiteralParsed = true; + $iPos = $mLiteralEndPos + 3; + $bIsGotoLiteral = true; + break; + } + } + if (!$bIsLiteralParsed) + { + $iPos = $iBufferEndIndex; + } + $sPreviousAtomUpperCase = null; + break; + case '"' === $sChar: + $bIsQuotedParsed = false; + while (true) + { + $iClosingPos = $iPos + 1; + if ($iClosingPos > $iBufferEndIndex) + { + break; + } + + while (true) + { + $iClosingPos = \strpos($this->sResponseBuffer, '"', $iClosingPos); + if (false === $iClosingPos) + { + break; + } + + // TODO + $iClosingPosNext = $iClosingPos + 1; + if ( + isset($this->sResponseBuffer[$iClosingPosNext]) && + ' ' !== $this->sResponseBuffer[$iClosingPosNext] && + "\r" !== $this->sResponseBuffer[$iClosingPosNext] && + "\n" !== $this->sResponseBuffer[$iClosingPosNext] && + ']' !== $this->sResponseBuffer[$iClosingPosNext] && + ')' !== $this->sResponseBuffer[$iClosingPosNext] + ) + { + $iClosingPos++; + continue; + } + + $iSlashCount = 0; + while ('\\' === $this->sResponseBuffer[$iClosingPos - $iSlashCount - 1]) + { + $iSlashCount++; + } + + if ($iSlashCount % 2 == 1) + { + $iClosingPos++; + continue; + } + else + { + break; + } + } + + if (false === $iClosingPos) + { + break; + } + else + { +// $iSkipClosingPos = 0; + $bIsQuotedParsed = true; + if ($bTreatAsAtom) + { + $sAtomBuilder .= \strtr( + \substr($this->sResponseBuffer, $iPos, $iClosingPos - $iPos + 1), + array('\\\\' => '\\', '\\"' => '"') + ); + } + else + { + $aList[] = \strtr( + \substr($this->sResponseBuffer, $iPos + 1, $iClosingPos - $iPos - 1), + array('\\\\' => '\\', '\\"' => '"') + ); + } + + $iPos = $iClosingPos + 1; + break; + } + } + + if (!$bIsQuotedParsed) + { + $iPos = $iBufferEndIndex; + } + + $sPreviousAtomUpperCase = null; + break; + + case 'GOTO_DEFAULT' === $sChar: + default: + $iCharBlockStartPos = $iPos; + + if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) + { + $iPos = $iBufferEndIndex; + + while ($iPos > $iCharBlockStartPos && $this->sResponseBuffer[$iCharBlockStartPos] === ' ') + { + $iCharBlockStartPos++; + } + } + + $bIsAtomDone = false; + while (!$bIsAtomDone && ($iPos <= $iBufferEndIndex)) + { + $sCharDef = $this->sResponseBuffer[$iPos]; + switch (true) + { + case '[' === $sCharDef: + if (null === $sAtomBuilder) + { + $sAtomBuilder = ''; + } + + $sAtomBuilder .= \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos + 1); + + $iPos++; + $this->iResponseBufParsedPos = $iPos; + + $sListBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, + null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase), '['); + + if (null !== $sListBlock) + { + $sAtomBuilder .= $sListBlock.']'; + } + + $iPos = $this->iResponseBufParsedPos; + $iCharBlockStartPos = $iPos; + break; + case ' ' === $sCharDef: + case ')' === $sCharDef && '(' === $sOpenBracket: + case ']' === $sCharDef && '[' === $sOpenBracket: + $bIsAtomDone = true; + break; + default: + $iPos++; + break; + } + } + + if ($iPos > $iCharBlockStartPos || null !== $sAtomBuilder) + { + $sLastCharBlock = \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos); + if (null === $sAtomBuilder) + { + $aList[] = $sLastCharBlock; + $sPreviousAtomUpperCase = $sLastCharBlock; + } + else + { + $sAtomBuilder .= $sLastCharBlock; + + if (!$bTreatAsAtom) + { + $aList[] = $sAtomBuilder; + $sPreviousAtomUpperCase = $sAtomBuilder; + $sAtomBuilder = null; + } + } + + if (null !== $oImapResponse) + { +// if (1 === \count($aList)) + if (!$bCountOneInited && 1 === \count($aList)) +// if (isset($aList[0]) && !isset($aList[1])) // fast 1 === \count($aList) + { + $bCountOneInited = true; + + $oImapResponse->Tag = $aList[0]; + if ('+' === $oImapResponse->Tag) + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::CONTINUATION; + } + else if ('*' === $oImapResponse->Tag) + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNTAGGED; + } + else if ($this->getCurrentTag() === $oImapResponse->Tag) + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::TAGGED; + } + else + { + $oImapResponse->ResponseType = \MailSo\Imap\Enumerations\ResponseType::UNKNOWN; + } + } +// else if (2 === \count($aList)) + else if (!$bCountTwoInited && 2 === \count($aList)) +// else if (isset($aList[1]) && !isset($aList[2])) // fast 2 === \count($aList) + { + $bCountTwoInited = true; + + $oImapResponse->StatusOrIndex = strtoupper($aList[1]); + + if ($oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::OK || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::NO || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BAD || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::BYE || + $oImapResponse->StatusOrIndex == \MailSo\Imap\Enumerations\ResponseStatus::PREAUTH) + { + $oImapResponse->IsStatusResponse = true; + } + } + else if (\MailSo\Imap\Enumerations\ResponseType::CONTINUATION === $oImapResponse->ResponseType) + { + $oImapResponse->HumanReadable = $sLastCharBlock; + } + else if ($oImapResponse->IsStatusResponse) + { + $oImapResponse->HumanReadable = $sLastCharBlock; + } + } + } + } + } + + $this->iResponseBufParsedPos = $iPos; + if (null !== $oImapResponse) + { + $this->bNeedNext = true; + $this->iResponseBufParsedPos = 0; + } + + if (100000 < $iDebugCount) + { + $this->Logger()->Write('PartialParseOverResult: '.$iDebugCount, \MailSo\Log\Enumerations\Type::ERROR); + } + + return $bTreatAsAtom ? $sAtomBuilder : $aList; + } + + /** + * @param string $sParent + * @param string $sLiteralAtomUpperCase + * @param resource $rImapStream + * @param int $iLiteralLen + * + * @return bool + */ + private function partialResponseLiteralCallbackCallable($sParent, $sLiteralAtomUpperCase, $rImapStream, $iLiteralLen) + { + $sLiteralAtomUpperCasePeek = ''; + if (0 === \strpos($sLiteralAtomUpperCase, 'BODY')) + { + $sLiteralAtomUpperCasePeek = \str_replace('BODY', 'BODY.PEEK', $sLiteralAtomUpperCase); + } + + $sFetchKey = ''; + if (\is_array($this->aFetchCallbacks)) + { + if (0 < \strlen($sLiteralAtomUpperCasePeek) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCasePeek])) + { + $sFetchKey = $sLiteralAtomUpperCasePeek; + } + else if (0 < \strlen($sLiteralAtomUpperCase) && isset($this->aFetchCallbacks[$sLiteralAtomUpperCase])) + { + $sFetchKey = $sLiteralAtomUpperCase; + } + } + + $bResult = false; + if (0 < \strlen($sFetchKey) && '' !== $this->aFetchCallbacks[$sFetchKey] && + \is_callable($this->aFetchCallbacks[$sFetchKey])) + { + $rImapLiteralStream = + \MailSo\Base\StreamWrappers\Literal::CreateStream($rImapStream, $iLiteralLen); + + $bResult = true; + $this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. + ' - try to read '.$iLiteralLen.' bytes.', \MailSo\Log\Enumerations\Type::NOTE); + + $this->bRunningCallback = true; + + try + { + \call_user_func($this->aFetchCallbacks[$sFetchKey], + $sParent, $sLiteralAtomUpperCase, $rImapLiteralStream); + } + catch (\Exception $oException) + { + $this->writeLog('Callback Exception', \MailSo\Log\Enumerations\Type::NOTICE); + $this->writeLogException($oException); + } + + if (\is_resource($rImapLiteralStream)) + { + $iNotReadLiteralLen = 0; + + $bFeof = \feof($rImapLiteralStream); + $this->writeLog('End Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. + ' - feof = '.($bFeof ? 'good' : 'BAD'), $bFeof ? + \MailSo\Log\Enumerations\Type::NOTE : \MailSo\Log\Enumerations\Type::WARNING); + + if (!$bFeof) + { + while (!@\feof($rImapLiteralStream)) + { + $sBuf = @\fread($rImapLiteralStream, 1024 * 1024); + if (false === $sBuf || 0 === \strlen($sBuf) || null === $sBuf) + { + break; + } + + \MailSo\Base\Utils::ResetTimeLimit(); + $iNotReadLiteralLen += \strlen($sBuf); + } + + if (\is_resource($rImapLiteralStream) && !@\feof($rImapLiteralStream)) + { + @\stream_get_contents($rImapLiteralStream); + } + } + + if (\is_resource($rImapLiteralStream)) + { + @\fclose($rImapLiteralStream); + } + + if ($iNotReadLiteralLen > 0) + { + $this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', + \MailSo\Log\Enumerations\Type::WARNING); + } + } + else + { + $this->writeLog('Literal stream is not resource after callback.', + \MailSo\Log\Enumerations\Type::WARNING); + } + + \MailSo\Base\Loader::IncStatistic('NetRead', $iLiteralLen); + + $this->bRunningCallback = false; + } + + return $bResult; + } + + /** + * @param array $aParams = null + * + * @return string + */ + private function prepearParamLine($aParams = array()) + { + $sReturn = ''; + if (\is_array($aParams) && 0 < \count($aParams)) + { + foreach ($aParams as $mParamItem) + { + if (\is_array($mParamItem) && 0 < \count($mParamItem)) + { + $sReturn .= ' ('.\trim($this->prepearParamLine($mParamItem)).')'; + } + else if (\is_string($mParamItem)) + { + $sReturn .= ' '.$mParamItem; + } + } + } + return $sReturn; + } + + /** + * @return string + */ + private function getNewTag() + { + $this->iTagCount++; + return $this->getCurrentTag(); + } + + /** + * @return string + */ + private function getCurrentTag() + { + return self::TAG_PREFIX.$this->iTagCount; + } + + /** + * @param string $sStringForEscape + * + * @return string + */ + public function EscapeString($sStringForEscape) + { + return '"'.\str_replace(array('\\', '"'), array('\\\\', '\\"'), $sStringForEscape).'"'; + } + + /** + * @return string + */ + protected function getLogName() + { + return 'IMAP'; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \MailSo\Imap\ImapClient + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + */ + public function SetLogger($oLogger) + { + parent::SetLogger($oLogger); + + return $this; + } + + /** + * @param resource $rConnect + * @param array $aCapabilityItems = array() + * + * @return \MailSo\Imap\ImapClient + */ + public function TestSetValues($rConnect, $aCapabilityItems = array()) + { + $this->rConnect = $rConnect; + $this->aCapabilityItems = $aCapabilityItems; + + return $this; + } + + /** + * @param string $sEndTag = null + * @param string $bFindCapa = false + * + * @return array + */ + public function TestParseResponseWithValidationProxy($sEndTag = null, $bFindCapa = false) + { + return $this->parseResponseWithValidation($sEndTag, $bFindCapa); + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/NamespaceResult.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/NamespaceResult.php new file mode 100644 index 0000000000000000000000000000000000000000..7d053f21e77c3e2fb110ac07d643686494c9d9f9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Response.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Imap/Response.php new file mode 100644 index 0000000000000000000000000000000000000000..fb787ba39de8ed495abbeb2b898a4d3c388e9987 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/LICENSE b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6229c40e683b6a8cbc0f4aaf96c880cd4462982f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Driver.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Driver.php new file mode 100644 index 0000000000000000000000000000000000000000..fd313fc2821ada387e555993f5cae1cf0480058a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/Callback.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/Callback.php new file mode 100644 index 0000000000000000000000000000000000000000..46e8789334536f719f3206dc82324644512531c2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/File.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/File.php new file mode 100644 index 0000000000000000000000000000000000000000..b3c3678c1fe1de3b570dbd82d4834071191d9f81 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/Inline.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/Inline.php new file mode 100644 index 0000000000000000000000000000000000000000..817ffbb5bb51e0ca30939a939a22a10ce086233b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/Syslog.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Drivers/Syslog.php new file mode 100644 index 0000000000000000000000000000000000000000..f1895067d110e55de1dbd5a08414da1fdef7d7dd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Enumerations/Type.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Log/Enumerations/Type.php new file mode 100644 index 0000000000000000000000000000000000000000..6c3db90d53363bd841cd0d49924383f7222de523 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/Attachment.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/Attachment.php new file mode 100644 index 0000000000000000000000000000000000000000..51def4efce742aca652b403821d7fe9de50be198 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/AttachmentCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/AttachmentCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..5cc64b576469692fa0b13486c9e5337306d6071f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/Exceptions/Exception.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/Exceptions/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..9bf1c42124a20bc54e8ed09eddcd1765a0e1ce71 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/FolderCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/FolderCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..9f0c00a0e12580fd538c7e2feace0a68862adb0d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/MailClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/MailClient.php new file mode 100644 index 0000000000000000000000000000000000000000..ff0500faf5ad0c911f50f28e8c199177bc9015d3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/MailClient.php @@ -0,0 +1,2677 @@ +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 + * + * @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) + { + $this->oImapClient->Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl); + 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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/Message.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/Message.php new file mode 100644 index 0000000000000000000000000000000000000000..9c1c525110dddba5aac7da93e9d5cbc514e11001 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/MessageCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mail/MessageCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..427a18cacc69b5b786dcf202926134a3899db6c4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/MailSo.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/MailSo.php new file mode 100644 index 0000000000000000000000000000000000000000..aa294dca51ae1cfb9be36702c8b08f3e40beb9f3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/AttachmentCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/AttachmentCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..68376fd79faffc3ba6063d0b4de95bd002d1e7db --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Email.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Email.php new file mode 100644 index 0000000000000000000000000000000000000000..6347f0f813255f3dc202f85ef13c08c1f9102792 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/EmailCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/EmailCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..53e5db39110ab4a4009854b3a0ea2aeeb503d7ed --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/EmailDep.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/EmailDep.php new file mode 100644 index 0000000000000000000000000000000000000000..7a923faee2a6f91054415c3f8fd74e6c82aaf009 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Enumerations/Constants.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Enumerations/Constants.php new file mode 100644 index 0000000000000000000000000000000000000000..41da58188336178fe93c674daab378bb3bf58897 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/HeaderCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/HeaderCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..f5e36d46272ec76f829554f0966ef567680528e2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Message.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Message.php new file mode 100644 index 0000000000000000000000000000000000000000..44dfa80b40ff11230cb3d6e764d310671276040e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Parameter.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Parameter.php new file mode 100644 index 0000000000000000000000000000000000000000..487a256e75e8b10b98dcdc806437f42bd989451e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/ParameterCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/ParameterCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..52b7dd3752b312750c6db774112b11adca05ffc6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Parser/ParserEmpty.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Parser/ParserEmpty.php new file mode 100644 index 0000000000000000000000000000000000000000..2349ade75077ada2de38d42003d024f83883ae57 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Part.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/Part.php new file mode 100644 index 0000000000000000000000000000000000000000..2e19af03f72e57a7e8ae657d73d6d12caf6fc483 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/PartCollection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Mime/PartCollection.php new file mode 100644 index 0000000000000000000000000000000000000000..d3c53ca6c42a10ff99a28a1d3d0f4e9146ff39bf --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Net/Enumerations/ConnectionSecurityType.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Net/Enumerations/ConnectionSecurityType.php new file mode 100644 index 0000000000000000000000000000000000000000..fed485dbcbacb1bd024458dcc0de822131a34f0d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php new file mode 100644 index 0000000000000000000000000000000000000000..b406b3d2a245938ec68ccf62f236a8ff3a246f76 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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 + * + * @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) + { + 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 + ) + ); + + \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 @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT): + case @\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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Pop3/Exceptions/Exception.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Pop3/Exceptions/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..0cc582d570f2ca885c6bb970fa55d3f7b08a4da9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Pop3/Exceptions/RuntimeException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Pop3/Exceptions/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..047c32a62833f5231abf64deeaea66ff2bcad394 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Poppassd/Exceptions/Exception.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Poppassd/Exceptions/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..76097ffbc37e34bebf07eab24ad90bae21b3125c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Poppassd/Exceptions/RuntimeException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Poppassd/Exceptions/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..5b94cb3f245ca6a58d1351dddc536fe21f77f49b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Sieve/Exceptions/Exception.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Sieve/Exceptions/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..0740485c40191f00969852fc90214210b0e04d21 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Sieve/Exceptions/RuntimeException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Sieve/Exceptions/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..8704e461b3188b5aea174924ece4a510f7c981d2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Smtp/Exceptions/Exception.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Smtp/Exceptions/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..7c18f18927e34e42f5a514eea9fdfc88f51a7bd0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Smtp/Exceptions/RuntimeException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Smtp/Exceptions/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..294b93b0f236457052fdc8f39f4cde4d1143525f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Vendors/Net/IDNA2.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Vendors/Net/IDNA2.php new file mode 100644 index 0000000000000000000000000000000000000000..8206834118a933d800120f8d3f887c69f22c6086 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Vendors/Net/IDNA2CustomExceptions.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Vendors/Net/IDNA2CustomExceptions.php new file mode 100644 index 0000000000000000000000000000000000000000..69b0f691463850626a37b6f4286167300fad6df5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/MailSo/Vendors/Net/IDNA2CustomExceptions.php @@ -0,0 +1,18 @@ +getSignature(); + } + + return $sSignature; + } +} diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/LICENSE.txt b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..90e632a7875d1264882be7e568ca899d581e1b03 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/Mobile_Detect.json b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/Mobile_Detect.json new file mode 100644 index 0000000000000000000000000000000000000000..63d2aa7ba833f4bb41b10e60335d2684c6ee1c8c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/Mobile_Detect.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/Mobile_Detect.php new file mode 100644 index 0000000000000000000000000000000000000000..6d0a36d5d7606e77b72baa295973e46d219a7483 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/README.md b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c33d70149fb4d6cdf86dc46c98daa5c3b271315e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/composer.json b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..0131e4937ecb8c7baa2169bece01b764454869d2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/namespaced/Detection/MobileDetect.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/namespaced/Detection/MobileDetect.php new file mode 100644 index 0000000000000000000000000000000000000000..ca7efec23fbc0f7bea7833ee5c31f46986c24b2b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/app/libraries/Mobile_Detect/namespaced/Detection/MobileDetect.php @@ -0,0 +1,22 @@ + + + diff --git a/rainloop/app/rainloop/v/1.12.1/app/libraries/PHP-OAuth2/Client.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/PHP-OAuth2/Client.php new file mode 100644 index 0000000000000000000000000000000000000000..6fae0925de76e9944c58f1d6c5f63733bb3c8164 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/PHP-OAuth2/GrantType/AuthorizationCode.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/PHP-OAuth2/GrantType/AuthorizationCode.php new file mode 100644 index 0000000000000000000000000000000000000000..f3436e4c5490fd50820ac586a0bab55265c058b3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPGangsta/GoogleAuthenticator.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPGangsta/GoogleAuthenticator.php new file mode 100644 index 0000000000000000000000000000000000000000..5e70eed7e62499083ec50b811fd4377f41b5a34c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPThumb/GD.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPThumb/GD.php new file mode 100644 index 0000000000000000000000000000000000000000..733fe476c0cd74d1414fc68b1b353e403025b226 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPThumb/PHPThumb.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPThumb/PHPThumb.php new file mode 100644 index 0000000000000000000000000000000000000000..a86c8c91e54ef6234c7e6bbbc892a77bd7a3b205 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPThumb/PluginInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/PHPThumb/PluginInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..56df9768ab93e5accf47d5691dd4995072fdb600 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Autoloader.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Autoloader.php new file mode 100644 index 0000000000000000000000000000000000000000..17ec2ff1dfea0daefad4f0c5c4f7b1adbbcc4c2f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Client.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Client.php new file mode 100644 index 0000000000000000000000000000000000000000..87596ec9286db14f7e8f84f176bc9262fea38fe2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/ClientContextInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/ClientContextInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7f695b78deb4b2deff01d715f0ff3a16c3a9c414 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/ClientException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/ClientException.php new file mode 100644 index 0000000000000000000000000000000000000000..6c07aaf0d06aba413b74033b031d9937a62ca4b6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/ClientInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/ClientInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f216c470ce212f2dbd08f64e8c67b180272ec85a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/ClusterStrategy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/ClusterStrategy.php new file mode 100644 index 0000000000000000000000000000000000000000..7635000ec6df61517706dac63e7779a3137f1ed8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/DistributorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/DistributorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..831f52c5293ac4441e1f61c7e111d36bb9b428e7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/EmptyRingException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/EmptyRingException.php new file mode 100644 index 0000000000000000000000000000000000000000..039f2f2e833a29c7f99c927e732388c42df90320 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/HashRing.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/HashRing.php new file mode 100644 index 0000000000000000000000000000000000000000..db864d9127bc927fbe99867dd5af944df68d42b3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/KetamaRing.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Distributor/KetamaRing.php new file mode 100644 index 0000000000000000000000000000000000000000..dc77f320f4937fe4892b252e59f4314af5b7b6ab --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Hash/CRC16.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Hash/CRC16.php new file mode 100644 index 0000000000000000000000000000000000000000..3add0cef2fe9e540e19ff9c6d3abffa57eb5b765 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Hash/HashGeneratorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/Hash/HashGeneratorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..271b9e720ec1be122da43fd480fa10d196cf1ce0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/PredisStrategy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/PredisStrategy.php new file mode 100644 index 0000000000000000000000000000000000000000..2066842798f4fa076c2b8e52f3725d9809d2f1d1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/RedisStrategy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/RedisStrategy.php new file mode 100644 index 0000000000000000000000000000000000000000..df0bdb49b2caf14a2065be4ef04e0d2571d33c67 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/StrategyInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Cluster/StrategyInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..cdf7d09fac86c2019d992f8a963de9a01317f93f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/CursorBasedIterator.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/CursorBasedIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..922883f05e45e4989cd64ff25ace71537ff1ed4e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/HashKey.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/HashKey.php new file mode 100644 index 0000000000000000000000000000000000000000..aa8aeaf02b2c5e232ce852dc46e3ffba8df3e1bd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/Keyspace.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/Keyspace.php new file mode 100644 index 0000000000000000000000000000000000000000..5d985b9bc38e884c0d6f2b6face7dc62d232c015 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/ListKey.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/ListKey.php new file mode 100644 index 0000000000000000000000000000000000000000..7a6eb479e35f03a031508dfbbc6b3e931aed3098 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/SetKey.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/SetKey.php new file mode 100644 index 0000000000000000000000000000000000000000..bf254397517b52f5ffdfcf67e24101e9a53ef57e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/SortedSetKey.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Collection/Iterator/SortedSetKey.php new file mode 100644 index 0000000000000000000000000000000000000000..e2f17892270387d38953c4e295108732bcac7cc7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Command.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Command.php new file mode 100644 index 0000000000000000000000000000000000000000..bb538e7c5ead16c6f8bc04d98e66a58aa0d89e8c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/CommandInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/CommandInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9f349e1dfc3fc59e4c21ae1cfc33dba0645ecac2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionAuth.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionAuth.php new file mode 100644 index 0000000000000000000000000000000000000000..c8c9dedce449c864e6377628cb09826c8d6cbc52 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionEcho.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionEcho.php new file mode 100644 index 0000000000000000000000000000000000000000..fd496097145059d487e409229fb317b8ac7a9188 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionPing.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionPing.php new file mode 100644 index 0000000000000000000000000000000000000000..fa9d7346f8cd72f8debee31c1580a4a74fba2db8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionQuit.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionQuit.php new file mode 100644 index 0000000000000000000000000000000000000000..e59e31e3eb3373463846891c44739a77a49cb31f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionSelect.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ConnectionSelect.php new file mode 100644 index 0000000000000000000000000000000000000000..1da82567761e2be43b438fc49d1e4371f2b02e9b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashDelete.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashDelete.php new file mode 100644 index 0000000000000000000000000000000000000000..d5d4c38c0601d87db04aa2eeb9513381f32d825a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashExists.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashExists.php new file mode 100644 index 0000000000000000000000000000000000000000..a2c69b90c7b6a3776aa1a6fb41b87296132bd371 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashGet.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashGet.php new file mode 100644 index 0000000000000000000000000000000000000000..20f33da5471c6724d0119b88256c32dfb92b6e10 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashGetAll.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashGetAll.php new file mode 100644 index 0000000000000000000000000000000000000000..d6986752109c07639b5b6ebd198c39bbd5b0d3c2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashGetMultiple.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashGetMultiple.php new file mode 100644 index 0000000000000000000000000000000000000000..820ce958eaff747abbb02016e70961d7187bfb76 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashIncrementBy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashIncrementBy.php new file mode 100644 index 0000000000000000000000000000000000000000..a37359ffb72f8ec8c364e89104a6f6e1fa0b55b7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashIncrementByFloat.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashIncrementByFloat.php new file mode 100644 index 0000000000000000000000000000000000000000..bce9714fc61b5921bcc18efb13ad092de1045042 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashKeys.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashKeys.php new file mode 100644 index 0000000000000000000000000000000000000000..28266020a4633ef63eb2f6dd75dee14967ff25e6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashLength.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashLength.php new file mode 100644 index 0000000000000000000000000000000000000000..d70926f1c7c8e58fd162e53d89c81b6764237726 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashScan.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashScan.php new file mode 100644 index 0000000000000000000000000000000000000000..afde74eb907c310567f6594b7d29ce661d8c82c9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashSet.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashSet.php new file mode 100644 index 0000000000000000000000000000000000000000..d3154a9b295ce5ae3b0b00882c73d05fcd0bb63f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashSetMultiple.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashSetMultiple.php new file mode 100644 index 0000000000000000000000000000000000000000..6069e2ad9a3ceebd70614199faf846ff8dcfdeb6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashSetPreserve.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashSetPreserve.php new file mode 100644 index 0000000000000000000000000000000000000000..582100d4999fb7734a935c4f1a90206411edd5e9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashStringLength.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashStringLength.php new file mode 100644 index 0000000000000000000000000000000000000000..7cfda80df933b3bd8cb2d3827dbea1ddb842905a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashValues.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HashValues.php new file mode 100644 index 0000000000000000000000000000000000000000..0a5ea5f618f8a891abf6004144cbdb7aa55e12ca --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HyperLogLogAdd.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HyperLogLogAdd.php new file mode 100644 index 0000000000000000000000000000000000000000..18d2bd7b555418cec658c3cb06d1a4ae4e0fa5be --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HyperLogLogCount.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HyperLogLogCount.php new file mode 100644 index 0000000000000000000000000000000000000000..0afe542700e533171356dd449f43d4d3283574cb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HyperLogLogMerge.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/HyperLogLogMerge.php new file mode 100644 index 0000000000000000000000000000000000000000..c160be5b200f6ac5d9961d6c7dd4702aec05a791 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyDelete.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyDelete.php new file mode 100644 index 0000000000000000000000000000000000000000..89bdfdb7f7971c258fabbaa756ce83e3d01b002f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyDump.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyDump.php new file mode 100644 index 0000000000000000000000000000000000000000..6d9c488004b9426cc1118a55c2f378befe9695b4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyExists.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyExists.php new file mode 100644 index 0000000000000000000000000000000000000000..5196ca16b2b037e56e203ec43352f05f77fe95d5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyExpire.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyExpire.php new file mode 100644 index 0000000000000000000000000000000000000000..fd7c9c802cf0886081d784f425dba523ac8041af --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyExpireAt.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyExpireAt.php new file mode 100644 index 0000000000000000000000000000000000000000..e2fe7aeb6d5bdb9b6b3cbe190a71353514677fa0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyKeys.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyKeys.php new file mode 100644 index 0000000000000000000000000000000000000000..6d74c40d28e39cefb7d59f071e5f64fded2d94d7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyMigrate.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyMigrate.php new file mode 100644 index 0000000000000000000000000000000000000000..3324ef94c2c7d7bfc637a4e67c3bfbad658534cd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyMove.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyMove.php new file mode 100644 index 0000000000000000000000000000000000000000..8f1ab2a735231c5fd74037b3f80f5f451b17e026 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPersist.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPersist.php new file mode 100644 index 0000000000000000000000000000000000000000..e77295536717f7b937e435e3a113f8e8780dda04 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPreciseExpire.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPreciseExpire.php new file mode 100644 index 0000000000000000000000000000000000000000..258ec4766e540c681d08a1efae2e673f2a7f63e6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPreciseExpireAt.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPreciseExpireAt.php new file mode 100644 index 0000000000000000000000000000000000000000..e41921870cb211c2609dfc0373623a7078f83059 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPreciseTimeToLive.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyPreciseTimeToLive.php new file mode 100644 index 0000000000000000000000000000000000000000..bdcd34b9251f424311f2cb65023b7c010b47be88 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRandom.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRandom.php new file mode 100644 index 0000000000000000000000000000000000000000..b208b2db43e88354887f17a88affa37ba1640a28 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRename.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRename.php new file mode 100644 index 0000000000000000000000000000000000000000..82e44fb2e63639b6f1a1ab7d44a98e5a9f01490b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRenamePreserve.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRenamePreserve.php new file mode 100644 index 0000000000000000000000000000000000000000..773ece6d315f62c6d3f6e00e7c0abcb98a35bda5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRestore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyRestore.php new file mode 100644 index 0000000000000000000000000000000000000000..a5b0b2dbc352384c7a5d2e542f06366d3a6df9f0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyScan.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyScan.php new file mode 100644 index 0000000000000000000000000000000000000000..05f5bb3accd263789f561ce48719c62dc3989c5b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeySort.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeySort.php new file mode 100644 index 0000000000000000000000000000000000000000..fd449f13df87cfa6e6f6651e1bc9b7d85ca31c6b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyTimeToLive.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyTimeToLive.php new file mode 100644 index 0000000000000000000000000000000000000000..67697a6fbdd34f4694cfb95a93515e3d9a4f30a8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyType.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/KeyType.php new file mode 100644 index 0000000000000000000000000000000000000000..f4f06e451a44e2cc6f0e3b038c879b4eb23f8f1c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListIndex.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListIndex.php new file mode 100644 index 0000000000000000000000000000000000000000..27c64be73208f291d5d8c7b661a6a3db0104ae03 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListInsert.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListInsert.php new file mode 100644 index 0000000000000000000000000000000000000000..7d53d11b21f6659f156b5c9489c4fba4b8891ad5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListLength.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListLength.php new file mode 100644 index 0000000000000000000000000000000000000000..6495beb778205ead83c4cd34f1841f7d116a66c5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopFirst.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopFirst.php new file mode 100644 index 0000000000000000000000000000000000000000..84d5d6734ad8194eabfa7c8f0626ff7ec4f8e5bc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopFirstBlocking.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopFirstBlocking.php new file mode 100644 index 0000000000000000000000000000000000000000..7dc7c0002a1b3ea8c5613dabae7def4a47c55f8b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLast.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLast.php new file mode 100644 index 0000000000000000000000000000000000000000..9e92db5f468bc289f72b78b767be55ef06577a93 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLastBlocking.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLastBlocking.php new file mode 100644 index 0000000000000000000000000000000000000000..781eb9192d2dc9fcf81777b3cd8e647be3b1de3e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLastPushHead.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLastPushHead.php new file mode 100644 index 0000000000000000000000000000000000000000..f430eb227e576486643ae8a35f5498794eb32805 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLastPushHeadBlocking.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPopLastPushHeadBlocking.php new file mode 100644 index 0000000000000000000000000000000000000000..ee9c93c85af97a421f718985a1119827f08d2ec5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushHead.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushHead.php new file mode 100644 index 0000000000000000000000000000000000000000..74bf7c491173413273216c0f000bef28b26f217d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushHeadX.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushHeadX.php new file mode 100644 index 0000000000000000000000000000000000000000..8e136b880f35f3e80337af21126d270f8457a9ae --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushTail.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushTail.php new file mode 100644 index 0000000000000000000000000000000000000000..f2a057c0b30e1b82648609fb4f8a0f51ecc614f8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushTailX.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListPushTailX.php new file mode 100644 index 0000000000000000000000000000000000000000..1af3645b475d66174f482b2b3752d2c1c46bc0d0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListRange.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListRange.php new file mode 100644 index 0000000000000000000000000000000000000000..32a21a6e63aff3bb64ce7d7589e32a4209c2fde4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListRemove.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListRemove.php new file mode 100644 index 0000000000000000000000000000000000000000..c5800899b390c74e4cf5b2538ee403abc4425b4c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListSet.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListSet.php new file mode 100644 index 0000000000000000000000000000000000000000..5e59864dea83a1482aed4253993c3a0a0bac661c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListTrim.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ListTrim.php new file mode 100644 index 0000000000000000000000000000000000000000..193141809f7c4d0aa0ea4d96ff47e9de1e1dd13e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PrefixableCommandInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PrefixableCommandInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6d54554faa4100e9b8b61e13eea928f5100ce242 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Processor/KeyPrefixProcessor.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Processor/KeyPrefixProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..d966d0e10796bd29c4726d17d5e17b28e0e9d182 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Processor/ProcessorChain.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Processor/ProcessorChain.php new file mode 100644 index 0000000000000000000000000000000000000000..0a4768b0a7044da1b0c31cbcbc2b28d48536db72 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Processor/ProcessorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/Processor/ProcessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2f9105802b5ce07b0f19b40200a470149a56ac62 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubPublish.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubPublish.php new file mode 100644 index 0000000000000000000000000000000000000000..55508f8d95c0659f913d251cd14a06a78f47cdd3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubPubsub.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubPubsub.php new file mode 100644 index 0000000000000000000000000000000000000000..8cf812973c21f948bc79c6fcdf58726d4e4df2c0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubSubscribe.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubSubscribe.php new file mode 100644 index 0000000000000000000000000000000000000000..e477b313b03d287b917e82cd8da8e7d23ac69fa1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubSubscribeByPattern.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubSubscribeByPattern.php new file mode 100644 index 0000000000000000000000000000000000000000..011828066509cbaf16da778de16aeeff01270400 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubUnsubscribe.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubUnsubscribe.php new file mode 100644 index 0000000000000000000000000000000000000000..d57c3ac6d2428642c4b94af96c41b437426f77fa --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubUnsubscribeByPattern.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/PubSubUnsubscribeByPattern.php new file mode 100644 index 0000000000000000000000000000000000000000..4d76508b2a2794ed4a7ebb2eeed242301ad2111e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/RawCommand.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/RawCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..2dd48ca173f68b80b5252928dc5c0c88a147d8e2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ScriptCommand.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ScriptCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..a30bc1d283ae855e9027e4fd1f8bb016d7f61935 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerBackgroundRewriteAOF.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerBackgroundRewriteAOF.php new file mode 100644 index 0000000000000000000000000000000000000000..c66a294e53386a18b1d2bb0ae30664a3781b825c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerBackgroundSave.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerBackgroundSave.php new file mode 100644 index 0000000000000000000000000000000000000000..4bf67ef30c3d0f109176494cd3b5997a76457a90 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerClient.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerClient.php new file mode 100644 index 0000000000000000000000000000000000000000..d00ebbfff9a9392c8a01d24e21fa2ea5b1644c44 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerCommand.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..e9b3393c7a0f50d76caecf507e845d26dfb581f7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerConfig.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..81e497aead190bd0e9e4a6a41dcc9efbf9242e2f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerDatabaseSize.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerDatabaseSize.php new file mode 100644 index 0000000000000000000000000000000000000000..6bc89724d2163b66130e178337ce8f4791f18ca7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerEval.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerEval.php new file mode 100644 index 0000000000000000000000000000000000000000..f5eefd811e6352541d74ff5c8a24248702336b42 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerEvalSHA.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerEvalSHA.php new file mode 100644 index 0000000000000000000000000000000000000000..520a8e9851e36a779224c30262f1e7b6373eb57d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerFlushAll.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerFlushAll.php new file mode 100644 index 0000000000000000000000000000000000000000..c35b2ad6ab92ea2b9a29a83b2a0fdd45e4a89ed2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerFlushDatabase.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerFlushDatabase.php new file mode 100644 index 0000000000000000000000000000000000000000..3da6b320d6bf73aa65d317d98becb5363b51af20 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerInfo.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..96d6adad989a341b8dd8028ecfbe97a337295085 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerInfoV26x.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerInfoV26x.php new file mode 100644 index 0000000000000000000000000000000000000000..90c9b716334e2b5952110d7a4086c8b787fea096 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerLastSave.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerLastSave.php new file mode 100644 index 0000000000000000000000000000000000000000..feeb19a8af001f6db2167235d5c4b59f48fa71f8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerMonitor.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerMonitor.php new file mode 100644 index 0000000000000000000000000000000000000000..1c3d3309527b2dccc549e016b02710100d1498ce --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerObject.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerObject.php new file mode 100644 index 0000000000000000000000000000000000000000..f921701c23c61d274d83dfa782dfccca07107551 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSave.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSave.php new file mode 100644 index 0000000000000000000000000000000000000000..addefe20fe004ef55838803ecb5721c177895984 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerScript.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerScript.php new file mode 100644 index 0000000000000000000000000000000000000000..7a01018d9f0941953a9d266088eb48952400e66a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSentinel.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSentinel.php new file mode 100644 index 0000000000000000000000000000000000000000..c0962db3de320a66da5f8cdc3d783efa10f77be9 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerShutdown.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerShutdown.php new file mode 100644 index 0000000000000000000000000000000000000000..f5b745a21317f08ccd6640b5780f10a68c51f2b0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSlaveOf.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSlaveOf.php new file mode 100644 index 0000000000000000000000000000000000000000..4ff44556ab9cba8ea26b7593087fe296edc6d049 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSlowlog.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerSlowlog.php new file mode 100644 index 0000000000000000000000000000000000000000..137ff59e7fab165843adc08be00749767334f4bb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerTime.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ServerTime.php new file mode 100644 index 0000000000000000000000000000000000000000..589f92c5567fcfec7ff9fa43977d148c6d5667a1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetAdd.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetAdd.php new file mode 100644 index 0000000000000000000000000000000000000000..c1188181652264d0426a4f4f8cf3008f901ae519 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetCardinality.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetCardinality.php new file mode 100644 index 0000000000000000000000000000000000000000..a9f959b7819fcbc1e07e18d102330c34f6da9d73 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetDifference.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetDifference.php new file mode 100644 index 0000000000000000000000000000000000000000..35f23f98a8d345c9e017c839407f5896b7c95228 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetDifferenceStore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetDifferenceStore.php new file mode 100644 index 0000000000000000000000000000000000000000..0cb78155fd4717ccae562471f3fb0b6907365a02 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetIntersection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetIntersection.php new file mode 100644 index 0000000000000000000000000000000000000000..d18258fd71215304b554f73549a8a17ecef8010e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetIntersectionStore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetIntersectionStore.php new file mode 100644 index 0000000000000000000000000000000000000000..b748618aab78dc16fdb1136917e7ee877b5159ec --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetIsMember.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetIsMember.php new file mode 100644 index 0000000000000000000000000000000000000000..1b484907e10cd0f37cf5b1f59db35e181884a92c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetMembers.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetMembers.php new file mode 100644 index 0000000000000000000000000000000000000000..f4076ae8bafdf0083e0385c375c9a8f733044cfc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetMove.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetMove.php new file mode 100644 index 0000000000000000000000000000000000000000..72d514be7e19757e9a9777ff0be9b61f16d63202 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetPop.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetPop.php new file mode 100644 index 0000000000000000000000000000000000000000..b78d3f33bcbfced66d655ea73f87f94256af3ec2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetRandomMember.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetRandomMember.php new file mode 100644 index 0000000000000000000000000000000000000000..2cb79a0494be65c7b3dfe7c59fe55c2e66c19052 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetRemove.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetRemove.php new file mode 100644 index 0000000000000000000000000000000000000000..b34710c6213ecf449d7dd2beb17d1a66692ddb36 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetScan.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetScan.php new file mode 100644 index 0000000000000000000000000000000000000000..d42b28dfb233a2a966b037ddae7dcc4426464fb6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetUnion.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetUnion.php new file mode 100644 index 0000000000000000000000000000000000000000..7da842b4597615a34577a9e9535b16b138678a15 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetUnionStore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/SetUnionStore.php new file mode 100644 index 0000000000000000000000000000000000000000..eac821ad798681c630e0582caca6bc7e457e8aa7 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringAppend.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringAppend.php new file mode 100644 index 0000000000000000000000000000000000000000..dac8b84793cc96bff16f4950a4e04ad092225bcb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringBitCount.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringBitCount.php new file mode 100644 index 0000000000000000000000000000000000000000..193cce91650b71bcc5ae206a7c2fd29c290c755f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringBitOp.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringBitOp.php new file mode 100644 index 0000000000000000000000000000000000000000..e04ee79c2e5cf0937ab3161bb10d497ace22e2a3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringBitPos.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringBitPos.php new file mode 100644 index 0000000000000000000000000000000000000000..42957665d362ff6643185fa338d288b8c3c4bd49 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringDecrement.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringDecrement.php new file mode 100644 index 0000000000000000000000000000000000000000..aa5808cd067472f1105970d4a729777044d97a33 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringDecrementBy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringDecrementBy.php new file mode 100644 index 0000000000000000000000000000000000000000..cbf3e1124c9c9789e24640326ed262e9502cef78 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGet.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGet.php new file mode 100644 index 0000000000000000000000000000000000000000..138e915c2f4e9edd33243dec6592764386bd6806 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetBit.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetBit.php new file mode 100644 index 0000000000000000000000000000000000000000..3c5b4f9b75e43b6319514d1706e6936ed8b245b0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetMultiple.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetMultiple.php new file mode 100644 index 0000000000000000000000000000000000000000..e340f9cfc6a0af462d89362d605466433b01e74c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetRange.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetRange.php new file mode 100644 index 0000000000000000000000000000000000000000..bb10565b56482a211a6fb8154531c7ba0504fa2c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetSet.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringGetSet.php new file mode 100644 index 0000000000000000000000000000000000000000..b68870d4c4cb1f3009c98a2ffe2b7208cff77ec0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringIncrement.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringIncrement.php new file mode 100644 index 0000000000000000000000000000000000000000..fa1846e2ea828736e81d822a7c36b86db2279f93 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringIncrementBy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringIncrementBy.php new file mode 100644 index 0000000000000000000000000000000000000000..9d8241a25ea6cf0c785653868e29f45205aa15ae --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringIncrementByFloat.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringIncrementByFloat.php new file mode 100644 index 0000000000000000000000000000000000000000..164a0869bd164473a507ccd9aef2c9d2e001fb5d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringPreciseSetExpire.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringPreciseSetExpire.php new file mode 100644 index 0000000000000000000000000000000000000000..2faa954d0b9a0b86130e1c01d6deb6519cc16ac6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSet.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSet.php new file mode 100644 index 0000000000000000000000000000000000000000..b1469945c5bbc68b31c722dc4ab2b872b6043ab4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetBit.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetBit.php new file mode 100644 index 0000000000000000000000000000000000000000..7933b6be36ecbb70f276a6f2fed8ce2310a1ac5b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetExpire.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetExpire.php new file mode 100644 index 0000000000000000000000000000000000000000..f088170853cea6544fa0c83b173369a1681c4b17 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetMultiple.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetMultiple.php new file mode 100644 index 0000000000000000000000000000000000000000..a3c5324dc37b52894c9bbb1080d12b3e217bb835 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetMultiplePreserve.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetMultiplePreserve.php new file mode 100644 index 0000000000000000000000000000000000000000..f98f1f7c26fc18edad17e7846666bbca0c5700bc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetPreserve.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetPreserve.php new file mode 100644 index 0000000000000000000000000000000000000000..726c35c87826f27452c1dc0e4d6341528b33c13b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetRange.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSetRange.php new file mode 100644 index 0000000000000000000000000000000000000000..4d9389f48b04de1ce148d6ecd4e6749a47a7b6af --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringStrlen.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringStrlen.php new file mode 100644 index 0000000000000000000000000000000000000000..10f492fd99130e55f77ea37a78c4f2e9d074e1a1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSubstr.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/StringSubstr.php new file mode 100644 index 0000000000000000000000000000000000000000..3aab7ade87415f958f575703e578239268340141 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionDiscard.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionDiscard.php new file mode 100644 index 0000000000000000000000000000000000000000..44aca2b11e689b5cd600f603b207397f5dfb6769 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionExec.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionExec.php new file mode 100644 index 0000000000000000000000000000000000000000..dbd81aae9612e41d6affc6426810c2480d8d7529 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionMulti.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionMulti.php new file mode 100644 index 0000000000000000000000000000000000000000..673bf55da97df1169df626e4f56b89efb4eda014 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionUnwatch.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionUnwatch.php new file mode 100644 index 0000000000000000000000000000000000000000..792555449cd30ce5f370fdb8ae495bef7f871855 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionWatch.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/TransactionWatch.php new file mode 100644 index 0000000000000000000000000000000000000000..d3607801bdb774819b6ec9c6947b545bdb96c999 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetAdd.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetAdd.php new file mode 100644 index 0000000000000000000000000000000000000000..55e4729e538a7c3255ffc7285d1946139b82cf49 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetCardinality.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetCardinality.php new file mode 100644 index 0000000000000000000000000000000000000000..10332009a43d7cb183961dbaacff894767d8c70e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetCount.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetCount.php new file mode 100644 index 0000000000000000000000000000000000000000..918bd2b808a6464aadc627c769e1987ccb9d9650 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetIncrementBy.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetIncrementBy.php new file mode 100644 index 0000000000000000000000000000000000000000..245a8e0f5c66603fe5691cc21325887b1e9d3201 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetIntersectionStore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetIntersectionStore.php new file mode 100644 index 0000000000000000000000000000000000000000..572a7a324c113b4bcd51e60ebfcdf2e644d85f97 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetLexCount.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetLexCount.php new file mode 100644 index 0000000000000000000000000000000000000000..447b8eb32916ce0b4de94a5f1ba4b680c6ceac38 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRange.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRange.php new file mode 100644 index 0000000000000000000000000000000000000000..ce72c7c0773d89f3c80053e74f9f9c7e1b92af2b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRangeByLex.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRangeByLex.php new file mode 100644 index 0000000000000000000000000000000000000000..9b2991a81f886f50b0554fd369cd8c9be8706195 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRangeByScore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRangeByScore.php new file mode 100644 index 0000000000000000000000000000000000000000..961a5bc2ee288fb25751db95402d801aba9d24d4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRank.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRank.php new file mode 100644 index 0000000000000000000000000000000000000000..d0c9c536eb402b9228046cb5dbba6b7c81a89028 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemove.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemove.php new file mode 100644 index 0000000000000000000000000000000000000000..cd8ada05cec8d446f086d5d44af00849dfb014f5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemoveRangeByLex.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemoveRangeByLex.php new file mode 100644 index 0000000000000000000000000000000000000000..9ea2d9e5bb2d79ec0b3fd853f1dc4a6a0e12c1e1 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemoveRangeByRank.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemoveRangeByRank.php new file mode 100644 index 0000000000000000000000000000000000000000..89cd5baffbaf1a46c2ab1674459caf3d5aa212f2 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemoveRangeByScore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetRemoveRangeByScore.php new file mode 100644 index 0000000000000000000000000000000000000000..a7c30814b1c82575863e3bad00d2f79b75d4e8db --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRange.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRange.php new file mode 100644 index 0000000000000000000000000000000000000000..6a46a7a5af9b10d4f848015fd4d0b0af9f2ce960 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRangeByLex.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRangeByLex.php new file mode 100644 index 0000000000000000000000000000000000000000..cdd8ba6233609defc69cf07bd8e785c19f3280de --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRangeByScore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRangeByScore.php new file mode 100644 index 0000000000000000000000000000000000000000..1078eb72bf9bce0cd10ab730232d6f2a66fb7e58 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRank.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetReverseRank.php new file mode 100644 index 0000000000000000000000000000000000000000..33fb81584e802686761271b6c7b8cd6787d7cc53 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetScan.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetScan.php new file mode 100644 index 0000000000000000000000000000000000000000..1dc2352ed183df48a2f93107d30a0656801d8301 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetScore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetScore.php new file mode 100644 index 0000000000000000000000000000000000000000..2e7fce8edc798b8c0bcec5550c257a2cef873426 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetUnionStore.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Command/ZSetUnionStore.php new file mode 100644 index 0000000000000000000000000000000000000000..befc5ce7c88c6f09be1767e2d90523476eafc426 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/CommunicationException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/CommunicationException.php new file mode 100644 index 0000000000000000000000000000000000000000..13fe357c318b4aaeb96c11c16590988a39c45b66 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ClusterOption.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ClusterOption.php new file mode 100644 index 0000000000000000000000000000000000000000..69e36de77adaa933f26d8db44807048d623e5ca4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ConnectionFactoryOption.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ConnectionFactoryOption.php new file mode 100644 index 0000000000000000000000000000000000000000..ba38df96fc51de85fc24881e3caabad5854e5fd6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ExceptionsOption.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ExceptionsOption.php new file mode 100644 index 0000000000000000000000000000000000000000..337733e4b2d8a133ac8f5da1020bc273aa9c7c7d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/OptionInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/OptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b31e0c98f2eda8fbab253812c022c51c915efdc8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/Options.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/Options.php new file mode 100644 index 0000000000000000000000000000000000000000..6f3b331b5588565456a024fecf92eafb72e57657 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/OptionsInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/OptionsInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f8116470834b7bdde496b9b2286ee601bae0c7ba --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/PrefixOption.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/PrefixOption.php new file mode 100644 index 0000000000000000000000000000000000000000..5827cdc37b3da666306e7c6d7b1a178b1836b26f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ProfileOption.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ProfileOption.php new file mode 100644 index 0000000000000000000000000000000000000000..864936e0af5d8bf449a345ac96773d4b3fee656e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ReplicationOption.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Configuration/ReplicationOption.php new file mode 100644 index 0000000000000000000000000000000000000000..fd2c8108d6c6292644d21318eb5fd8be81c1d59f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/AbstractConnection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/AbstractConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..029a337eb0aea60ff35c08b897f857ae07310813 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/ClusterInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/ClusterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..af0f5aab55222e3f3e222384b052d7e0f9cf6a3f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/MasterSlaveReplication.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/MasterSlaveReplication.php new file mode 100644 index 0000000000000000000000000000000000000000..3104a753c1f15c1ed22d76866843424a5284bc02 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/PredisCluster.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/PredisCluster.php new file mode 100644 index 0000000000000000000000000000000000000000..33f98bf2ef9cf4e63a0592dad8f7291ee1200d2b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/RedisCluster.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/RedisCluster.php new file mode 100644 index 0000000000000000000000000000000000000000..337c28702d0968caa15c96de8dd69e4a8f2a9363 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/ReplicationInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Aggregate/ReplicationInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e09e8265c4502b3b5e1621ce2326f283213e3eb5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/AggregateConnectionInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/AggregateConnectionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7eeaede769c9e70066319debd33b61a1f2d2f705 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/CompositeConnectionInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/CompositeConnectionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..286e082ccf2d0e75bf1a56c36b27df62c52c8067 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/CompositeStreamConnection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/CompositeStreamConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..7a35340542b5af32faecedc21533867b55bf5c6d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/ConnectionException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/ConnectionException.php new file mode 100644 index 0000000000000000000000000000000000000000..ef2e9d73ae2627f808c76789857b7fd101b5138b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/ConnectionInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/ConnectionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..11ace1b697b989ebd3841ed0b633d2caaf771fca --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Factory.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Factory.php new file mode 100644 index 0000000000000000000000000000000000000000..c2e93f880176147c843556a388e23095571676a4 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/FactoryInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/FactoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2bae0839e981b305d96b82c818c0628076a55bdd --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/NodeConnectionInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/NodeConnectionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..665b862c1be3d1708ca0fe062f68d8b2ef1e8161 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Parameters.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/Parameters.php new file mode 100644 index 0000000000000000000000000000000000000000..b7d9861532c7d280d1a9fd567b3e2a445355aae5 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/ParametersInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/ParametersInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..fd8a908e66257a29a16cef57d508f09fc7a2c32f --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/PhpiredisSocketConnection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/PhpiredisSocketConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..6948f035f784f36bed508ea52d47b627e3d6d97b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/PhpiredisStreamConnection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/PhpiredisStreamConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..beb235758c59aec092d142bd82e1debc5a22601a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/StreamConnection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/StreamConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..ed6540b87834a0ff93126d21f304b1672e15cd0c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/WebdisConnection.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Connection/WebdisConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..9cff9d023b48add4edfb5b12d0d82902177c1cdc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Monitor/Consumer.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Monitor/Consumer.php new file mode 100644 index 0000000000000000000000000000000000000000..d10bad1a0958dc276bcf19c46791691d11904a4c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/NotSupportedException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/NotSupportedException.php new file mode 100644 index 0000000000000000000000000000000000000000..be82aba7239c95234209eda776d3c81ce3ffe955 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/Atomic.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/Atomic.php new file mode 100644 index 0000000000000000000000000000000000000000..1c9c92aa29588614f404d227d3f3eea5f6c9b16b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/ConnectionErrorProof.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/ConnectionErrorProof.php new file mode 100644 index 0000000000000000000000000000000000000000..d3bc732e417f579e153f508f645d152edda3bdee --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/FireAndForget.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/FireAndForget.php new file mode 100644 index 0000000000000000000000000000000000000000..95a062b64e7e07f4a9f611995d0feb5db1c115de --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/Pipeline.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Pipeline/Pipeline.php new file mode 100644 index 0000000000000000000000000000000000000000..cf9c59e4fcf5e913dca7af21be436417b1528e29 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/PredisException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/PredisException.php new file mode 100644 index 0000000000000000000000000000000000000000..122bde16d95f339b5cb1e64ed7dcf2a4cf47a1c3 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/Factory.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/Factory.php new file mode 100644 index 0000000000000000000000000000000000000000..bcee3c2cfb0dfec4da130e38d454ed35342d34db --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/ProfileInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/ProfileInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..abe71aa6398ec5ddbc74e5bcc967f09a87bdc8f0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisProfile.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisProfile.php new file mode 100644 index 0000000000000000000000000000000000000000..3ef316886a362d74f589ceffedbe17b971c01bd6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisUnstable.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisUnstable.php new file mode 100644 index 0000000000000000000000000000000000000000..cf6174feb46792e5a361a811f2bb777272109cbb --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion200.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion200.php new file mode 100644 index 0000000000000000000000000000000000000000..234d53c0033e5019dd9efad7a2e8c0995021c773 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion220.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion220.php new file mode 100644 index 0000000000000000000000000000000000000000..899014e2856b92c9d002f62fc5ebcd0d202a2abc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion240.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion240.php new file mode 100644 index 0000000000000000000000000000000000000000..0856c37c44c9e1b5353652320897da98b0c9b18d --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion260.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion260.php new file mode 100644 index 0000000000000000000000000000000000000000..ba5084aa0a2fa49d6e4a47f74cf5fb5d5bd59b5a --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion280.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion280.php new file mode 100644 index 0000000000000000000000000000000000000000..ea17e6829648ce0f62441731f84428ab604ab17c --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion300.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Profile/RedisVersion300.php new file mode 100644 index 0000000000000000000000000000000000000000..8a2fac8b79037b155e049179fb64452cf4f797c0 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/ProtocolException.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/ProtocolException.php new file mode 100644 index 0000000000000000000000000000000000000000..6fe5d6d3ab51480748bf0a085093525eddb881fc --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/ProtocolProcessorInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/ProtocolProcessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b34ea18143330e1878dc4049e14025e1cfbf2074 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/RequestSerializerInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/RequestSerializerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..eef72a640f40eebc8e981aaf77ae10f8fe7cb659 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/ResponseReaderInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/ResponseReaderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..86a7bdcce85aa8328ca304e2701b672ecb5bdb06 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/CompositeProtocolProcessor.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/CompositeProtocolProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..ea85ed303960147d9d176e8e99c40b62a5b623f8 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/BulkResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/BulkResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..5b0bf3c2d1de92d99463febe64b252256953ce2b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/ErrorResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/ErrorResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..3e18b7b9e948240c041e4c0b83fdfc51c112f525 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/IntegerResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/IntegerResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..4639d7792f6ec1d75a32aad36f82cb0bd47fd467 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/MultiBulkResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/MultiBulkResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..820b9b4a6fc5aa56c6a12f726c2f1dfaae5b705e --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/ResponseHandlerInterface.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/ResponseHandlerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ca08a9c5386bc7b5e074e3c5e58a2e0ab342a3a6 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/StatusResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/StatusResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..7bde5558f277994cc76d8777cf35d3651638b34b --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/StreamableMultiBulkResponse.php b/rainloop/app/rainloop/v/1.12.1/app/libraries/Predis/Protocol/Text/Handler/StreamableMultiBulkResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..7cdb736af390a8aa72c32bb36d8b01d6b13f2471 --- /dev/null +++ b/rainloop/app/rainloop/v/1.12.1/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